am 7a61b840: am 8f36a744: am cd24dae9: Merge "Fix bug 2605504 Don\'t leak a metric ton of NotificationPlayer threads The Looper on the thread created in order to be notified of the playback completion of notification sounds, was never stopped, causing the threads to s
diff --git a/Android.mk b/Android.mk
index 8783934..f6aa1e9c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -197,6 +197,9 @@
LOCAL_MODULE := framework
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_NO_EMMA_INSTRUMENT := true
+LOCAL_NO_EMMA_COMPILE := true
+
# List of classes and interfaces which should be loaded by the Zygote.
LOCAL_JAVA_RESOURCE_FILES += $(LOCAL_PATH)/preloaded-classes
@@ -583,6 +586,9 @@
LOCAL_MODULE := ext
+LOCAL_NO_EMMA_INSTRUMENT := true
+LOCAL_NO_EMMA_COMPILE := true
+
include $(BUILD_JAVA_LIBRARY)
diff --git a/api/current.xml b/api/current.xml
index 721d133..ac39248 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2132,6 +2132,17 @@
visibility="public"
>
</field>
+<field name="adapter"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843521"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="addStatesFromChildren"
type="int"
transient="false"
@@ -2341,6 +2352,17 @@
visibility="public"
>
</field>
+<field name="as"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843527"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="author"
type="int"
transient="false"
@@ -3001,6 +3023,17 @@
visibility="public"
>
</field>
+<field name="column"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843530"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="columnDelay"
type="int"
transient="false"
@@ -4167,6 +4200,17 @@
visibility="public"
>
</field>
+<field name="from"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843525"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="fromAlpha"
type="int"
transient="false"
@@ -4189,6 +4233,17 @@
visibility="public"
>
</field>
+<field name="fromValue"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843528"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="fromXDelta"
type="int"
transient="false"
@@ -8314,6 +8369,17 @@
visibility="public"
>
</field>
+<field name="selection"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843522"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="settingsActivity"
type="int"
transient="false"
@@ -8501,6 +8567,17 @@
visibility="public"
>
</field>
+<field name="sortOrder"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843523"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="soundEffectsEnabled"
type="int"
transient="false"
@@ -9689,6 +9766,17 @@
visibility="public"
>
</field>
+<field name="to"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843526"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="toAlpha"
type="int"
transient="false"
@@ -9711,6 +9799,17 @@
visibility="public"
>
</field>
+<field name="toValue"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843529"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="toXDelta"
type="int"
transient="false"
@@ -9887,6 +9986,17 @@
visibility="public"
>
</field>
+<field name="uri"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843524"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="useLevel"
type="int"
transient="false"
@@ -10437,6 +10547,28 @@
visibility="public"
>
</field>
+<field name="withClass"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843532"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="withExpression"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843531"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="writePermission"
type="int"
transient="false"
@@ -20304,6 +20436,17 @@
<parameter name="view" type="android.view.View">
</parameter>
</method>
+<method name="openFragmentTransaction"
+ return="android.app.FragmentTransaction"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="openOptionsMenu"
return="void"
abstract="false"
@@ -24658,6 +24801,300 @@
</parameter>
</method>
</class>
+<class name="Fragment"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.content.ComponentCallbacks">
+</implements>
+<constructor name="Fragment"
+ type="android.app.Fragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="Fragment"
+ type="android.app.Fragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</constructor>
+<method name="getActivity"
+ return="android.app.Activity"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onAttach"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="activity" type="android.app.Activity">
+</parameter>
+</method>
+<method name="onConfigurationChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="newConfig" type="android.content.res.Configuration">
+</parameter>
+</method>
+<method name="onCreate"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="savedInstanceState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onCreateView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="inflater" type="android.view.LayoutInflater">
+</parameter>
+<parameter name="container" type="android.view.ViewGroup">
+</parameter>
+</method>
+<method name="onDestroy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onDetach"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onLowMemory"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onPause"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRestart"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRestoreInstanceState"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="savedInstanceState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onResume"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRetainNonConfigurationInstance"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onSaveInstanceState"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="outState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onStart"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onStop"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<interface name="FragmentTransaction"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="add"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+<parameter name="containerViewId" type="int">
+</parameter>
+</method>
+<method name="add"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="containerViewId" type="int">
+</parameter>
+</method>
+<method name="commit"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="remove"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+</method>
+</interface>
<class name="Instrumentation"
extends="java.lang.Object"
abstract="false"
@@ -38074,6 +38511,17 @@
visibility="public"
>
</field>
+<field name="STORAGE_SERVICE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""storage""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="TELEPHONY_SERVICE"
type="java.lang.String"
transient="false"
@@ -44699,6 +45147,21 @@
<parameter name="defValue" type="java.lang.String">
</parameter>
</method>
+<method name="getStringSet"
+ return="java.util.Set<java.lang.String>"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="key" type="java.lang.String">
+</parameter>
+<parameter name="defValues" type="java.util.Set<java.lang.String>">
+</parameter>
+</method>
<method name="registerOnSharedPreferenceChangeListener"
return="void"
abstract="true"
@@ -44830,6 +45293,21 @@
<parameter name="value" type="java.lang.String">
</parameter>
</method>
+<method name="putStringSet"
+ return="android.content.SharedPreferences.Editor"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="key" type="java.lang.String">
+</parameter>
+<parameter name="values" type="java.util.Set<java.lang.String>">
+</parameter>
+</method>
<method name="remove"
return="android.content.SharedPreferences.Editor"
abstract="true"
@@ -55631,6 +56109,27 @@
>
</method>
</class>
+<interface name="DatabaseErrorHandler"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onCorruption"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dbObj" type="android.database.sqlite.SQLiteDatabase">
+</parameter>
+</method>
+</interface>
<class name="DatabaseUtils"
extends="java.lang.Object"
abstract="false"
@@ -56515,6 +57014,38 @@
>
</field>
</class>
+<class name="DefaultDatabaseErrorHandler"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.database.DatabaseErrorHandler">
+</implements>
+<constructor name="DefaultDatabaseErrorHandler"
+ type="android.database.DefaultDatabaseErrorHandler"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onCorruption"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dbObj" type="android.database.sqlite.SQLiteDatabase">
+</parameter>
+</method>
+</class>
<class name="MatrixCursor"
extends="android.database.AbstractCursor"
abstract="false"
@@ -57383,6 +57914,17 @@
<parameter name="tables" type="java.lang.String">
</parameter>
</method>
+<method name="getAttachedDbs"
+ return="java.util.ArrayList<android.util.Pair<java.lang.String, java.lang.String>>"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getMaximumSize"
return="long"
abstract="false"
@@ -57504,6 +58046,17 @@
<parameter name="conflictAlgorithm" type="int">
</parameter>
</method>
+<method name="isDatabaseIntegrityOk"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="isDbLockedByCurrentThread"
return="boolean"
abstract="false"
@@ -57621,6 +58174,25 @@
<parameter name="flags" type="int">
</parameter>
</method>
+<method name="openDatabase"
+ return="android.database.sqlite.SQLiteDatabase"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="path" type="java.lang.String">
+</parameter>
+<parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+<parameter name="errorHandler" type="android.database.DatabaseErrorHandler">
+</parameter>
+</method>
<method name="openOrCreateDatabase"
return="android.database.sqlite.SQLiteDatabase"
abstract="false"
@@ -57651,6 +58223,23 @@
<parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
</parameter>
</method>
+<method name="openOrCreateDatabase"
+ return="android.database.sqlite.SQLiteDatabase"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="path" type="java.lang.String">
+</parameter>
+<parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
+</parameter>
+<parameter name="errorHandler" type="android.database.DatabaseErrorHandler">
+</parameter>
+</method>
<method name="query"
return="android.database.Cursor"
abstract="false"
@@ -57870,6 +58459,19 @@
<parameter name="lockingEnabled" type="boolean">
</parameter>
</method>
+<method name="setMaxSqlCacheSize"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cacheSize" type="int">
+</parameter>
+</method>
<method name="setMaximumSize"
return="long"
abstract="false"
@@ -58072,6 +58674,17 @@
visibility="public"
>
</field>
+<field name="MAX_SQL_CACHE_SIZE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="100"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="NO_LOCALIZED_COLLATORS"
type="int"
transient="false"
@@ -72930,6 +73543,17 @@
visibility="public"
>
</method>
+<method name="computeConstantSize"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
<method name="getChangingConfigurations"
return="int"
abstract="false"
@@ -73824,6 +74448,36 @@
</parameter>
</method>
</class>
+<class name="MipmapDrawable"
+ extends="android.graphics.drawable.DrawableContainer"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="MipmapDrawable"
+ type="android.graphics.drawable.MipmapDrawable"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="addDrawable"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="drawable" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+</class>
<class name="NinePatchDrawable"
extends="android.graphics.drawable.Drawable"
abstract="false"
@@ -124159,6 +124813,240 @@
</method>
</class>
</package>
+<package name="android.os.storage"
+>
+<class name="StorageEventListener"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StorageEventListener"
+ type="android.os.storage.StorageEventListener"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onStorageStateChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="path" type="java.lang.String">
+</parameter>
+<parameter name="oldState" type="java.lang.String">
+</parameter>
+<parameter name="newState" type="java.lang.String">
+</parameter>
+</method>
+<method name="onUsbMassStorageConnectionChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="connected" type="boolean">
+</parameter>
+</method>
+</class>
+<class name="StorageManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="disableUsbMassStorage"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="enableUsbMassStorage"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isUsbMassStorageConnected"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isUsbMassStorageEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="registerListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.os.storage.StorageEventListener">
+</parameter>
+</method>
+<method name="unregisterListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.os.storage.StorageEventListener">
+</parameter>
+</method>
+</class>
+<class name="StorageResultCode"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StorageResultCode"
+ type="android.os.storage.StorageResultCode"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<field name="OperationFailedInternalError"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedMediaBlank"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedMediaCorrupt"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedNoMedia"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedStorageBusy"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedStorageMounted"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedStorageNotMounted"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationSucceeded"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+</package>
<package name="android.preference"
>
<class name="CheckBoxPreference"
@@ -124952,6 +125840,148 @@
</parameter>
</method>
</class>
+<class name="MultiSelectListPreference"
+ extends="android.preference.DialogPreference"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="MultiSelectListPreference"
+ type="android.preference.MultiSelectListPreference"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+</constructor>
+<constructor name="MultiSelectListPreference"
+ type="android.preference.MultiSelectListPreference"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<method name="findIndexOfValue"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="java.lang.String">
+</parameter>
+</method>
+<method name="getEntries"
+ return="java.lang.CharSequence[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getEntryValues"
+ return="java.lang.CharSequence[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getValues"
+ return="java.util.Set<java.lang.String>"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setEntries"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="entries" type="java.lang.CharSequence[]">
+</parameter>
+</method>
+<method name="setEntries"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="entriesResId" type="int">
+</parameter>
+</method>
+<method name="setEntryValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="entryValues" type="java.lang.CharSequence[]">
+</parameter>
+</method>
+<method name="setEntryValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="entryValuesResId" type="int">
+</parameter>
+</method>
+<method name="setValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="values" type="java.util.Set<java.lang.String>">
+</parameter>
+</method>
+</class>
<class name="Preference"
extends="java.lang.Object"
abstract="false"
@@ -133322,6 +134352,17 @@
deprecated="not deprecated"
visibility="protected"
>
+<field name="AUTO_ADD"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""auto_add""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="DELETED"
type="java.lang.String"
transient="false"
@@ -133333,6 +134374,17 @@
visibility="public"
>
</field>
+<field name="FAVORITES"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""favorites""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="GROUP_VISIBLE"
type="java.lang.String"
transient="false"
@@ -158103,7 +159155,7 @@
>
<parameter name="text" type="java.lang.CharSequence">
</parameter>
-<parameter name="p" type="android.text.TextPaint">
+<parameter name="paint" type="android.text.TextPaint">
</parameter>
<parameter name="avail" type="float">
</parameter>
@@ -201218,6 +202270,195 @@
</parameter>
</method>
</interface>
+<class name="Adapters"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Adapters"
+ type="android.widget.Adapters"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="loadAdapter"
+ return="android.widget.BaseAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="parameters" type="java.lang.Object...">
+</parameter>
+</method>
+<method name="loadCursorAdapter"
+ return="android.widget.CursorAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="uri" type="java.lang.String">
+</parameter>
+<parameter name="parameters" type="java.lang.Object...">
+</parameter>
+</method>
+<method name="loadCursorAdapter"
+ return="android.widget.CursorAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="parameters" type="java.lang.Object...">
+</parameter>
+</method>
+</class>
+<class name="Adapters.CursorBinder"
+ extends="java.lang.Object"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Adapters.CursorBinder"
+ type="android.widget.Adapters.CursorBinder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="transformation" type="android.widget.Adapters.CursorTransformation">
+</parameter>
+</constructor>
+<method name="bind"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<field name="mContext"
+ type="android.content.Context"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
+<field name="mTransformation"
+ type="android.widget.Adapters.CursorTransformation"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
+</class>
+<class name="Adapters.CursorTransformation"
+ extends="java.lang.Object"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Adapters.CursorTransformation"
+ type="android.widget.Adapters.CursorTransformation"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<method name="transform"
+ return="java.lang.String"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="transformToResource"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<field name="mContext"
+ type="android.content.Context"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
+</class>
<class name="AlphabetIndexer"
extends="android.database.DataSetObserver"
abstract="false"
@@ -203179,6 +204420,20 @@
<parameter name="autoRequery" type="boolean">
</parameter>
</constructor>
+<constructor name="CursorAdapter"
+ type="android.widget.CursorAdapter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="c" type="android.database.Cursor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</constructor>
<method name="bindView"
return="void"
abstract="true"
@@ -203326,6 +204581,23 @@
<parameter name="autoRequery" type="boolean">
</parameter>
</method>
+<method name="init"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="c" type="android.database.Cursor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
<method name="newDropDownView"
return="android.view.View"
abstract="false"
@@ -203397,6 +204669,28 @@
<parameter name="filterQueryProvider" type="android.widget.FilterQueryProvider">
</parameter>
</method>
+<field name="FLAG_AUTO_REQUERY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FLAG_REGISTER_CONTENT_OBSERVER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
</class>
<class name="CursorTreeAdapter"
extends="android.widget.BaseExpandableListAdapter"
@@ -209404,6 +210698,17 @@
<parameter name="excludeMimes" type="java.lang.String[]">
</parameter>
</method>
+<method name="setImageToDefault"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="setMode"
return="void"
abstract="false"
diff --git a/cmds/input/input b/cmds/input/input
old mode 100644
new mode 100755
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a962391..15bf242 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -650,6 +650,65 @@
private CharSequence mTitle;
private int mTitleColor = 0;
+ final FragmentManager mFragments = new FragmentManager();
+
+ private final class FragmentTransactionImpl implements FragmentTransaction {
+ ArrayList<Fragment> mAdded;
+ ArrayList<Fragment> mRemoved;
+
+ public FragmentTransaction add(Fragment fragment, int containerViewId) {
+ return add(fragment, null, containerViewId);
+ }
+
+ public FragmentTransaction add(Fragment fragment, String name, int containerViewId) {
+ if (fragment.mActivity != null) {
+ throw new IllegalStateException("Fragment already added: " + fragment);
+ }
+ if (name != null) {
+ fragment.mName = name;
+ }
+ if (mRemoved != null) {
+ mRemoved.remove(fragment);
+ }
+ if (mAdded == null) {
+ mAdded = new ArrayList<Fragment>();
+ }
+ fragment.mContainerId = containerViewId;
+ mAdded.add(fragment);
+ return this;
+ }
+
+ public FragmentTransaction remove(Fragment fragment) {
+ if (fragment.mActivity == null) {
+ throw new IllegalStateException("Fragment not added: " + fragment);
+ }
+ if (mAdded != null) {
+ mAdded.remove(fragment);
+ }
+ if (mRemoved == null) {
+ mRemoved = new ArrayList<Fragment>();
+ }
+ mRemoved.add(fragment);
+ return this;
+ }
+
+ public void commit() {
+ if (mRemoved != null) {
+ for (int i=mRemoved.size()-1; i>=0; i--) {
+ mFragments.removeFragment(mRemoved.get(i));
+ }
+ }
+ if (mAdded != null) {
+ for (int i=mAdded.size()-1; i>=0; i--) {
+ mFragments.addFragment(mAdded.get(i));
+ }
+ }
+ if (mFragments != null) {
+ mFragments.moveToState(mFragments.mCurState);
+ }
+ }
+ }
+
private static final class ManagedCursor {
ManagedCursor(Cursor cursor) {
mCursor = cursor;
@@ -1464,6 +1523,14 @@
}
/**
+ * Start a series of edit operations on the Fragments associated with
+ * this activity.
+ */
+ public FragmentTransaction openFragmentTransaction() {
+ return new FragmentTransactionImpl();
+ }
+
+ /**
* Wrapper around
* {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
* that gives the resulting {@link Cursor} to call
@@ -3743,6 +3810,8 @@
Configuration config) {
attachBaseContext(context);
+ mFragments.attachActivity(this);
+
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
@@ -3776,6 +3845,11 @@
return mParent != null ? mParent.getActivityToken() : mToken;
}
+ final void performCreate(Bundle icicle) {
+ onCreate(icicle);
+ mFragments.dispatchCreate(icicle);
+ }
+
final void performStart() {
mCalled = false;
mInstrumentation.callActivityOnStart(this);
@@ -3784,6 +3858,7 @@
"Activity " + mComponent.toShortString() +
" did not call through to super.onStart()");
}
+ mFragments.dispatchStart();
}
final void performRestart() {
@@ -3830,6 +3905,9 @@
// Now really resume, and install the current status bar and menu.
mResumed = true;
mCalled = false;
+
+ mFragments.dispatchResume();
+
onPostResume();
if (!mCalled) {
throw new SuperNotCalledException(
@@ -3839,6 +3917,7 @@
}
final void performPause() {
+ mFragments.dispatchPause();
onPause();
}
@@ -3853,6 +3932,8 @@
mWindow.closeAllPanels();
}
+ mFragments.dispatchStop();
+
mCalled = false;
mInstrumentation.callActivityOnStop(this);
if (!mCalled) {
@@ -3877,6 +3958,11 @@
mResumed = false;
}
+ final void performDestroy() {
+ mFragments.dispatchDestroy();
+ onDestroy();
+ }
+
final boolean isResumed() {
return mResumed;
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c9096cf..b3223e5 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -145,13 +145,6 @@
public static final int RECENT_WITH_EXCLUDED = 0x0001;
/**
- * @hide
- * TODO: Make this public. Provides a list that does not contain any
- * recent tasks that currently are not available to the user.
- */
- public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
-
- /**
* Return a list of the tasks that the user has recently launched, with
* the most recent being first and older ones after in order.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index fd84859..87b77d65 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1465,7 +1465,7 @@
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
- private static final String DB_INFO_FORMAT = " %8d %8d %10d %s";
+ private static final String DB_INFO_FORMAT = " %4d %6d %8d %14s %s";
// Formatting for checkin service - update version if row format changes
private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
@@ -1865,7 +1865,7 @@
for (int i = 0; i < stats.dbStats.size(); i++) {
DbStats dbStats = stats.dbStats.get(i);
printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
- dbStats.lookaside, dbStats.dbName);
+ dbStats.lookaside, dbStats.cache, dbStats.dbName);
pw.print(',');
}
@@ -1916,11 +1916,12 @@
int N = stats.dbStats.size();
if (N > 0) {
pw.println(" DATABASES");
- printRow(pw, " %8s %8s %10s %s", "Pagesize", "Dbsize", "Lookaside", "Dbname");
+ printRow(pw, " %4s %6s %8s %14s %s", "pgsz", "dbsz", "lkaside", "cache",
+ "Dbname");
for (int i = 0; i < N; i++) {
DbStats dbStats = stats.dbStats.get(i);
printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
- dbStats.lookaside, dbStats.dbName);
+ dbStats.lookaside, dbStats.cache, dbStats.dbName);
}
}
@@ -3642,7 +3643,7 @@
}
try {
r.activity.mCalled = false;
- r.activity.onDestroy();
+ mInstrumentation.callActivityOnDestroy(r.activity);
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + safeToComponentShortString(r.intent) +
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 15ce2b2..e646827 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2776,6 +2776,13 @@
return v != null ? v : defValue;
}
}
+
+ public Set<String> getStringSet(String key, Set<String> defValues) {
+ synchronized (this) {
+ Set<String> v = (Set<String>) mMap.get(key);
+ return v != null ? v : defValues;
+ }
+ }
public int getInt(String key, int defValue) {
synchronized (this) {
@@ -2818,6 +2825,12 @@
return this;
}
}
+ public Editor putStringSet(String key, Set<String> values) {
+ synchronized (this) {
+ mModified.put(key, values);
+ return this;
+ }
+ }
public Editor putInt(String key, int value) {
synchronized (this) {
mModified.put(key, value);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
new file mode 100644
index 0000000..c0dc869
--- /dev/null
+++ b/core/java/android/app/Fragment.java
@@ -0,0 +1,119 @@
+/*
+ * 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.app;
+
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A Fragment is a piece of an application's user interface or behavior
+ * that can be placed in an {@link Activity}.
+ */
+public class Fragment implements ComponentCallbacks {
+ static final int INITIALIZING = 0; // Not yet created.
+ static final int CREATED = 1; // Created.
+ static final int STARTED = 2; // Created and started, not resumed.
+ static final int RESUMED = 3; // Created started and resumed.
+
+ String mName;
+
+ int mState = INITIALIZING;
+ Activity mActivity;
+
+ boolean mCalled;
+ int mContainerId;
+
+ ViewGroup mContainer;
+ View mView;
+
+ public Fragment() {
+ }
+
+ public Fragment(String name) {
+ mName = name;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public Activity getActivity() {
+ return mActivity;
+ }
+
+ public void onAttach(Activity activity) {
+ mCalled = true;
+ }
+
+ public void onCreate(Bundle savedInstanceState) {
+ mCalled = true;
+ }
+
+ public View onCreateView(LayoutInflater inflater, ViewGroup container) {
+ return null;
+ }
+
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ }
+
+ public void onStart() {
+ mCalled = true;
+ }
+
+ public void onRestart() {
+ mCalled = true;
+ }
+
+ public void onResume() {
+ mCalled = true;
+ }
+
+ public void onSaveInstanceState(Bundle outState) {
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ mCalled = true;
+ }
+
+ public Object onRetainNonConfigurationInstance() {
+ return null;
+ }
+
+ public void onPause() {
+ mCalled = true;
+ }
+
+ public void onStop() {
+ mCalled = true;
+ }
+
+ public void onLowMemory() {
+ mCalled = true;
+ }
+
+ public void onDestroy() {
+ mCalled = true;
+ }
+
+ public void onDetach() {
+ mCalled = true;
+ }
+}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
new file mode 100644
index 0000000..d5e49cf
--- /dev/null
+++ b/core/java/android/app/FragmentManager.java
@@ -0,0 +1,183 @@
+/*
+ * 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.app;
+
+import android.os.Bundle;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Container for fragments associated with an activity.
+ */
+class FragmentManager {
+ final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
+
+ int mCurState = Fragment.INITIALIZING;
+ Activity mActivity;
+
+ void moveToState(Fragment f, int newState) {
+ if (f.mState < newState) {
+ switch (f.mState) {
+ case Fragment.INITIALIZING:
+ f.mActivity = mActivity;
+ f.mCalled = false;
+ f.onAttach(mActivity);
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onAttach()");
+ }
+ f.mCalled = false;
+ f.onCreate(null);
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onCreate()");
+ }
+
+ ViewGroup container = null;
+ if (f.mContainerId != 0) {
+ container = (ViewGroup)mActivity.findViewById(f.mContainerId);
+ if (container == null) {
+ throw new IllegalArgumentException("New view found for id 0x"
+ + Integer.toHexString(f.mContainerId)
+ + " for fragment " + f);
+ }
+ }
+ f.mContainer = container;
+ f.mView = f.onCreateView(mActivity.getLayoutInflater(), container);
+ if (container != null && f.mView != null) {
+ container.addView(f.mView);
+ }
+
+ case Fragment.CREATED:
+ if (newState > Fragment.CREATED) {
+ f.mCalled = false;
+ f.onStart();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onStart()");
+ }
+ }
+ case Fragment.STARTED:
+ if (newState > Fragment.STARTED) {
+ f.mCalled = false;
+ f.onResume();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onResume()");
+ }
+ }
+ }
+ } else if (f.mState > newState) {
+ switch (f.mState) {
+ case Fragment.RESUMED:
+ if (newState < Fragment.RESUMED) {
+ f.mCalled = false;
+ f.onPause();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onPause()");
+ }
+ }
+ case Fragment.STARTED:
+ if (newState < Fragment.STARTED) {
+ f.mCalled = false;
+ f.onStop();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onStop()");
+ }
+ }
+ case Fragment.CREATED:
+ if (newState < Fragment.CREATED) {
+ if (f.mContainer != null && f.mView != null) {
+ f.mContainer.removeView(f.mView);
+ }
+ f.mContainer = null;
+ f.mView = null;
+
+ f.mCalled = false;
+ f.onDestroy();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onDestroy()");
+ }
+ f.mCalled = false;
+ f.onDetach();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onDetach()");
+ }
+ f.mActivity = null;
+ }
+ }
+ }
+
+ f.mState = newState;
+ }
+
+ void moveToState(int newState) {
+ if (mActivity == null && newState != Fragment.INITIALIZING) {
+ throw new IllegalStateException("No activity");
+ }
+
+ mCurState = newState;
+ for (int i=0; i<mFragments.size(); i++) {
+ Fragment f = mFragments.get(i);
+ moveToState(f, newState);
+ }
+ }
+
+ public void addFragment(Fragment fragment) {
+ mFragments.add(fragment);
+ }
+
+ public void removeFragment(Fragment fragment) {
+ mFragments.remove(fragment);
+ moveToState(fragment, Fragment.INITIALIZING);
+ }
+
+ public void attachActivity(Activity activity) {
+ if (mActivity != null) throw new IllegalStateException();
+ mActivity = activity;
+ }
+
+ public void dispatchCreate(Bundle state) {
+ moveToState(Fragment.CREATED);
+ }
+
+ public void dispatchStart() {
+ moveToState(Fragment.STARTED);
+ }
+
+ public void dispatchResume() {
+ moveToState(Fragment.RESUMED);
+ }
+
+ public void dispatchPause() {
+ moveToState(Fragment.STARTED);
+ }
+
+ public void dispatchStop() {
+ moveToState(Fragment.CREATED);
+ }
+
+ public void dispatchDestroy() {
+ moveToState(Fragment.INITIALIZING);
+ mActivity = null;
+ }
+}
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
new file mode 100644
index 0000000..f97e510
--- /dev/null
+++ b/core/java/android/app/FragmentTransaction.java
@@ -0,0 +1,11 @@
+package android.app;
+
+/**
+ * API for performing a set of Fragment operations.
+ */
+public interface FragmentTransaction {
+ public FragmentTransaction add(Fragment fragment, int containerViewId);
+ public FragmentTransaction add(Fragment fragment, String name, int containerViewId);
+ public FragmentTransaction remove(Fragment fragment);
+ public void commit();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b8c3aa3..7ed7c49 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1072,7 +1072,7 @@
}
}
- activity.onDestroy();
+ activity.performDestroy();
if (mActivityMonitors != null) {
synchronized (mSync) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 30822d4..0afd6d2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1372,7 +1372,6 @@
public static final String SENSOR_SERVICE = "sensor";
/**
- * @hide
* Use with {@link #getSystemService} to retrieve a {@link
* android.os.storage.StorageManager} for accesssing system storage
* functions.
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index a15e29e..5847216 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -17,6 +17,7 @@
package android.content;
import java.util.Map;
+import java.util.Set;
/**
* Interface for accessing and modifying preference data returned by {@link
@@ -69,6 +70,17 @@
Editor putString(String key, String value);
/**
+ * Set a set of String values in the preferences editor, to be written
+ * back once {@link #commit} is called.
+ *
+ * @param key The name of the preference to modify.
+ * @param values The new values for the preference.
+ * @return Returns a reference to the same Editor object, so you can
+ * chain put calls together.
+ */
+ Editor putStringSet(String key, Set<String> values);
+
+ /**
* Set an int value in the preferences editor, to be written back once
* {@link #commit} is called.
*
@@ -186,6 +198,20 @@
String getString(String key, String defValue);
/**
+ * Retrieve a set of String values from the preferences.
+ *
+ * @param key The name of the preference to retrieve.
+ * @param defValues Values to return if this preference does not exist.
+ *
+ * @return Returns the preference values if they exist, or defValues.
+ * Throws ClassCastException if there is a preference with this name
+ * that is not a Set.
+ *
+ * @throws ClassCastException
+ */
+ Set<String> getStringSet(String key, Set<String> defValues);
+
+ /**
* Retrieve an int value from the preferences.
*
* @param key The name of the preference to retrieve.
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 455815f..18f69af 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,6 +16,8 @@
package android.content;
+import com.google.android.collect.Maps;
+
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
@@ -55,6 +57,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
@@ -126,14 +129,13 @@
private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
- private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
+ private static final String SYNC_WAKE_LOCK_PREFIX = "SyncWakeLock";
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
private Context mContext;
private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
- volatile private PowerManager.WakeLock mSyncWakeLock;
volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
@@ -195,6 +197,8 @@
private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0];
+ private final PowerManager mPowerManager;
+
public void onAccountsUpdated(Account[] accounts) {
// remember if this was the first time this was called after an update
final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
@@ -356,15 +360,13 @@
} else {
mNotificationMgr = null;
}
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mSyncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SYNC_WAKE_LOCK);
- mSyncWakeLock.setReferenceCounted(false);
+ mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
// This WakeLock is used to ensure that we stay awake between the time that we receive
// a sync alarm notification and when we finish processing it. We need to do this
// because we don't do the work in the alarm handler, rather we do it in a message
// handler.
- mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
HANDLE_SYNC_ALARM_WAKE_LOCK);
mHandleAlarmWakeLock.setReferenceCounted(false);
@@ -1302,6 +1304,9 @@
public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
private Long mAlarmScheduleTime = null;
public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
+ private PowerManager.WakeLock mSyncWakeLock;
+ private final HashMap<Pair<String, String>, PowerManager.WakeLock> mWakeLocks =
+ Maps.newHashMap();
// used to track if we have installed the error notification so that we don't reinstall
// it if sync is still failing
@@ -1315,6 +1320,18 @@
}
}
+ private PowerManager.WakeLock getSyncWakeLock(String accountType, String authority) {
+ final Pair<String, String> wakeLockKey = Pair.create(accountType, authority);
+ PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
+ if (wakeLock == null) {
+ final String name = SYNC_WAKE_LOCK_PREFIX + "_" + authority + "_" + accountType;
+ wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
+ wakeLock.setReferenceCounted(false);
+ mWakeLocks.put(wakeLockKey, wakeLock);
+ }
+ return wakeLock;
+ }
+
private void waitUntilReadyToRun() {
CountDownLatch latch = mReadyToRunLatch;
if (latch != null) {
@@ -1477,8 +1494,9 @@
}
} finally {
final boolean isSyncInProgress = mActiveSyncContext != null;
- if (!isSyncInProgress) {
+ if (!isSyncInProgress && mSyncWakeLock != null) {
mSyncWakeLock.release();
+ mSyncWakeLock = null;
}
manageSyncNotification();
manageErrorNotification();
@@ -1708,7 +1726,26 @@
return;
}
- mSyncWakeLock.acquire();
+ // Find the wakelock for this account and authority and store it in mSyncWakeLock.
+ // Be sure to release the previous wakelock so that we don't end up with it being
+ // held until it is used again.
+ // There are a couple tricky things about this code:
+ // - make sure that we acquire the new wakelock before releasing the old one,
+ // otherwise the device might go to sleep as soon as we release it.
+ // - since we use non-reference counted wakelocks we have to be sure not to do
+ // the release if the wakelock didn't change. Othewise we would do an
+ // acquire followed by a release on the same lock, resulting in no lock
+ // being held.
+ PowerManager.WakeLock oldWakeLock = mSyncWakeLock;
+ try {
+ mSyncWakeLock = getSyncWakeLock(op.account.type, op.authority);
+ mSyncWakeLock.acquire();
+ } finally {
+ if (oldWakeLock != null && oldWakeLock != mSyncWakeLock) {
+ oldWakeLock.release();
+ }
+ }
+
// no need to schedule an alarm, as that will be done by our caller.
// the next step will occur when we get either a timeout or a
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 038eedf..6f301da 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -16,15 +16,14 @@
package android.database;
+import android.app.ActivityThread;
import android.content.ContentResolver;
import android.net.Uri;
import android.util.Config;
import android.util.Log;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Looper;
-import android.os.Message;
+import android.os.Process;
import java.lang.ref.WeakReference;
import java.lang.UnsupportedOperationException;
@@ -88,8 +87,16 @@
}
mDataSetObservable.notifyInvalidated();
}
-
+
public boolean requery() {
+ // print a warning if this method is being called from Main (UI) thread
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ // yes this is UI thread.
+ String packageName = ActivityThread.currentPackageName();
+ Log.w(TAG, "should not attempt requery on main (UI) thread: app = " +
+ packageName == null ? "'unknown'" : packageName,
+ new RequeryOnUiThreadException(packageName));
+ }
if (mSelfObserver != null && mSelfObserverRegistered == false) {
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
mSelfObserverRegistered = true;
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index 79178f4..dc4471e 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -493,6 +493,10 @@
* contents. This may be done at any time, including after a call to {@link
* #deactivate}.
*
+ * Since this method could execute a query on the database and potentially take
+ * a while, it could cause ANR if it is called on Main (UI) thread.
+ * A warning is printed if this method is being executed on Main thread.
+ *
* @return true if the requery succeeded, false if not, in which case the
* cursor becomes invalid.
*/
diff --git a/core/java/android/database/DataSetObservable.java b/core/java/android/database/DataSetObservable.java
index 9200e81..51c72c1 100644
--- a/core/java/android/database/DataSetObservable.java
+++ b/core/java/android/database/DataSetObservable.java
@@ -27,8 +27,12 @@
*/
public void notifyChanged() {
synchronized(mObservers) {
- for (DataSetObserver observer : mObservers) {
- observer.onChanged();
+ // since onChanged() is implemented by the app, it could do anything, including
+ // removing itself from {@link mObservers} - and that could cause problems if
+ // an iterator is used on the ArrayList {@link mObservers}.
+ // to avoid such problems, just march thru the list in the reverse order.
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onChanged();
}
}
}
@@ -39,8 +43,8 @@
*/
public void notifyInvalidated() {
synchronized (mObservers) {
- for (DataSetObserver observer : mObservers) {
- observer.onInvalidated();
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onInvalidated();
}
}
}
diff --git a/core/java/android/database/DatabaseErrorHandler.java b/core/java/android/database/DatabaseErrorHandler.java
new file mode 100644
index 0000000..f0c5452
--- /dev/null
+++ b/core/java/android/database/DatabaseErrorHandler.java
@@ -0,0 +1,33 @@
+/*
+ * 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.database;
+
+import android.database.sqlite.SQLiteDatabase;
+
+/**
+ * An interface to let the apps define the actions to take when the following errors are detected
+ * database corruption
+ */
+public interface DatabaseErrorHandler {
+
+ /**
+ * defines the method to be invoked when database corruption is detected.
+ * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
+ * is detected.
+ */
+ void onCorruption(SQLiteDatabase dbObj);
+}
diff --git a/core/java/android/database/DefaultDatabaseErrorHandler.java b/core/java/android/database/DefaultDatabaseErrorHandler.java
new file mode 100644
index 0000000..98aa54a
--- /dev/null
+++ b/core/java/android/database/DefaultDatabaseErrorHandler.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.database;
+
+import java.io.File;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+import android.util.Pair;
+
+/**
+ * Default class used defining the actions to take when the following errors are detected
+ * database corruption
+ */
+public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler {
+
+ private static final String TAG = "DefaultDatabaseErrorHandler";
+
+ /**
+ * defines the default method to be invoked when database corruption is detected.
+ * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
+ * is detected.
+ */
+ public void onCorruption(SQLiteDatabase dbObj) {
+ Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath());
+
+ // is the corruption detected even before database could be 'opened'?
+ if (!dbObj.isOpen()) {
+ // database files are not even openable. delete this database file.
+ // NOTE if the database has attached databases, then any of them could be corrupt.
+ // and not deleting all of them could cause corrupted database file to remain and
+ // make the application crash on database open operation. To avoid this problem,
+ // the application should provide its own {@link DatabaseErrorHandler} impl class
+ // to delete ALL files of the database (including the attached databases).
+ if (!dbObj.getPath().equalsIgnoreCase(":memory")) {
+ // not memory database.
+ try {
+ new File(dbObj.getPath()).delete();
+ } catch (Exception e) {
+ /* ignore */
+ }
+ }
+ return;
+ }
+
+ try {
+ // Close the database, which will cause subsequent operations to fail.
+ try {
+ dbObj.close();
+ } catch (SQLiteException e) {
+ /* ignore */
+ }
+ } finally {
+ // Delete all files of this corrupt database and/or attached databases
+ for (Pair<String, String> p : dbObj.getAttachedDbs()) {
+ Log.e(TAG, "deleting the database file: " + p.second);
+ if (!p.second.equalsIgnoreCase(":memory:")) {
+ // delete file if it is a non-memory database file
+ try {
+ new File(p.second).delete();
+ } catch (Exception e) {
+ /* ignore */
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/database/RequeryOnUiThreadException.java b/core/java/android/database/RequeryOnUiThreadException.java
new file mode 100644
index 0000000..97a50d8
--- /dev/null
+++ b/core/java/android/database/RequeryOnUiThreadException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2006 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.database;
+
+/**
+ * An exception that indicates invoking {@link Cursor#requery()} on Main thread could cause ANR.
+ * This exception should encourage apps to invoke {@link Cursor#requery()} in a background thread.
+ * @hide
+ */
+public class RequeryOnUiThreadException extends RuntimeException {
+ public RequeryOnUiThreadException(String packageName) {
+ super("In " + packageName + " Requery is executing on main (UI) thread. could cause ANR. " +
+ "do it in background thread.");
+ }
+}
diff --git a/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java b/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
index 8ac4c0f..f28c70f 100644
--- a/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
+++ b/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
@@ -21,13 +21,11 @@
* that is not explicitly closed
* @hide
*/
-public class DatabaseObjectNotClosedException extends RuntimeException
-{
+public class DatabaseObjectNotClosedException extends RuntimeException {
private static final String s = "Application did not close the cursor or database object " +
"that was opened here";
- public DatabaseObjectNotClosedException()
- {
+ public DatabaseObjectNotClosedException() {
super(s);
}
}
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index 25aa9b3..d5c6ad1 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -134,6 +134,10 @@
mInUse = false;
}
+ /* package */ synchronized boolean isInUse() {
+ return mInUse;
+ }
+
/**
* Make sure that the native resource is cleaned up.
*/
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 70f681f..0a7eac2 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,12 +16,12 @@
package android.database.sqlite;
-import com.google.android.collect.Maps;
-
import android.app.ActivityThread;
import android.content.ContentValues;
import android.database.Cursor;
+import android.database.DatabaseErrorHandler;
import android.database.DatabaseUtils;
+import android.database.DefaultDatabaseErrorHandler;
import android.database.SQLException;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.os.Debug;
@@ -35,11 +35,11 @@
import java.io.File;
import java.lang.ref.WeakReference;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
@@ -251,7 +251,7 @@
private WeakHashMap<SQLiteClosable, Object> mPrograms;
/**
- * for each instance of this class, a cache is maintained to store
+ * for each instance of this class, a LRU cache is maintained to store
* the compiled query statement ids returned by sqlite database.
* key = sql statement with "?" for bind args
* value = {@link SQLiteCompiledSql}
@@ -263,15 +263,40 @@
* invoked.
*
* this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method
- * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because
- * most of the apps don't use "?" syntax in their sql, caching is not useful for them.
+ * (@link setMaxSqlCacheSize(int)}).
*/
- /* package */ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap();
+ // default statement-cache size per database connection ( = instance of this class)
+ private int mMaxSqlCacheSize = 25;
+ /* package */ Map<String, SQLiteCompiledSql> mCompiledQueries =
+ new LinkedHashMap<String, SQLiteCompiledSql>(mMaxSqlCacheSize + 1, 0.75f, true) {
+ @Override
+ public boolean removeEldestEntry(Map.Entry<String, SQLiteCompiledSql> eldest) {
+ // eldest = least-recently used entry
+ // if it needs to be removed to accommodate a new entry,
+ // close {@link SQLiteCompiledSql} represented by this entry, if not in use
+ // and then let it be removed from the Map.
+ synchronized(mCompiledQueries) { // probably not necessary, but can't hurt
+ if (this.size() <= mMaxSqlCacheSize) {
+ // cache is not full. nothing needs to be removed
+ return false;
+ }
+ // cache is full. eldest will be removed.
+ SQLiteCompiledSql entry = eldest.getValue();
+ if (!entry.isInUse()) {
+ // this {@link SQLiteCompiledSql} is not in use. release it.
+ entry.releaseSqlStatement();
+ }
+ // return true, so that this entry is removed automatically by the caller.
+ return true;
+ }
+ }
+ };
/**
- * @hide
+ * absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}
+ * size of each prepared-statement is between 1K - 6K, depending on the complexity of the
+ * sql statement & schema.
*/
- public static final int MAX_SQL_CACHE_SIZE = 250;
- private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance
+ public static final int MAX_SQL_CACHE_SIZE = 100;
private int mCacheFullWarnings;
private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1;
@@ -279,10 +304,6 @@
private int mNumCacheHits;
private int mNumCacheMisses;
- /** the following 2 members maintain the time when a database is opened and closed */
- private String mTimeOpened = null;
- private String mTimeClosed = null;
-
/** Used to find out where this object was created in case it never got closed. */
private Throwable mStackTrace = null;
@@ -290,6 +311,11 @@
private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold";
private final int mSlowQueryThreshold;
+ /** {@link DatabaseErrorHandler} to be used when SQLite returns any of the following errors
+ * Corruption
+ * */
+ private DatabaseErrorHandler errorHandler;
+
/**
* @param closable
*/
@@ -314,9 +340,6 @@
@Override
protected void onAllReferencesReleased() {
if (isOpen()) {
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- mTimeClosed = getTime();
- }
dbclose();
}
}
@@ -347,19 +370,8 @@
private boolean mLockingEnabled = true;
/* package */ void onCorruption() {
- Log.e(TAG, "Removing corrupt database: " + mPath);
EventLog.writeEvent(EVENT_DB_CORRUPT, mPath);
- try {
- // Close the database (if we can), which will cause subsequent operations to fail.
- close();
- } finally {
- // Delete the corrupt file. Don't re-create it now -- that would just confuse people
- // -- but the next time someone tries to open it, they can set it up from scratch.
- if (!mPath.equalsIgnoreCase(":memory")) {
- // delete is only for non-memory database files
- new File(mPath).delete();
- }
- }
+ errorHandler.onCorruption(this);
}
/**
@@ -811,10 +823,25 @@
* @throws SQLiteException if the database cannot be opened
*/
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
- SQLiteDatabase sqliteDatabase = null;
+ return openDatabase(path, factory, flags, new DefaultDatabaseErrorHandler());
+ }
+
+ /**
+ * same as {@link #openDatabase(String, CursorFactory, int)} except for an additional param
+ * errorHandler.
+ * @param errorHandler the {@link DatabaseErrorHandler} obj to be used when database
+ * corruption is detected on the database.
+ */
+ public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
+ DatabaseErrorHandler errorHandler) {
+ SQLiteDatabase sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+
+ // set the ErrorHandler to be used when SQLite reports exceptions
+ sqliteDatabase.errorHandler = errorHandler;
+
try {
// Open the database.
- sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+ sqliteDatabase.openDatabase(path, flags);
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
sqliteDatabase.enableSqlTracing(path);
}
@@ -822,14 +849,8 @@
sqliteDatabase.enableSqlProfiling(path);
}
} catch (SQLiteDatabaseCorruptException e) {
- // Try to recover from this, if we can.
- // TODO: should we do this for other open failures?
- Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
- EventLog.writeEvent(EVENT_DB_CORRUPT, path);
- if (!path.equalsIgnoreCase(":memory")) {
- // delete is only for non-memory database files
- new File(path).delete();
- }
+ // Database is not even openable.
+ errorHandler.onCorruption(sqliteDatabase);
sqliteDatabase = new SQLiteDatabase(path, factory, flags);
}
ActiveDatabases.getInstance().mActiveDatabases.add(
@@ -837,6 +858,18 @@
return sqliteDatabase;
}
+ private void openDatabase(String path, int flags) {
+ // Open the database.
+ dbopen(path, flags);
+ try {
+ setLocale(Locale.getDefault());
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Failed to setLocale(). closing the database", e);
+ dbclose();
+ throw e;
+ }
+ }
+
/**
* Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
*/
@@ -852,6 +885,17 @@
}
/**
+ * same as {@link #openOrCreateDatabase(String, CursorFactory)} except for an additional param
+ * errorHandler.
+ * @param errorHandler the {@link DatabaseErrorHandler} obj to be used when database
+ * corruption is detected on the database.
+ */
+ public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory,
+ DatabaseErrorHandler errorHandler) {
+ return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
+ }
+
+ /**
* Create a memory backed SQLite database. Its contents will be destroyed
* when the database is closed.
*
@@ -1809,25 +1853,7 @@
mSlowQueryThreshold = SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1);
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
mFactory = factory;
- dbopen(mPath, mFlags);
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- mTimeOpened = getTime();
- }
mPrograms = new WeakHashMap<SQLiteClosable,Object>();
- try {
- setLocale(Locale.getDefault());
- } catch (RuntimeException e) {
- Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e);
- dbclose();
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- mTimeClosed = getTime();
- }
- throw e;
- }
- }
-
- private String getTime() {
- return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis());
}
/**
@@ -1968,14 +1994,6 @@
* mapping is NOT replaced with the new mapping).
*/
/* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) {
- if (mMaxSqlCacheSize == 0) {
- // for this database, there is no cache of compiled sql.
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql);
- }
- return;
- }
-
SQLiteCompiledSql compiledSql = null;
synchronized(mCompiledQueries) {
// don't insert the new mapping if a mapping already exists
@@ -1983,35 +2001,30 @@
if (compiledSql != null) {
return;
}
- // add this <sql, compiledStatement> to the cache
+
if (mCompiledQueries.size() == mMaxSqlCacheSize) {
/*
* cache size of {@link #mMaxSqlCacheSize} is not enough for this app.
- * log a warning MAX_WARNINGS_ON_CACHESIZE_CONDITION times
- * chances are it is NOT using ? for bindargs - so caching is useless.
- * TODO: either let the callers set max cchesize for their app, or intelligently
- * figure out what should be cached for a given app.
+ * log a warning.
+ * chances are it is NOT using ? for bindargs - or cachesize is too small.
*/
if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) {
Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
- getPath() + "; i.e., NO space for this sql statement in cache: " +
- sql + ". Please change your sql statements to use '?' for " +
- "bindargs, instead of using actual values");
+ getPath() + ". Consider increasing cachesize.");
}
- // don't add this entry to cache
- } else {
- // cache is NOT full. add this to cache.
- mCompiledQueries.put(sql, compiledStatement);
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" +
- mCompiledQueries.size() + "|" + sql);
- }
+ }
+ /* add the given SQLiteCompiledSql compiledStatement to cache.
+ * no need to worry about the cache size - because {@link #mCompiledQueries}
+ * self-limits its size to {@link #mMaxSqlCacheSize}.
+ */
+ mCompiledQueries.put(sql, compiledStatement);
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" +
+ mCompiledQueries.size() + "|" + sql);
}
}
- return;
}
-
private void deallocCachedSqlStatements() {
synchronized (mCompiledQueries) {
for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) {
@@ -2029,13 +2042,6 @@
SQLiteCompiledSql compiledStatement = null;
boolean cacheHit;
synchronized(mCompiledQueries) {
- if (mMaxSqlCacheSize == 0) {
- // for this database, there is no cache of compiled sql.
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "|cache NOT found|" + getPath());
- }
- return null;
- }
cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null;
}
if (cacheHit) {
@@ -2048,63 +2054,23 @@
Log.v(TAG, "|cache_stats|" +
getPath() + "|" + mCompiledQueries.size() +
"|" + mNumCacheHits + "|" + mNumCacheMisses +
- "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql);
+ "|" + cacheHit + "|" + sql);
}
return compiledStatement;
}
/**
- * returns true if the given sql is cached in compiled-sql cache.
- * @hide
- */
- public boolean isInCompiledSqlCache(String sql) {
- synchronized(mCompiledQueries) {
- return mCompiledQueries.containsKey(sql);
- }
- }
-
- /**
- * purges the given sql from the compiled-sql cache.
- * @hide
- */
- public void purgeFromCompiledSqlCache(String sql) {
- synchronized(mCompiledQueries) {
- mCompiledQueries.remove(sql);
- }
- }
-
- /**
- * remove everything from the compiled sql cache
- * @hide
- */
- public void resetCompiledSqlCache() {
- synchronized(mCompiledQueries) {
- mCompiledQueries.clear();
- }
- }
-
- /**
- * return the current maxCacheSqlCacheSize
- * @hide
- */
- public synchronized int getMaxSqlCacheSize() {
- return mMaxSqlCacheSize;
- }
-
- /**
- * set the max size of the compiled sql cache for this database after purging the cache.
+ * set the max size of the prepared-statement cache for this database.
* (size of the cache = number of compiled-sql-statements stored in the cache).
*
- * max cache size can ONLY be increased from its current size (default = 0).
+ * max cache size can ONLY be increased from its current size (default = 10).
* if this method is called with smaller size than the current value of mMaxSqlCacheSize,
* then IllegalStateException is thrown
*
* synchronized because we don't want t threads to change cache size at the same time.
- * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE)
- * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or
- * < the value set with previous setMaxSqlCacheSize() call.
- *
- * @hide
+ * @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE})
+ * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or
+ * > the value set with previous setMaxSqlCacheSize() call.
*/
public synchronized void setMaxSqlCacheSize(int cacheSize) {
if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
@@ -2144,7 +2110,7 @@
String lastnode = path.substring((indx != -1) ? ++indx : 0);
// get list of attached dbs and for each db, get its size and pagesize
- ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(db);
+ ArrayList<Pair<String, String>> attachedDbs = db.getAttachedDbs();
if (attachedDbs == null) {
continue;
}
@@ -2169,7 +2135,8 @@
}
if (pageCount > 0) {
dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
- lookasideUsed));
+ lookasideUsed, db.mNumCacheHits, db.mNumCacheMisses,
+ db.mCompiledQueries.size()));
}
}
}
@@ -2197,24 +2164,74 @@
}
/**
- * returns list of full pathnames of all attached databases
- * including the main database
- * TODO: move this to {@link DatabaseUtils}
+ * returns list of full pathnames of all attached databases including the main database
+ * @return ArrayList of pairs of (database name, database file path) or null if the database
+ * is not open.
*/
- private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbObj) {
- if (!dbObj.isOpen()) {
+ public ArrayList<Pair<String, String>> getAttachedDbs() {
+ if (!isOpen()) {
return null;
}
ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>();
- Cursor c = dbObj.rawQuery("pragma database_list;", null);
- while (c.moveToNext()) {
- attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
+ Cursor c = null;
+ try {
+ c = rawQuery("pragma database_list;", null);
+ while (c.moveToNext()) {
+ // sqlite returns a row for each database in the returned list of databases.
+ // in each row,
+ // 1st column is the database name such as main, or the database
+ // name specified on the "ATTACH" command
+ // 2nd column is the database file path.
+ attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
}
- c.close();
return attachedDbs;
}
/**
+ * run pragma integrity_check on the given database (and all the attached databases)
+ * and return true if the given database (and all its attached databases) pass integrity_check,
+ * false otherwise.
+ *
+ * if the result is false, then this method logs the errors reported by the integrity_check
+ * command execution.
+ *
+ * @return true if the given database (and all its attached databases) pass integrity_check,
+ * false otherwise
+ */
+ public boolean isDatabaseIntegrityOk() {
+ if (!isOpen()) {
+ throw new IllegalStateException("database: " + getPath() + " is NOT open");
+ }
+ ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs();
+ if (attachedDbs == null) {
+ throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " +
+ "be retrieved. probably because the database is closed");
+ }
+ boolean isDatabaseCorrupt = false;
+ for (int i = 0; i < attachedDbs.size(); i++) {
+ Pair<String, String> p = attachedDbs.get(i);
+ SQLiteStatement prog = null;
+ try {
+ prog = compileStatement("PRAGMA " + p.first + ".integrity_check(1);");
+ String rslt = prog.simpleQueryForString();
+ if (!rslt.equalsIgnoreCase("ok")) {
+ // integrity_checker failed on main or attached databases
+ isDatabaseCorrupt = true;
+ Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt);
+ }
+ } finally {
+ if (prog != null) prog.close();
+ }
+ }
+ return isDatabaseCorrupt;
+ }
+
+ /**
* Native call to open the database.
*
* @param path The full path to the database
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index a4db6d9..2970c9e 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -134,11 +134,16 @@
/** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
public int lookaside;
- public DbStats(String dbName, long pageCount, long pageSize, int lookaside) {
+ /** statement cache stats: hits/misses/cachesize */
+ public String cache;
+
+ public DbStats(String dbName, long pageCount, long pageSize, int lookaside,
+ int hits, int misses, int cachesize) {
this.dbName = dbName;
- this.pageSize = pageSize;
+ this.pageSize = pageSize / 1024;
dbSize = (pageCount * pageSize) / 1024;
this.lookaside = lookaside;
+ this.cache = hits + "/" + misses + "/" + cachesize;
}
}
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 52aac3a..d4907d9 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -99,6 +99,10 @@
}
int version = db.getVersion();
+ if (version > mNewVersion) {
+ throw new IllegalStateException("Database " + mName +
+ " cannot be downgraded. instead, please uninstall new version first.");
+ }
if (version != mNewVersion) {
db.beginTransaction();
try {
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 89a5f0d1..a9c1ac6 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -147,7 +147,7 @@
* @return a unique identifier for this program
*/
public final int getUniqueId() {
- return nStatement;
+ return (mCompiledSql != null) ? mCompiledSql.nStatement : 0;
}
/* package */ String getSqlString() {
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
index 7b883a7..d3d39d6 100644
--- a/core/java/android/os/storage/StorageEventListener.java
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -18,7 +18,6 @@
/**
* Used for receiving notifications from the StorageManager
- * @hide
*/
public abstract class StorageEventListener {
/**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index a12603c..b49979c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -45,8 +45,6 @@
* {@link android.content.Context#getSystemService(java.lang.String)} with an argument
* of {@link android.content.Context#STORAGE_SERVICE}.
*
- * @hide
- *
*/
public class StorageManager
diff --git a/core/java/android/os/storage/StorageResultCode.java b/core/java/android/os/storage/StorageResultCode.java
index 075f47f..07d95df 100644
--- a/core/java/android/os/storage/StorageResultCode.java
+++ b/core/java/android/os/storage/StorageResultCode.java
@@ -19,8 +19,6 @@
/**
* Class that provides access to constants returned from StorageManager
* and lower level MountService APIs.
- *
- * @hide
*/
public class StorageResultCode
{
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index 635323e..282417d 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -181,7 +181,9 @@
boolean inUtc = start.parse(dtstart);
boolean allDay = start.allDay;
- if (inUtc) {
+ // We force TimeZone to UTC for "all day recurring events" as the server is sending no
+ // TimeZone in DTSTART for them
+ if (inUtc || allDay) {
tzid = Time.TIMEZONE_UTC;
}
@@ -204,10 +206,7 @@
}
if (allDay) {
- // TODO: also change tzid to be UTC? that would be consistent, but
- // that would not reflect the original timezone value back to the
- // server.
- start.timezone = Time.TIMEZONE_UTC;
+ start.timezone = Time.TIMEZONE_UTC;
}
long millis = start.toMillis(false /* use isDst */);
values.put(Calendar.Events.DTSTART, millis);
diff --git a/core/java/android/pim/vcard/JapaneseUtils.java b/core/java/android/pim/vcard/JapaneseUtils.java
index 875c29e..dcfe980 100644
--- a/core/java/android/pim/vcard/JapaneseUtils.java
+++ b/core/java/android/pim/vcard/JapaneseUtils.java
@@ -27,7 +27,6 @@
new HashMap<Character, String>();
static {
- // There's no logical mapping rule in Unicode. Sigh.
sHalfWidthMap.put('\u3001', "\uFF64");
sHalfWidthMap.put('\u3002', "\uFF61");
sHalfWidthMap.put('\u300C', "\uFF62");
@@ -366,11 +365,11 @@
}
/**
- * Return half-width version of that character if possible. Return null if not possible
+ * Returns half-width version of that character if possible. Returns null if not possible
* @param ch input character
* @return CharSequence object if the mapping for ch exists. Return null otherwise.
*/
- public static String tryGetHalfWidthText(char ch) {
+ public static String tryGetHalfWidthText(final char ch) {
if (sHalfWidthMap.containsKey(ch)) {
return sHalfWidthMap.get(ch);
} else {
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
index 0a6415d..789b5f8 100644
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -47,7 +47,23 @@
import java.util.Set;
/**
- * The class which lets users create their own vCard String.
+ * <p>
+ * The class which lets users create their own vCard String. Typical usage is as follows:
+ * </p>
+ * <pre class="prettyprint">final VCardBuilder builder = new VCardBuilder(vcardType);
+ * builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ * .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ * .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ * .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ * .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ * .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ * .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+ * .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+ * .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ * .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ * .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ * .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+ * return builder.toString();</pre>
*/
public class VCardBuilder {
private static final String LOG_TAG = "VCardBuilder";
@@ -75,13 +91,14 @@
private static final String VCARD_WS = " ";
private static final String VCARD_PARAM_EQUAL = "=";
- private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
- private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
- private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
+ private static final String VCARD_PARAM_ENCODING_QP =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_QP;
+ private static final String VCARD_PARAM_ENCODING_BASE64_V21 =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_BASE64;
+ private static final String VCARD_PARAM_ENCODING_BASE64_V30 =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_B;
private static final String SHIFT_JIS = "SHIFT_JIS";
- private static final String UTF_8 = "UTF-8";
private final int mVCardType;
@@ -92,21 +109,28 @@
private final boolean mShouldUseQuotedPrintable;
private final boolean mUsesAndroidProperty;
private final boolean mUsesDefactProperty;
- private final boolean mUsesUtf8;
- private final boolean mUsesShiftJis;
private final boolean mAppendTypeParamName;
private final boolean mRefrainsQPToNameProperties;
private final boolean mNeedsToConvertPhoneticString;
private final boolean mShouldAppendCharsetParam;
- private final String mCharsetString;
+ private final String mCharset;
private final String mVCardCharsetParameter;
private StringBuilder mBuilder;
private boolean mEndAppended;
public VCardBuilder(final int vcardType) {
+ // Default charset should be used
+ this(vcardType, null);
+ }
+
+ /**
+ * @param vcardType
+ * @param charset If null, we use default charset for export.
+ */
+ public VCardBuilder(final int vcardType, String charset) {
mVCardType = vcardType;
mIsV30 = VCardConfig.isV30(vcardType);
@@ -116,40 +140,74 @@
mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
- mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
- mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
- mShouldAppendCharsetParam = !(mIsV30 && mUsesUtf8);
+ // vCard 2.1 requires charset.
+ // vCard 3.0 does not allow it but we found some devices use it to determine
+ // the exact charset.
+ // We currently append it only when charset other than UTF_8 is used.
+ mShouldAppendCharsetParam = !(mIsV30 && "UTF-8".equalsIgnoreCase(charset));
- if (mIsDoCoMo) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
+ if (VCardConfig.isDoCoMo(vcardType)) {
+ if (!SHIFT_JIS.equalsIgnoreCase(charset)) {
+ Log.w(LOG_TAG,
+ "The charset \"" + charset + "\" is used while "
+ + SHIFT_JIS + " is needed to be used.");
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = SHIFT_JIS;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
+ } else {
+ if (mIsDoCoMo) {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "DoCoMo-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "Career-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ }
+ mCharset = charset;
}
- mCharsetString = charset;
- // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
- // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
- // Android, not shown to the public).
- mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
- } else if (mUsesShiftJis) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
- }
- mCharsetString = charset;
mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
} else {
- mCharsetString = UTF_8;
- mVCardCharsetParameter = "CHARSET=" + UTF_8;
+ if (TextUtils.isEmpty(charset)) {
+ Log.i(LOG_TAG,
+ "Use the charset \"" + VCardConfig.DEFAULT_EXPORT_CHARSET
+ + "\" for export.");
+ mCharset = VCardConfig.DEFAULT_EXPORT_CHARSET;
+ mVCardCharsetParameter = "CHARSET=" + VCardConfig.DEFAULT_EXPORT_CHARSET;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ mVCardCharsetParameter = "CHARSET=" + charset;
+ }
}
clear();
}
@@ -379,8 +437,8 @@
mBuilder.append(VCardConstants.PROPERTY_FN);
// Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
- // when it would be useful for external importers, assuming no external
- // importer allows this vioration.
+ // when it would be useful or necessary for external importers,
+ // assuming the external importer allows this vioration of the spec.
if (shouldAppendCharsetParam(displayName)) {
mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(mVCardCharsetParameter);
@@ -454,18 +512,18 @@
mBuilder.append(VCARD_END_OF_LINE);
} else if (mIsJapaneseMobilePhone) {
// Note: There is no appropriate property for expressing
- // phonetic name in vCard 2.1, while there is in
+ // phonetic name (Yomigana in Japanese) in vCard 2.1, while there is in
// vCard 3.0 (SORT-STRING).
- // We chose to use DoCoMo's way when the device is Japanese one
- // since it is supported by
- // a lot of Japanese mobile phones. This is "X-" property, so
- // any parser hopefully would not get confused with this.
+ // We use DoCoMo's way when the device is Japanese one since it is already
+ // supported by a lot of Japanese mobile phones.
+ // This is "X-" property, so any parser hopefully would not get
+ // confused with this.
//
// Also, DoCoMo's specification requires vCard composer to use just the first
// column.
// i.e.
- // o SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
- // x SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
+ // good: SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
+ // bad : SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
mBuilder.append(VCardConstants.PROPERTY_SOUND);
mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
@@ -519,10 +577,10 @@
mBuilder.append(encodedPhoneticGivenName);
}
}
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // family;given
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // given;middle
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // middle;prefix
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // prefix;suffix
mBuilder.append(VCARD_END_OF_LINE);
}
@@ -549,7 +607,7 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticGivenName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticMiddleName)) {
final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable &&
@@ -572,7 +630,7 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticMiddleName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticFamilyName)) {
final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable &&
@@ -595,7 +653,7 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticFamilyName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticFamilyName))
}
}
@@ -642,22 +700,18 @@
if (TextUtils.isEmpty(phoneNumber)) {
continue;
}
- int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
- if (type == Phone.TYPE_PAGER) {
+
+ // PAGER number needs unformatted "phone number".
+ final int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
+ if (type == Phone.TYPE_PAGER ||
+ VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
phoneLineExists = true;
if (!phoneSet.contains(phoneNumber)) {
phoneSet.add(phoneNumber);
appendTelLine(type, label, phoneNumber, isPrimary);
}
} else {
- // The entry "may" have several phone numbers when the contact entry is
- // corrupted because of its original source.
- //
- // e.g. I encountered the entry like the following.
- // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
- // This kind of entry is not able to be inserted via Android devices, but
- // possible if the source of the data is already corrupted.
- List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
+ final List<String> phoneNumberList = splitAndTrimPhoneNumbers(phoneNumber);
if (phoneNumberList.isEmpty()) {
continue;
}
@@ -670,7 +724,7 @@
phoneSet.add(actualPhoneNumber);
appendTelLine(type, label, formattedPhoneNumber, isPrimary);
}
- }
+ } // for (String actualPhoneNumber : phoneNumberList) {
}
}
}
@@ -682,15 +736,38 @@
return this;
}
- private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
- List<String> phoneList = new ArrayList<String>();
+ /**
+ * <p>
+ * Splits a given string expressing phone numbers into several strings, and remove
+ * unnecessary characters inside them. The size of a returned list becomes 1 when
+ * no split is needed.
+ * </p>
+ * <p>
+ * The given number "may" have several phone numbers when the contact entry is corrupted
+ * because of its original source.
+ * e.g. "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami)"
+ * </p>
+ * <p>
+ * This kind of "phone numbers" will not be created with Android vCard implementation,
+ * but we may encounter them if the source of the input data has already corrupted
+ * implementation.
+ * </p>
+ * <p>
+ * To handle this case, this method first splits its input into multiple parts
+ * (e.g. "111-222-3333 (Miami)", "444-555-6666 (Broward", and 305653-6796 (Miami)") and
+ * removes unnecessary strings like "(Miami)".
+ * </p>
+ * <p>
+ * Do not call this method when trimming is inappropriate for its receivers.
+ * </p>
+ */
+ private List<String> splitAndTrimPhoneNumbers(final String phoneNumber) {
+ final List<String> phoneList = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
final int length = phoneNumber.length();
for (int i = 0; i < length; i++) {
final char ch = phoneNumber.charAt(i);
- // TODO: add a test case for string with '+', and care the other possible issues
- // which may happen by ignoring non-digits other than '+'.
if (Character.isDigit(ch) || ch == '+') {
builder.append(ch);
} else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
@@ -903,21 +980,21 @@
encodedCountry = escapeCharacters(rawCountry);
encodedNeighborhood = escapeCharacters(rawNeighborhood);
}
- final StringBuffer addressBuffer = new StringBuffer();
- addressBuffer.append(encodedPoBox);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedStreet);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedLocality);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedRegion);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedPostalCode);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedCountry);
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(encodedPoBox);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(encodedStreet);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(encodedLocality);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(encodedRegion);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(encodedPostalCode);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
+ addressBuilder.append(encodedCountry);
return new PostalStruct(
- reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
} else { // VCardUtils.areAllEmpty(rawAddressArray) == true
// Try to use FORMATTED_ADDRESS instead.
final String rawFormattedAddress =
@@ -940,16 +1017,16 @@
// We use the second value ("Extended Address") just because Japanese mobile phones
// do so. If the other importer expects the value be in the other field, some flag may
// be needed.
- final StringBuffer addressBuffer = new StringBuffer();
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedFormattedAddress);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(encodedFormattedAddress);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
return new PostalStruct(
- reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
}
}
@@ -1146,6 +1223,8 @@
}
public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
+ // There's possibility where a given object may have more than one birthday, which
+ // is inappropriate. We just build one birthday.
if (contentValuesList != null) {
String primaryBirthday = null;
String secondaryBirthday = null;
@@ -1213,16 +1292,19 @@
return this;
}
+ /**
+ * @param emitEveryTime If true, builder builds the line even when there's no entry.
+ */
public void appendPostalLine(final int type, final String label,
final ContentValues contentValues,
- final boolean isPrimary, final boolean emitLineEveryTime) {
+ final boolean isPrimary, final boolean emitEveryTime) {
final boolean reallyUseQuotedPrintable;
final boolean appendCharset;
final String addressValue;
{
PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
if (postalStruct == null) {
- if (emitLineEveryTime) {
+ if (emitEveryTime) {
reallyUseQuotedPrintable = false;
appendCharset = false;
addressValue = "";
@@ -1537,7 +1619,8 @@
mBuilder.append(VCARD_END_OF_LINE);
}
- public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) {
+ public void appendAndroidSpecificProperty(
+ final String mimeType, ContentValues contentValues) {
if (!sAllowedAndroidPropertySet.contains(mimeType)) {
return;
}
@@ -1659,7 +1742,7 @@
encodedValue = encodeQuotedPrintable(rawValue);
} else {
// TODO: one line may be too huge, which may be invalid in vCard spec, though
- // several (even well-known) applications do not care this.
+ // several (even well-known) applications do not care that violation.
encodedValue = escapeCharacters(rawValue);
}
@@ -1794,9 +1877,9 @@
byte[] strArray = null;
try {
- strArray = str.getBytes(mCharsetString);
+ strArray = str.getBytes(mCharset);
} catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+ Log.e(LOG_TAG, "Charset " + mCharset + " cannot be used. "
+ "Try default charset");
strArray = str.getBytes();
}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 0e8b665..170d6fa 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -41,6 +41,7 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.TextUtils;
import android.util.CharsetUtils;
import android.util.Log;
@@ -61,15 +62,11 @@
/**
* <p>
- * The class for composing VCard from Contacts information. Note that this is
- * completely differnt implementation from
- * android.syncml.pim.vcard.VCardComposer, which is not maintained anymore.
+ * The class for composing vCard from Contacts information.
* </p>
- *
* <p>
* Usually, this class should be used like this.
* </p>
- *
* <pre class="prettyprint">VCardComposer composer = null;
* try {
* composer = new VCardComposer(context);
@@ -93,15 +90,18 @@
* if (composer != null) {
* composer.terminate();
* }
- * } </pre>
+ * }</pre>
+ * <p>
+ * Users have to manually take care of memory efficiency. Even one vCard may contain
+ * image of non-trivial size for mobile devices.
+ * </p>
+ * <p>
+ * {@link VCardBuilder} is used to build each vCard.
+ * </p>
*/
public class VCardComposer {
private static final String LOG_TAG = "VCardComposer";
- public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
- public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
- public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
-
public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
"Failed to get database information";
@@ -119,6 +119,8 @@
public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
+ // Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here,
+ // since usual vCard devices for Japanese devices already use it.
private static final String SHIFT_JIS = "SHIFT_JIS";
private static final String UTF_8 = "UTF-8";
@@ -141,7 +143,7 @@
sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
- // Google talk is a special case.
+ // We don't add Google talk here since it has to be handled separately.
}
public static interface OneEntryHandler {
@@ -152,37 +154,37 @@
/**
* <p>
- * An useful example handler, which emits VCard String to outputstream one by one.
+ * An useful handler for emitting vCard String to an OutputStream object one by one.
* </p>
* <p>
* The input OutputStream object is closed() on {@link #onTerminate()}.
- * Must not close the stream outside.
+ * Must not close the stream outside this class.
* </p>
*/
- public class HandlerForOutputStream implements OneEntryHandler {
+ public final class HandlerForOutputStream implements OneEntryHandler {
@SuppressWarnings("hiding")
- private static final String LOG_TAG = "vcard.VCardComposer.HandlerForOutputStream";
-
- final private OutputStream mOutputStream; // mWriter will close this.
- private Writer mWriter;
+ private static final String LOG_TAG = "VCardComposer.HandlerForOutputStream";
private boolean mOnTerminateIsCalled = false;
+ private final OutputStream mOutputStream; // mWriter will close this.
+ private Writer mWriter;
+
/**
* Input stream will be closed on the detruction of this object.
*/
- public HandlerForOutputStream(OutputStream outputStream) {
+ public HandlerForOutputStream(final OutputStream outputStream) {
mOutputStream = outputStream;
}
- public boolean onInit(Context context) {
+ public boolean onInit(final Context context) {
try {
mWriter = new BufferedWriter(new OutputStreamWriter(
- mOutputStream, mCharsetString));
+ mOutputStream, mCharset));
} catch (UnsupportedEncodingException e1) {
- Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString);
+ Log.e(LOG_TAG, "Unsupported charset: " + mCharset);
mErrorReason = "Encoding is not supported (usually this does not happen!): "
- + mCharsetString;
+ + mCharset;
return false;
}
@@ -235,14 +237,19 @@
"IOException during closing the output stream: "
+ e.getMessage());
} finally {
- try {
- mWriter.close();
- } catch (IOException e) {
- }
+ closeOutputStream();
}
}
}
+ public void closeOutputStream() {
+ try {
+ mWriter.close();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring.");
+ }
+ }
+
@Override
public void finalize() {
if (!mOnTerminateIsCalled) {
@@ -257,11 +264,10 @@
private final ContentResolver mContentResolver;
private final boolean mIsDoCoMo;
- private final boolean mUsesShiftJis;
private Cursor mCursor;
private int mIdColumn;
- private final String mCharsetString;
+ private final String mCharset;
private boolean mTerminateIsCalled;
private final List<OneEntryHandler> mHandlerList;
@@ -272,21 +278,39 @@
};
public VCardComposer(Context context) {
- this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
+ this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true);
}
+ /**
+ * The variant which sets charset to null and sets careHandlerErrors to true.
+ */
public VCardComposer(Context context, int vcardType) {
- this(context, vcardType, true);
+ this(context, vcardType, null, true);
}
- public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
- this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
+ public VCardComposer(Context context, int vcardType, String charset) {
+ this(context, vcardType, charset, true);
+ }
+
+ /**
+ * The variant which sets charset to null.
+ */
+ public VCardComposer(final Context context, final int vcardType,
+ final boolean careHandlerErrors) {
+ this(context, vcardType, null, careHandlerErrors);
}
/**
* Construct for supporting call log entry vCard composing.
+ *
+ * @param context Context to be used during the composition.
+ * @param vcardType The type of vCard, typically available via {@link VCardConfig}.
+ * @param charset The charset to be used. Use null when you don't need the charset.
+ * @param careHandlerErrors If true, This object returns false everytime
+ * a Handler object given via {{@link #addHandler(OneEntryHandler)} returns false.
+ * If false, this ignores those errors.
*/
- public VCardComposer(final Context context, final int vcardType,
+ public VCardComposer(final Context context, final int vcardType, String charset,
final boolean careHandlerErrors) {
mContext = context;
mVCardType = vcardType;
@@ -294,30 +318,67 @@
mContentResolver = context.getContentResolver();
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
- if (mIsDoCoMo) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
+ charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
+ final boolean shouldAppendCharsetParam = !(
+ VCardConfig.isV30(vcardType) && UTF_8.equalsIgnoreCase(charset));
+
+ if (mIsDoCoMo || shouldAppendCharsetParam) {
+ if (SHIFT_JIS.equalsIgnoreCase(charset)) {
+ if (mIsDoCoMo) {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "DoCoMo-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "Career-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ }
+ mCharset = charset;
+ } else {
+ Log.w(LOG_TAG,
+ "The charset \"" + charset + "\" is used while "
+ + SHIFT_JIS + " is needed to be used.");
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = SHIFT_JIS;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
}
- mCharsetString = charset;
- } else if (mUsesShiftJis) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
- }
- mCharsetString = charset;
} else {
- mCharsetString = UTF_8;
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = UTF_8;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
}
+
+ Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
}
/**
@@ -351,7 +412,7 @@
}
if (mCareHandlerErrors) {
- List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
+ final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
for (OneEntryHandler handler : mHandlerList) {
if (!handler.onInit(mContext)) {
@@ -414,7 +475,7 @@
mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
return false;
}
- String vcard;
+ final String vcard;
try {
if (mIdColumn >= 0) {
vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
@@ -437,8 +498,7 @@
mCursor.moveToNext();
}
- // This function does not care the OutOfMemoryError on the handler side
- // :-P
+ // This function does not care the OutOfMemoryError on the handler side :-P
if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
@@ -457,7 +517,7 @@
}
private String createOneEntryInternal(final String contactId,
- Method getEntityIteratorMethod) throws VCardException {
+ final Method getEntityIteratorMethod) throws VCardException {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
// The resolver may return the entity iterator with no data. It is possible.
@@ -471,7 +531,7 @@
final String selection = Data.CONTACT_ID + "=?";
final String[] selectionArgs = new String[] {contactId};
if (getEntityIteratorMethod != null) {
- // Please note that this branch is executed by some tests only
+ // Please note that this branch is executed by unit tests only
try {
entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
mContentResolver, uri, selection, selectionArgs, null);
@@ -527,22 +587,33 @@
}
}
- final VCardBuilder builder = new VCardBuilder(mVCardType);
- builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
- .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
- .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
- .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
- .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
- .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
- .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));
- if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
- builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
+ return buildVCard(contentValuesListMap);
+ }
+
+ /**
+ * Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
+ * {ContactsContract}. Developers can override this method to customize the output.
+ */
+ public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
+ if (contentValuesListMap == null) {
+ Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
+ return "";
+ } else {
+ final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);
+ builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+ .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+ .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+ return builder.toString();
}
- builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
- .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
- .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
- .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
- return builder.toString();
}
public void terminate() {
@@ -565,26 +636,38 @@
@Override
public void finalize() {
if (!mTerminateIsCalled) {
+ Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step.");
terminate();
}
}
+ /**
+ * @return returns the number of available entities. The return value is undefined
+ * when this object is not ready yet (typically when {{@link #init()} is not called
+ * or when {@link #terminate()} is already called).
+ */
public int getCount() {
if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
return 0;
}
return mCursor.getCount();
}
+ /**
+ * @return true when there's no entity to be built. The return value is undefined
+ * when this object is not ready yet.
+ */
public boolean isAfterLast() {
if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
return false;
}
return mCursor.isAfterLast();
}
/**
- * @return Return the error reason if possible.
+ * @return Returns the error reason.
*/
public String getErrorReason() {
return mErrorReason;
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 3409be6..6c25216 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,6 +15,7 @@
*/
package android.pim.vcard;
+import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
@@ -37,16 +38,32 @@
/* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
- /* package */ static final int PARSE_TYPE_UNKNOWN = 0;
- /* package */ static final int PARSE_TYPE_APPLE = 1;
- /* package */ static final int PARSE_TYPE_MOBILE_PHONE_JP = 2; // For Japanese mobile phones.
- /* package */ static final int PARSE_TYPE_FOMA = 3; // For Japanese FOMA mobile phones.
- /* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4;
+ /**
+ * <p>
+ * The charset used during import.
+ * </p>
+ * <p>
+ * We cannot determine which charset should be used to interpret a given vCard file
+ * at first, while we have to decode sime encoded data (e.g. BASE64) to binary.
+ * In order to avoid "misinterpretation" of charset as much as possible,
+ * "ISO-8859-1" (a.k.a Latin-1) is first used for reading a stream.
+ * When charset is specified in a property (with "CHARSET=..." parameter),
+ * the string is decoded to raw bytes and encoded into the specific charset,
+ * assuming "ISO-8859-1" is able to map "all" 8bit characters to some unicode,
+ * and it has 1 to 1 mapping in all 8bit characters.
+ * If the assumption is not correct, this setting will cause some bug.
+ * </p>
+ * @hide made public just for unit test
+ */
+ public static final String DEFAULT_INTERMEDIATE_CHARSET = "ISO-8859-1";
- // Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and
- // decode the unicode to the original charset. If not, this setting will cause some bug.
- public static final String DEFAULT_CHARSET = "iso-8859-1";
-
+ /**
+ * The charset used when there's no information affbout what charset should be used to
+ * encode the binary given from vCard.
+ */
+ public static final String DEFAULT_IMPORT_CHARSET = "UTF-8";
+ public static final String DEFAULT_EXPORT_CHARSET = "UTF-8";
+
public static final int FLAG_V21 = 0;
public static final int FLAG_V30 = 1;
@@ -58,311 +75,329 @@
private static final int NAME_ORDER_MASK = 0xC;
// 0x10 is reserved for safety
-
- private static final int FLAG_CHARSET_UTF8 = 0;
- private static final int FLAG_CHARSET_SHIFT_JIS = 0x100;
- private static final int FLAG_CHARSET_MASK = 0xF00;
/**
+ * <p>
* The flag indicating the vCard composer will add some "X-" properties used only in Android
* when the formal vCard specification does not have appropriate fields for that data.
- *
+ * </p>
+ * <p>
* For example, Android accepts nickname information while vCard 2.1 does not.
* When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
* instead of just dropping it.
- *
+ * </p>
+ * <p>
* vCard parser code automatically parses the field emitted even when this flag is off.
- *
- * Note that this flag does not assure all the information must be hold in the emitted vCard.
+ * </p>
*/
private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
/**
+ * <p>
* The flag indicating the vCard composer will add some "X-" properties seen in the
* vCard data emitted by the other softwares/devices when the formal vCard specification
- * does not have appropriate field(s) for that data.
- *
+ * does not have appropriate field(s) for that data.
+ * </p>
+ * <p>
* One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are
* for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
* non-Android devices/softwares. We chose to enable the vCard composer to use those
* defact properties since they are also useful for Android devices.
- *
+ * </p>
+ * <p>
* Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
* allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
* in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
+ * </p>
*/
private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
/**
- * The flag indicating some specific dialect seen in vcard of DoCoMo (one of Japanese
+ * <p>
+ * The flag indicating some specific dialect seen in vCard of DoCoMo (one of Japanese
* mobile careers) should be used. This flag does not include any other information like
* that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's
* dialect but the name order should be European", but it is not recommended.
+ * </p>
*/
private static final int FLAG_DOCOMO = 0x20000000;
/**
- * <P>
+ * <p>
* The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
* properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
- * </P>
- * <P>
+ * </p>
+ * <p>
* We actually cannot define what is the "primary" property. Note that this is NOT defined
* in vCard specification either. Also be aware that it is NOT related to "primary" notion
* used in {@link android.provider.ContactsContract}.
* This notion is just for vCard composition in Android.
- * </P>
- * <P>
+ * </p>
+ * <p>
* We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
* do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc.
* even when their values contain non-ascii or/and CR/LF, while they use the encoding in the
* other properties like "ADR", "ORG", etc.
- * <P>
+ * <p>
* We are afraid of the case where some vCard importer also forget handling QP presuming QP is
* not used in such fields.
- * </P>
- * <P>
+ * </p>
+ * <p>
* This flag is useful when some target importer you are going to focus on does not accept
* such properties with Quoted-Printable encoding.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Again, we should not use this flag at all for complying vCard 2.1 spec.
- * </P>
- * <P>
+ * </p>
+ * <p>
* In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
* kind of problem (hopefully).
- * </P>
+ * </p>
+ * @hide
*/
public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
/**
- * <P>
+ * <p>
* The flag indicating that phonetic name related fields must be converted to
* appropriate form. Note that "appropriate" is not defined in any vCard specification.
* This is Android-specific.
- * </P>
- * <P>
+ * </p>
+ * <p>
* One typical (and currently sole) example where we need this flag is the time when
* we need to emit Japanese phonetic names into vCard entries. The property values
* should be encoded into half-width katakana when the target importer is Japanese mobile
* phones', which are probably not able to parse full-width hiragana/katakana for
* historical reasons, while the vCard importers embedded to softwares for PC should be
* able to parse them as we expect.
- * </P>
+ * </p>
*/
- public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000;
+ public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x08000000;
/**
- * <P>
+ * <p>
* The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params
* every time possible. The default behavior does not emit it and is valid in the spec.
* In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Detail:
* How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
* </p>
- * <P>
- * e.g.<BR />
- * 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."<BR />
- * 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."<BR />
- * 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."<BR />
- * </P>
- * <P>
- * 2) had been the default of VCard exporter/importer in Android, but it is found that
- * some external exporter is not able to parse the type format like 2) but only 3).
- * </P>
- * <P>
+ * <p>
+ * e.g.
+ * </p>
+ * <ol>
+ * <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li>
+ * <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li>
+ * <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li>
+ * </ol>
+ * <p>
* If you are targeting to the importer which cannot accept TYPE params without "TYPE="
* strings (which should be rare though), please use this flag.
- * </P>
- * <P>
- * Example usage: int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
- * </P>
+ * </p>
+ * <p>
+ * Example usage:
+ * <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre>
+ * </p>
*/
public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
/**
- * <P>
- * The flag asking exporter to refrain image export.
- * </P>
- * @hide will be deleted in the near future.
+ * <p>
+ * The flag indicating the vCard composer does touch nothing toward phone number Strings
+ * but leave it as is.
+ * </p>
+ * <p>
+ * The vCard specifications mention nothing toward phone numbers, while some devices
+ * do (wrongly, but with innevitable reasons).
+ * For example, there's a possibility Japanese mobile phones are expected to have
+ * just numbers, hypens, plus, etc. but not usual alphabets, while US mobile phones
+ * should get such characters. To make exported vCard simple for external parsers,
+ * we have used {@link PhoneNumberUtils#formatNumber(String)} during export, and
+ * removed unnecessary characters inside the number (e.g. "111-222-3333 (Miami)"
+ * becomes "111-222-3333").
+ * Unfortunate side effect of that use was some control characters used in the other
+ * areas may be badly affected by the formatting.
+ * </p>
+ * <p>
+ * This flag disables that formatting, affecting both importer and exporter.
+ * If the user is aware of some side effects due to the implicit formatting, use this flag.
+ * </p>
*/
- public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x02000000;
+ public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000;
+
+ /**
+ * <p>
+ * For importer only. Ignored in exporter.
+ * </p>
+ * <p>
+ * The flag indicating the parser should handle a nested vCard, in which vCard clause starts
+ * in another vCard clause. Here's a typical example.
+ * </p>
+ * <pre class="prettyprint">BEGIN:VCARD
+ * BEGIN:VCARD
+ * VERSION:2.1
+ * ...
+ * END:VCARD
+ * END:VCARD</pre>
+ * <p>
+ * The vCard 2.1 specification allows the nest, but also let parsers ignore nested entries,
+ * while some mobile devices emit nested ones as primary data to be imported.
+ * </p>
+ * <p>
+ * This flag forces a vCard parser to torelate such a nest and understand its content.
+ * </p>
+ */
+ public static final int FLAG_TORELATE_NEST = 0x01000000;
//// The followings are VCard types available from importer/exporter. ////
/**
- * <P>
- * Generic vCard format with the vCard 2.1. Uses UTF-8 for the charset.
- * When composing a vCard entry, the US convension will be used toward formatting
- * some values.
- * </P>
- * <P>
+ * <p>
+ * The type indicating nothing. Used by {@link VCardSourceDetector} when it
+ * was not able to guess the exact vCard type.
+ * </p>
+ */
+ public static final int VCARD_TYPE_UNKNOWN = 0;
+
+ /**
+ * <p>
+ * Generic vCard format with the vCard 2.1. When composing a vCard entry,
+ * the US convension will be used toward formatting some values.
+ * </p>
+ * <p>
* e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
* while it should be "Prefix Family Middle Given Suffix" in Japan for example.
- * </P>
+ * </p>
+ * <p>
+ * Uses UTF-8 for the charset as a charset for exporting. Note that old vCard importer
+ * outside Android cannot accept it since vCard 2.1 specifically does not allow
+ * that charset, while we need to use it to support various languages around the world.
+ * </p>
+ * <p>
+ * If you want to use alternative charset, you should notify the charset to the other
+ * compontent to be used.
+ * </p>
*/
- public static final int VCARD_TYPE_V21_GENERIC_UTF8 =
- (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V21_GENERIC =
+ (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
+ /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
/**
- * <P>
+ * <p>
* General vCard format with the version 3.0. Uses UTF-8 for the charset.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not fully ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
- (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V30_GENERIC =
+ (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
+ /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
/**
- * <P>
+ * <p>
* General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
* Currently, only name order is considered ("Prefix Middle Given Family Suffix")
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
- (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
- /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
+ public static final int VCARD_TYPE_V21_EUROPE =
+ (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
/**
- * <P>
+ * <p>
* General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
- (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V30_EUROPE =
+ (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
/**
- * <P>
+ * <p>
* The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V21_JAPANESE =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese_utf8";
/**
- * <P>
- * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
- * parsing/composing the vCard data.
- * </P>
- * <P>
- * Not ready yet. Use with caution when you use this.
- * </P>
- */
- public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
- /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
-
- /**
- * <P>
- * vCard format for miscellaneous Japanese devices, using Shift_Jis for
- * parsing/composing the vCard data.
- * </P>
- * <P>
- * Not ready yet. Use with caution when you use this.
- * </P>
- */
- public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
- (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
- /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
-
- /**
- * <P>
+ * <p>
* The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
- (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V30_JAPANESE =
+ (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
+ /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese_utf8";
/**
- * <P>
+ * <p>
* The vCard 2.1 based format which (partially) considers the convention in Japanese
* mobile phones, where phonetic names are translated to half-width katakana if
- * possible, etc.
- * </P>
- * <P>
- * Not ready yet. Use with caution when you use this.
- * </P>
+ * possible, etc. It would be better to use Shift_JIS as a charset for maximum
+ * compatibility.
+ * </p>
+ * @hide Should not be available world wide.
*/
public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_CONVERT_PHONETIC_NAME_STRINGS |
- FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
+ (FLAG_V21 | NAME_ORDER_JAPANESE |
+ FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
/**
- * <P>
- * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+ * <p>
+ * The vCard format used in DoCoMo, which is one of Japanese mobile phone careers.
* </p>
- * <P>
+ * <p>
* Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
* No Android-specific property nor defact property is included. The "Primary" properties
* are NOT encoded to Quoted-Printable.
- * </P>
+ * </p>
+ * @hide Should not be available world wide.
*/
public static final int VCARD_TYPE_DOCOMO =
(VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
/* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
- public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8;
+ public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
private static final Map<String, Integer> sVCardTypeMap;
private static final Set<Integer> sJapaneseMobileTypeSet;
static {
sVCardTypeMap = new HashMap<String, Integer>();
- sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS);
- sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS);
- sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
+ sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
+ sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
+ sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
sJapaneseMobileTypeSet = new HashSet<Integer>();
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8);
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS);
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
}
@@ -387,14 +422,6 @@
return !isV30(vcardType);
}
- public static boolean usesUtf8(final int vcardType) {
- return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8);
- }
-
- public static boolean usesShiftJis(final int vcardType) {
- return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS);
- }
-
public static int getNameOrderType(final int vcardType) {
return vcardType & NAME_ORDER_MASK;
}
@@ -431,6 +458,10 @@
return sJapaneseMobileTypeSet.contains(vcardType);
}
+ /* package */ static boolean refrainPhoneNumberFormatting(final int vcardType) {
+ return ((vcardType & FLAG_REFRAIN_PHONE_NUMBER_FORMATTING) != 0);
+ }
+
public static boolean needsToConvertPhoneticString(final int vcardType) {
return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
}
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
index 8c07126..e11b1fd 100644
--- a/core/java/android/pim/vcard/VCardConstants.java
+++ b/core/java/android/pim/vcard/VCardConstants.java
@@ -109,6 +109,12 @@
public static final String PARAM_TYPE_BBS = "BBS";
public static final String PARAM_TYPE_VIDEO = "VIDEO";
+ public static final String PARAM_ENCODING_7BIT = "7BIT";
+ public static final String PARAM_ENCODING_8BIT = "8BIT";
+ public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
+ public static final String PARAM_ENCODING_BASE64 = "BASE64"; // Available in vCard 2.1
+ public static final String PARAM_ENCODING_B = "B"; // Available in vCard 3.0
+
// TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
// These types are basically encoded to "X-" parameters when composing vCard.
// Parser passes these when "X-" is added to the parameter or not.
@@ -130,10 +136,6 @@
// Do not use in composer side.
public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
- // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
- // vCard 3.0.
- public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
-
public interface ImportOnly {
public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
// Some device emits this "X-" parameter for expressing Google Talk,
@@ -142,6 +144,12 @@
public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
}
+ //// Mainly for package constants.
+
+ // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
+ // SORT-STRING invCard 3.0.
+ /* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
+
/* package */ static final int MAX_DATA_COLUMN = 15;
/* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java
index 1327770..5b9cf17 100644
--- a/core/java/android/pim/vcard/VCardEntry.java
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -61,9 +61,6 @@
private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
- private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
- private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
-
private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
static {
@@ -78,12 +75,12 @@
Im.PROTOCOL_GOOGLE_TALK);
}
- static public class PhoneData {
+ public static class PhoneData {
public final int type;
public final String data;
public final String label;
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
+ // isPrimary is (not final but) changable, only when there's no appropriate one existing
+ // in the original VCard.
public boolean isPrimary;
public PhoneData(int type, String data, String label, boolean isPrimary) {
this.type = type;
@@ -109,13 +106,11 @@
}
}
- static public class EmailData {
+ public static class EmailData {
public final int type;
public final String data;
// Used only when TYPE is TYPE_CUSTOM.
public final String label;
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
public boolean isPrimary;
public EmailData(int type, String data, String label, boolean isPrimary) {
this.type = type;
@@ -141,9 +136,9 @@
}
}
- static public class PostalData {
- // Determined by vCard spec.
- // PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
+ public static class PostalData {
+ // Determined by vCard specification.
+ // - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
public static final int ADDR_MAX_DATA_SIZE = 7;
private final String[] dataArray;
public final String pobox;
@@ -248,10 +243,11 @@
}
}
- static public class OrganizationData {
+ public static class OrganizationData {
public final int type;
// non-final is Intentional: we may change the values since this info is separated into
- // two parts in vCard: "ORG" + "TITLE".
+ // two parts in vCard: "ORG" + "TITLE", and we have to cope with each field in
+ // different timing.
public String companyName;
public String departmentName;
public String titleName;
@@ -313,7 +309,7 @@
}
}
- static public class ImData {
+ public static class ImData {
public final int protocol;
public final String customProtocol;
public final int type;
@@ -441,7 +437,7 @@
private String mSuffix;
// Used only when no family nor given name is found.
- private String mFullName;
+ private String mFormattedName;
private String mPhoneticFamilyName;
private String mPhoneticGivenName;
@@ -469,7 +465,7 @@
private final Account mAccount;
public VCardEntry() {
- this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC);
}
public VCardEntry(int vcardType) {
@@ -488,7 +484,7 @@
final StringBuilder builder = new StringBuilder();
final String trimed = data.trim();
final String formattedNumber;
- if (type == Phone.TYPE_PAGER) {
+ if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
formattedNumber = trimed;
} else {
final int length = trimed.length();
@@ -499,9 +495,7 @@
}
}
- // Use NANP in default when there's no information about locale.
- final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ?
- PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP);
+ final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType);
formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
}
PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
@@ -755,11 +749,11 @@
if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
// vCard version. Ignore this.
} else if (propName.equals(VCardConstants.PROPERTY_FN)) {
- mFullName = propValue;
- } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) {
+ mFormattedName = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFormattedName == null) {
// Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
// actually exist in the real vCard data, does not exist.
- mFullName = propValue;
+ mFormattedName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_N)) {
handleNProperty(propValueList);
} else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
@@ -1017,8 +1011,8 @@
*/
private void constructDisplayName() {
// FullName (created via "FN" or "NAME" field) is prefered.
- if (!TextUtils.isEmpty(mFullName)) {
- mDisplayName = mFullName;
+ if (!TextUtils.isEmpty(mFormattedName)) {
+ mDisplayName = mFormattedName;
} else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
@@ -1063,23 +1057,6 @@
if (mAccount != null) {
builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
-
- // Assume that caller side creates this group if it does not exist.
- if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
- final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
- Groups.SOURCE_ID },
- Groups.TITLE + "=?", new String[] {
- GOOGLE_MY_CONTACTS_GROUP }, null);
- try {
- if (cursor != null && cursor.moveToFirst()) {
- myGroupsId = cursor.getString(0);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
} else {
builder.withValue(RawContacts.ACCOUNT_NAME, null);
builder.withValue(RawContacts.ACCOUNT_TYPE, null);
@@ -1321,7 +1298,7 @@
&& TextUtils.isEmpty(mGivenName)
&& TextUtils.isEmpty(mPrefix)
&& TextUtils.isEmpty(mSuffix)
- && TextUtils.isEmpty(mFullName)
+ && TextUtils.isEmpty(mFormattedName)
&& TextUtils.isEmpty(mPhoneticFamilyName)
&& TextUtils.isEmpty(mPhoneticMiddleName)
&& TextUtils.isEmpty(mPhoneticGivenName)
@@ -1380,7 +1357,7 @@
}
public String getFullName() {
- return mFullName;
+ return mFormattedName;
}
public String getPhoneticFamilyName() {
diff --git a/core/java/android/pim/vcard/VCardEntryCommitter.java b/core/java/android/pim/vcard/VCardEntryCommitter.java
index 59a2baf..a8c8057 100644
--- a/core/java/android/pim/vcard/VCardEntryCommitter.java
+++ b/core/java/android/pim/vcard/VCardEntryCommitter.java
@@ -52,9 +52,9 @@
}
}
- public void onEntryCreated(final VCardEntry contactStruct) {
+ public void onEntryCreated(final VCardEntry vcardEntry) {
long start = System.currentTimeMillis();
- mCreatedUris.add(contactStruct.pushIntoContentResolver(mContentResolver));
+ mCreatedUris.add(vcardEntry.pushIntoContentResolver(mContentResolver));
mTimeToCommit += System.currentTimeMillis() - start;
}
diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java
index 290ca2b..a0abae8 100644
--- a/core/java/android/pim/vcard/VCardEntryConstructor.java
+++ b/core/java/android/pim/vcard/VCardEntryConstructor.java
@@ -16,12 +16,11 @@
package android.pim.vcard;
import android.accounts.Account;
+import android.text.TextUtils;
import android.util.CharsetUtils;
import android.util.Log;
-import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.net.QuotedPrintableCodec;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
@@ -30,64 +29,73 @@
import java.util.Collection;
import java.util.List;
+/**
+ * <p>
+ * The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
+ * to easily handle each vCard entry.
+ * </p>
+ * <p>
+ * This class understand details inside vCard and translates it to {@link VCardEntry}.
+ * Then the class throw it to {@link VCardEntryHandler} registered via
+ * {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
+ * are able to handle the {@link VCardEntry} object.
+ * </p>
+ * <p>
+ * If you want to know the detail inside vCard, it would be better to implement
+ * {@link VCardInterpreter} directly, instead of relying on this class and
+ * {@link VCardEntry} created by the object.
+ * </p>
+ */
public class VCardEntryConstructor implements VCardInterpreter {
private static String LOG_TAG = "VCardEntryConstructor";
- /**
- * If there's no other information available, this class uses this charset for encoding
- * byte arrays to String.
- */
- /* package */ static final String DEFAULT_CHARSET_FOR_DECODED_BYTES = "UTF-8";
-
private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
- private VCardEntry mCurrentContactStruct;
+ private VCardEntry mCurrentVCardEntry;
private String mParamType;
- /**
- * The charset using which {@link VCardInterpreter} parses the text.
- */
- private String mInputCharset;
+ // The charset using which {@link VCardInterpreter} parses the text.
+ // Each String is first decoded into binary stream with this charset, and encoded back
+ // to "target charset", which may be explicitly specified by the vCard with "CHARSET"
+ // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
+ private final String mSourceCharset;
- /**
- * The charset with which byte array is encoded to String.
- */
- final private String mCharsetForDecodedBytes;
- final private boolean mStrictLineBreakParsing;
- final private int mVCardType;
- final private Account mAccount;
+ private final boolean mStrictLineBreaking;
+ private final int mVCardType;
+ private final Account mAccount;
- /** For measuring performance. */
+ // For measuring performance.
private long mTimePushIntoContentResolver;
- final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
+ private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
public VCardEntryConstructor() {
- this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC, null, null, false);
}
public VCardEntryConstructor(final int vcardType) {
- this(null, null, false, vcardType, null);
+ this(vcardType, null, null, false);
}
- public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing,
- final int vcardType, final Account account) {
- this(null, charset, strictLineBreakParsing, vcardType, account);
+ public VCardEntryConstructor(final int vcardType, final Account account) {
+ this(vcardType, account, null, false);
}
- public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes,
- final boolean strictLineBreakParsing, final int vcardType,
- final Account account) {
+ public VCardEntryConstructor(final int vcardType, final Account account,
+ final String inputCharset) {
+ this(vcardType, account, inputCharset, false);
+ }
+
+ /**
+ * @hide
+ */
+ public VCardEntryConstructor(final int vcardType, final Account account,
+ final String inputCharset, final boolean strictLineBreakParsing) {
if (inputCharset != null) {
- mInputCharset = inputCharset;
+ mSourceCharset = inputCharset;
} else {
- mInputCharset = VCardConfig.DEFAULT_CHARSET;
+ mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
}
- if (charsetForDetodedBytes != null) {
- mCharsetForDecodedBytes = charsetForDetodedBytes;
- } else {
- mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES;
- }
- mStrictLineBreakParsing = strictLineBreakParsing;
+ mStrictLineBreaking = strictLineBreakParsing;
mVCardType = vcardType;
mAccount = account;
}
@@ -108,30 +116,24 @@
}
}
- /**
- * Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}.
- */
public void clear() {
- mCurrentContactStruct = null;
+ mCurrentVCardEntry = null;
mCurrentProperty = new VCardEntry.Property();
}
- /**
- * Assume that VCard is not nested. In other words, this code does not accept
- */
public void startEntry() {
- if (mCurrentContactStruct != null) {
+ if (mCurrentVCardEntry != null) {
Log.e(LOG_TAG, "Nested VCard code is not supported now.");
}
- mCurrentContactStruct = new VCardEntry(mVCardType, mAccount);
+ mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount);
}
public void endEntry() {
- mCurrentContactStruct.consolidateFields();
+ mCurrentVCardEntry.consolidateFields();
for (VCardEntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onEntryCreated(mCurrentContactStruct);
+ entryHandler.onEntryCreated(mCurrentVCardEntry);
}
- mCurrentContactStruct = null;
+ mCurrentVCardEntry = null;
}
public void startProperty() {
@@ -139,7 +141,7 @@
}
public void endProperty() {
- mCurrentContactStruct.addProperty(mCurrentProperty);
+ mCurrentVCardEntry.addProperty(mCurrentProperty);
}
public void propertyName(String name) {
@@ -166,113 +168,41 @@
mParamType = null;
}
- private String encodeString(String originalString, String charsetForDecodedBytes) {
- if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) {
+ private static String encodeToSystemCharset(String originalString,
+ String sourceCharset, String targetCharset) {
+ if (sourceCharset.equalsIgnoreCase(targetCharset)) {
return originalString;
}
- Charset charset = Charset.forName(mInputCharset);
- ByteBuffer byteBuffer = charset.encode(originalString);
+ final Charset charset = Charset.forName(sourceCharset);
+ final ByteBuffer byteBuffer = charset.encode(originalString);
// byteBuffer.array() "may" return byte array which is larger than
// byteBuffer.remaining(). Here, we keep on the safe side.
- byte[] bytes = new byte[byteBuffer.remaining()];
+ final byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
try {
- return new String(bytes, charsetForDecodedBytes);
+ String ret = new String(bytes, targetCharset);
+ return ret;
} catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
return null;
}
}
- private String handleOneValue(String value, String charsetForDecodedBytes, String encoding) {
+ private String handleOneValue(String value,
+ String sourceCharset, String targetCharset, String encoding) {
if (encoding != null) {
if (encoding.equals("BASE64") || encoding.equals("B")) {
mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes()));
return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) {
- // "= " -> " ", "=\t" -> "\t".
- // Previous code had done this replacement. Keep on the safe side.
- StringBuilder builder = new StringBuilder();
- int length = value.length();
- for (int i = 0; i < length; i++) {
- char ch = value.charAt(i);
- if (ch == '=' && i < length - 1) {
- char nextCh = value.charAt(i + 1);
- if (nextCh == ' ' || nextCh == '\t') {
-
- builder.append(nextCh);
- i++;
- continue;
- }
- }
- builder.append(ch);
- }
- String quotedPrintable = builder.toString();
-
- String[] lines;
- if (mStrictLineBreakParsing) {
- lines = quotedPrintable.split("\r\n");
- } else {
- builder = new StringBuilder();
- length = quotedPrintable.length();
- ArrayList<String> list = new ArrayList<String>();
- for (int i = 0; i < length; i++) {
- char ch = quotedPrintable.charAt(i);
- if (ch == '\n') {
- list.add(builder.toString());
- builder = new StringBuilder();
- } else if (ch == '\r') {
- list.add(builder.toString());
- builder = new StringBuilder();
- if (i < length - 1) {
- char nextCh = quotedPrintable.charAt(i + 1);
- if (nextCh == '\n') {
- i++;
- }
- }
- } else {
- builder.append(ch);
- }
- }
- String finalLine = builder.toString();
- if (finalLine.length() > 0) {
- list.add(finalLine);
- }
- lines = list.toArray(new String[0]);
- }
-
- builder = new StringBuilder();
- for (String line : lines) {
- if (line.endsWith("=")) {
- line = line.substring(0, line.length() - 1);
- }
- builder.append(line);
- }
- byte[] bytes;
- try {
- bytes = builder.toString().getBytes(mInputCharset);
- } catch (UnsupportedEncodingException e1) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + mInputCharset);
- bytes = builder.toString().getBytes();
- }
-
- try {
- bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
- } catch (DecoderException e) {
- Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
- return "";
- }
-
- try {
- return new String(bytes, charsetForDecodedBytes);
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
- return new String(bytes);
- }
+ return VCardUtils.parseQuotedPrintable(
+ value, mStrictLineBreaking, sourceCharset, targetCharset);
}
- // Unknown encoding. Fall back to default.
+ Log.w(LOG_TAG, "Unknown encoding. Fall back to default.");
}
- return encodeString(value, charsetForDecodedBytes);
+
+ // Just translate the charset of a given String from inputCharset to a system one.
+ return encodeToSystemCharset(value, sourceCharset, targetCharset);
}
public void propertyValues(List<String> values) {
@@ -281,23 +211,24 @@
}
final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET");
- final String charset =
- ((charsetCollection != null) ? charsetCollection.iterator().next() : null);
final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
final String encoding =
((encodingCollection != null) ? encodingCollection.iterator().next() : null);
-
- String charsetForDecodedBytes = CharsetUtils.nameForDefaultVendor(charset);
- if (charsetForDecodedBytes == null || charsetForDecodedBytes.length() == 0) {
- charsetForDecodedBytes = mCharsetForDecodedBytes;
+ String targetCharset = CharsetUtils.nameForDefaultVendor(
+ ((charsetCollection != null) ? charsetCollection.iterator().next() : null));
+ if (TextUtils.isEmpty(targetCharset)) {
+ targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
}
for (final String value : values) {
mCurrentProperty.addToPropertyValueList(
- handleOneValue(value, charsetForDecodedBytes, encoding));
+ handleOneValue(value, mSourceCharset, targetCharset, encoding));
}
}
+ /**
+ * @hide
+ */
public void showPerformanceInfo() {
Log.d(LOG_TAG, "time for insert ContactStruct to database: " +
mTimePushIntoContentResolver + " ms");
diff --git a/core/java/android/pim/vcard/VCardEntryHandler.java b/core/java/android/pim/vcard/VCardEntryHandler.java
index 83a67fe..56bf69d 100644
--- a/core/java/android/pim/vcard/VCardEntryHandler.java
+++ b/core/java/android/pim/vcard/VCardEntryHandler.java
@@ -16,8 +16,13 @@
package android.pim.vcard;
/**
- * The interface called by {@link VCardEntryConstructor}. Useful when you don't want to
- * handle detailed information as what {@link VCardParser} provides via {@link VCardInterpreter}.
+ * <p>
+ * The interface called by {@link VCardEntryConstructor}.
+ * </p>
+ * <p>
+ * This class is useful when you don't want to know vCard data in detail. If you want to know
+ * it, it would be better to consider using {@link VCardInterpreter}.
+ * </p>
*/
public interface VCardEntryHandler {
/**
diff --git a/core/java/android/pim/vcard/VCardInterpreter.java b/core/java/android/pim/vcard/VCardInterpreter.java
index b5237c0..03704a2 100644
--- a/core/java/android/pim/vcard/VCardInterpreter.java
+++ b/core/java/android/pim/vcard/VCardInterpreter.java
@@ -20,7 +20,7 @@
/**
* <P>
* The interface which should be implemented by the classes which have to analyze each
- * vCard entry more minutely than {@link VCardEntry} class analysis.
+ * vCard entry minutely.
* </P>
* <P>
* Here, there are several terms specific to vCard (and this library).
diff --git a/core/java/android/pim/vcard/VCardInterpreterCollection.java b/core/java/android/pim/vcard/VCardInterpreterCollection.java
index 99f81f7..4952dc7 100644
--- a/core/java/android/pim/vcard/VCardInterpreterCollection.java
+++ b/core/java/android/pim/vcard/VCardInterpreterCollection.java
@@ -23,7 +23,7 @@
* {@link VCardInterpreter} objects and make a user object treat them as one
* {@link VCardInterpreter} object.
*/
-public class VCardInterpreterCollection implements VCardInterpreter {
+public final class VCardInterpreterCollection implements VCardInterpreter {
private final Collection<VCardInterpreter> mInterpreterCollection;
public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index 57c52a6..31b9369 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -20,82 +20,36 @@
import java.io.IOException;
import java.io.InputStream;
-public abstract class VCardParser {
- protected final int mParseType;
- protected boolean mCanceled;
-
- public VCardParser() {
- this(VCardConfig.PARSE_TYPE_UNKNOWN);
- }
-
- public VCardParser(int parseType) {
- mParseType = parseType;
- }
-
+public interface VCardParser {
/**
- * <P>
- * Parses the given stream and send the VCard data into VCardBuilderBase object.
- * </P.
- * <P>
+ * <p>
+ * Parses the given stream and send the vCard data into VCardBuilderBase object.
+ * </p>.
+ * <p>
* Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
* local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
- * formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1,
- * In some exreme case, some VCard may have different charsets in one VCard (though
- * we do not see any device which emits such kind of malicious data)
- * </P>
- * <P>
- * In order to avoid "misunderstanding" charset as much as possible, this method
- * use "ISO-8859-1" for reading the stream. When charset is specified in some property
- * (with "CHARSET=..." parameter), the string is decoded to raw bytes and encoded to
- * the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit
- * characters, which is not completely sure. In some cases, this "decoding-encoding"
- * scheme may fail. To avoid the case,
- * </P>
- * <P>
- * We recommend you to use {@link VCardSourceDetector} and detect which kind of source the
- * VCard comes from and explicitly specify a charset using the result.
- * </P>
+ * formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1,
+ * In some exreme case, it is allowed for vCard to have different charsets in one vCard.
+ * </p>
+ * <p>
+ * We recommend you use {@link VCardSourceDetector} and detect which kind of source the
+ * vCard comes from and explicitly specify a charset using the result.
+ * </p>
*
* @param is The source to parse.
* @param interepreter A {@link VCardInterpreter} object which used to construct data.
- * @return Returns true for success. Otherwise returns false.
* @throws IOException, VCardException
*/
- public abstract boolean parse(InputStream is, VCardInterpreter interepreter)
+ public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException;
-
+
/**
- * <P>
- * The method variants which accept charset.
- * </P>
- * <P>
- * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use
- * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese
- * phone uses Shift_JIS as a charset (e.g. W61SH), and another uses
- * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification (e.g. W53K).
- * </P>
- *
- * @param is The source to parse.
- * @param charset Charset to be used.
- * @param builder The VCardBuilderBase object.
- * @return Returns true when successful. Otherwise returns false.
- * @throws IOException, VCardException
+ * <p>
+ * Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
+ * </p>
+ * <p>
+ * Actual cancel is done after parsing the current vcard.
+ * </p>
*/
- public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder)
- throws IOException, VCardException;
-
- /**
- * The method variants which tells this object the operation is already canceled.
- */
- public abstract void parse(InputStream is, String charset,
- VCardInterpreter builder, boolean canceled)
- throws IOException, VCardException;
-
- /**
- * Cancel parsing.
- * Actual cancel is done after the end of the current one vcard entry parsing.
- */
- public void cancel() {
- mCanceled = true;
- }
+ public abstract void cancel();
}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V21.java b/core/java/android/pim/vcard/VCardParserImpl_V21.java
new file mode 100644
index 0000000..7d294cc
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V21.java
@@ -0,0 +1,967 @@
+/*
+ * 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.pim.vcard;
+
+import android.pim.vcard.exception.VCardAgentNotSupportedException;
+import android.pim.vcard.exception.VCardException;
+import android.pim.vcard.exception.VCardInvalidCommentLineException;
+import android.pim.vcard.exception.VCardInvalidLineException;
+import android.pim.vcard.exception.VCardNestedException;
+import android.pim.vcard.exception.VCardVersionException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard parsing. Based on vCard 2.1,
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V21 {
+ private static final String LOG_TAG = "VCardParserImpl_V21";
+
+ private static final class CustomBufferedReader extends BufferedReader {
+ private long mTime;
+
+ public CustomBufferedReader(Reader in) {
+ super(in);
+ }
+
+ @Override
+ public String readLine() throws IOException {
+ long start = System.currentTimeMillis();
+ String ret = super.readLine();
+ long end = System.currentTimeMillis();
+ mTime += end - start;
+ return ret;
+ }
+
+ public long getTotalmillisecond() {
+ return mTime;
+ }
+ }
+
+ private static final String sDefaultEncoding = "8BIT";
+
+ protected boolean mCanceled;
+ protected VCardInterpreter mInterpreter;
+
+ protected final String mImportCharset;
+
+ /**
+ * <p>
+ * The encoding type for deconding byte streams. This member variable is
+ * reset to a default encoding every time when a new item comes.
+ * </p>
+ * <p>
+ * "Encoding" in vCard is different from "Charset". It is mainly used for
+ * addresses, notes, images. "7BIT", "8BIT", "BASE64", and
+ * "QUOTED-PRINTABLE" are known examples.
+ * </p>
+ */
+ protected String mCurrentEncoding;
+
+ /**
+ * <p>
+ * The reader object to be used internally.
+ * </p>
+ * <p>
+ * Developers should not directly read a line from this object. Use
+ * getLine() unless there some reason.
+ * </p>
+ */
+ protected BufferedReader mReader;
+
+ /**
+ * <p>
+ * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard
+ * specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownTypeSet = new HashSet<String>();
+
+ /**
+ * <p>
+ * Set for storing unkonwn VALUE attributes, which is not acceptable in
+ * vCard specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownValueSet = new HashSet<String>();
+
+
+ // In some cases, vCard is nested. Currently, we only consider the most
+ // interior vCard data.
+ // See v21_foma_1.vcf in test directory for more information.
+ // TODO: Don't ignore by using count, but read all of information outside vCard.
+ private int mNestCount;
+
+ // Used only for parsing END:VCARD.
+ private String mPreviousLine;
+
+ // For measuring performance.
+ private long mTimeTotal;
+ private long mTimeReadStartRecord;
+ private long mTimeReadEndRecord;
+ private long mTimeStartProperty;
+ private long mTimeEndProperty;
+ private long mTimeParseItems;
+ private long mTimeParseLineAndHandleGroup;
+ private long mTimeParsePropertyValues;
+ private long mTimeParseAdrOrgN;
+ private long mTimeHandleMiscPropertyValue;
+ private long mTimeHandleQuotedPrintable;
+ private long mTimeHandleBase64;
+
+ public VCardParserImpl_V21() {
+ this(VCardConfig.VCARD_TYPE_DEFAULT, null);
+ }
+
+ public VCardParserImpl_V21(int vcardType) {
+ this(vcardType, null);
+ }
+
+ public VCardParserImpl_V21(int vcardType, String importCharset) {
+ if ((vcardType & VCardConfig.FLAG_TORELATE_NEST) != 0) {
+ mNestCount = 1;
+ }
+
+ mImportCharset = (!TextUtils.isEmpty(importCharset) ? importCharset :
+ VCardConfig.DEFAULT_INTERMEDIATE_CHARSET);
+ }
+
+ /**
+ * <p>
+ * Parses the file at the given position.
+ * </p>
+ */
+ // <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre>
+ protected void parseVCardFile() throws IOException, VCardException {
+ boolean readingFirstFile = true;
+ while (true) {
+ if (mCanceled) {
+ break;
+ }
+ if (!parseOneVCard(readingFirstFile)) {
+ break;
+ }
+ readingFirstFile = false;
+ }
+
+ if (mNestCount > 0) {
+ boolean useCache = true;
+ for (int i = 0; i < mNestCount; i++) {
+ readEndVCard(useCache, true);
+ useCache = false;
+ }
+ }
+ }
+
+ /**
+ * @return true when a given property name is a valid property name.
+ */
+ protected boolean isValidPropertyName(final String propertyName) {
+ if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) ||
+ propertyName.startsWith("X-"))
+ && !mUnknownTypeSet.contains(propertyName)) {
+ mUnknownTypeSet.add(propertyName);
+ Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
+ }
+ return true;
+ }
+
+ /**
+ * @return String. It may be null, or its length may be 0
+ * @throws IOException
+ */
+ protected String getLine() throws IOException {
+ return mReader.readLine();
+ }
+
+ /**
+ * @return String with it's length > 0
+ * @throws IOException
+ * @throws VCardException when the stream reached end of line
+ */
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.trim().length() > 0) {
+ return line;
+ }
+ }
+ }
+
+ /*
+ * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+ * items *CRLF
+ * "END" [ws] ":" [ws] "VCARD"
+ */
+ private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException {
+ boolean allowGarbage = false;
+ if (firstRead) {
+ if (mNestCount > 0) {
+ for (int i = 0; i < mNestCount; i++) {
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ allowGarbage = true;
+ }
+ }
+ }
+
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ long start;
+ if (mInterpreter != null) {
+ start = System.currentTimeMillis();
+ mInterpreter.startEntry();
+ mTimeReadStartRecord += System.currentTimeMillis() - start;
+ }
+ start = System.currentTimeMillis();
+ parseItems();
+ mTimeParseItems += System.currentTimeMillis() - start;
+ readEndVCard(true, false);
+ if (mInterpreter != null) {
+ start = System.currentTimeMillis();
+ mInterpreter.endEntry();
+ mTimeReadEndRecord += System.currentTimeMillis() - start;
+ }
+ return true;
+ }
+
+ /**
+ * @return True when successful. False when reaching the end of line
+ * @throws IOException
+ * @throws VCardException
+ */
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+ String line;
+ do {
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ return false;
+ } else if (line.trim().length() > 0) {
+ break;
+ }
+ }
+ String[] strArray = line.split(":", 2);
+ int length = strArray.length;
+
+ // Though vCard 2.1/3.0 specification does not allow lower cases,
+ // vCard file emitted by some external vCard expoter have such
+ // invalid Strings.
+ // So we allow it.
+ // e.g. BEGIN:vCard
+ if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN")
+ && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+ return true;
+ } else if (!allowGarbage) {
+ if (mNestCount > 0) {
+ mPreviousLine = line;
+ return false;
+ } else {
+ throw new VCardException("Expected String \"BEGIN:VCARD\" did not come "
+ + "(Instead, \"" + line + "\" came)");
+ }
+ }
+ } while (allowGarbage);
+
+ throw new VCardException("Reached where must not be reached.");
+ }
+
+ /**
+ * <p>
+ * The arguments useCache and allowGarbase are usually true and false
+ * accordingly when this function is called outside this function itself.
+ * </p>
+ *
+ * @param useCache When true, line is obtained from mPreviousline.
+ * Otherwise, getLine() is used.
+ * @param allowGarbage When true, ignore non "END:VCARD" line.
+ * @throws IOException
+ * @throws VCardException
+ */
+ protected void readEndVCard(boolean useCache, boolean allowGarbage) throws IOException,
+ VCardException {
+ String line;
+ do {
+ if (useCache) {
+ // Though vCard specification does not allow lower cases,
+ // some data may have them, so we allow it.
+ line = mPreviousLine;
+ } else {
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("Expected END:VCARD was not found.");
+ } else if (line.trim().length() > 0) {
+ break;
+ }
+ }
+ }
+
+ String[] strArray = line.split(":", 2);
+ if (strArray.length == 2 && strArray[0].trim().equalsIgnoreCase("END")
+ && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+ return;
+ } else if (!allowGarbage) {
+ throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
+ }
+ useCache = false;
+ } while (allowGarbage);
+ }
+
+ /*
+ * items = *CRLF item / item
+ */
+ protected void parseItems() throws IOException, VCardException {
+ boolean ended = false;
+
+ if (mInterpreter != null) {
+ long start = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - start;
+ }
+ ended = parseItem();
+ if (mInterpreter != null && !ended) {
+ long start = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - start;
+ }
+
+ while (!ended) {
+ // follow VCARD ,it wont reach endProperty
+ if (mInterpreter != null) {
+ long start = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - start;
+ }
+ try {
+ ended = parseItem();
+ } catch (VCardInvalidCommentLineException e) {
+ Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
+ ended = false;
+ }
+ if (mInterpreter != null && !ended) {
+ long start = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - start;
+ }
+ }
+ }
+
+ /*
+ * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR"
+ * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts
+ * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."]
+ * "AGENT" [params] ":" vcard CRLF
+ */
+ protected boolean parseItem() throws IOException, VCardException {
+ mCurrentEncoding = sDefaultEncoding;
+
+ final String line = getNonEmptyLine();
+ long start = System.currentTimeMillis();
+
+ String[] propertyNameAndValue = separateLineAndHandleGroup(line);
+ if (propertyNameAndValue == null) {
+ return true;
+ }
+ if (propertyNameAndValue.length != 2) {
+ throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
+ }
+ String propertyName = propertyNameAndValue[0].toUpperCase();
+ String propertyValue = propertyNameAndValue[1];
+
+ mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
+
+ if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) {
+ start = System.currentTimeMillis();
+ handleMultiplePropertyValue(propertyName, propertyValue);
+ mTimeParseAdrOrgN += System.currentTimeMillis() - start;
+ return false;
+ } else if (propertyName.equals("AGENT")) {
+ handleAgent(propertyValue);
+ return false;
+ } else if (isValidPropertyName(propertyName)) {
+ if (propertyName.equals("BEGIN")) {
+ if (propertyValue.equals("VCARD")) {
+ throw new VCardNestedException("This vCard has nested vCard data in it.");
+ } else {
+ throw new VCardException("Unknown BEGIN type: " + propertyValue);
+ }
+ } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) {
+ throw new VCardVersionException("Incompatible version: " + propertyValue + " != "
+ + getVersionString());
+ }
+ start = System.currentTimeMillis();
+ handlePropertyValue(propertyName, propertyValue);
+ mTimeParsePropertyValues += System.currentTimeMillis() - start;
+ return false;
+ }
+
+ throw new VCardException("Unknown property name: \"" + propertyName + "\"");
+ }
+
+ // For performance reason, the states for group and property name are merged into one.
+ static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
+ static private final int STATE_PARAMS = 1;
+ // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
+ static private final int STATE_PARAMS_IN_DQUOTE = 2;
+
+ protected String[] separateLineAndHandleGroup(String line) throws VCardException {
+ final String[] propertyNameAndValue = new String[2];
+ final int length = line.length();
+ if (length > 0 && line.charAt(0) == '#') {
+ throw new VCardInvalidCommentLineException();
+ }
+
+ int state = STATE_GROUP_OR_PROPERTY_NAME;
+ int nameIndex = 0;
+
+ // This loop is developed so that we don't have to take care of bottle neck here.
+ // Refactor carefully when you need to do so.
+ for (int i = 0; i < length; i++) {
+ final char ch = line.charAt(i);
+ switch (state) {
+ case STATE_GROUP_OR_PROPERTY_NAME: {
+ if (ch == ':') { // End of a property name.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyName(propertyName);
+ }
+ propertyNameAndValue[0] = propertyName;
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ } else if (ch == '.') { // Each group is followed by the dot.
+ final String groupName = line.substring(nameIndex, i);
+ if (groupName.length() == 0) {
+ Log.w(LOG_TAG, "Empty group found. Ignoring.");
+ } else if (mInterpreter != null) {
+ mInterpreter.propertyGroup(groupName);
+ }
+ nameIndex = i + 1; // Next should be another group or a property name.
+ } else if (ch == ';') { // End of property name and beginneng of parameters.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyName(propertyName);
+ }
+ propertyNameAndValue[0] = propertyName;
+ nameIndex = i + 1;
+ state = STATE_PARAMS; // Start parameter parsing.
+ }
+ break;
+ }
+ case STATE_PARAMS: {
+ if (ch == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ state = STATE_PARAMS_IN_DQUOTE;
+ } else if (ch == ';') { // Starts another param.
+ handleParams(line.substring(nameIndex, i));
+ nameIndex = i + 1;
+ } else if (ch == ':') { // End of param and beginenning of values.
+ handleParams(line.substring(nameIndex, i));
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ }
+ break;
+ }
+ case STATE_PARAMS_IN_DQUOTE: {
+ if (ch == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ state = STATE_PARAMS;
+ }
+ break;
+ }
+ }
+ }
+
+ throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
+ }
+
+ /*
+ * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param /
+ * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws]
+ * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "="
+ * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "="
+ * [ws] word / knowntype
+ */
+ protected void handleParams(String params) throws VCardException {
+ final String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ final String paramName = strArray[0].trim().toUpperCase();
+ String paramValue = strArray[1].trim();
+ if (paramName.equals("TYPE")) {
+ handleType(paramValue);
+ } else if (paramName.equals("VALUE")) {
+ handleValue(paramValue);
+ } else if (paramName.equals("ENCODING")) {
+ handleEncoding(paramValue);
+ } else if (paramName.equals("CHARSET")) {
+ handleCharset(paramValue);
+ } else if (paramName.equals("LANGUAGE")) {
+ handleLanguage(paramValue);
+ } else if (paramName.startsWith("X-")) {
+ handleAnyParam(paramName, paramValue);
+ } else {
+ throw new VCardException("Unknown type \"" + paramName + "\"");
+ }
+ } else {
+ handleParamWithoutName(strArray[0]);
+ }
+ }
+
+ /**
+ * vCard 3.0 parser implementation may throw VCardException.
+ */
+ @SuppressWarnings("unused")
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ handleType(paramValue);
+ }
+
+ /*
+ * ptypeval = knowntype / "X-" word
+ */
+ protected void handleType(final String ptypeval) {
+ if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
+ || ptypeval.startsWith("X-"))
+ && !mUnknownTypeSet.contains(ptypeval)) {
+ mUnknownTypeSet.add(ptypeval);
+ Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("TYPE");
+ mInterpreter.propertyParamValue(ptypeval);
+ }
+ }
+
+ /*
+ * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+ */
+ protected void handleValue(final String pvalueval) {
+ if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
+ || pvalueval.startsWith("X-")
+ || mUnknownValueSet.contains(pvalueval))) {
+ mUnknownValueSet.add(pvalueval);
+ Log.w(LOG_TAG, String.format(
+ "The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("VALUE");
+ mInterpreter.propertyParamValue(pvalueval);
+ }
+ }
+
+ /*
+ * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
+ */
+ protected void handleEncoding(String pencodingval) throws VCardException {
+ if (getAvailableEncodingSet().contains(pencodingval) ||
+ pencodingval.startsWith("X-")) {
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("ENCODING");
+ mInterpreter.propertyParamValue(pencodingval);
+ }
+ mCurrentEncoding = pencodingval;
+ } else {
+ throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
+ }
+ }
+
+ /**
+ * <p>
+ * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+ * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
+ * We allow any charset.
+ * </p>
+ */
+ protected void handleCharset(String charsetval) {
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("CHARSET");
+ mInterpreter.propertyParamValue(charsetval);
+ }
+ }
+
+ /**
+ * See also Section 7.1 of RFC 1521
+ */
+ protected void handleLanguage(String langval) throws VCardException {
+ String[] strArray = langval.split("-");
+ if (strArray.length != 2) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ String tmp = strArray[0];
+ int length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ tmp = strArray[1];
+ length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("LANGUAGE");
+ mInterpreter.propertyParamValue(langval);
+ }
+ }
+
+ private boolean isAsciiLetter(char ch) {
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Mainly for "X-" type. This accepts any kind of type without check.
+ */
+ protected void handleAnyParam(String paramName, String paramValue) {
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType(paramName);
+ mInterpreter.propertyParamValue(paramValue);
+ }
+ }
+
+ protected void handlePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ final String upperEncoding = mCurrentEncoding.toUpperCase();
+ if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
+ final long start = System.currentTimeMillis();
+ final String result = getQuotedPrintable(propertyValue);
+ if (mInterpreter != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(result);
+ mInterpreter.propertyValues(v);
+ }
+ mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
+ } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
+ || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) {
+ final long start = System.currentTimeMillis();
+ // It is very rare, but some BASE64 data may be so big that
+ // OutOfMemoryError occurs. To ignore such cases, use try-catch.
+ try {
+ final String result = getBase64(propertyValue);
+ if (mInterpreter != null) {
+ ArrayList<String> arrayList = new ArrayList<String>();
+ arrayList.add(result);
+ mInterpreter.propertyValues(arrayList);
+ }
+ } catch (OutOfMemoryError error) {
+ Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
+ if (mInterpreter != null) {
+ mInterpreter.propertyValues(null);
+ }
+ }
+ mTimeHandleBase64 += System.currentTimeMillis() - start;
+ } else {
+ if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
+ upperEncoding.startsWith("X-"))) {
+ Log.w(LOG_TAG,
+ String.format("The encoding \"%s\" is unsupported by vCard %s",
+ mCurrentEncoding, getVersionString()));
+ }
+
+ final long start = System.currentTimeMillis();
+ if (mInterpreter != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(maybeUnescapeText(propertyValue));
+ mInterpreter.propertyValues(v);
+ }
+ mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
+ }
+ }
+
+ /**
+ * <p>
+ * Parses and returns Quoted-Printable.
+ * </p>
+ *
+ * @param firstString The string following a parameter name and attributes.
+ * Example: "string" in
+ * "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r".
+ * @return whole Quoted-Printable string, including a given argument and
+ * following lines. Excludes the last empty line following to Quoted
+ * Printable lines.
+ * @throws IOException
+ * @throws VCardException
+ */
+ private String getQuotedPrintable(String firstString) throws IOException, VCardException {
+ // Specifically, there may be some padding between = and CRLF.
+ // See the following:
+ //
+ // qp-line := *(qp-segment transport-padding CRLF)
+ // qp-part transport-padding
+ // qp-segment := qp-section *(SPACE / TAB) "="
+ // ; Maximum length of 76 characters
+ //
+ // e.g. (from RFC 2045)
+ // Now's the time =
+ // for all folk to come=
+ // to the aid of their country.
+ if (firstString.trim().endsWith("=")) {
+ // remove "transport-padding"
+ int pos = firstString.length() - 1;
+ while (firstString.charAt(pos) != '=') {
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString.substring(0, pos + 1));
+ builder.append("\r\n");
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing a Quoted-Printable String");
+ }
+ if (line.trim().endsWith("=")) {
+ // remove "transport-padding"
+ pos = line.length() - 1;
+ while (line.charAt(pos) != '=') {
+ }
+ builder.append(line.substring(0, pos + 1));
+ builder.append("\r\n");
+ } else {
+ builder.append(line);
+ break;
+ }
+ }
+ return builder.toString();
+ } else {
+ return firstString;
+ }
+ }
+
+ protected String getBase64(String firstString) throws IOException, VCardException {
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ String line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ }
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * <p>
+ * Mainly for "ADR", "ORG", and "N"
+ * </p>
+ */
+ /*
+ * addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr,
+ * Street, Locality, Region, Postal Code, Country Name orgparts =
+ * *(strnosemi ";") strnosemi ; First is Organization Name, remainder are
+ * Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family,
+ * Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III,
+ * Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a
+ * semicolon in this string, it must be escaped ; with a "\" character. We
+ * do not care the number of "strnosemi" here. We are not sure whether we
+ * should add "\" CRLF to each value. We exclude them for now.
+ */
+ protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some
+ // softwares/devices
+ // emit such data.
+ if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ propertyValue = getQuotedPrintable(propertyValue);
+ }
+
+ if (mInterpreter != null) {
+ mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue,
+ (getVersion() == VCardConfig.FLAG_V30)));
+ }
+ }
+
+ /*
+ * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an
+ * error toward the AGENT property.
+ * // TODO: Support AGENT property.
+ * item =
+ * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws]
+ * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD"
+ */
+ protected void handleAgent(final String propertyValue) throws VCardException {
+ if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
+ // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
+ return;
+ } else {
+ throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
+ }
+ }
+
+ /**
+ * For vCard 3.0.
+ */
+ protected String maybeUnescapeText(final String text) {
+ return text;
+ }
+
+ /**
+ * Returns unescaped String if the character should be unescaped. Return
+ * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";"
+ * while "\x" should not be.
+ */
+ protected String maybeUnescapeCharacter(final char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ /* package */ static String unescapeCharacter(final char ch) {
+ // Original vCard 2.1 specification does not allow transformation
+ // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous
+ // implementation of
+ // this class allowed them, so keep it as is.
+ if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
+ return String.valueOf(ch);
+ } else {
+ return null;
+ }
+ }
+
+ private void showPerformanceInfo() {
+ Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
+ if (mReader instanceof CustomBufferedReader) {
+ Log.d(LOG_TAG, "Total readLine time: "
+ + ((CustomBufferedReader) mReader).getTotalmillisecond() + " ms");
+ }
+ Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord
+ + " ms");
+ Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms");
+ Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup
+ + " ms");
+ Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
+ Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
+ Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue
+ + " ms");
+ Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms");
+ Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
+ }
+
+ /**
+ * @return {@link VCardConfig#FLAG_V21}
+ */
+ protected int getVersion() {
+ return VCardConfig.FLAG_V21;
+ }
+
+ /**
+ * @return {@link VCardConfig#FLAG_V30}
+ */
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V21;
+ }
+
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V21.sKnownPropertyNameSet;
+ }
+
+ protected Set<String> getKnownTypeSet() {
+ return VCardParser_V21.sKnownTypeSet;
+ }
+
+ protected Set<String> getKnownValueSet() {
+ return VCardParser_V21.sKnownValueSet;
+ }
+
+ protected Set<String> getAvailableEncodingSet() {
+ return VCardParser_V21.sAvailableEncoding;
+ }
+
+ protected String getDefaultEncoding() {
+ return sDefaultEncoding;
+ }
+
+
+ public void parse(InputStream is, VCardInterpreter interpreter)
+ throws IOException, VCardException {
+ if (is == null) {
+ throw new NullPointerException("InputStream must not be null.");
+ }
+
+ final InputStreamReader tmpReader = new InputStreamReader(is, mImportCharset);
+ if (VCardConfig.showPerformanceLog()) {
+ mReader = new CustomBufferedReader(tmpReader);
+ } else {
+ mReader = new BufferedReader(tmpReader);
+ }
+
+ mInterpreter = interpreter;
+
+ final long start = System.currentTimeMillis();
+ if (mInterpreter != null) {
+ mInterpreter.start();
+ }
+ parseVCardFile();
+ if (mInterpreter != null) {
+ mInterpreter.end();
+ }
+ mTimeTotal += System.currentTimeMillis() - start;
+
+ if (VCardConfig.showPerformanceLog()) {
+ showPerformanceInfo();
+ }
+ }
+
+ public final void cancel() {
+ mCanceled = true;
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V30.java b/core/java/android/pim/vcard/VCardParserImpl_V30.java
new file mode 100644
index 0000000..a48a3b4
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V30.java
@@ -0,0 +1,316 @@
+/*
+ * 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.pim.vcard;
+
+import java.io.IOException;
+import java.util.Set;
+
+import android.pim.vcard.exception.VCardException;
+import android.util.Log;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard 3.0 parsing.
+ * </p>
+ * <p>
+ * This class inherits vCard 2.1 implementation since technically they are similar,
+ * while specifically there's logical no relevance between them.
+ * So that developers are not confused with the inheritance,
+ * {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
+ * {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
+ private static final String LOG_TAG = "VCardParserImpl_V30";
+
+ private String mPreviousLine;
+ private boolean mEmittedAgentWarning = false;
+
+ public VCardParserImpl_V30() {
+ super();
+ }
+
+ public VCardParserImpl_V30(int vcardType) {
+ super(vcardType, null);
+ }
+
+ public VCardParserImpl_V30(int vcardType, String importCharset) {
+ super(vcardType, importCharset);
+ }
+
+ @Override
+ protected int getVersion() {
+ return VCardConfig.FLAG_V30;
+ }
+
+ @Override
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V30;
+ }
+
+ @Override
+ protected String getLine() throws IOException {
+ if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ } else {
+ return mReader.readLine();
+ }
+ }
+
+ /**
+ * vCard 3.0 requires that the line with space at the beginning of the line
+ * must be combined with previous line.
+ */
+ @Override
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ StringBuilder builder = null;
+ while (true) {
+ line = mReader.readLine();
+ if (line == null) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.length() == 0) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
+ if (builder != null) {
+ // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
+ // Following is the excerpts from it.
+ //
+ // DESCRIPTION:This is a long description that exists on a long line.
+ //
+ // Can be represented as:
+ //
+ // DESCRIPTION:This is a long description
+ // that exists on a long line.
+ //
+ // It could also be represented as:
+ //
+ // DESCRIPTION:This is a long descrip
+ // tion that exists o
+ // n a long line.
+ builder.append(line.substring(1));
+ } else if (mPreviousLine != null) {
+ builder = new StringBuilder();
+ builder.append(mPreviousLine);
+ mPreviousLine = null;
+ builder.append(line.substring(1));
+ } else {
+ throw new VCardException("Space exists at the beginning of the line");
+ }
+ } else {
+ if (mPreviousLine == null) {
+ mPreviousLine = line;
+ if (builder != null) {
+ return builder.toString();
+ }
+ } else {
+ String ret = mPreviousLine;
+ mPreviousLine = line;
+ return ret;
+ }
+ }
+ }
+ }
+
+ /*
+ * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
+ * 1 * (contentline)
+ * ;A vCard object MUST include the VERSION, FN and N types.
+ * [group "."] "END" ":" "VCARD" 1 * CRLF
+ */
+ @Override
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+ // TODO: vCard 3.0 supports group.
+ return super.readBeginVCard(allowGarbage);
+ }
+
+ @Override
+ protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ throws IOException, VCardException {
+ // TODO: vCard 3.0 supports group.
+ super.readEndVCard(useCache, allowGarbage);
+ }
+
+ /**
+ * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
+ */
+ @Override
+ protected void handleParams(final String params) throws VCardException {
+ try {
+ super.handleParams(params);
+ } catch (VCardException e) {
+ // maybe IANA type
+ String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ handleAnyParam(strArray[0], strArray[1]);
+ } else {
+ // Must not come here in the current implementation.
+ throw new VCardException(
+ "Unknown params value: " + params);
+ }
+ }
+ }
+
+ @Override
+ protected void handleAnyParam(final String paramName, final String paramValue) {
+ super.handleAnyParam(paramName, paramValue);
+ }
+
+ @Override
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ super.handleParamWithoutName(paramValue);
+ }
+
+ /*
+ * vCard 3.0 defines
+ *
+ * param = param-name "=" param-value *("," param-value)
+ * param-name = iana-token / x-name
+ * param-value = ptext / quoted-string
+ * quoted-string = DQUOTE QSAFE-CHAR DQUOTE
+ */
+ @Override
+ protected void handleType(final String ptypevalues) {
+ String[] ptypeArray = ptypevalues.split(",");
+ mInterpreter.propertyParamType("TYPE");
+ for (String value : ptypeArray) {
+ int length = value.length();
+ if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
+ mInterpreter.propertyParamValue(value.substring(1, value.length() - 1));
+ } else {
+ mInterpreter.propertyParamValue(value);
+ }
+ }
+ }
+
+ @Override
+ protected void handleAgent(final String propertyValue) {
+ // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
+ //
+ // e.g.
+ // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
+ // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
+ // ET:jfriday@host.com\nEND:VCARD\n
+ //
+ // TODO: fix this.
+ //
+ // issue:
+ // vCard 3.0 also allows this as an example.
+ //
+ // AGENT;VALUE=uri:
+ // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
+ //
+ // This is not vCard. Should we support this?
+ //
+ // Just ignore the line for now, since we cannot know how to handle it...
+ if (!mEmittedAgentWarning) {
+ Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
+ mEmittedAgentWarning = true;
+ }
+ }
+
+ /**
+ * vCard 3.0 does not require two CRLF at the last of BASE64 data.
+ * It only requires that data should be MIME-encoded.
+ */
+ @Override
+ protected String getBase64(final String firstString)
+ throws IOException, VCardException {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ final String line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
+ mPreviousLine = line;
+ break;
+ }
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
+ * ; \\ encodes \, \n or \N encodes newline
+ * ; \; encodes ;, \, encodes ,
+ *
+ * Note: Apple escapes ':' into '\:' while does not escape '\'
+ */
+ @Override
+ protected String maybeUnescapeText(final String text) {
+ return unescapeText(text);
+ }
+
+ public static String unescapeText(final String text) {
+ StringBuilder builder = new StringBuilder();
+ final int length = text.length();
+ for (int i = 0; i < length; i++) {
+ char ch = text.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ final char next_ch = text.charAt(++i);
+ if (next_ch == 'n' || next_ch == 'N') {
+ builder.append("\n");
+ } else {
+ builder.append(next_ch);
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ return builder.toString();
+ }
+
+ @Override
+ protected String maybeUnescapeCharacter(final char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(final char ch) {
+ if (ch == 'n' || ch == 'N') {
+ return "\n";
+ } else {
+ return String.valueOf(ch);
+ }
+ }
+
+ @Override
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V30.sKnownPropertyNameSet;
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index fe8cfb0..b625695 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -15,922 +15,99 @@
*/
package android.pim.vcard;
-import android.pim.vcard.exception.VCardAgentNotSupportedException;
import android.pim.vcard.exception.VCardException;
-import android.pim.vcard.exception.VCardInvalidCommentLineException;
-import android.pim.vcard.exception.VCardInvalidLineException;
-import android.pim.vcard.exception.VCardNestedException;
-import android.pim.vcard.exception.VCardVersionException;
-import android.util.Log;
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
- * This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail.
+ * </p>
+ * vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
+ * </p>
+ * <p>
+ * The spec is written in 1996, and currently various types of "vCard 2.1" exist.
+ * To handle real the world vCard formats appropriately and effectively, this class does not
+ * obey with strict vCard 2.1.
+ * In stead, not only vCard spec but also real world vCard is considered.
+ * </p>
+ * e.g. A lot of devices and softwares let vCard importer/exporter to use
+ * the PNG format to determine the type of image, while it is not allowed in
+ * the original specification. As of 2010, we can see even the FLV format
+ * (possible in Japanese mobile phones).
+ * </p>
*/
-public class VCardParser_V21 extends VCardParser {
- private static final String LOG_TAG = "VCardParser_V21";
-
- /** Store the known-type */
- private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
- Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
- "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
- "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
- "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
- "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
- "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
- "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
- "WAVE", "AIFF", "PCM", "X509", "PGP"));
-
- /** Store the known-value */
- private static final HashSet<String> sKnownValueSet = new HashSet<String>(
- Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
-
- /** Store the property names available in vCard 2.1 */
- private static final HashSet<String> sAvailablePropertyNameSetV21 =
- new HashSet<String>(Arrays.asList(
- "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
- "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
- "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
+public final class VCardParser_V21 implements VCardParser {
+ /**
+ * A unmodifiable Set storing the property names available in the vCard 2.1 specification.
+ */
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+ "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+ "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")));
/**
+ * A unmodifiable Set storing the types known in vCard 2.1.
+ */
+ /* package */ static final Set<String> sKnownTypeSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
+ "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
+ "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
+ "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
+ "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
+ "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
+ "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
+ "WAVE", "AIFF", "PCM", "X509", "PGP")));
+
+ /**
+ * A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
+ */
+ /* package */ static final Set<String> sKnownValueSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
+
+ /**
+ * <p>
+ * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
+ * </p>
+ * <p>
* Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
- * We allow it for safety...
+ * We allow it for safety.
+ * </p>
*/
- private static final HashSet<String> sAvailableEncodingV21 =
- new HashSet<String>(Arrays.asList(
- "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B"));
-
- // Used only for parsing END:VCARD.
- private String mPreviousLine;
-
- /** The builder to build parsed data */
- protected VCardInterpreter mBuilder = null;
+ /* package */ static final Set<String> sAvailableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_QP,
+ VCardConstants.PARAM_ENCODING_BASE64,
+ VCardConstants.PARAM_ENCODING_B)));
- /**
- * The encoding type. "Encoding" in vCard is different from "Charset".
- * e.g. 7BIT, 8BIT, QUOTED-PRINTABLE.
- */
- protected String mEncoding = null;
-
- protected final String sDefaultEncoding = "8BIT";
-
- // Should not directly read a line from this object. Use getLine() instead.
- protected BufferedReader mReader;
-
- // In some cases, vCard is nested. Currently, we only consider the most interior vCard data.
- // See v21_foma_1.vcf in test directory for more information.
- private int mNestCount;
-
- // In order to reduce warning message as much as possible, we hold the value which made Logger
- // emit a warning message.
- protected Set<String> mUnknownTypeMap = new HashSet<String>();
- protected Set<String> mUnknownValueMap = new HashSet<String>();
-
- // For measuring performance.
- private long mTimeTotal;
- private long mTimeReadStartRecord;
- private long mTimeReadEndRecord;
- private long mTimeStartProperty;
- private long mTimeEndProperty;
- private long mTimeParseItems;
- private long mTimeParseLineAndHandleGroup;
- private long mTimeParsePropertyValues;
- private long mTimeParseAdrOrgN;
- private long mTimeHandleMiscPropertyValue;
- private long mTimeHandleQuotedPrintable;
- private long mTimeHandleBase64;
+ private final VCardParserImpl_V21 mVCardParserImpl;
public VCardParser_V21() {
- this(null);
+ mVCardParserImpl = new VCardParserImpl_V21();
}
- public VCardParser_V21(VCardSourceDetector detector) {
- this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
+ public VCardParser_V21(int vcardType) {
+ mVCardParserImpl = new VCardParserImpl_V21(vcardType);
}
- public VCardParser_V21(int parseType) {
- super(parseType);
- if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
- mNestCount = 1;
- }
+ public VCardParser_V21(int parseType, String inputCharset) {
+ mVCardParserImpl = new VCardParserImpl_V21(parseType, null);
}
- /**
- * Parses the file at the given position.
- *
- * vcard_file = [wsls] vcard [wsls]
- */
- protected void parseVCardFile() throws IOException, VCardException {
- boolean firstReading = true;
- while (true) {
- if (mCanceled) {
- break;
- }
- if (!parseOneVCard(firstReading)) {
- break;
- }
- firstReading = false;
- }
-
- if (mNestCount > 0) {
- boolean useCache = true;
- for (int i = 0; i < mNestCount; i++) {
- readEndVCard(useCache, true);
- useCache = false;
- }
- }
- }
-
- protected int getVersion() {
- return VCardConfig.FLAG_V21;
- }
-
- protected String getVersionString() {
- return VCardConstants.VERSION_V21;
- }
-
- /**
- * @return true when the propertyName is a valid property name.
- */
- protected boolean isValidPropertyName(String propertyName) {
- if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
- propertyName.startsWith("X-")) &&
- !mUnknownTypeMap.contains(propertyName)) {
- mUnknownTypeMap.add(propertyName);
- Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
- }
- return true;
- }
-
- /**
- * @return true when the encoding is a valid encoding.
- */
- protected boolean isValidEncoding(String encoding) {
- return sAvailableEncodingV21.contains(encoding.toUpperCase());
- }
-
- /**
- * @return String. It may be null, or its length may be 0
- * @throws IOException
- */
- protected String getLine() throws IOException {
- return mReader.readLine();
- }
-
- /**
- * @return String with it's length > 0
- * @throws IOException
- * @throws VCardException when the stream reached end of line
- */
- protected String getNonEmptyLine() throws IOException, VCardException {
- String line;
- while (true) {
- line = getLine();
- if (line == null) {
- throw new VCardException("Reached end of buffer.");
- } else if (line.trim().length() > 0) {
- return line;
- }
- }
- }
-
- /**
- * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
- * items *CRLF
- * "END" [ws] ":" [ws] "VCARD"
- */
- private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException {
- boolean allowGarbage = false;
- if (firstReading) {
- if (mNestCount > 0) {
- for (int i = 0; i < mNestCount; i++) {
- if (!readBeginVCard(allowGarbage)) {
- return false;
- }
- allowGarbage = true;
- }
- }
- }
-
- if (!readBeginVCard(allowGarbage)) {
- return false;
- }
- long start;
- if (mBuilder != null) {
- start = System.currentTimeMillis();
- mBuilder.startEntry();
- mTimeReadStartRecord += System.currentTimeMillis() - start;
- }
- start = System.currentTimeMillis();
- parseItems();
- mTimeParseItems += System.currentTimeMillis() - start;
- readEndVCard(true, false);
- if (mBuilder != null) {
- start = System.currentTimeMillis();
- mBuilder.endEntry();
- mTimeReadEndRecord += System.currentTimeMillis() - start;
- }
- return true;
- }
-
- /**
- * @return True when successful. False when reaching the end of line
- * @throws IOException
- * @throws VCardException
- */
- protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
- String line;
- do {
- while (true) {
- line = getLine();
- if (line == null) {
- return false;
- } else if (line.trim().length() > 0) {
- break;
- }
- }
- String[] strArray = line.split(":", 2);
- int length = strArray.length;
-
- // Though vCard 2.1/3.0 specification does not allow lower cases,
- // vCard file emitted by some external vCard expoter have such invalid Strings.
- // So we allow it.
- // e.g. BEGIN:vCard
- if (length == 2 &&
- strArray[0].trim().equalsIgnoreCase("BEGIN") &&
- strArray[1].trim().equalsIgnoreCase("VCARD")) {
- return true;
- } else if (!allowGarbage) {
- if (mNestCount > 0) {
- mPreviousLine = line;
- return false;
- } else {
- throw new VCardException(
- "Expected String \"BEGIN:VCARD\" did not come "
- + "(Instead, \"" + line + "\" came)");
- }
- }
- } while(allowGarbage);
-
- throw new VCardException("Reached where must not be reached.");
- }
-
- /**
- * The arguments useCache and allowGarbase are usually true and false accordingly when
- * this function is called outside this function itself.
- *
- * @param useCache When true, line is obtained from mPreviousline. Otherwise, getLine()
- * is used.
- * @param allowGarbage When true, ignore non "END:VCARD" line.
- * @throws IOException
- * @throws VCardException
- */
- protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
- String line;
- do {
- if (useCache) {
- // Though vCard specification does not allow lower cases,
- // some data may have them, so we allow it.
- line = mPreviousLine;
- } else {
- while (true) {
- line = getLine();
- if (line == null) {
- throw new VCardException("Expected END:VCARD was not found.");
- } else if (line.trim().length() > 0) {
- break;
- }
- }
- }
-
- String[] strArray = line.split(":", 2);
- if (strArray.length == 2 &&
- strArray[0].trim().equalsIgnoreCase("END") &&
- strArray[1].trim().equalsIgnoreCase("VCARD")) {
- return;
- } else if (!allowGarbage) {
- throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
- }
- useCache = false;
- } while (allowGarbage);
- }
-
- /**
- * items = *CRLF item
- * / item
- */
- protected void parseItems() throws IOException, VCardException {
- boolean ended = false;
-
- if (mBuilder != null) {
- long start = System.currentTimeMillis();
- mBuilder.startProperty();
- mTimeStartProperty += System.currentTimeMillis() - start;
- }
- ended = parseItem();
- if (mBuilder != null && !ended) {
- long start = System.currentTimeMillis();
- mBuilder.endProperty();
- mTimeEndProperty += System.currentTimeMillis() - start;
- }
-
- while (!ended) {
- // follow VCARD ,it wont reach endProperty
- if (mBuilder != null) {
- long start = System.currentTimeMillis();
- mBuilder.startProperty();
- mTimeStartProperty += System.currentTimeMillis() - start;
- }
- try {
- ended = parseItem();
- } catch (VCardInvalidCommentLineException e) {
- Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
- ended = false;
- }
- if (mBuilder != null && !ended) {
- long start = System.currentTimeMillis();
- mBuilder.endProperty();
- mTimeEndProperty += System.currentTimeMillis() - start;
- }
- }
- }
-
- /**
- * item = [groups "."] name [params] ":" value CRLF
- * / [groups "."] "ADR" [params] ":" addressparts CRLF
- * / [groups "."] "ORG" [params] ":" orgparts CRLF
- * / [groups "."] "N" [params] ":" nameparts CRLF
- * / [groups "."] "AGENT" [params] ":" vcard CRLF
- */
- protected boolean parseItem() throws IOException, VCardException {
- mEncoding = sDefaultEncoding;
-
- final String line = getNonEmptyLine();
- long start = System.currentTimeMillis();
-
- String[] propertyNameAndValue = separateLineAndHandleGroup(line);
- if (propertyNameAndValue == null) {
- return true;
- }
- if (propertyNameAndValue.length != 2) {
- throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
- }
- String propertyName = propertyNameAndValue[0].toUpperCase();
- String propertyValue = propertyNameAndValue[1];
-
- mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
-
- if (propertyName.equals("ADR") || propertyName.equals("ORG") ||
- propertyName.equals("N")) {
- start = System.currentTimeMillis();
- handleMultiplePropertyValue(propertyName, propertyValue);
- mTimeParseAdrOrgN += System.currentTimeMillis() - start;
- return false;
- } else if (propertyName.equals("AGENT")) {
- handleAgent(propertyValue);
- return false;
- } else if (isValidPropertyName(propertyName)) {
- if (propertyName.equals("BEGIN")) {
- if (propertyValue.equals("VCARD")) {
- throw new VCardNestedException("This vCard has nested vCard data in it.");
- } else {
- throw new VCardException("Unknown BEGIN type: " + propertyValue);
- }
- } else if (propertyName.equals("VERSION") &&
- !propertyValue.equals(getVersionString())) {
- throw new VCardVersionException("Incompatible version: " +
- propertyValue + " != " + getVersionString());
- }
- start = System.currentTimeMillis();
- handlePropertyValue(propertyName, propertyValue);
- mTimeParsePropertyValues += System.currentTimeMillis() - start;
- return false;
- }
-
- throw new VCardException("Unknown property name: \"" + propertyName + "\"");
+ mVCardParserImpl.parse(is, interepreter);
}
- static private final int STATE_GROUP_OR_PROPNAME = 0;
- static private final int STATE_PARAMS = 1;
- // vCard 3.0 specification allows double-quoted param-value, while vCard 2.1 does not.
- // This is just for safety.
- static private final int STATE_PARAMS_IN_DQUOTE = 2;
-
- protected String[] separateLineAndHandleGroup(String line) throws VCardException {
- int state = STATE_GROUP_OR_PROPNAME;
- int nameIndex = 0;
-
- final String[] propertyNameAndValue = new String[2];
-
- final int length = line.length();
- if (length > 0 && line.charAt(0) == '#') {
- throw new VCardInvalidCommentLineException();
- }
-
- for (int i = 0; i < length; i++) {
- char ch = line.charAt(i);
- switch (state) {
- case STATE_GROUP_OR_PROPNAME: {
- if (ch == ':') {
- final String propertyName = line.substring(nameIndex, i);
- if (propertyName.equalsIgnoreCase("END")) {
- mPreviousLine = line;
- return null;
- }
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- }
- propertyNameAndValue[0] = propertyName;
- if (i < length - 1) {
- propertyNameAndValue[1] = line.substring(i + 1);
- } else {
- propertyNameAndValue[1] = "";
- }
- return propertyNameAndValue;
- } else if (ch == '.') {
- String groupName = line.substring(nameIndex, i);
- if (mBuilder != null) {
- mBuilder.propertyGroup(groupName);
- }
- nameIndex = i + 1;
- } else if (ch == ';') {
- String propertyName = line.substring(nameIndex, i);
- if (propertyName.equalsIgnoreCase("END")) {
- mPreviousLine = line;
- return null;
- }
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- }
- propertyNameAndValue[0] = propertyName;
- nameIndex = i + 1;
- state = STATE_PARAMS;
- }
- break;
- }
- case STATE_PARAMS: {
- if (ch == '"') {
- state = STATE_PARAMS_IN_DQUOTE;
- } else if (ch == ';') {
- handleParams(line.substring(nameIndex, i));
- nameIndex = i + 1;
- } else if (ch == ':') {
- handleParams(line.substring(nameIndex, i));
- if (i < length - 1) {
- propertyNameAndValue[1] = line.substring(i + 1);
- } else {
- propertyNameAndValue[1] = "";
- }
- return propertyNameAndValue;
- }
- break;
- }
- case STATE_PARAMS_IN_DQUOTE: {
- if (ch == '"') {
- state = STATE_PARAMS;
- }
- break;
- }
- }
- }
-
- throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
- }
-
- /**
- * params = ";" [ws] paramlist
- * paramlist = paramlist [ws] ";" [ws] param
- * / param
- * param = "TYPE" [ws] "=" [ws] ptypeval
- * / "VALUE" [ws] "=" [ws] pvalueval
- * / "ENCODING" [ws] "=" [ws] pencodingval
- * / "CHARSET" [ws] "=" [ws] charsetval
- * / "LANGUAGE" [ws] "=" [ws] langval
- * / "X-" word [ws] "=" [ws] word
- * / knowntype
- */
- protected void handleParams(String params) throws VCardException {
- String[] strArray = params.split("=", 2);
- if (strArray.length == 2) {
- final String paramName = strArray[0].trim().toUpperCase();
- String paramValue = strArray[1].trim();
- if (paramName.equals("TYPE")) {
- handleType(paramValue);
- } else if (paramName.equals("VALUE")) {
- handleValue(paramValue);
- } else if (paramName.equals("ENCODING")) {
- handleEncoding(paramValue);
- } else if (paramName.equals("CHARSET")) {
- handleCharset(paramValue);
- } else if (paramName.equals("LANGUAGE")) {
- handleLanguage(paramValue);
- } else if (paramName.startsWith("X-")) {
- handleAnyParam(paramName, paramValue);
- } else {
- throw new VCardException("Unknown type \"" + paramName + "\"");
- }
- } else {
- handleParamWithoutName(strArray[0]);
- }
- }
-
- /**
- * vCard 3.0 parser may throw VCardException.
- */
- @SuppressWarnings("unused")
- protected void handleParamWithoutName(final String paramValue) throws VCardException {
- handleType(paramValue);
- }
-
- /**
- * ptypeval = knowntype / "X-" word
- */
- protected void handleType(final String ptypeval) {
- String upperTypeValue = ptypeval;
- if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) &&
- !mUnknownTypeMap.contains(ptypeval)) {
- mUnknownTypeMap.add(ptypeval);
- Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval);
- }
- if (mBuilder != null) {
- mBuilder.propertyParamType("TYPE");
- mBuilder.propertyParamValue(upperTypeValue);
- }
- }
-
- /**
- * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
- */
- protected void handleValue(final String pvalueval) {
- if (!sKnownValueSet.contains(pvalueval.toUpperCase()) &&
- pvalueval.startsWith("X-") &&
- !mUnknownValueMap.contains(pvalueval)) {
- mUnknownValueMap.add(pvalueval);
- Log.w(LOG_TAG, "VALUE unsupported by vCard 2.1: " + pvalueval);
- }
- if (mBuilder != null) {
- mBuilder.propertyParamType("VALUE");
- mBuilder.propertyParamValue(pvalueval);
- }
- }
-
- /**
- * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
- */
- protected void handleEncoding(String pencodingval) throws VCardException {
- if (isValidEncoding(pencodingval) ||
- pencodingval.startsWith("X-")) {
- if (mBuilder != null) {
- mBuilder.propertyParamType("ENCODING");
- mBuilder.propertyParamValue(pencodingval);
- }
- mEncoding = pencodingval;
- } else {
- throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
- }
- }
-
- /**
- * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
- * but today's vCard often contains other charset, so we allow them.
- */
- protected void handleCharset(String charsetval) {
- if (mBuilder != null) {
- mBuilder.propertyParamType("CHARSET");
- mBuilder.propertyParamValue(charsetval);
- }
- }
-
- /**
- * See also Section 7.1 of RFC 1521
- */
- protected void handleLanguage(String langval) throws VCardException {
- String[] strArray = langval.split("-");
- if (strArray.length != 2) {
- throw new VCardException("Invalid Language: \"" + langval + "\"");
- }
- String tmp = strArray[0];
- int length = tmp.length();
- for (int i = 0; i < length; i++) {
- if (!isLetter(tmp.charAt(i))) {
- throw new VCardException("Invalid Language: \"" + langval + "\"");
- }
- }
- tmp = strArray[1];
- length = tmp.length();
- for (int i = 0; i < length; i++) {
- if (!isLetter(tmp.charAt(i))) {
- throw new VCardException("Invalid Language: \"" + langval + "\"");
- }
- }
- if (mBuilder != null) {
- mBuilder.propertyParamType("LANGUAGE");
- mBuilder.propertyParamValue(langval);
- }
- }
-
- /**
- * Mainly for "X-" type. This accepts any kind of type without check.
- */
- protected void handleAnyParam(String paramName, String paramValue) {
- if (mBuilder != null) {
- mBuilder.propertyParamType(paramName);
- mBuilder.propertyParamValue(paramValue);
- }
- }
-
- protected void handlePropertyValue(String propertyName, String propertyValue)
- throws IOException, VCardException {
- if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
- final long start = System.currentTimeMillis();
- final String result = getQuotedPrintable(propertyValue);
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(result);
- mBuilder.propertyValues(v);
- }
- mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
- } else if (mEncoding.equalsIgnoreCase("BASE64") ||
- mEncoding.equalsIgnoreCase("B")) {
- final long start = System.currentTimeMillis();
- // It is very rare, but some BASE64 data may be so big that
- // OutOfMemoryError occurs. To ignore such cases, use try-catch.
- try {
- final String result = getBase64(propertyValue);
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(result);
- mBuilder.propertyValues(v);
- }
- } catch (OutOfMemoryError error) {
- Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
- if (mBuilder != null) {
- mBuilder.propertyValues(null);
- }
- }
- mTimeHandleBase64 += System.currentTimeMillis() - start;
- } else {
- if (!(mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
- || mEncoding.equalsIgnoreCase("8BIT")
- || mEncoding.toUpperCase().startsWith("X-"))) {
- Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\".");
- }
-
- final long start = System.currentTimeMillis();
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(maybeUnescapeText(propertyValue));
- mBuilder.propertyValues(v);
- }
- mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
- }
- }
-
- protected String getQuotedPrintable(String firstString) throws IOException, VCardException {
- // Specifically, there may be some padding between = and CRLF.
- // See the following:
- //
- // qp-line := *(qp-segment transport-padding CRLF)
- // qp-part transport-padding
- // qp-segment := qp-section *(SPACE / TAB) "="
- // ; Maximum length of 76 characters
- //
- // e.g. (from RFC 2045)
- // Now's the time =
- // for all folk to come=
- // to the aid of their country.
- if (firstString.trim().endsWith("=")) {
- // remove "transport-padding"
- int pos = firstString.length() - 1;
- while(firstString.charAt(pos) != '=') {
- }
- StringBuilder builder = new StringBuilder();
- builder.append(firstString.substring(0, pos + 1));
- builder.append("\r\n");
- String line;
- while (true) {
- line = getLine();
- if (line == null) {
- throw new VCardException(
- "File ended during parsing quoted-printable String");
- }
- if (line.trim().endsWith("=")) {
- // remove "transport-padding"
- pos = line.length() - 1;
- while(line.charAt(pos) != '=') {
- }
- builder.append(line.substring(0, pos + 1));
- builder.append("\r\n");
- } else {
- builder.append(line);
- break;
- }
- }
- return builder.toString();
- } else {
- return firstString;
- }
- }
-
- protected String getBase64(String firstString) throws IOException, VCardException {
- StringBuilder builder = new StringBuilder();
- builder.append(firstString);
-
- while (true) {
- String line = getLine();
- if (line == null) {
- throw new VCardException(
- "File ended during parsing BASE64 binary");
- }
- if (line.length() == 0) {
- break;
- }
- builder.append(line);
- }
-
- return builder.toString();
- }
-
- /**
- * Mainly for "ADR", "ORG", and "N"
- * We do not care the number of strnosemi here.
- *
- * addressparts = 0*6(strnosemi ";") strnosemi
- * ; PO Box, Extended Addr, Street, Locality, Region,
- * Postal Code, Country Name
- * orgparts = *(strnosemi ";") strnosemi
- * ; First is Organization Name,
- * remainder are Organization Units.
- * nameparts = 0*4(strnosemi ";") strnosemi
- * ; Family, Given, Middle, Prefix, Suffix.
- * ; Example:Public;John;Q.;Reverend Dr.;III, Esq.
- * strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi
- * ; To include a semicolon in this string, it must be escaped
- * ; with a "\" character.
- *
- * We are not sure whether we should add "\" CRLF to each value.
- * For now, we exclude them.
- */
- protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
- throws IOException, VCardException {
- // vCard 2.1 does not allow QUOTED-PRINTABLE here,
- // but some softwares/devices emit such data.
- if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
- propertyValue = getQuotedPrintable(propertyValue);
- }
-
- if (mBuilder != null) {
- mBuilder.propertyValues(VCardUtils.constructListFromValue(
- propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
- }
- }
-
- /**
- * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
- *
- * item = ...
- * / [groups "."] "AGENT"
- * [params] ":" vcard CRLF
- * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
- * items *CRLF "END" [ws] ":" [ws] "VCARD"
- */
- protected void handleAgent(final String propertyValue) throws VCardException {
- if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
- // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
- return;
- } else {
- throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
- }
- // TODO: Support AGENT property.
- }
-
- /**
- * For vCard 3.0.
- */
- protected String maybeUnescapeText(final String text) {
- return text;
- }
-
- /**
- * Returns unescaped String if the character should be unescaped. Return null otherwise.
- * e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be.
- */
- protected String maybeUnescapeCharacter(final char ch) {
- return unescapeCharacter(ch);
- }
-
- public static String unescapeCharacter(final char ch) {
- // Original vCard 2.1 specification does not allow transformation
- // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
- // this class allowed them, so keep it as is.
- if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
- return String.valueOf(ch);
- } else {
- return null;
- }
- }
-
- @Override
- public boolean parse(final InputStream is, final VCardInterpreter builder)
- throws IOException, VCardException {
- return parse(is, VCardConfig.DEFAULT_CHARSET, builder);
- }
-
- @Override
- public boolean parse(InputStream is, String charset, VCardInterpreter builder)
- throws IOException, VCardException {
- if (charset == null) {
- charset = VCardConfig.DEFAULT_CHARSET;
- }
- final InputStreamReader tmpReader = new InputStreamReader(is, charset);
- if (VCardConfig.showPerformanceLog()) {
- mReader = new CustomBufferedReader(tmpReader);
- } else {
- mReader = new BufferedReader(tmpReader);
- }
-
- mBuilder = builder;
-
- long start = System.currentTimeMillis();
- if (mBuilder != null) {
- mBuilder.start();
- }
- parseVCardFile();
- if (mBuilder != null) {
- mBuilder.end();
- }
- mTimeTotal += System.currentTimeMillis() - start;
-
- if (VCardConfig.showPerformanceLog()) {
- showPerformanceInfo();
- }
-
- return true;
- }
-
- @Override
- public void parse(InputStream is, String charset, VCardInterpreter builder, boolean canceled)
- throws IOException, VCardException {
- mCanceled = canceled;
- parse(is, charset, builder);
- }
-
- private void showPerformanceInfo() {
- Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
- if (mReader instanceof CustomBufferedReader) {
- Log.d(LOG_TAG, "Total readLine time: " +
- ((CustomBufferedReader)mReader).getTotalmillisecond() + " ms");
- }
- Log.d(LOG_TAG, "Time for handling the beggining of the record: " +
- mTimeReadStartRecord + " ms");
- Log.d(LOG_TAG, "Time for handling the end of the record: " +
- mTimeReadEndRecord + " ms");
- Log.d(LOG_TAG, "Time for parsing line, and handling group: " +
- mTimeParseLineAndHandleGroup + " ms");
- Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
- Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
- Log.d(LOG_TAG, "Time for handling normal property values: " +
- mTimeHandleMiscPropertyValue + " ms");
- Log.d(LOG_TAG, "Time for handling Quoted-Printable: " +
- mTimeHandleQuotedPrintable + " ms");
- Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
- }
-
- private boolean isLetter(char ch) {
- if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
- return true;
- }
- return false;
- }
-}
-
-class CustomBufferedReader extends BufferedReader {
- private long mTime;
-
- public CustomBufferedReader(Reader in) {
- super(in);
- }
-
- @Override
- public String readLine() throws IOException {
- long start = System.currentTimeMillis();
- String ret = super.readLine();
- long end = System.currentTimeMillis();
- mTime += end - start;
- return ret;
- }
-
- public long getTotalmillisecond() {
- return mTime;
+ public void cancel() {
+ mVCardParserImpl.cancel();
}
}
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 4ecfe97..40792ab 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -16,343 +16,76 @@
package android.pim.vcard;
import android.pim.vcard.exception.VCardException;
-import android.util.Log;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.Set;
/**
- * The class used to parse vCard 3.0.
- * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
+ * <p>
+ * vCard parser for vCard 3.0. See RFC 2426 for more detail.
+ * </p>
+ * <p>
+ * This parser allows vCard format which is not allowed in the RFC, since
+ * we have seen several vCard 3.0 files which don't comply with it.
+ * </p>
+ * <p>
+ * e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
+ * have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
+ * but it is not a must. We silently allow "CHARSET".
+ * </p>
*/
-public class VCardParser_V30 extends VCardParser_V21 {
- private static final String LOG_TAG = "VCardParser_V30";
-
- private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
- Arrays.asList(
+public class VCardParser_V30 implements VCardParser {
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
"NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
- "SORT-STRING", "CATEGORIES", "PRODID")); // 3.0
-
- // Although "7bit" and "BASE64" is not allowed in vCard 3.0, we allow it for safety.
- private static final HashSet<String> sAcceptableEncodingV30 = new HashSet<String>(
- Arrays.asList("7BIT", "8BIT", "BASE64", "B"));
-
- // Although RFC 2426 specifies some property must not have parameters, we allow it,
- // since there may be some careers which violates the RFC...
- private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
-
- private String mPreviousLine;
-
- private boolean mEmittedAgentWarning = false;
+ "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0
/**
- * True when the caller wants the parser to be strict about the input.
- * Currently this is only for testing.
+ * <p>
+ * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
+ * </p>
+ * <p>
+ * Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
+ * </p>
+ * <p>
+ * "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
+ * because the encoding ambiguates how the vCard file to be parsed.
+ * </p>
*/
- private final boolean mStrictParsing;
+ /* package */ static final Set<String> sAcceptableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ VCardConstants.PARAM_ENCODING_7BIT,
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_BASE64,
+ VCardConstants.PARAM_ENCODING_B)));
+
+ private final VCardParserImpl_V30 mVCardParserImpl;
public VCardParser_V30() {
- super();
- mStrictParsing = false;
+ mVCardParserImpl = new VCardParserImpl_V30();
}
- /**
- * @param strictParsing when true, this object throws VCardException when the vcard is not
- * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
- * is not fully yet for being used with this flag and may not notice invalid line(s).
- *
- * @hide currently only for testing!
- */
- public VCardParser_V30(boolean strictParsing) {
- super();
- mStrictParsing = strictParsing;
+ public VCardParser_V30(int vcardType) {
+ mVCardParserImpl = new VCardParserImpl_V30(vcardType);
}
- public VCardParser_V30(int parseMode) {
- super(parseMode);
- mStrictParsing = false;
+ public VCardParser_V30(int vcardType, String importCharset) {
+ mVCardParserImpl = new VCardParserImpl_V30(vcardType, importCharset);
}
- @Override
- protected int getVersion() {
- return VCardConfig.FLAG_V30;
- }
-
- @Override
- protected String getVersionString() {
- return VCardConstants.VERSION_V30;
- }
-
- @Override
- protected boolean isValidPropertyName(String propertyName) {
- if (!(sAcceptablePropsWithParam.contains(propertyName) ||
- acceptablePropsWithoutParam.contains(propertyName) ||
- propertyName.startsWith("X-")) &&
- !mUnknownTypeMap.contains(propertyName)) {
- mUnknownTypeMap.add(propertyName);
- Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName);
- }
- return true;
- }
-
- @Override
- protected boolean isValidEncoding(String encoding) {
- return sAcceptableEncodingV30.contains(encoding.toUpperCase());
- }
-
- @Override
- protected String getLine() throws IOException {
- if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- } else {
- return mReader.readLine();
- }
- }
-
- /**
- * vCard 3.0 requires that the line with space at the beginning of the line
- * must be combined with previous line.
- */
- @Override
- protected String getNonEmptyLine() throws IOException, VCardException {
- String line;
- StringBuilder builder = null;
- while (true) {
- line = mReader.readLine();
- if (line == null) {
- if (builder != null) {
- return builder.toString();
- } else if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- }
- throw new VCardException("Reached end of buffer.");
- } else if (line.length() == 0) {
- if (builder != null) {
- return builder.toString();
- } else if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- }
- } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
- if (builder != null) {
- // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
- // Following is the excerpts from it.
- //
- // DESCRIPTION:This is a long description that exists on a long line.
- //
- // Can be represented as:
- //
- // DESCRIPTION:This is a long description
- // that exists on a long line.
- //
- // It could also be represented as:
- //
- // DESCRIPTION:This is a long descrip
- // tion that exists o
- // n a long line.
- builder.append(line.substring(1));
- } else if (mPreviousLine != null) {
- builder = new StringBuilder();
- builder.append(mPreviousLine);
- mPreviousLine = null;
- builder.append(line.substring(1));
- } else {
- throw new VCardException("Space exists at the beginning of the line");
- }
- } else {
- if (mPreviousLine == null) {
- mPreviousLine = line;
- if (builder != null) {
- return builder.toString();
- }
- } else {
- String ret = mPreviousLine;
- mPreviousLine = line;
- return ret;
- }
- }
- }
- }
-
-
- /**
- * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
- * 1 * (contentline)
- * ;A vCard object MUST include the VERSION, FN and N types.
- * [group "."] "END" ":" "VCARD" 1 * CRLF
- */
- @Override
- protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
- // TODO: vCard 3.0 supports group.
- return super.readBeginVCard(allowGarbage);
- }
-
- @Override
- protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
- // TODO: vCard 3.0 supports group.
- super.readEndVCard(useCache, allowGarbage);
+ mVCardParserImpl.parse(is, interepreter);
}
- /**
- * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
- */
- @Override
- protected void handleParams(String params) throws VCardException {
- try {
- super.handleParams(params);
- } catch (VCardException e) {
- // maybe IANA type
- String[] strArray = params.split("=", 2);
- if (strArray.length == 2) {
- handleAnyParam(strArray[0], strArray[1]);
- } else {
- // Must not come here in the current implementation.
- throw new VCardException(
- "Unknown params value: " + params);
- }
- }
- }
-
- @Override
- protected void handleAnyParam(String paramName, String paramValue) {
- super.handleAnyParam(paramName, paramValue);
- }
-
- @Override
- protected void handleParamWithoutName(final String paramValue) throws VCardException {
- if (mStrictParsing) {
- throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
- } else {
- super.handleParamWithoutName(paramValue);
- }
- }
-
- /**
- * vCard 3.0 defines
- *
- * param = param-name "=" param-value *("," param-value)
- * param-name = iana-token / x-name
- * param-value = ptext / quoted-string
- * quoted-string = DQUOTE QSAFE-CHAR DQUOTE
- */
- @Override
- protected void handleType(String ptypevalues) {
- String[] ptypeArray = ptypevalues.split(",");
- mBuilder.propertyParamType("TYPE");
- for (String value : ptypeArray) {
- int length = value.length();
- if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
- mBuilder.propertyParamValue(value.substring(1, value.length() - 1));
- } else {
- mBuilder.propertyParamValue(value);
- }
- }
- }
-
- @Override
- protected void handleAgent(String propertyValue) {
- // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
- //
- // e.g.
- // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
- // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
- // ET:jfriday@host.com\nEND:VCARD\n
- //
- // TODO: fix this.
- //
- // issue:
- // vCard 3.0 also allows this as an example.
- //
- // AGENT;VALUE=uri:
- // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
- //
- // This is not vCard. Should we support this?
- //
- // Just ignore the line for now, since we cannot know how to handle it...
- if (!mEmittedAgentWarning) {
- Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
- mEmittedAgentWarning = true;
- }
- }
-
- /**
- * vCard 3.0 does not require two CRLF at the last of BASE64 data.
- * It only requires that data should be MIME-encoded.
- */
- @Override
- protected String getBase64(String firstString) throws IOException, VCardException {
- StringBuilder builder = new StringBuilder();
- builder.append(firstString);
-
- while (true) {
- String line = getLine();
- if (line == null) {
- throw new VCardException(
- "File ended during parsing BASE64 binary");
- }
- if (line.length() == 0) {
- break;
- } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
- mPreviousLine = line;
- break;
- }
- builder.append(line);
- }
-
- return builder.toString();
- }
-
- /**
- * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
- * ; \\ encodes \, \n or \N encodes newline
- * ; \; encodes ;, \, encodes ,
- *
- * Note: Apple escapes ':' into '\:' while does not escape '\'
- */
- @Override
- protected String maybeUnescapeText(String text) {
- return unescapeText(text);
- }
-
- public static String unescapeText(String text) {
- StringBuilder builder = new StringBuilder();
- int length = text.length();
- for (int i = 0; i < length; i++) {
- char ch = text.charAt(i);
- if (ch == '\\' && i < length - 1) {
- char next_ch = text.charAt(++i);
- if (next_ch == 'n' || next_ch == 'N') {
- builder.append("\n");
- } else {
- builder.append(next_ch);
- }
- } else {
- builder.append(ch);
- }
- }
- return builder.toString();
- }
-
- @Override
- protected String maybeUnescapeCharacter(char ch) {
- return unescapeCharacter(ch);
- }
-
- public static String unescapeCharacter(char ch) {
- if (ch == 'n' || ch == 'N') {
- return "\n";
- } else {
- return String.valueOf(ch);
- }
+ public void cancel() {
+ mVCardParserImpl.cancel();
}
}
diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java
index 7297c50..291deca 100644
--- a/core/java/android/pim/vcard/VCardSourceDetector.java
+++ b/core/java/android/pim/vcard/VCardSourceDetector.java
@@ -15,15 +15,28 @@
*/
package android.pim.vcard;
+import android.text.TextUtils;
+
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
- * Class which tries to detects the source of the vCard from its properties.
- * Currently this implementation is very premature.
- * @hide
+ * <p>
+ * The class which tries to detects the source of a vCard file from its contents.
+ * </p>
+ * <p>
+ * The specification of vCard (including both 2.1 and 3.0) is not so strict as to
+ * guess its format just by reading beginning few lines (usually we can, but in
+ * some most pessimistic case, we cannot until at almost the end of the file).
+ * Also we cannot store all vCard entries in memory, while there's no specification
+ * how big the vCard entry would become after the parse.
+ * </p>
+ * <p>
+ * This class is usually used for the "first scan", in which we can understand which vCard
+ * version is used (and how many entries exist in a file).
+ * </p>
*/
public class VCardSourceDetector implements VCardInterpreter {
private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
@@ -42,8 +55,22 @@
"X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED",
"X-SD-DESCRIPTION"));
private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
-
- private int mType = VCardConfig.PARSE_TYPE_UNKNOWN;
+
+
+ // TODO: Should replace this with types in VCardConfig
+ private static final int PARSE_TYPE_UNKNOWN = 0;
+ // For Apple's software, which does not mean this type is effective for all its products.
+ // We confirmed they usually use UTF-8, but not sure about vCard type.
+ private static final int PARSE_TYPE_APPLE = 1;
+ // For Japanese mobile phones, which are usually using Shift_JIS as a charset.
+ private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;
+ // For some of mobile phones released from DoCoMo, which use nested vCard.
+ private static final int PARSE_TYPE_DOCOMO_TORELATE_NEST = 3;
+ // For Japanese Windows Mobel phones. It's version is supposed to be 6.5.
+ private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
+
+ private int mParseType = 0; // Not sure.
+
// Some mobile phones (like FOMA) tells us the charset of the data.
private boolean mNeedParseSpecifiedCharset;
private String mSpecifiedCharset;
@@ -72,21 +99,22 @@
public void propertyName(String name) {
if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
- mType = VCardConfig.PARSE_TYPE_FOMA;
+ mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
+ // Probably Shift_JIS is used, but we should double confirm.
mNeedParseSpecifiedCharset = true;
return;
}
- if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) {
+ if (mParseType != PARSE_TYPE_UNKNOWN) {
return;
}
if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
- mType = VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP;
+ mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
} else if (FOMA_SIGNS.contains(name)) {
- mType = VCardConfig.PARSE_TYPE_FOMA;
+ mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
} else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
- mType = VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP;
+ mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
} else if (APPLE_SIGNS.contains(name)) {
- mType = VCardConfig.PARSE_TYPE_APPLE;
+ mParseType = PARSE_TYPE_APPLE;
}
}
@@ -102,25 +130,40 @@
}
}
- /* package */ int getEstimatedType() {
- return mType;
- }
-
/**
- * Return charset String guessed from the source's properties.
+ * @return The available type can be used with vCard parser. You probably need to
+ * use {{@link #getEstimatedCharset()} to understand the charset to be used.
+ */
+ public int getEstimatedType() {
+ switch (mParseType) {
+ case PARSE_TYPE_DOCOMO_TORELATE_NEST:
+ return VCardConfig.VCARD_TYPE_DOCOMO | VCardConfig.FLAG_TORELATE_NEST;
+ case PARSE_TYPE_MOBILE_PHONE_JP:
+ return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
+ case PARSE_TYPE_APPLE:
+ case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
+ default:
+ return VCardConfig.VCARD_TYPE_UNKNOWN;
+ }
+ }
+
+ /**
+ * <p>
+ * Returns charset String guessed from the source's properties.
* This method must be called after parsing target file(s).
+ * </p>
* @return Charset String. Null is returned if guessing the source fails.
*/
public String getEstimatedCharset() {
- if (mSpecifiedCharset != null) {
+ if (TextUtils.isEmpty(mSpecifiedCharset)) {
return mSpecifiedCharset;
}
- switch (mType) {
- case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP:
- case VCardConfig.PARSE_TYPE_FOMA:
- case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP:
+ switch (mParseType) {
+ case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
+ case PARSE_TYPE_DOCOMO_TORELATE_NEST:
+ case PARSE_TYPE_MOBILE_PHONE_JP:
return "SHIFT_JIS";
- case VCardConfig.PARSE_TYPE_APPLE:
+ case PARSE_TYPE_APPLE:
return "UTF-8";
default:
return null;
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 11b112b..680ef6f 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -22,7 +22,12 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Log;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.net.QuotedPrintableCodec;
+
+import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -34,8 +39,11 @@
/**
* Utilities for VCard handling codes.
+ * @hide
*/
public class VCardUtils {
+ private static final String LOG_TAG = "VCardUtils";
+
// Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
// converted to two parameter Strings. These only contain some minor fields valid in both
// vCard and current (as of 2009-08-07) Contacts structure.
@@ -240,10 +248,13 @@
}
/**
+ * <p>
* Inserts postal data into the builder object.
- *
+ * </p>
+ * <p>
* Note that the data structure of ContactsContract is different from that defined in vCard.
* So some conversion may be performed in this method.
+ * </p>
*/
public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
final ContentProviderOperation.Builder builder,
@@ -329,8 +340,8 @@
if (ch == '\\' && i < length - 1) {
char nextCh = value.charAt(i + 1);
final String unescapedString =
- (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
- VCardParser_V21.unescapeCharacter(nextCh));
+ (isV30 ? VCardParserImpl_V30.unescapeCharacter(nextCh) :
+ VCardParserImpl_V21.unescapeCharacter(nextCh));
if (unescapedString != null) {
builder.append(unescapedString);
i++;
@@ -371,9 +382,13 @@
}
/**
+ * <p>
* This is useful when checking the string should be encoded into quoted-printable
* or not, which is required by vCard 2.1.
+ * </p>
+ * <p>
* See the definition of "7bit" in vCard 2.1 spec for more information.
+ * </p>
*/
public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
if (values == null) {
@@ -407,13 +422,16 @@
new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
/**
+ * <p>
* This is useful since vCard 3.0 often requires the ("X-") properties and groups
* should contain only alphabets, digits, and hyphen.
- *
+ * </p>
+ * <p>
* Note: It is already known some devices (wrongly) outputs properties with characters
* which should not be in the field. One example is "X-GOOGLE TALK". We accept
* such kind of input but must never output it unless the target is very specific
- * to the device which is able to parse the malformed input.
+ * to the device which is able to parse the malformed input.
+ * </p>
*/
public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
if (values == null) {
@@ -452,13 +470,13 @@
}
/**
- * <P>
+ * <p>
* Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
- * </P>
- * <P>
- * vCard 2.1 specifies:<BR />
+ * </p>
+ * <p>
+ * vCard 2.1 specifies:<br />
* word = <any printable 7bit us-ascii except []=:., >
- * </P>
+ * </p>
*/
public static boolean isV21Word(final String value) {
if (TextUtils.isEmpty(value)) {
@@ -540,6 +558,96 @@
return true;
}
+ //// The methods bellow may be used by unit test.
+
+ /**
+ * @hide
+ */
+ public static String parseQuotedPrintable(String value, boolean strictLineBreaking,
+ String sourceCharset, String targetCharset) {
+ // "= " -> " ", "=\t" -> "\t".
+ // Previous code had done this replacement. Keep on the safe side.
+ final String quotedPrintable;
+ {
+ final StringBuilder builder = new StringBuilder();
+ final int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char ch = value.charAt(i);
+ if (ch == '=' && i < length - 1) {
+ char nextCh = value.charAt(i + 1);
+ if (nextCh == ' ' || nextCh == '\t') {
+ builder.append(nextCh);
+ i++;
+ continue;
+ }
+ }
+ builder.append(ch);
+ }
+ quotedPrintable = builder.toString();
+ }
+
+ String[] lines;
+ if (strictLineBreaking) {
+ lines = quotedPrintable.split("\r\n");
+ } else {
+ StringBuilder builder = new StringBuilder();
+ final int length = quotedPrintable.length();
+ ArrayList<String> list = new ArrayList<String>();
+ for (int i = 0; i < length; i++) {
+ char ch = quotedPrintable.charAt(i);
+ if (ch == '\n') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ } else if (ch == '\r') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ if (i < length - 1) {
+ char nextCh = quotedPrintable.charAt(i + 1);
+ if (nextCh == '\n') {
+ i++;
+ }
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ final String lastLine = builder.toString();
+ if (lastLine.length() > 0) {
+ list.add(lastLine);
+ }
+ lines = list.toArray(new String[0]);
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ for (String line : lines) {
+ if (line.endsWith("=")) {
+ line = line.substring(0, line.length() - 1);
+ }
+ builder.append(line);
+ }
+ byte[] bytes;
+ try {
+ bytes = builder.toString().getBytes(sourceCharset);
+ } catch (UnsupportedEncodingException e1) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ bytes = builder.toString().getBytes();
+ }
+
+ try {
+ bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
+ } catch (DecoderException e) {
+ Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
+ return "";
+ }
+
+ try {
+ return new String(bytes, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return new String(bytes);
+ }
+ }
+
private VCardUtils() {
}
}
diff --git a/core/java/android/preference/MultiSelectListPreference.java b/core/java/android/preference/MultiSelectListPreference.java
new file mode 100644
index 0000000..42d555cf
--- /dev/null
+++ b/core/java/android/preference/MultiSelectListPreference.java
@@ -0,0 +1,274 @@
+/*
+ * 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.preference;
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A {@link Preference} that displays a list of entries as
+ * a dialog.
+ * <p>
+ * This preference will store a set of strings into the SharedPreferences.
+ * This set will contain one or more values from the
+ * {@link #setEntryValues(CharSequence[])} array.
+ *
+ * @attr ref android.R.styleable#MultiSelectListPreference_entries
+ * @attr ref android.R.styleable#MultiSelectListPreference_entryValues
+ */
+public class MultiSelectListPreference extends DialogPreference {
+ private CharSequence[] mEntries;
+ private CharSequence[] mEntryValues;
+ private Set<String> mValues = new HashSet<String>();
+ private Set<String> mNewValues = new HashSet<String>();
+ private boolean mPreferenceChanged;
+
+ public MultiSelectListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.MultiSelectListPreference, 0, 0);
+ mEntries = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entries);
+ mEntryValues = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entryValues);
+ a.recycle();
+ }
+
+ public MultiSelectListPreference(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Sets the human-readable entries to be shown in the list. This will be
+ * shown in subsequent dialogs.
+ * <p>
+ * Each entry must have a corresponding index in
+ * {@link #setEntryValues(CharSequence[])}.
+ *
+ * @param entries The entries.
+ * @see #setEntryValues(CharSequence[])
+ */
+ public void setEntries(CharSequence[] entries) {
+ mEntries = entries;
+ }
+
+ /**
+ * @see #setEntries(CharSequence[])
+ * @param entriesResId The entries array as a resource.
+ */
+ public void setEntries(int entriesResId) {
+ setEntries(getContext().getResources().getTextArray(entriesResId));
+ }
+
+ /**
+ * The list of entries to be shown in the list in subsequent dialogs.
+ *
+ * @return The list as an array.
+ */
+ public CharSequence[] getEntries() {
+ return mEntries;
+ }
+
+ /**
+ * The array to find the value to save for a preference when an entry from
+ * entries is selected. If a user clicks on the second item in entries, the
+ * second item in this array will be saved to the preference.
+ *
+ * @param entryValues The array to be used as values to save for the preference.
+ */
+ public void setEntryValues(CharSequence[] entryValues) {
+ mEntryValues = entryValues;
+ }
+
+ /**
+ * @see #setEntryValues(CharSequence[])
+ * @param entryValuesResId The entry values array as a resource.
+ */
+ public void setEntryValues(int entryValuesResId) {
+ setEntryValues(getContext().getResources().getTextArray(entryValuesResId));
+ }
+
+ /**
+ * Returns the array of values to be saved for the preference.
+ *
+ * @return The array of values.
+ */
+ public CharSequence[] getEntryValues() {
+ return mEntryValues;
+ }
+
+ /**
+ * Sets the value of the key. This should contain entries in
+ * {@link #getEntryValues()}.
+ *
+ * @param values The values to set for the key.
+ */
+ public void setValues(Set<String> values) {
+ mValues = values;
+
+ persistStringSet(values);
+ }
+
+ /**
+ * Retrieves the current value of the key.
+ */
+ public Set<String> getValues() {
+ return mValues;
+ }
+
+ /**
+ * Returns the index of the given value (in the entry values array).
+ *
+ * @param value The value whose index should be returned.
+ * @return The index of the value, or -1 if not found.
+ */
+ public int findIndexOfValue(String value) {
+ if (value != null && mEntryValues != null) {
+ for (int i = mEntryValues.length - 1; i >= 0; i--) {
+ if (mEntryValues[i].equals(value)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+
+ if (mEntries == null || mEntryValues == null) {
+ throw new IllegalStateException(
+ "MultiSelectListPreference requires an entries array and " +
+ "an entryValues array.");
+ }
+
+ boolean[] checkedItems = getSelectedItems();
+ builder.setMultiChoiceItems(mEntries, checkedItems,
+ new DialogInterface.OnMultiChoiceClickListener() {
+ public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+ if (isChecked) {
+ mPreferenceChanged |= mNewValues.add(mEntries[which].toString());
+ } else {
+ mPreferenceChanged |= mNewValues.remove(mEntries[which].toString());
+ }
+ }
+ });
+ mNewValues.clear();
+ mNewValues.addAll(mValues);
+ }
+
+ private boolean[] getSelectedItems() {
+ final CharSequence[] entries = mEntries;
+ final int entryCount = entries.length;
+ final Set<String> values = mValues;
+ boolean[] result = new boolean[entryCount];
+
+ for (int i = 0; i < entryCount; i++) {
+ result[i] = values.contains(entries[i].toString());
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (positiveResult && mPreferenceChanged) {
+ final Set<String> values = mNewValues;
+ if (callChangeListener(values)) {
+ setValues(values);
+ }
+ }
+ mPreferenceChanged = false;
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ final CharSequence[] defaultValues = a.getTextArray(index);
+ final int valueCount = defaultValues.length;
+ final Set<String> result = new HashSet<String>();
+
+ for (int i = 0; i < valueCount; i++) {
+ result.add(defaultValues[i].toString());
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setValues(restoreValue ? getPersistedStringSet(mValues) : (Set<String>) defaultValue);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ // No need to save instance state
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.values = getValues();
+ return myState;
+ }
+
+ private static class SavedState extends BaseSavedState {
+ Set<String> values;
+
+ public SavedState(Parcel source) {
+ super(source);
+ values = new HashSet<String>();
+ String[] strings = source.readStringArray();
+
+ final int stringCount = strings.length;
+ for (int i = 0; i < stringCount; i++) {
+ values.add(strings[i]);
+ }
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeStringArray(values.toArray(new String[0]));
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 197d976..381f794 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -16,8 +16,7 @@
package android.preference;
-import java.util.ArrayList;
-import java.util.List;
+import com.android.internal.util.CharSequences;
import android.content.Context;
import android.content.Intent;
@@ -28,7 +27,6 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
-import com.android.internal.util.CharSequences;
import android.view.AbsSavedState;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,6 +34,10 @@
import android.widget.ListView;
import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
/**
* Represents the basic Preference UI building
* block displayed by a {@link PreferenceActivity} in the form of a
@@ -1250,6 +1252,61 @@
}
/**
+ * Attempts to persist a set of Strings to the {@link android.content.SharedPreferences}.
+ * <p>
+ * This will check if this Preference is persistent, get an editor from
+ * the {@link PreferenceManager}, put in the strings, and check if we should commit (and
+ * commit if so).
+ *
+ * @param values The values to persist.
+ * @return True if the Preference is persistent. (This is not whether the
+ * value was persisted, since we may not necessarily commit if there
+ * will be a batch commit later.)
+ * @see #getPersistedString(Set)
+ *
+ * @hide Pending API approval
+ */
+ protected boolean persistStringSet(Set<String> values) {
+ if (shouldPersist()) {
+ // Shouldn't store null
+ if (values.equals(getPersistedStringSet(null))) {
+ // It's already there, so the same as persisting
+ return true;
+ }
+
+ SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+ editor.putStringSet(mKey, values);
+ tryCommit(editor);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to get a persisted set of Strings from the
+ * {@link android.content.SharedPreferences}.
+ * <p>
+ * This will check if this Preference is persistent, get the SharedPreferences
+ * from the {@link PreferenceManager}, and get the value.
+ *
+ * @param defaultReturnValue The default value to return if either the
+ * Preference is not persistent or the Preference is not in the
+ * shared preferences.
+ * @return The value from the SharedPreferences or the default return
+ * value.
+ * @see #persistStringSet(Set)
+ *
+ * @hide Pending API approval
+ */
+ protected Set<String> getPersistedStringSet(Set<String> defaultReturnValue) {
+ if (!shouldPersist()) {
+ return defaultReturnValue;
+ }
+
+ return mPreferenceManager.getSharedPreferences().getStringSet(mKey, defaultReturnValue);
+ }
+
+ /**
* Attempts to persist an int to the {@link android.content.SharedPreferences}.
*
* @param value The value to persist.
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 726793d..4686978 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -23,7 +23,10 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.text.TextUtils;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
/**
* Shows a hierarchy of {@link Preference} objects as
@@ -69,30 +72,43 @@
* As a convenience, this activity implements a click listener for any
* preference in the current hierarchy, see
* {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
- *
+ *
* @see Preference
* @see PreferenceScreen
*/
public abstract class PreferenceActivity extends ListActivity implements
PreferenceManager.OnPreferenceTreeClickListener {
-
+
private static final String PREFERENCES_TAG = "android:preferences";
-
+
+ // extras that allow any preference activity to be launched as part of a wizard
+
+ // show Back and Next buttons? takes boolean parameter
+ // Back will then return RESULT_CANCELED and Next RESULT_OK
+ private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
+
+ // specify custom text for the Back or Next buttons, or cause a button to not appear
+ // at all by setting it to null
+ private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
+ private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
+
+ private Button mNextButton;
+
private PreferenceManager mPreferenceManager;
-
+
private Bundle mSavedInstanceState;
/**
* The starting request code given out to preference framework.
*/
private static final int FIRST_REQUEST_CODE = 100;
-
+
private static final int MSG_BIND_PREFERENCES = 0;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
-
+
case MSG_BIND_PREFERENCES:
bindPreferences();
break;
@@ -105,7 +121,49 @@
super.onCreate(savedInstanceState);
setContentView(com.android.internal.R.layout.preference_list_content);
-
+
+ // see if we should show Back/Next buttons
+ Intent intent = getIntent();
+ if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
+
+ findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
+
+ Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
+ backButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+ mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
+ mNextButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ setResult(RESULT_OK);
+ finish();
+ }
+ });
+
+ // set our various button parameters
+ if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
+ String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
+ if (TextUtils.isEmpty(buttonText)) {
+ mNextButton.setVisibility(View.GONE);
+ }
+ else {
+ mNextButton.setText(buttonText);
+ }
+ }
+ if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
+ String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
+ if (TextUtils.isEmpty(buttonText)) {
+ backButton.setVisibility(View.GONE);
+ }
+ else {
+ backButton.setText(buttonText);
+ }
+ }
+ }
+
mPreferenceManager = onCreatePreferenceManager();
getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
}
@@ -113,14 +171,13 @@
@Override
protected void onStop() {
super.onStop();
-
+
mPreferenceManager.dispatchActivityStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
-
mPreferenceManager.dispatchActivityDestroy();
}
@@ -156,7 +213,7 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
-
+
mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
}
@@ -176,7 +233,7 @@
if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
}
-
+
private void bindPreferences() {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
@@ -187,10 +244,10 @@
}
}
}
-
+
/**
* Creates the {@link PreferenceManager}.
- *
+ *
* @return The {@link PreferenceManager} used by this activity.
*/
private PreferenceManager onCreatePreferenceManager() {
@@ -198,7 +255,7 @@
preferenceManager.setOnPreferenceTreeClickListener(this);
return preferenceManager;
}
-
+
/**
* Returns the {@link PreferenceManager} used by this activity.
* @return The {@link PreferenceManager}.
@@ -206,7 +263,7 @@
public PreferenceManager getPreferenceManager() {
return mPreferenceManager;
}
-
+
private void requirePreferenceManager() {
if (mPreferenceManager == null) {
throw new RuntimeException("This should be called after super.onCreate.");
@@ -215,7 +272,7 @@
/**
* Sets the root of the preference hierarchy that this activity is showing.
- *
+ *
* @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
*/
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
@@ -228,37 +285,37 @@
}
}
}
-
+
/**
* Gets the root of the preference hierarchy that this activity is showing.
- *
+ *
* @return The {@link PreferenceScreen} that is the root of the preference
* hierarchy.
*/
public PreferenceScreen getPreferenceScreen() {
return mPreferenceManager.getPreferenceScreen();
}
-
+
/**
* Adds preferences from activities that match the given {@link Intent}.
- *
+ *
* @param intent The {@link Intent} to query activities.
*/
public void addPreferencesFromIntent(Intent intent) {
requirePreferenceManager();
-
+
setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
}
-
+
/**
* Inflates the given XML resource and adds the preference hierarchy to the current
* preference hierarchy.
- *
+ *
* @param preferencesResId The XML resource ID to inflate.
*/
public void addPreferencesFromResource(int preferencesResId) {
requirePreferenceManager();
-
+
setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
getPreferenceScreen()));
}
@@ -269,20 +326,20 @@
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
return false;
}
-
+
/**
* Finds a {@link Preference} based on its key.
- *
+ *
* @param key The key of the preference to retrieve.
* @return The {@link Preference} with the key, or null.
* @see PreferenceGroup#findPreference(CharSequence)
*/
public Preference findPreference(CharSequence key) {
-
+
if (mPreferenceManager == null) {
return null;
}
-
+
return mPreferenceManager.findPreference(key);
}
@@ -292,5 +349,14 @@
mPreferenceManager.dispatchNewIntent(intent);
}
}
-
+
+ // give subclasses access to the Next button
+ /** @hide */
+ protected boolean hasNextButton() {
+ return mNextButton != null;
+ }
+ /** @hide */
+ protected Button getNextButton() {
+ return mNextButton;
+ }
}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 40a408a..abeb931 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -4902,6 +4902,23 @@
* Type: INTEGER (boolean)
*/
public static final String SHOULD_SYNC = "should_sync";
+
+ /**
+ * Any newly created contacts will automatically be added to groups that have this
+ * flag set to true.
+ * <p>
+ * Type: INTEGER (boolean)
+ */
+ public static final String AUTO_ADD = "auto_add";
+
+ /**
+ * When a contacts is marked as a favorites it will be automatically added
+ * to the groups that have this flag set, and when it is removed from favorites
+ * it will be removed from these groups.
+ * <p>
+ * Type: INTEGER (boolean)
+ */
+ public static final String FAVORITES = "favorites";
}
/**
@@ -5042,6 +5059,8 @@
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, DELETED);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, NOTES);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SHOULD_SYNC);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, FAVORITES);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, AUTO_ADD);
cursor.moveToNext();
return new Entity(values);
}
@@ -5558,6 +5577,28 @@
"com.android.contacts.action.SHOW_OR_CREATE_CONTACT";
/**
+ * Starts an Activity that lets the user select the multiple phones from a
+ * list of phone numbers which come from the contacts or
+ * {@link #EXTRA_PHONE_URIS}.
+ * <p>
+ * The phone numbers being passed in through {@link #EXTRA_PHONE_URIS}
+ * could belong to the contacts or not, and will be selected by default.
+ * <p>
+ * The user's selection will be returned from
+ * {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
+ * if the resultCode is
+ * {@link android.app.Activity#RESULT_OK}, the array of picked phone
+ * numbers are in the Intent's
+ * {@link #EXTRA_PHONE_URIS}; otherwise, the
+ * {@link android.app.Activity#RESULT_CANCELED} is returned if the user
+ * left the Activity without changing the selection.
+ *
+ * @hide
+ */
+ public static final String ACTION_GET_MULTIPLE_PHONES =
+ "com.android.contacts.action.GET_MULTIPLE_PHONES";
+
+ /**
* Used with {@link #SHOW_OR_CREATE_CONTACT} to force creating a new
* contact if no matching contact found. Otherwise, default behavior is
* to prompt user with dialog before creating.
@@ -5578,6 +5619,23 @@
"com.android.contacts.action.CREATE_DESCRIPTION";
/**
+ * Used with {@link #ACTION_GET_MULTIPLE_PHONES} as the input or output value.
+ * <p>
+ * The phone numbers want to be picked by default should be passed in as
+ * input value. These phone numbers could belong to the contacts or not.
+ * <p>
+ * The phone numbers which were picked by the user are returned as output
+ * value.
+ * <p>
+ * Type: array of URIs, the tel URI is used for the phone numbers which don't
+ * belong to any contact, the content URI is used for phone id in contacts.
+ *
+ * @hide
+ */
+ public static final String EXTRA_PHONE_URIS =
+ "com.android.contacts.extra.PHONE_URIS";
+
+ /**
* Optional extra used with {@link #SHOW_OR_CREATE_CONTACT} to specify a
* dialog location using screen coordinates. When not specified, the
* dialog will be centered.
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 893db2e..ac89934 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -554,6 +554,7 @@
if (!result) {
if (deviceObjectPath != null) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
+ if (address == null) return;
BluetoothDevice device = mAdapter.getRemoteDevice(address);
int state = getSinkState(device);
handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java
index e4f934e..eacd40d 100644
--- a/core/java/android/text/AndroidBidi.java
+++ b/core/java/android/text/AndroidBidi.java
@@ -16,6 +16,8 @@
package android.text;
+import android.text.Layout.Directions;
+
/**
* Access the ICU bidi implementation.
* @hide
@@ -44,5 +46,132 @@
return result;
}
+ /**
+ * Returns run direction information for a line within a paragraph.
+ *
+ * @param dir base line direction, either Layout.DIR_LEFT_TO_RIGHT or
+ * Layout.DIR_RIGHT_TO_LEFT
+ * @param levels levels as returned from {@link #bidi}
+ * @param lstart start of the line in the levels array
+ * @param chars the character array (used to determine whitespace)
+ * @param cstart the start of the line in the chars array
+ * @param len the length of the line
+ * @return the directions
+ */
+ public static Directions directions(int dir, byte[] levels, int lstart,
+ char[] chars, int cstart, int len) {
+
+ int baseLevel = dir == Layout.DIR_LEFT_TO_RIGHT ? 0 : 1;
+ int curLevel = levels[lstart];
+ int minLevel = curLevel;
+ int runCount = 1;
+ for (int i = lstart + 1, e = lstart + len; i < e; ++i) {
+ int level = levels[i];
+ if (level != curLevel) {
+ curLevel = level;
+ ++runCount;
+ }
+ }
+
+ // add final run for trailing counter-directional whitespace
+ int visLen = len;
+ if ((curLevel & 1) != (baseLevel & 1)) {
+ // look for visible end
+ while (--visLen >= 0) {
+ char ch = chars[cstart + visLen];
+
+ if (ch == '\n') {
+ --visLen;
+ break;
+ }
+
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ }
+ ++visLen;
+ if (visLen != len) {
+ ++runCount;
+ }
+ }
+
+ if (runCount == 1 && minLevel == baseLevel) {
+ // we're done, only one run on this line
+ if ((minLevel & 1) != 0) {
+ return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+ }
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ }
+
+ int[] ld = new int[runCount * 2];
+ int maxLevel = minLevel;
+ int levelBits = minLevel << Layout.RUN_LEVEL_SHIFT;
+ {
+ // Start of first pair is always 0, we write
+ // length then start at each new run, and the
+ // last run length after we're done.
+ int n = 1;
+ int prev = lstart;
+ curLevel = minLevel;
+ for (int i = lstart, e = lstart + visLen; i < e; ++i) {
+ int level = levels[i];
+ if (level != curLevel) {
+ curLevel = level;
+ if (level > maxLevel) {
+ maxLevel = level;
+ } else if (level < minLevel) {
+ minLevel = level;
+ }
+ // XXX ignore run length limit of 2^RUN_LEVEL_SHIFT
+ ld[n++] = (i - prev) | levelBits;
+ ld[n++] = i - lstart;
+ levelBits = curLevel << Layout.RUN_LEVEL_SHIFT;
+ prev = i;
+ }
+ }
+ ld[n] = (lstart + visLen - prev) | levelBits;
+ if (visLen < len) {
+ ld[++n] = visLen;
+ ld[++n] = (len - visLen) | (baseLevel << Layout.RUN_LEVEL_SHIFT);
+ }
+ }
+
+ // See if we need to swap any runs.
+ // If the min level run direction doesn't match the base
+ // direction, we always need to swap (at this point
+ // we have more than one run).
+ // Otherwise, we don't need to swap the lowest level.
+ // Since there are no logically adjacent runs at the same
+ // level, if the max level is the same as the (new) min
+ // level, we have a series of alternating levels that
+ // is already in order, so there's no more to do.
+ //
+ boolean swap;
+ if ((minLevel & 1) == baseLevel) {
+ minLevel += 1;
+ swap = maxLevel > minLevel;
+ } else {
+ swap = runCount > 1;
+ }
+ if (swap) {
+ for (int level = maxLevel - 1; level >= minLevel; --level) {
+ for (int i = 0; i < ld.length; i += 2) {
+ if (levels[ld[i]] >= level) {
+ int e = i + 2;
+ while (e < ld.length && levels[ld[e]] >= level) {
+ e += 2;
+ }
+ for (int low = i, hi = e - 2; low < hi; low += 2, hi -= 2) {
+ int x = ld[low]; ld[low] = ld[hi]; ld[hi] = x;
+ x = ld[low+1]; ld[low+1] = ld[hi+1]; ld[hi+1] = x;
+ }
+ i = e + 2;
+ }
+ }
+ }
+ }
+ return new Directions(ld);
+ }
+
private native static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo);
}
\ No newline at end of file
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 944f735..9309b05 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -208,11 +208,11 @@
* width because the width that was passed in was for the
* full text, not the ellipsized form.
*/
- synchronized (sTemp) {
- mMax = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
- source, 0, source.length(),
- null)));
- }
+ TextLine line = TextLine.obtain();
+ line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
+ Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
+ mMax = (int) FloatMath.ceil(line.metrics(null));
+ TextLine.recycle(line);
}
if (includepad) {
@@ -276,14 +276,13 @@
if (fm == null) {
fm = new Metrics();
}
-
- int wid;
- synchronized (sTemp) {
- wid = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
- text, 0, text.length(), fm)));
- }
- fm.width = wid;
+ TextLine line = TextLine.obtain();
+ line.set(paint, text, 0, text.length(), Layout.DIR_LEFT_TO_RIGHT,
+ Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
+ fm.width = (int) FloatMath.ceil(line.metrics(fm));
+ TextLine.recycle(line);
+
return fm;
} else {
return null;
@@ -389,7 +388,7 @@
public static class Metrics extends Paint.FontMetricsInt {
public int width;
-
+
@Override public String toString() {
return super.toString() + " width=" + width;
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 14e5655..b6aa03a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -310,7 +310,6 @@
Directions[] objects = new Directions[1];
-
for (int i = 0; i < n; i++) {
ints[START] = reflowed.getLineStart(i) |
(reflowed.getParagraphDirection(i) << DIR_SHIFT) |
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 38ac9b7..3b8f295 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,25 +16,29 @@
package android.text;
-import android.emoji.EmojiFactory;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Path;
import com.android.internal.util.ArrayUtils;
-import junit.framework.Assert;
-import android.text.style.*;
+import android.emoji.EmojiFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
import android.text.method.TextKeyListener;
+import android.text.style.AlignmentSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineBackgroundSpan;
+import android.text.style.ParagraphStyle;
+import android.text.style.ReplacementSpan;
+import android.text.style.TabStopSpan;
import android.view.KeyEvent;
+import junit.framework.Assert;
+
/**
- * A base class that manages text layout in visual elements on
- * the screen.
- * <p>For text that will be edited, use a {@link DynamicLayout},
- * which will be updated as the text changes.
+ * A base class that manages text layout in visual elements on
+ * the screen.
+ * <p>For text that will be edited, use a {@link DynamicLayout},
+ * which will be updated as the text changes.
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
@@ -54,9 +58,7 @@
MIN_EMOJI = -1;
MAX_EMOJI = -1;
}
- };
-
- private RectF mEmojiRect;
+ }
/**
* Return how wide a layout must be in order to display the
@@ -66,7 +68,7 @@
TextPaint paint) {
return getDesiredWidth(source, 0, source.length(), paint);
}
-
+
/**
* Return how wide a layout must be in order to display the
* specified text slice with one line per paragraph.
@@ -85,8 +87,8 @@
next = end;
// note, omits trailing paragraph char
- float w = measureText(paint, workPaint,
- source, i, next, null, true, null);
+ float w = measurePara(paint, workPaint,
+ source, i, next, true, null);
if (w > need)
need = w;
@@ -116,6 +118,15 @@
if (width < 0)
throw new IllegalArgumentException("Layout: " + width + " < 0");
+ // Ensure paint doesn't have baselineShift set.
+ // While normally we don't modify the paint the user passed in,
+ // we were already doing this in Styled.drawUniformRun with both
+ // baselineShift and bgColor. We probably should reevaluate bgColor.
+ if (paint != null) {
+ paint.bgColor = 0;
+ paint.baselineShift = 0;
+ }
+
mText = text;
mPaint = paint;
mWorkPaint = new TextPaint();
@@ -185,13 +196,13 @@
if (dbottom < bottom) {
bottom = dbottom;
}
-
- int first = getLineForVertical(top);
+
+ int first = getLineForVertical(top);
int last = getLineForVertical(bottom);
-
+
int previousLineBottom = getLineTop(first);
int previousLineEnd = getLineStart(first);
-
+
TextPaint paint = mPaint;
CharSequence buf = mText;
int width = mWidth;
@@ -238,7 +249,7 @@
previousLineBottom = getLineTop(first);
previousLineEnd = getLineStart(first);
spans = NO_PARA_SPANS;
- }
+ }
// There can be a highlight even without spans if we are drawing
// a non-spanned transformation of a spanned editing buffer.
@@ -255,7 +266,8 @@
}
Alignment align = mAlignment;
-
+
+ TextLine tl = TextLine.obtain();
// Next draw the lines, one at a time.
// the baseline is the top of the following line minus the current
// line's descent.
@@ -271,7 +283,7 @@
int lbaseline = lbottom - getLineDescent(i);
boolean isFirstParaLine = false;
- if (spannedText) {
+ if (spannedText) {
if (start == 0 || buf.charAt(start - 1) == '\n') {
isFirstParaLine = true;
}
@@ -282,7 +294,7 @@
spanend = sp.nextSpanTransition(start, textLength,
ParagraphStyle.class);
spans = sp.getSpans(start, spanend, ParagraphStyle.class);
-
+
align = mAlignment;
for (int n = spans.length-1; n >= 0; n--) {
if (spans[n] instanceof AlignmentSpan) {
@@ -292,7 +304,7 @@
}
}
}
-
+
int dir = getParagraphDirection(i);
int left = 0;
int right = mWidth;
@@ -309,7 +321,7 @@
margin.drawLeadingMargin(c, paint, right, dir, ltop,
lbaseline, lbottom, buf,
start, end, isFirstParaLine, this);
-
+
right -= margin.getLeadingMargin(isFirstParaLine);
} else {
margin.drawLeadingMargin(c, paint, left, dir, ltop,
@@ -367,11 +379,11 @@
// XXX: assumes there's nothing additional to be done
c.drawText(buf, start, end, x, lbaseline, paint);
} else {
- drawText(c, buf, start, end, dir, directions,
- x, ltop, lbaseline, lbottom, paint, mWorkPaint,
- hasTab, spans);
+ tl.set(paint, buf, start, end, dir, directions, hasTab, spans);
+ tl.draw(c, x, ltop, lbaseline, lbottom);
}
}
+ TextLine.recycle(tl);
}
/**
@@ -417,7 +429,7 @@
mWidth = wid;
}
-
+
/**
* Return the total height of this layout.
*/
@@ -450,7 +462,7 @@
* Return the number of lines of text in this layout.
*/
public abstract int getLineCount();
-
+
/**
* Return the baseline for the specified line (0…getLineCount() - 1)
* If bounds is not null, return the top, left, right, bottom extents
@@ -524,13 +536,95 @@
*/
public abstract int getBottomPadding();
+
+ /**
+ * Returns true if the character at offset and the preceding character
+ * are at different run levels (and thus there's a split caret).
+ * @param offset the offset
+ * @return true if at a level boundary
+ */
+ private boolean isLevelBoundary(int offset) {
+ int line = getLineForOffset(offset);
+ Directions dirs = getLineDirections(line);
+ if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
+ return false;
+ }
+
+ int[] runs = dirs.mDirections;
+ int lineStart = getLineStart(line);
+ int lineEnd = getLineEnd(line);
+ if (offset == lineStart || offset == lineEnd) {
+ int paraLevel = getParagraphDirection(line) == 1 ? 0 : 1;
+ int runIndex = offset == lineStart ? 0 : runs.length - 2;
+ return ((runs[runIndex + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK) != paraLevel;
+ }
+
+ offset -= lineStart;
+ for (int i = 0; i < runs.length; i += 2) {
+ if (offset == runs[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean primaryIsTrailingPrevious(int offset) {
+ int line = getLineForOffset(offset);
+ int lineStart = getLineStart(line);
+ int lineEnd = getLineEnd(line);
+ int[] runs = getLineDirections(line).mDirections;
+
+ int levelAt = -1;
+ for (int i = 0; i < runs.length; i += 2) {
+ int start = lineStart + runs[i];
+ int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+ if (limit > lineEnd) {
+ limit = lineEnd;
+ }
+ if (offset >= start && offset < limit) {
+ if (offset > start) {
+ // Previous character is at same level, so don't use trailing.
+ return false;
+ }
+ levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+ break;
+ }
+ }
+ if (levelAt == -1) {
+ // Offset was limit of line.
+ levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
+ }
+
+ // At level boundary, check previous level.
+ int levelBefore = -1;
+ if (offset == lineStart) {
+ levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
+ } else {
+ offset -= 1;
+ for (int i = 0; i < runs.length; i += 2) {
+ int start = lineStart + runs[i];
+ int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+ if (limit > lineEnd) {
+ limit = lineEnd;
+ }
+ if (offset >= start && offset < limit) {
+ levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+ break;
+ }
+ }
+ }
+
+ return levelBefore < levelAt;
+ }
+
/**
* Get the primary horizontal position for the specified text offset.
* This is the location where a new character would be inserted in
* the paragraph's primary direction.
*/
public float getPrimaryHorizontal(int offset) {
- return getHorizontal(offset, false, true);
+ boolean trailing = primaryIsTrailingPrevious(offset);
+ return getHorizontal(offset, trailing);
}
/**
@@ -539,19 +633,19 @@
* the direction other than the paragraph's primary direction.
*/
public float getSecondaryHorizontal(int offset) {
- return getHorizontal(offset, true, true);
+ boolean trailing = primaryIsTrailingPrevious(offset);
+ return getHorizontal(offset, !trailing);
}
- private float getHorizontal(int offset, boolean trailing, boolean alt) {
+ private float getHorizontal(int offset, boolean trailing) {
int line = getLineForOffset(offset);
- return getHorizontal(offset, trailing, alt, line);
+ return getHorizontal(offset, trailing, line);
}
- private float getHorizontal(int offset, boolean trailing, boolean alt,
- int line) {
+ private float getHorizontal(int offset, boolean trailing, int line) {
int start = getLineStart(line);
- int end = getLineVisibleEnd(line);
+ int end = getLineEnd(line);
int dir = getParagraphDirection(line);
boolean tab = getLineContainsTab(line);
Directions directions = getLineDirections(line);
@@ -561,17 +655,10 @@
tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
}
- float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
- dir, directions, trailing, alt, tab, tabs);
-
- if (offset > end) {
- if (dir == DIR_RIGHT_TO_LEFT)
- wid -= measureText(mPaint, mWorkPaint,
- mText, end, offset, null, tab, tabs);
- else
- wid += measureText(mPaint, mWorkPaint,
- mText, end, offset, null, tab, tabs);
- }
+ TextLine tl = TextLine.obtain();
+ tl.set(mPaint, mText, start, end, dir, directions, tab, tabs);
+ float wid = tl.measure(offset - start, trailing, null);
+ TextLine.recycle(tl);
Alignment align = getParagraphAlignment(line);
int left = getParagraphLeft(line);
@@ -673,21 +760,15 @@
private float getLineMax(int line, Object[] tabs, boolean full) {
int start = getLineStart(line);
- int end;
+ int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
+ boolean hasTabs = getLineContainsTab(line);
+ Directions directions = getLineDirections(line);
- if (full) {
- end = getLineEnd(line);
- } else {
- end = getLineVisibleEnd(line);
- }
- boolean tab = getLineContainsTab(line);
-
- if (tabs == null && tab && mText instanceof Spanned) {
- tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
- }
-
- return measureText(mPaint, mWorkPaint,
- mText, start, end, null, tab, tabs);
+ TextLine tl = TextLine.obtain();
+ tl.set(mPaint, mText, start, end, 1, directions, hasTabs, tabs);
+ float width = tl.metrics(null);
+ TextLine.recycle(tl);
+ return width;
}
/**
@@ -738,7 +819,7 @@
}
/**
- * Get the character offset on the specfied line whose position is
+ * Get the character offset on the specified line whose position is
* closest to the specified horizontal position.
*/
public int getOffsetForHorizontal(int line, float horiz) {
@@ -752,14 +833,13 @@
int best = min;
float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
- int here = min;
- for (int i = 0; i < dirs.mDirections.length; i++) {
- int there = here + dirs.mDirections[i];
- int swap = ((i & 1) == 0) ? 1 : -1;
+ for (int i = 0; i < dirs.mDirections.length; i += 2) {
+ int here = min + dirs.mDirections[i];
+ int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
+ int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
if (there > max)
there = max;
-
int high = there - 1 + 1, low = here + 1 - 1, guess;
while (high - low > 1) {
@@ -792,7 +872,7 @@
if (dist < bestdist) {
bestdist = dist;
- best = low;
+ best = low;
}
}
@@ -802,8 +882,6 @@
bestdist = dist;
best = here;
}
-
- here = there;
}
float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
@@ -823,14 +901,14 @@
return getLineStart(line + 1);
}
- /**
+ /**
* Return the text offset after the last visible character (so whitespace
* is not counted) on the specified line.
*/
public int getLineVisibleEnd(int line) {
return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
}
-
+
private int getLineVisibleEnd(int line, int start, int end) {
if (DEBUG) {
Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
@@ -882,207 +960,62 @@
return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
}
- /**
- * Return the text offset that would be reached by moving left
- * (possibly onto another line) from the specified offset.
- */
public int getOffsetToLeftOf(int offset) {
- int line = getLineForOffset(offset);
- int start = getLineStart(line);
- int end = getLineEnd(line);
- Directions dirs = getLineDirections(line);
-
- if (line != getLineCount() - 1)
- end--;
-
- float horiz = getPrimaryHorizontal(offset);
-
- int best = offset;
- float besth = Integer.MIN_VALUE;
- int candidate;
-
- candidate = TextUtils.getOffsetBefore(mText, offset);
- if (candidate >= start && candidate <= end) {
- float h = getPrimaryHorizontal(candidate);
-
- if (h < horiz && h > besth) {
- best = candidate;
- besth = h;
- }
- }
-
- candidate = TextUtils.getOffsetAfter(mText, offset);
- if (candidate >= start && candidate <= end) {
- float h = getPrimaryHorizontal(candidate);
-
- if (h < horiz && h > besth) {
- best = candidate;
- besth = h;
- }
- }
-
- int here = start;
- for (int i = 0; i < dirs.mDirections.length; i++) {
- int there = here + dirs.mDirections[i];
- if (there > end)
- there = end;
-
- float h = getPrimaryHorizontal(here);
-
- if (h < horiz && h > besth) {
- best = here;
- besth = h;
- }
-
- candidate = TextUtils.getOffsetAfter(mText, here);
- if (candidate >= start && candidate <= end) {
- h = getPrimaryHorizontal(candidate);
-
- if (h < horiz && h > besth) {
- best = candidate;
- besth = h;
- }
- }
-
- candidate = TextUtils.getOffsetBefore(mText, there);
- if (candidate >= start && candidate <= end) {
- h = getPrimaryHorizontal(candidate);
-
- if (h < horiz && h > besth) {
- best = candidate;
- besth = h;
- }
- }
-
- here = there;
- }
-
- float h = getPrimaryHorizontal(end);
-
- if (h < horiz && h > besth) {
- best = end;
- besth = h;
- }
-
- if (best != offset)
- return best;
-
- int dir = getParagraphDirection(line);
-
- if (dir > 0) {
- if (line == 0)
- return best;
- else
- return getOffsetForHorizontal(line - 1, 10000);
- } else {
- if (line == getLineCount() - 1)
- return best;
- else
- return getOffsetForHorizontal(line + 1, 10000);
- }
+ return getOffsetToLeftRightOf(offset, true);
}
- /**
- * Return the text offset that would be reached by moving right
- * (possibly onto another line) from the specified offset.
- */
public int getOffsetToRightOf(int offset) {
- int line = getLineForOffset(offset);
- int start = getLineStart(line);
- int end = getLineEnd(line);
- Directions dirs = getLineDirections(line);
+ return getOffsetToLeftRightOf(offset, false);
+ }
- if (line != getLineCount() - 1)
- end--;
+ private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
+ int line = getLineForOffset(caret);
+ int lineStart = getLineStart(line);
+ int lineEnd = getLineEnd(line);
+ int lineDir = getParagraphDirection(line);
- float horiz = getPrimaryHorizontal(offset);
-
- int best = offset;
- float besth = Integer.MAX_VALUE;
- int candidate;
-
- candidate = TextUtils.getOffsetBefore(mText, offset);
- if (candidate >= start && candidate <= end) {
- float h = getPrimaryHorizontal(candidate);
-
- if (h > horiz && h < besth) {
- best = candidate;
- besth = h;
- }
- }
-
- candidate = TextUtils.getOffsetAfter(mText, offset);
- if (candidate >= start && candidate <= end) {
- float h = getPrimaryHorizontal(candidate);
-
- if (h > horiz && h < besth) {
- best = candidate;
- besth = h;
- }
- }
-
- int here = start;
- for (int i = 0; i < dirs.mDirections.length; i++) {
- int there = here + dirs.mDirections[i];
- if (there > end)
- there = end;
-
- float h = getPrimaryHorizontal(here);
-
- if (h > horiz && h < besth) {
- best = here;
- besth = h;
- }
-
- candidate = TextUtils.getOffsetAfter(mText, here);
- if (candidate >= start && candidate <= end) {
- h = getPrimaryHorizontal(candidate);
-
- if (h > horiz && h < besth) {
- best = candidate;
- besth = h;
+ boolean advance = toLeft == (lineDir == DIR_RIGHT_TO_LEFT);
+ if (caret == (advance ? lineEnd : lineStart)) {
+ // walking off line, so look at the line we're headed to
+ if (caret == lineStart) {
+ if (line > 0) {
+ --line;
+ } else {
+ return caret; // at very start, don't move
+ }
+ } else {
+ if (line < getLineCount() - 1) {
+ ++line;
+ } else {
+ return caret; // at very end, don't move
}
}
- candidate = TextUtils.getOffsetBefore(mText, there);
- if (candidate >= start && candidate <= end) {
- h = getPrimaryHorizontal(candidate);
-
- if (h > horiz && h < besth) {
- best = candidate;
- besth = h;
- }
+ lineStart = getLineStart(line);
+ lineEnd = getLineEnd(line);
+ int newDir = getParagraphDirection(line);
+ if (newDir != lineDir) {
+ // unusual case. we want to walk onto the line, but it runs
+ // in a different direction than this one, so we fake movement
+ // in the opposite direction.
+ toLeft = !toLeft;
+ lineDir = newDir;
}
-
- here = there;
}
- float h = getPrimaryHorizontal(end);
+ Directions directions = getLineDirections(line);
- if (h > horiz && h < besth) {
- best = end;
- besth = h;
- }
-
- if (best != offset)
- return best;
-
- int dir = getParagraphDirection(line);
-
- if (dir > 0) {
- if (line == getLineCount() - 1)
- return best;
- else
- return getOffsetForHorizontal(line + 1, -10000);
- } else {
- if (line == 0)
- return best;
- else
- return getOffsetForHorizontal(line - 1, -10000);
- }
+ TextLine tl = TextLine.obtain();
+ // XXX: we don't care about tabs
+ tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
+ caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
+ tl = TextLine.recycle(tl);
+ return caret;
}
private int getOffsetAtStartOf(int offset) {
+ // XXX this probably should skip local reorderings and
+ // zero-width characters, look at callers
if (offset == 0)
return 0;
@@ -1115,7 +1048,7 @@
/**
* Fills in the specified Path with a representation of a cursor
* at the specified offset. This will often be a vertical line
- * but can be multiple discontinous lines in text with multiple
+ * but can be multiple discontinuous lines in text with multiple
* directionalities.
*/
public void getCursorPath(int point, Path dest,
@@ -1127,7 +1060,8 @@
int bottom = getLineTop(line+1);
float h1 = getPrimaryHorizontal(point) - 0.5f;
- float h2 = getSecondaryHorizontal(point) - 0.5f;
+ float h2 = isLevelBoundary(point) ?
+ getSecondaryHorizontal(point) - 0.5f : h1;
int caps = TextKeyListener.getMetaState(editingBuffer,
KeyEvent.META_SHIFT_ON) |
@@ -1204,9 +1138,10 @@
if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
lineend--;
- int here = linestart;
- for (int i = 0; i < dirs.mDirections.length; i++) {
- int there = here + dirs.mDirections[i];
+ for (int i = 0; i < dirs.mDirections.length; i += 2) {
+ int here = linestart + dirs.mDirections[i];
+ int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
+
if (there > lineend)
there = lineend;
@@ -1215,14 +1150,12 @@
int en = Math.min(end, there);
if (st != en) {
- float h1 = getHorizontal(st, false, false, line);
- float h2 = getHorizontal(en, true, false, line);
+ float h1 = getHorizontal(st, false, line);
+ float h2 = getHorizontal(en, true, line);
dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
}
}
-
- here = there;
}
}
@@ -1257,7 +1190,7 @@
addSelection(startline, start, getLineEnd(startline),
top, getLineBottom(startline), dest);
-
+
if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
dest.addRect(getLineLeft(startline), top,
0, getLineBottom(startline), Path.Direction.CW);
@@ -1371,361 +1304,28 @@
return right;
}
- private void drawText(Canvas canvas,
- CharSequence text, int start, int end,
- int dir, Directions directions,
- float x, int top, int y, int bottom,
- TextPaint paint,
- TextPaint workPaint,
- boolean hasTabs, Object[] parspans) {
- char[] buf;
- if (!hasTabs) {
- if (directions == DIRS_ALL_LEFT_TO_RIGHT) {
- if (DEBUG) {
- Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir);
- }
- Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false);
- return;
+ /* package */
+ static float measurePara(TextPaint paint, TextPaint workPaint,
+ CharSequence text, int start, int end, boolean hasTabs,
+ Object[] tabs) {
+
+ MeasuredText mt = MeasuredText.obtain();
+ TextLine tl = TextLine.obtain();
+ try {
+ mt.setPara(text, start, end, DIR_REQUEST_LTR);
+ Directions directions;
+ if (mt.mEasy){
+ directions = DIRS_ALL_LEFT_TO_RIGHT;
+ } else {
+ directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
+ 0, mt.mChars, 0, mt.mLen);
}
- buf = null;
- } else {
- buf = TextUtils.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
+ tl.set(paint, text, start, end, 1, directions, hasTabs, tabs);
+ return tl.metrics(null);
+ } finally {
+ TextLine.recycle(tl);
+ MeasuredText.recycle(mt);
}
-
- float h = 0;
-
- int here = 0;
- for (int i = 0; i < directions.mDirections.length; i++) {
- int there = here + directions.mDirections[i];
- if (there > end - start)
- there = end - start;
-
- int segstart = here;
- for (int j = hasTabs ? here : there; j <= there; j++) {
- if (j == there || buf[j] == '\t') {
- h += Styled.drawText(canvas, text,
- start + segstart, start + j,
- dir, (i & 1) != 0, x + h,
- top, y, bottom, paint, workPaint,
- start + j != end);
-
- if (j != there && buf[j] == '\t')
- h = dir * nextTab(text, start, end, h * dir, parspans);
-
- segstart = j + 1;
- } else if (hasTabs && buf[j] >= 0xD800 && buf[j] <= 0xDFFF && j + 1 < there) {
- int emoji = Character.codePointAt(buf, j);
-
- if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
- Bitmap bm = EMOJI_FACTORY.
- getBitmapFromAndroidPua(emoji);
-
- if (bm != null) {
- h += Styled.drawText(canvas, text,
- start + segstart, start + j,
- dir, (i & 1) != 0, x + h,
- top, y, bottom, paint, workPaint,
- start + j != end);
-
- if (mEmojiRect == null) {
- mEmojiRect = new RectF();
- }
-
- workPaint.set(paint);
- Styled.measureText(paint, workPaint, text,
- start + j, start + j + 1,
- null);
-
- float bitmapHeight = bm.getHeight();
- float textHeight = -workPaint.ascent();
- float scale = textHeight / bitmapHeight;
- float width = bm.getWidth() * scale;
-
- mEmojiRect.set(x + h, y - textHeight,
- x + h + width, y);
-
- canvas.drawBitmap(bm, null, mEmojiRect, paint);
- h += width;
-
- j++;
- segstart = j + 1;
- }
- }
- }
- }
-
- here = there;
- }
-
- if (hasTabs)
- TextUtils.recycle(buf);
- }
-
- private static float measureText(TextPaint paint,
- TextPaint workPaint,
- CharSequence text,
- int start, int offset, int end,
- int dir, Directions directions,
- boolean trailing, boolean alt,
- boolean hasTabs, Object[] tabs) {
- char[] buf = null;
-
- if (hasTabs) {
- buf = TextUtils.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
- }
-
- float h = 0;
-
- if (alt) {
- if (dir == DIR_RIGHT_TO_LEFT)
- trailing = !trailing;
- }
-
- int here = 0;
- for (int i = 0; i < directions.mDirections.length; i++) {
- if (alt)
- trailing = !trailing;
-
- int there = here + directions.mDirections[i];
- if (there > end - start)
- there = end - start;
-
- int segstart = here;
- for (int j = hasTabs ? here : there; j <= there; j++) {
- int codept = 0;
- Bitmap bm = null;
-
- if (hasTabs && j < there) {
- codept = buf[j];
- }
-
- if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) {
- codept = Character.codePointAt(buf, j);
-
- if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
- bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
- }
- }
-
- if (j == there || codept == '\t' || bm != null) {
- float segw;
-
- if (offset < start + j ||
- (trailing && offset <= start + j)) {
- if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) {
- h += Styled.measureText(paint, workPaint, text,
- start + segstart, offset,
- null);
- return h;
- }
-
- if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) {
- h -= Styled.measureText(paint, workPaint, text,
- start + segstart, offset,
- null);
- return h;
- }
- }
-
- segw = Styled.measureText(paint, workPaint, text,
- start + segstart, start + j,
- null);
-
- if (offset < start + j ||
- (trailing && offset <= start + j)) {
- if (dir == DIR_LEFT_TO_RIGHT) {
- h += segw - Styled.measureText(paint, workPaint,
- text,
- start + segstart,
- offset, null);
- return h;
- }
-
- if (dir == DIR_RIGHT_TO_LEFT) {
- h -= segw - Styled.measureText(paint, workPaint,
- text,
- start + segstart,
- offset, null);
- return h;
- }
- }
-
- if (dir == DIR_RIGHT_TO_LEFT)
- h -= segw;
- else
- h += segw;
-
- if (j != there && buf[j] == '\t') {
- if (offset == start + j)
- return h;
-
- h = dir * nextTab(text, start, end, h * dir, tabs);
- }
-
- if (bm != null) {
- workPaint.set(paint);
- Styled.measureText(paint, workPaint, text,
- j, j + 2, null);
-
- float wid = (float) bm.getWidth() *
- -workPaint.ascent() / bm.getHeight();
-
- if (dir == DIR_RIGHT_TO_LEFT) {
- h -= wid;
- } else {
- h += wid;
- }
-
- j++;
- }
-
- segstart = j + 1;
- }
- }
-
- here = there;
- }
-
- if (hasTabs)
- TextUtils.recycle(buf);
-
- return h;
- }
-
- /**
- * Measure width of a run of text on a single line that is known to all be
- * in the same direction as the paragraph base direction. Returns the width,
- * and the line metrics in fm if fm is not null.
- *
- * @param paint the paint for the text; will not be modified
- * @param workPaint paint available for modification
- * @param text text
- * @param start start of the line
- * @param end limit of the line
- * @param fm object to return integer metrics in, can be null
- * @param hasTabs true if it is known that the line has tabs
- * @param tabs tab position information
- * @return the width of the text from start to end
- */
- /* package */ static float measureText(TextPaint paint,
- TextPaint workPaint,
- CharSequence text,
- int start, int end,
- Paint.FontMetricsInt fm,
- boolean hasTabs, Object[] tabs) {
- char[] buf = null;
-
- if (hasTabs) {
- buf = TextUtils.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
- }
-
- int len = end - start;
-
- int lastPos = 0;
- float width = 0;
- int ascent = 0, descent = 0, top = 0, bottom = 0;
-
- if (fm != null) {
- fm.ascent = 0;
- fm.descent = 0;
- }
-
- for (int pos = hasTabs ? 0 : len; pos <= len; pos++) {
- int codept = 0;
- Bitmap bm = null;
-
- if (hasTabs && pos < len) {
- codept = buf[pos];
- }
-
- if (codept >= 0xD800 && codept <= 0xDFFF && pos < len) {
- codept = Character.codePointAt(buf, pos);
-
- if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
- bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
- }
- }
-
- if (pos == len || codept == '\t' || bm != null) {
- workPaint.baselineShift = 0;
-
- width += Styled.measureText(paint, workPaint, text,
- start + lastPos, start + pos,
- fm);
-
- if (fm != null) {
- if (workPaint.baselineShift < 0) {
- fm.ascent += workPaint.baselineShift;
- fm.top += workPaint.baselineShift;
- } else {
- fm.descent += workPaint.baselineShift;
- fm.bottom += workPaint.baselineShift;
- }
- }
-
- if (pos != len) {
- if (bm == null) {
- // no emoji, must have hit a tab
- width = nextTab(text, start, end, width, tabs);
- } else {
- // This sets up workPaint with the font on the emoji
- // text, so that we can extract the ascent and scale.
-
- // We can't use the result of the previous call to
- // measureText because the emoji might have its own style.
- // We have to initialize workPaint here because if the
- // text is unstyled measureText might not use workPaint
- // at all.
- workPaint.set(paint);
- Styled.measureText(paint, workPaint, text,
- start + pos, start + pos + 1, null);
-
- width += (float) bm.getWidth() *
- -workPaint.ascent() / bm.getHeight();
-
- // Since we had an emoji, we bump past the second half
- // of the surrogate pair.
- pos++;
- }
- }
-
- if (fm != null) {
- if (fm.ascent < ascent) {
- ascent = fm.ascent;
- }
- if (fm.descent > descent) {
- descent = fm.descent;
- }
-
- if (fm.top < top) {
- top = fm.top;
- }
- if (fm.bottom > bottom) {
- bottom = fm.bottom;
- }
-
- // No need to take bitmap height into account here,
- // since it is scaled to match the text height.
- }
-
- lastPos = pos + 1;
- }
- }
-
- if (fm != null) {
- fm.ascent = ascent;
- fm.descent = descent;
- fm.top = top;
- fm.bottom = bottom;
- }
-
- if (hasTabs)
- TextUtils.recycle(buf);
-
- return width;
}
/**
@@ -1804,23 +1404,22 @@
/**
* Stores information about bidirectional (left-to-right or right-to-left)
- * text within the layout of a line. TODO: This work is not complete
- * or correct and will be fleshed out in a later revision.
+ * text within the layout of a line.
*/
public static class Directions {
- private short[] mDirections;
+ // Directions represents directional runs within a line of text.
+ // Runs are pairs of ints listed in visual order, starting from the
+ // leading margin. The first int of each pair is the offset from
+ // the first character of the line to the start of the run. The
+ // second int represents both the length and level of the run.
+ // The length is in the lower bits, accessed by masking with
+ // DIR_LENGTH_MASK. The level is in the higher bits, accessed
+ // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
+ // To simply test for an RTL direction, test the bit using
+ // DIR_RTL_FLAG, if set then the direction is rtl.
- // The values in mDirections are the offsets from the first character
- // in the line to the next flip in direction. Runs at even indices
- // are left-to-right, the others are right-to-left. So, for example,
- // a line that starts with a right-to-left run has 0 at mDirections[0],
- // since the 'first' (ltr) run is zero length.
- //
- // The code currently assumes that each run is adjacent to the previous
- // one, progressing in the base line direction. This isn't sufficient
- // to handle nested runs, for example numeric text in an rtl context
- // in an ltr paragraph.
- /* package */ Directions(short[] dirs) {
+ /* package */ int[] mDirections;
+ /* package */ Directions(int[] dirs) {
mDirections = dirs;
}
}
@@ -1831,6 +1430,7 @@
* line is ellipsized, not getLineStart().)
*/
public abstract int getEllipsisStart(int line);
+
/**
* Returns the number of characters to be ellipsized away, or 0 if
* no ellipsis is to take place.
@@ -1870,7 +1470,7 @@
public int length() {
return mText.length();
}
-
+
public CharSequence subSequence(int start, int end) {
char[] s = new char[end - start];
getChars(start, end, s, 0);
@@ -1936,12 +1536,17 @@
public static final int DIR_LEFT_TO_RIGHT = 1;
public static final int DIR_RIGHT_TO_LEFT = -1;
-
+
/* package */ static final int DIR_REQUEST_LTR = 1;
/* package */ static final int DIR_REQUEST_RTL = -1;
/* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
/* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
+ /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
+ /* package */ static final int RUN_LEVEL_SHIFT = 26;
+ /* package */ static final int RUN_LEVEL_MASK = 0x3f;
+ /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
+
public enum Alignment {
ALIGN_NORMAL,
ALIGN_OPPOSITE,
@@ -1953,9 +1558,8 @@
private static final int TAB_INCREMENT = 20;
/* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
- new Directions(new short[] { 32767 });
+ new Directions(new int[] { 0, RUN_LENGTH_MASK });
/* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
- new Directions(new short[] { 0, 32767 });
-
+ new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
}
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
new file mode 100644
index 0000000..e3a113d
--- /dev/null
+++ b/core/java/android/text/MeasuredText.java
@@ -0,0 +1,250 @@
+/*
+ * 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.text;
+
+import com.android.internal.util.ArrayUtils;
+
+import android.graphics.Paint;
+import android.icu.text.ArabicShaping;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+class MeasuredText {
+ /* package */ CharSequence mText;
+ /* package */ int mTextStart;
+ /* package */ float[] mWidths;
+ /* package */ char[] mChars;
+ /* package */ byte[] mLevels;
+ /* package */ int mDir;
+ /* package */ boolean mEasy;
+ /* package */ int mLen;
+ private int mPos;
+ private float[] mWorkWidths; // temp buffer for Paint.measureText, arrgh
+ private TextPaint mWorkPaint;
+
+ private MeasuredText() {
+ mWorkPaint = new TextPaint();
+ }
+
+ private static MeasuredText[] cached = new MeasuredText[3];
+
+ /* package */
+ static MeasuredText obtain() {
+ MeasuredText mt;
+ synchronized (cached) {
+ for (int i = cached.length; --i >= 0;) {
+ if (cached[i] != null) {
+ mt = cached[i];
+ cached[i] = null;
+ return mt;
+ }
+ }
+ }
+ mt = new MeasuredText();
+ Log.e("MEAS", "new: " + mt);
+ return mt;
+ }
+
+ /* package */
+ static MeasuredText recycle(MeasuredText mt) {
+ mt.mText = null;
+ if (mt.mLen < 1000) {
+ synchronized(cached) {
+ for (int i = 0; i < cached.length; ++i) {
+ if (cached[i] == null) {
+ cached[i] = mt;
+ break;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Analyzes text for
+ * bidirectional runs. Allocates working buffers.
+ */
+ /* package */
+ void setPara(CharSequence text, int start, int end, int bidiRequest) {
+ mText = text;
+ mTextStart = start;
+
+ int len = end - start;
+ mLen = len;
+ mPos = 0;
+
+ if (mWidths == null || mWidths.length < len) {
+ mWidths = new float[ArrayUtils.idealFloatArraySize(len)];
+ mWorkWidths = new float[mWidths.length];
+ }
+ if (mChars == null || mChars.length < len) {
+ mChars = new char[ArrayUtils.idealCharArraySize(len)];
+ }
+ TextUtils.getChars(text, start, end, mChars, 0);
+
+ if (text instanceof Spanned) {
+ Spanned spanned = (Spanned) text;
+ ReplacementSpan[] spans = spanned.getSpans(start, end,
+ ReplacementSpan.class);
+
+ for (int i = 0; i < spans.length; i++) {
+ int startInPara = spanned.getSpanStart(spans[i]) - start;
+ int endInPara = spanned.getSpanEnd(spans[i]) - start;
+ for (int j = startInPara; j < endInPara; j++) {
+ mChars[j] = '\uFFFC';
+ }
+ }
+ }
+
+ if (TextUtils.doesNotNeedBidi(mChars, 0, len)) {
+ mDir = 1;
+ mEasy = true;
+ } else {
+ if (mLevels == null || mLevels.length < len) {
+ mLevels = new byte[ArrayUtils.idealByteArraySize(len)];
+ }
+ mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
+ mEasy = false;
+
+ // shape
+ if (mLen > 0) {
+ byte[] levels = mLevels;
+ char[] chars = mChars;
+ byte level = levels[0];
+ int pi = 0;
+ for (int i = 1, e = mLen;; ++i) {
+ if (i == e || levels[i] != level) {
+ if ((level & 0x1) != 0) {
+ AndroidCharacter.mirror(chars, pi, i - pi);
+ ArabicShaping.SHAPER.shape(chars, pi, i - pi);
+ }
+ if (i == e) {
+ break;
+ }
+ pi = i;
+ level = levels[i];
+ }
+ }
+ }
+ }
+ }
+
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+ int p = mPos;
+ float[] w = mWidths, ww = mWorkWidths;
+ int count = paint.getTextWidths(mChars, p, len, ww);
+ int width = 0;
+ if (count < len) {
+ // must have surrogate pairs in here, pad out the array with zero
+ // for the trailing surrogates
+ char[] chars = mChars;
+ for (int i = 0, e = mLen; i < count; ++i) {
+ width += (w[p++] = ww[i]);
+ if (p < e && chars[p] >= '\udc00' && chars[p] < '\ue000' &&
+ chars[p-1] >= '\ud800' && chars[p-1] < '\udc00') {
+ w[p++] = 0;
+ }
+ }
+ } else {
+ for (int i = 0; i < len; ++i) {
+ width += (w[p++] = ww[i]);
+ }
+ }
+ mPos = p;
+ if (fm != null) {
+ paint.getFontMetricsInt(fm);
+ }
+ return width;
+ }
+
+ float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+ Paint.FontMetricsInt fm) {
+
+ TextPaint workPaint = mWorkPaint;
+ workPaint.set(paint);
+ // XXX paint should not have a baseline shift, but...
+ workPaint.baselineShift = 0;
+
+ ReplacementSpan replacement = null;
+ for (int i = 0; i < spans.length; i++) {
+ MetricAffectingSpan span = spans[i];
+ if (span instanceof ReplacementSpan) {
+ replacement = (ReplacementSpan)span;
+ } else {
+ span.updateMeasureState(workPaint);
+ }
+ }
+
+ float wid;
+ if (replacement == null) {
+ wid = addStyleRun(workPaint, len, fm);
+ } else {
+ // Use original text. Shouldn't matter.
+ wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
+ mTextStart + mPos + len, fm);
+ float[] w = mWidths;
+ w[mPos] = wid;
+ for (int i = mPos + 1, e = mPos + len; i < e; i++)
+ w[i] = 0;
+ }
+
+ if (fm != null) {
+ if (workPaint.baselineShift < 0) {
+ fm.ascent += workPaint.baselineShift;
+ fm.top += workPaint.baselineShift;
+ } else {
+ fm.descent += workPaint.baselineShift;
+ fm.bottom += workPaint.baselineShift;
+ }
+ }
+
+ return wid;
+ }
+
+ int breakText(int start, int limit, boolean forwards, float width) {
+ float[] w = mWidths;
+ if (forwards) {
+ for (int i = start; i < limit; ++i) {
+ if ((width -= w[i]) < 0) {
+ return i - start;
+ }
+ }
+ } else {
+ for (int i = limit; --i >= start;) {
+ if ((width -= w[i]) < 0) {
+ return limit - i -1;
+ }
+ }
+ }
+
+ return limit - start;
+ }
+
+ float measure(int start, int limit) {
+ float width = 0;
+ float[] w = mWidths;
+ for (int i = start; i < limit; ++i) {
+ width += w[i];
+ }
+ return width;
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f02ad2a..0c6c545 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,14 +16,13 @@
package android.text;
+import com.android.internal.util.ArrayUtils;
+
import android.graphics.Bitmap;
import android.graphics.Paint;
-import com.android.internal.util.ArrayUtils;
-import android.util.Log;
import android.text.style.LeadingMarginSpan;
import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
/**
* StaticLayout is a Layout for text that will not be edited after it
@@ -31,8 +30,9 @@
* <p>This is used by widgets to control text layout. You should not need
* to use this class directly unless you are implementing your own widget
* or custom display object, or would be tempted to call
- * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
- * Canvas.drawText()} directly.</p>
+ * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
+ * float, float, android.graphics.Paint)
+ * Canvas.drawText()} directly.</p>
*/
public class
StaticLayout
@@ -62,7 +62,7 @@
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
super((ellipsize == null)
- ? source
+ ? source
: (source instanceof Spanned)
? new SpannedEllipsizer(source)
: new Ellipsizer(source),
@@ -72,7 +72,7 @@
* This is annoying, but we can't refer to the layout until
* superclass construction is finished, and the superclass
* constructor wants the reference to the display text.
- *
+ *
* This will break if the superclass constructor ever actually
* cares about the content instead of just holding the reference.
*/
@@ -94,13 +94,13 @@
mLineDirections = new Directions[
ArrayUtils.idealIntArraySize(2 * mColumns)];
+ mMeasured = MeasuredText.obtain();
+
generate(source, bufstart, bufend, paint, outerwidth, align,
spacingmult, spacingadd, includepad, includepad,
ellipsize != null, ellipsizedWidth, ellipsize);
- mChdirs = null;
- mChs = null;
- mWidths = null;
+ mMeasured = MeasuredText.recycle(mMeasured);
mFontMetricsInt = null;
}
@@ -111,6 +111,7 @@
mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
mLineDirections = new Directions[
ArrayUtils.idealIntArraySize(2 * mColumns)];
+ mMeasured = MeasuredText.obtain();
}
/* package */ void generate(CharSequence source, int bufstart, int bufend,
@@ -128,38 +129,22 @@
Paint.FontMetricsInt fm = mFontMetricsInt;
int[] choosehtv = null;
- int end = TextUtils.indexOf(source, '\n', bufstart, bufend);
- int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart;
- boolean first = true;
+ MeasuredText measured = mMeasured;
- if (mChdirs == null) {
- mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)];
- mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)];
- mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)];
- }
-
- byte[] chdirs = mChdirs;
- char[] chs = mChs;
- float[] widths = mWidths;
-
- AlteredCharSequence alter = null;
Spanned spanned = null;
-
if (source instanceof Spanned)
spanned = (Spanned) source;
int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
- for (int start = bufstart; start <= bufend; start = end) {
- if (first)
- first = false;
+ int paraEnd;
+ for (int paraStart = bufstart; paraStart <= bufend; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(source, '\n', paraStart, bufend);
+ if (paraEnd < 0)
+ paraEnd = bufend;
else
- end = TextUtils.indexOf(source, '\n', start, bufend);
-
- if (end < 0)
- end = bufend;
- else
- end++;
+ paraEnd++;
+ int paraLen = paraEnd - paraStart;
int firstWidthLineCount = 1;
int firstwidth = outerwidth;
@@ -168,19 +153,20 @@
LineHeightSpan[] chooseht = null;
if (spanned != null) {
- LeadingMarginSpan[] sp;
-
- sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
+ LeadingMarginSpan[] sp = spanned.getSpans(paraStart, paraEnd,
+ LeadingMarginSpan.class);
for (int i = 0; i < sp.length; i++) {
LeadingMarginSpan lms = sp[i];
firstwidth -= sp[i].getLeadingMargin(true);
restwidth -= sp[i].getLeadingMargin(false);
if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
- firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
+ firstWidthLineCount =
+ ((LeadingMarginSpan.LeadingMarginSpan2)lms)
+ .getLeadingMarginLineCount();
}
}
- chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
+ chooseht = spanned.getSpans(paraStart, paraEnd, LineHeightSpan.class);
if (chooseht.length != 0) {
if (choosehtv == null ||
@@ -192,11 +178,11 @@
for (int i = 0; i < chooseht.length; i++) {
int o = spanned.getSpanStart(chooseht[i]);
- if (o < start) {
+ if (o < paraStart) {
// starts in this layout, before the
// current paragraph
- choosehtv[i] = getLineTop(getLineForOffset(o));
+ choosehtv[i] = getLineTop(getLineForOffset(o));
} else {
// starts in this paragraph
@@ -206,134 +192,48 @@
}
}
- if (end - start > chdirs.length) {
- chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)];
- mChdirs = chdirs;
- }
- if (end - start > chs.length) {
- chs = new char[ArrayUtils.idealCharArraySize(end - start)];
- mChs = chs;
- }
- if ((end - start) * 2 > widths.length) {
- widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)];
- mWidths = widths;
- }
+ measured.setPara(source, paraStart, paraEnd, DIR_REQUEST_DEFAULT_LTR);
+ char[] chs = measured.mChars;
+ float[] widths = measured.mWidths;
+ byte[] chdirs = measured.mLevels;
+ int dir = measured.mDir;
+ boolean easy = measured.mEasy;
- TextUtils.getChars(source, start, end, chs, 0);
- final int n = end - start;
-
- boolean easy = true;
- boolean altered = false;
- int dir = DEFAULT_DIR; // XXX
-
- for (int i = 0; i < n; i++) {
- if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
- easy = false;
- break;
- }
- }
-
- // Ensure that none of the underlying characters are treated
- // as viable breakpoints, and that the entire run gets the
- // same bidi direction.
-
- if (source instanceof Spanned) {
- Spanned sp = (Spanned) source;
- ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
-
- for (int y = 0; y < spans.length; y++) {
- int a = sp.getSpanStart(spans[y]);
- int b = sp.getSpanEnd(spans[y]);
-
- for (int x = a; x < b; x++) {
- chs[x - start] = '\uFFFC';
- }
- }
- }
-
- if (!easy) {
- // XXX put override flags, etc. into chdirs
- dir = bidi(dir, chs, chdirs, n, false);
-
- // Do mirroring for right-to-left segments
-
- for (int i = 0; i < n; i++) {
- if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- int j;
-
- for (j = i; j < n; j++) {
- if (chdirs[j] !=
- Character.DIRECTIONALITY_RIGHT_TO_LEFT)
- break;
- }
-
- if (AndroidCharacter.mirror(chs, i, j - i))
- altered = true;
-
- i = j - 1;
- }
- }
- }
-
- CharSequence sub;
-
- if (altered) {
- if (alter == null)
- alter = AlteredCharSequence.make(source, chs, start, end);
- else
- alter.update(chs, start, end);
-
- sub = alter;
- } else {
- sub = source;
- }
+ CharSequence sub = source;
int width = firstwidth;
float w = 0;
- int here = start;
+ int here = paraStart;
- int ok = start;
+ int ok = paraStart;
float okwidth = w;
int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
- int fit = start;
+ int fit = paraStart;
float fitwidth = w;
int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
boolean tab = false;
- int next;
- for (int i = start; i < end; i = next) {
+ int spanEnd;
+ for (int spanStart = paraStart; spanStart < paraEnd; spanStart = spanEnd) {
if (spanned == null)
- next = end;
+ spanEnd = paraEnd;
else
- next = spanned.nextSpanTransition(i, end,
- MetricAffectingSpan.
- class);
+ spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+ MetricAffectingSpan.class);
+
+ int spanLen = spanEnd - spanStart;
+ int startInPara = spanStart - paraStart;
+ int endInPara = spanEnd - paraStart;
if (spanned == null) {
- paint.getTextWidths(sub, i, next, widths);
- System.arraycopy(widths, 0, widths,
- end - start + (i - start), next - i);
-
- paint.getFontMetricsInt(fm);
+ measured.addStyleRun(paint, spanLen, fm);
} else {
- mWorkPaint.baselineShift = 0;
-
- Styled.getTextWidths(paint, mWorkPaint,
- spanned, i, next,
- widths, fm);
- System.arraycopy(widths, 0, widths,
- end - start + (i - start), next - i);
-
- if (mWorkPaint.baselineShift < 0) {
- fm.ascent += mWorkPaint.baselineShift;
- fm.top += mWorkPaint.baselineShift;
- } else {
- fm.descent += mWorkPaint.baselineShift;
- fm.bottom += mWorkPaint.baselineShift;
- }
+ MetricAffectingSpan[] spans =
+ spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+ measured.addStyleRun(paint, spans, spanLen, fm);
}
int fmtop = fm.top;
@@ -341,27 +241,17 @@
int fmascent = fm.ascent;
int fmdescent = fm.descent;
- if (false) {
- StringBuilder sb = new StringBuilder();
- for (int j = i; j < next; j++) {
- sb.append(widths[j - start + (end - start)]);
- sb.append(' ');
- }
-
- Log.e("text", sb.toString());
- }
-
- for (int j = i; j < next; j++) {
- char c = chs[j - start];
+ for (int j = spanStart; j < spanEnd; j++) {
+ char c = chs[j - paraStart];
float before = w;
if (c == '\n') {
;
} else if (c == '\t') {
- w = Layout.nextTab(sub, start, end, w, null);
+ w = Layout.nextTab(sub, paraStart, paraEnd, w, null);
tab = true;
- } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < next) {
- int emoji = Character.codePointAt(chs, j - start);
+ } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) {
+ int emoji = Character.codePointAt(chs, j - paraStart);
if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
Bitmap bm = EMOJI_FACTORY.
@@ -376,7 +266,7 @@
whichPaint = mWorkPaint;
}
- float wid = (float) bm.getWidth() *
+ float wid = bm.getWidth() *
-whichPaint.ascent() /
bm.getHeight();
@@ -384,13 +274,13 @@
tab = true;
j++;
} else {
- w += widths[j - start + (end - start)];
+ w += widths[j - paraStart];
}
} else {
- w += widths[j - start + (end - start)];
+ w += widths[j - paraStart];
}
} else {
- w += widths[j - start + (end - start)];
+ w += widths[j - paraStart];
}
// Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
@@ -411,7 +301,7 @@
/*
* From the Unicode Line Breaking Algorithm:
* (at least approximately)
- *
+ *
* .,:; are class IS: breakpoints
* except when adjacent to digits
* / is class SY: a breakpoint
@@ -426,12 +316,12 @@
if (c == ' ' || c == '\t' ||
((c == '.' || c == ',' || c == ':' || c == ';') &&
- (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
- (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+ (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
+ (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
((c == '/' || c == '-') &&
- (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+ (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
(c >= FIRST_CJK && isIdeographic(c, true) &&
- j + 1 < next && isIdeographic(chs[j + 1 - start], false))) {
+ j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
okwidth = w;
ok = j + 1;
@@ -448,7 +338,7 @@
if (ok != here) {
// Log.e("text", "output ok " + here + " to " +ok);
- while (ok < next && chs[ok - start] == ' ') {
+ while (ok < spanEnd && chs[ok - paraStart] == ' ') {
ok++;
}
@@ -458,9 +348,9 @@
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
+ needMultiply, paraStart, chdirs, dir, easy,
ok == bufend, includepad, trackpad,
- widths, start, end - start,
+ chs, widths, here - paraStart,
where, ellipsizedWidth, okwidth,
paint);
@@ -484,7 +374,7 @@
if (ok != here) {
// Log.e("text", "output ok " + here + " to " +ok);
- while (ok < next && chs[ok - start] == ' ') {
+ while (ok < spanEnd && chs[ok - paraStart] == ' ') {
ok++;
}
@@ -494,9 +384,9 @@
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
+ needMultiply, paraStart, chdirs, dir, easy,
ok == bufend, includepad, trackpad,
- widths, start, end - start,
+ chs, widths, here - paraStart,
where, ellipsizedWidth, okwidth,
paint);
@@ -510,18 +400,19 @@
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
+ needMultiply, paraStart, chdirs, dir, easy,
fit == bufend, includepad, trackpad,
- widths, start, end - start,
+ chs, widths, here - paraStart,
where, ellipsizedWidth, fitwidth,
paint);
here = fit;
} else {
// Log.e("text", "output one " + here + " to " +(here + 1));
- measureText(paint, mWorkPaint,
- source, here, here + 1, fm, tab,
- null);
+ // XXX not sure why the existing fm wasn't ok.
+ // measureText(paint, mWorkPaint,
+ // source, here, here + 1, fm, tab,
+ // null);
v = out(source,
here, here+1,
@@ -530,18 +421,18 @@
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
+ needMultiply, paraStart, chdirs, dir, easy,
here + 1 == bufend, includepad,
trackpad,
- widths, start, end - start,
+ chs, widths, here - paraStart,
where, ellipsizedWidth,
- widths[here - start], paint);
+ widths[here - paraStart], paint);
here = here + 1;
}
- if (here < i) {
- j = next = here; // must remeasure
+ if (here < spanStart) {
+ j = spanEnd = here; // must remeasure
} else {
j = here - 1; // continue looping
}
@@ -558,7 +449,7 @@
}
}
- if (end != here) {
+ if (paraEnd != here) {
if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
paint.getFontMetricsInt(fm);
@@ -571,20 +462,20 @@
// Log.e("text", "output rest " + here + " to " + end);
v = out(source,
- here, end, fitascent, fitdescent,
+ here, paraEnd, fitascent, fitdescent,
fittop, fitbottom,
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
- end == bufend, includepad, trackpad,
- widths, start, end - start,
+ needMultiply, paraStart, chdirs, dir, easy,
+ paraEnd == bufend, includepad, trackpad,
+ chs, widths, here - paraStart,
where, ellipsizedWidth, w, paint);
}
- start = end;
+ paraStart = paraEnd;
- if (end == bufend)
+ if (paraEnd == bufend)
break;
}
@@ -599,246 +490,13 @@
v,
spacingmult, spacingadd, null,
null, fm, false,
- needMultiply, bufend, chdirs, DEFAULT_DIR, true,
+ needMultiply, bufend, null, DEFAULT_DIR, true,
true, includepad, trackpad,
- widths, bufstart, 0,
+ null, null, bufstart,
where, ellipsizedWidth, 0, paint);
}
}
- /**
- * Runs the unicode bidi algorithm on the first n chars in chs, returning
- * the char dirs in chInfo and the base line direction of the first
- * paragraph.
- *
- * XXX change result from dirs to levels
- *
- * @param dir the direction flag, either DIR_REQUEST_LTR,
- * DIR_REQUEST_RTL, DIR_REQUEST_DEFAULT_LTR, or DIR_REQUEST_DEFAULT_RTL.
- * @param chs the text to examine
- * @param chInfo on input, if hasInfo is true, override and other flags
- * representing out-of-band embedding information. On output, the generated
- * dirs of the text.
- * @param n the length of the text/information in chs and chInfo
- * @param hasInfo true if chInfo has input information, otherwise the
- * input data in chInfo is ignored.
- * @return the resolved direction level of the first paragraph, either
- * DIR_LEFT_TO_RIGHT or DIR_RIGHT_TO_LEFT.
- */
- /* package */ static int bidi(int dir, char[] chs, byte[] chInfo, int n,
- boolean hasInfo) {
-
- AndroidCharacter.getDirectionalities(chs, chInfo, n);
-
- /*
- * Determine primary paragraph direction if not specified
- */
- if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) {
- // set up default
- dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT;
- for (int j = 0; j < n; j++) {
- int d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
- dir = DIR_LEFT_TO_RIGHT;
- break;
- }
- if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- dir = DIR_RIGHT_TO_LEFT;
- break;
- }
- }
- }
-
- final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
- Character.DIRECTIONALITY_LEFT_TO_RIGHT :
- Character.DIRECTIONALITY_RIGHT_TO_LEFT;
-
- /*
- * XXX Explicit overrides should go here
- */
-
- /*
- * Weak type resolution
- */
-
- // dump(chdirs, n, "initial");
-
- // W1 non spacing marks
- for (int j = 0; j < n; j++) {
- if (chInfo[j] == Character.NON_SPACING_MARK) {
- if (j == 0)
- chInfo[j] = SOR;
- else
- chInfo[j] = chInfo[j - 1];
- }
- }
-
- // dump(chdirs, n, "W1");
-
- // W2 european numbers
- byte cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- cur = d;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
- if (cur ==
- Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
- }
- }
-
- // dump(chdirs, n, "W2");
-
- // W3 arabic letters
- for (int j = 0; j < n; j++) {
- if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- }
-
- // dump(chdirs, n, "W3");
-
- // W4 single separator between numbers
- for (int j = 1; j < n - 1; j++) {
- byte d = chInfo[j];
- byte prev = chInfo[j - 1];
- byte next = chInfo[j + 1];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
- if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
- next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
- if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
- next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
- next == Character.DIRECTIONALITY_ARABIC_NUMBER)
- chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
- }
- }
-
- // dump(chdirs, n, "W4");
-
- // W5 european number terminators
- boolean adjacent = false;
- for (int j = 0; j < n; j++) {
- byte d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- adjacent = true;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
- chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- else
- adjacent = false;
- }
-
- //dump(chdirs, n, "W5");
-
- // W5 european number terminators part 2,
- // W6 separators and terminators
- adjacent = false;
- for (int j = n - 1; j >= 0; j--) {
- byte d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- adjacent = true;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
- if (adjacent)
- chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- else
- chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
- }
- else {
- adjacent = false;
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
- d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
- d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
- d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
- chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
- }
- }
-
- // dump(chdirs, n, "W6");
-
- // W7 strong direction of european numbers
- cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chInfo[j];
-
- if (d == SOR ||
- d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
- cur = d;
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chInfo[j] = cur;
- }
-
- // dump(chdirs, n, "W7");
-
- // N1, N2 neutrals
- cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- cur = d;
- } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
- d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
- cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- } else {
- byte dd = SOR;
- int k;
-
- for (k = j + 1; k < n; k++) {
- dd = chInfo[k];
-
- if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- break;
- }
- if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
- dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
- dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- break;
- }
- }
-
- for (int y = j; y < k; y++) {
- if (dd == cur)
- chInfo[y] = cur;
- else
- chInfo[y] = SOR;
- }
-
- j = k - 1;
- }
- }
-
- // dump(chdirs, n, "final");
-
- // extra: enforce that all tabs and surrogate characters go the
- // primary direction
- // TODO: actually do directions right for surrogates
-
- for (int j = 0; j < n; j++) {
- char c = chs[j];
-
- if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
- chInfo[j] = SOR;
- }
- }
-
- return dir;
- }
-
private static final char FIRST_CJK = '\u2E80';
/**
* Returns true if the specified character is one of those specified
@@ -944,28 +602,6 @@
}
*/
- private static int getFit(TextPaint paint,
- TextPaint workPaint,
- CharSequence text, int start, int end,
- float wid) {
- int high = end + 1, low = start - 1, guess;
-
- while (high - low > 1) {
- guess = (high + low) / 2;
-
- if (measureText(paint, workPaint,
- text, start, guess, null, true, null) > wid)
- high = guess;
- else
- low = guess;
- }
-
- if (low < start)
- return start;
- else
- return low;
- }
-
private int out(CharSequence text, int start, int end,
int above, int below, int top, int bottom, int v,
float spacingmult, float spacingadd,
@@ -974,7 +610,7 @@
boolean needMultiply, int pstart, byte[] chdirs,
int dir, boolean easy, boolean last,
boolean includepad, boolean trackpad,
- float[] widths, int widstart, int widoff,
+ char[] chs, float[] widths, int widstart,
TextUtils.TruncateAt ellipsize, float ellipsiswidth,
float textwidth, TextPaint paint) {
int j = mLineCount;
@@ -982,8 +618,6 @@
int want = off + mColumns + TOP;
int[] lines = mLines;
- // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
-
if (want >= lines.length) {
int nlen = ArrayUtils.idealIntArraySize(want + 1);
int[] grow = new int[nlen];
@@ -1062,56 +696,20 @@
if (tab)
lines[off + TAB] |= TAB_MASK;
- {
- lines[off + DIR] |= dir << DIR_SHIFT;
-
- int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
- int count = 0;
-
- if (!easy) {
- for (int k = start; k < end; k++) {
- if (chdirs[k - pstart] != cur) {
- count++;
- cur = chdirs[k - pstart];
- }
- }
- }
-
- Directions linedirs;
-
- if (count == 0) {
- linedirs = DIRS_ALL_LEFT_TO_RIGHT;
- } else {
- short[] ld = new short[count + 1];
-
- cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
- count = 0;
- int here = start;
-
- for (int k = start; k < end; k++) {
- if (chdirs[k - pstart] != cur) {
- // XXX check to make sure we don't
- // overflow short
- ld[count++] = (short) (k - here);
- cur = chdirs[k - pstart];
- here = k;
- }
- }
-
- ld[count] = (short) (end - here);
-
- if (count == 1 && ld[0] == 0) {
- linedirs = DIRS_ALL_RIGHT_TO_LEFT;
- } else {
- linedirs = new Directions(ld);
- }
- }
-
+ lines[off + DIR] |= dir << DIR_SHIFT;
+ Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
+ // easy means all chars < the first RTL, so no emoji, no nothing
+ // XXX a run with no text or all spaces is easy but might be an empty
+ // RTL paragraph. Make sure easy is false if this is the case.
+ if (easy) {
mLineDirections[j] = linedirs;
+ } else {
+ mLineDirections[j] = AndroidBidi.directions(dir, chdirs, widstart, chs,
+ widstart, end - start);
// If ellipsize is in marquee mode, do not apply ellipsis on the first line
if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
- calculateEllipsis(start, end, widths, widstart, widoff,
+ calculateEllipsis(start, end, widths, widstart,
ellipsiswidth, ellipsize, j,
textwidth, paint);
}
@@ -1122,7 +720,7 @@
}
private void calculateEllipsis(int linestart, int lineend,
- float[] widths, int widstart, int widoff,
+ float[] widths, int widstart,
float avail, TextUtils.TruncateAt where,
int line, float textwidth, TextPaint paint) {
int len = lineend - linestart;
@@ -1142,7 +740,7 @@
int i;
for (i = len; i >= 0; i--) {
- float w = widths[i - 1 + linestart - widstart + widoff];
+ float w = widths[i - 1 + linestart - widstart];
if (w + sum + ellipsiswid > avail) {
break;
@@ -1158,7 +756,7 @@
int i;
for (i = 0; i < len; i++) {
- float w = widths[i + linestart - widstart + widoff];
+ float w = widths[i + linestart - widstart];
if (w + sum + ellipsiswid > avail) {
break;
@@ -1175,7 +773,7 @@
float ravail = (avail - ellipsiswid) / 2;
for (right = len; right >= 0; right--) {
- float w = widths[right - 1 + linestart - widstart + widoff];
+ float w = widths[right - 1 + linestart - widstart];
if (w + rsum > ravail) {
break;
@@ -1186,7 +784,7 @@
float lavail = avail - ellipsiswid - rsum;
for (left = 0; left < right; left++) {
- float w = widths[left + linestart - widstart + widoff];
+ float w = widths[left + linestart - widstart];
if (w + lsum > lavail) {
break;
@@ -1203,7 +801,7 @@
mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
}
- // Override the baseclass so we can directly access our members,
+ // Override the base class so we can directly access our members,
// rather than relying on member functions.
// The logic mirrors that of Layout.getLineForVertical
// FIXME: It may be faster to do a linear search for layouts without many lines.
@@ -1232,11 +830,11 @@
}
public int getLineTop(int line) {
- return mLines[mColumns * line + TOP];
+ return mLines[mColumns * line + TOP];
}
public int getLineDescent(int line) {
- return mLines[mColumns * line + DESCENT];
+ return mLines[mColumns * line + DESCENT];
}
public int getLineStart(int line) {
@@ -1312,10 +910,8 @@
private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
/*
- * These are reused across calls to generate()
+ * This is reused across calls to generate()
*/
- private byte[] mChdirs;
- private char[] mChs;
- private float[] mWidths;
+ private MeasuredText mMeasured;
private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
}
diff --git a/core/java/android/text/Styled.java b/core/java/android/text/Styled.java
deleted file mode 100644
index 513b2cd..0000000
--- a/core/java/android/text/Styled.java
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- * Copyright (C) 2006 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.text;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.text.style.CharacterStyle;
-import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
-
-/**
- * This class provides static methods for drawing and measuring styled text,
- * like {@link android.text.Spanned} object with
- * {@link android.text.style.ReplacementSpan}.
- *
- * @hide
- */
-public class Styled
-{
- /**
- * Draws and/or measures a uniform run of text on a single line. No span of
- * interest should start or end in the middle of this run (if not
- * drawing, character spans that don't affect metrics can be ignored).
- * Neither should the run direction change in the middle of the run.
- *
- * <p>The x position is the leading edge of the text. In a right-to-left
- * paragraph, this will be to the right of the text to be drawn. Paint
- * should not have an Align value other than LEFT or positioning will get
- * confused.
- *
- * <p>On return, workPaint will reflect the original paint plus any
- * modifications made by character styles on the run.
- *
- * <p>The returned width is signed and will be < 0 if the paragraph
- * direction is right-to-left.
- */
- private static float drawUniformRun(Canvas canvas,
- Spanned text, int start, int end,
- int dir, boolean runIsRtl,
- float x, int top, int y, int bottom,
- Paint.FontMetricsInt fmi,
- TextPaint paint,
- TextPaint workPaint,
- boolean needWidth) {
-
- boolean haveWidth = false;
- float ret = 0;
- CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class);
-
- ReplacementSpan replacement = null;
-
- // XXX: This shouldn't be modifying paint, only workPaint.
- // However, the members belonging to TextPaint should have default
- // values anyway. Better to ensure this in the Layout constructor.
- paint.bgColor = 0;
- paint.baselineShift = 0;
- workPaint.set(paint);
-
- if (spans.length > 0) {
- for (int i = 0; i < spans.length; i++) {
- CharacterStyle span = spans[i];
-
- if (span instanceof ReplacementSpan) {
- replacement = (ReplacementSpan)span;
- }
- else {
- span.updateDrawState(workPaint);
- }
- }
- }
-
- if (replacement == null) {
- CharSequence tmp;
- int tmpstart, tmpend;
-
- if (runIsRtl) {
- tmp = TextUtils.getReverse(text, start, end);
- tmpstart = 0;
- // XXX: assumes getReverse doesn't change the length of the text
- tmpend = end - start;
- } else {
- tmp = text;
- tmpstart = start;
- tmpend = end;
- }
-
- if (fmi != null) {
- workPaint.getFontMetricsInt(fmi);
- }
-
- if (canvas != null) {
- if (workPaint.bgColor != 0) {
- int c = workPaint.getColor();
- Paint.Style s = workPaint.getStyle();
- workPaint.setColor(workPaint.bgColor);
- workPaint.setStyle(Paint.Style.FILL);
-
- if (!haveWidth) {
- ret = workPaint.measureText(tmp, tmpstart, tmpend);
- haveWidth = true;
- }
-
- if (dir == Layout.DIR_RIGHT_TO_LEFT)
- canvas.drawRect(x - ret, top, x, bottom, workPaint);
- else
- canvas.drawRect(x, top, x + ret, bottom, workPaint);
-
- workPaint.setStyle(s);
- workPaint.setColor(c);
- }
-
- if (dir == Layout.DIR_RIGHT_TO_LEFT) {
- if (!haveWidth) {
- ret = workPaint.measureText(tmp, tmpstart, tmpend);
- haveWidth = true;
- }
-
- canvas.drawText(tmp, tmpstart, tmpend,
- x - ret, y + workPaint.baselineShift, workPaint);
- } else {
- if (needWidth) {
- if (!haveWidth) {
- ret = workPaint.measureText(tmp, tmpstart, tmpend);
- haveWidth = true;
- }
- }
-
- canvas.drawText(tmp, tmpstart, tmpend,
- x, y + workPaint.baselineShift, workPaint);
- }
- } else {
- if (needWidth && !haveWidth) {
- ret = workPaint.measureText(tmp, tmpstart, tmpend);
- haveWidth = true;
- }
- }
- } else {
- ret = replacement.getSize(workPaint, text, start, end, fmi);
-
- if (canvas != null) {
- if (dir == Layout.DIR_RIGHT_TO_LEFT)
- replacement.draw(canvas, text, start, end,
- x - ret, top, y, bottom, workPaint);
- else
- replacement.draw(canvas, text, start, end,
- x, top, y, bottom, workPaint);
- }
- }
-
- if (dir == Layout.DIR_RIGHT_TO_LEFT)
- return -ret;
- else
- return ret;
- }
-
- /**
- * Returns the advance widths for a uniform left-to-right run of text with
- * no style changes in the middle of the run. If any style is replacement
- * text, the first character will get the width of the replacement and the
- * remaining characters will get a width of 0.
- *
- * @param paint the paint, will not be modified
- * @param workPaint a paint to modify; on return will reflect the original
- * paint plus the effect of all spans on the run
- * @param text the text
- * @param start the start of the run
- * @param end the limit of the run
- * @param widths array to receive the advance widths of the characters. Must
- * be at least a large as (end - start).
- * @param fmi FontMetrics information; can be null
- * @return the actual number of widths returned
- */
- public static int getTextWidths(TextPaint paint,
- TextPaint workPaint,
- Spanned text, int start, int end,
- float[] widths, Paint.FontMetricsInt fmi) {
- MetricAffectingSpan[] spans =
- text.getSpans(start, end, MetricAffectingSpan.class);
-
- ReplacementSpan replacement = null;
- workPaint.set(paint);
-
- for (int i = 0; i < spans.length; i++) {
- MetricAffectingSpan span = spans[i];
- if (span instanceof ReplacementSpan) {
- replacement = (ReplacementSpan)span;
- }
- else {
- span.updateMeasureState(workPaint);
- }
- }
-
- if (replacement == null) {
- workPaint.getFontMetricsInt(fmi);
- workPaint.getTextWidths(text, start, end, widths);
- } else {
- int wid = replacement.getSize(workPaint, text, start, end, fmi);
-
- if (end > start) {
- widths[0] = wid;
- for (int i = start + 1; i < end; i++)
- widths[i - start] = 0;
- }
- }
- return end - start;
- }
-
- /**
- * Renders and/or measures a directional run of text on a single line.
- * Unlike {@link #drawUniformRun}, this can render runs that cross style
- * boundaries. Returns the signed advance width, if requested.
- *
- * <p>The x position is the leading edge of the text. In a right-to-left
- * paragraph, this will be to the right of the text to be drawn. Paint
- * should not have an Align value other than LEFT or positioning will get
- * confused.
- *
- * <p>This optimizes for unstyled text and so workPaint might not be
- * modified by this call.
- *
- * <p>The returned advance width will be < 0 if the paragraph
- * direction is right-to-left.
- */
- private static float drawDirectionalRun(Canvas canvas,
- CharSequence text, int start, int end,
- int dir, boolean runIsRtl,
- float x, int top, int y, int bottom,
- Paint.FontMetricsInt fmi,
- TextPaint paint,
- TextPaint workPaint,
- boolean needWidth) {
-
- // XXX: It looks like all calls to this API match dir and runIsRtl, so
- // having both parameters is redundant and confusing.
-
- // fast path for unstyled text
- if (!(text instanceof Spanned)) {
- float ret = 0;
-
- if (runIsRtl) {
- CharSequence tmp = TextUtils.getReverse(text, start, end);
- // XXX: this assumes getReverse doesn't tweak the length of
- // the text
- int tmpend = end - start;
-
- if (canvas != null || needWidth)
- ret = paint.measureText(tmp, 0, tmpend);
-
- if (canvas != null)
- canvas.drawText(tmp, 0, tmpend,
- x - ret, y, paint);
- } else {
- if (needWidth)
- ret = paint.measureText(text, start, end);
-
- if (canvas != null)
- canvas.drawText(text, start, end, x, y, paint);
- }
-
- if (fmi != null) {
- paint.getFontMetricsInt(fmi);
- }
-
- return ret * dir; // Layout.DIR_RIGHT_TO_LEFT == -1
- }
-
- float ox = x;
- int minAscent = 0, maxDescent = 0, minTop = 0, maxBottom = 0;
-
- Spanned sp = (Spanned) text;
- Class<?> division;
-
- if (canvas == null)
- division = MetricAffectingSpan.class;
- else
- division = CharacterStyle.class;
-
- int next;
- for (int i = start; i < end; i = next) {
- next = sp.nextSpanTransition(i, end, division);
-
- // XXX: if dir and runIsRtl were not the same, this would draw
- // spans in the wrong order, but no one appears to call it this
- // way.
- x += drawUniformRun(canvas, sp, i, next, dir, runIsRtl,
- x, top, y, bottom, fmi, paint, workPaint,
- needWidth || next != end);
-
- if (fmi != null) {
- if (fmi.ascent < minAscent)
- minAscent = fmi.ascent;
- if (fmi.descent > maxDescent)
- maxDescent = fmi.descent;
-
- if (fmi.top < minTop)
- minTop = fmi.top;
- if (fmi.bottom > maxBottom)
- maxBottom = fmi.bottom;
- }
- }
-
- if (fmi != null) {
- if (start == end) {
- paint.getFontMetricsInt(fmi);
- } else {
- fmi.ascent = minAscent;
- fmi.descent = maxDescent;
- fmi.top = minTop;
- fmi.bottom = maxBottom;
- }
- }
-
- return x - ox;
- }
-
- /**
- * Draws a unidirectional run of text on a single line, and optionally
- * returns the signed advance. Unlike drawDirectionalRun, the paragraph
- * direction and run direction can be different.
- */
- /* package */ static float drawText(Canvas canvas,
- CharSequence text, int start, int end,
- int dir, boolean runIsRtl,
- float x, int top, int y, int bottom,
- TextPaint paint,
- TextPaint workPaint,
- boolean needWidth) {
- // XXX this logic is (dir == DIR_LEFT_TO_RIGHT) == runIsRtl
- if ((dir == Layout.DIR_RIGHT_TO_LEFT && !runIsRtl) ||
- (runIsRtl && dir == Layout.DIR_LEFT_TO_RIGHT)) {
- // TODO: this needs the real direction
- float ch = drawDirectionalRun(null, text, start, end,
- Layout.DIR_LEFT_TO_RIGHT, false, 0, 0, 0, 0, null, paint,
- workPaint, true);
-
- ch *= dir; // DIR_RIGHT_TO_LEFT == -1
- drawDirectionalRun(canvas, text, start, end, -dir,
- runIsRtl, x + ch, top, y, bottom, null, paint,
- workPaint, true);
-
- return ch;
- }
-
- return drawDirectionalRun(canvas, text, start, end, dir, runIsRtl,
- x, top, y, bottom, null, paint, workPaint,
- needWidth);
- }
-
- /**
- * Draws a run of text on a single line, with its
- * origin at (x,y), in the specified Paint. The origin is interpreted based
- * on the Align setting in the Paint.
- *
- * This method considers style information in the text (e.g. even when text
- * is an instance of {@link android.text.Spanned}, this method correctly
- * draws the text). See also
- * {@link android.graphics.Canvas#drawText(CharSequence, int, int, float,
- * float, Paint)} and
- * {@link android.graphics.Canvas#drawRect(float, float, float, float,
- * Paint)}.
- *
- * @param canvas The target canvas
- * @param text The text to be drawn
- * @param start The index of the first character in text to draw
- * @param end (end - 1) is the index of the last character in text to draw
- * @param direction The direction of the text. This must be
- * {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
- * {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
- * @param x The x-coordinate of origin for where to draw the text
- * @param top The top side of the rectangle to be drawn
- * @param y The y-coordinate of origin for where to draw the text
- * @param bottom The bottom side of the rectangle to be drawn
- * @param paint The main {@link TextPaint} object.
- * @param workPaint The {@link TextPaint} object used for temporal
- * workspace.
- * @param needWidth If true, this method returns the width of drawn text
- * @return Width of the drawn text if needWidth is true
- */
- public static float drawText(Canvas canvas,
- CharSequence text, int start, int end,
- int direction,
- float x, int top, int y, int bottom,
- TextPaint paint,
- TextPaint workPaint,
- boolean needWidth) {
- // For safety.
- direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT
- : Layout.DIR_RIGHT_TO_LEFT;
-
- // Hide runIsRtl parameter since it is meaningless for external
- // developers.
- // XXX: the runIsRtl probably ought to be the same as direction, then
- // this could draw rtl text.
- return drawText(canvas, text, start, end, direction, false,
- x, top, y, bottom, paint, workPaint, needWidth);
- }
-
- /**
- * Returns the width of a run of left-to-right text on a single line,
- * considering style information in the text (e.g. even when text is an
- * instance of {@link android.text.Spanned}, this method correctly measures
- * the width of the text).
- *
- * @param paint the main {@link TextPaint} object; will not be modified
- * @param workPaint the {@link TextPaint} object available for modification;
- * will not necessarily be used
- * @param text the text to measure
- * @param start the index of the first character to start measuring
- * @param end 1 beyond the index of the last character to measure
- * @param fmi FontMetrics information; can be null
- * @return The width of the text
- */
- public static float measureText(TextPaint paint,
- TextPaint workPaint,
- CharSequence text, int start, int end,
- Paint.FontMetricsInt fmi) {
- return drawDirectionalRun(null, text, start, end,
- Layout.DIR_LEFT_TO_RIGHT, false,
- 0, 0, 0, 0, fmi, paint, workPaint, true);
- }
-}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
new file mode 100644
index 0000000..8ab481b
--- /dev/null
+++ b/core/java/android/text/TextLine.java
@@ -0,0 +1,1053 @@
+/*
+ * 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.text;
+
+import com.android.internal.util.ArrayUtils;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Paint.FontMetricsInt;
+import android.icu.text.ArabicShaping;
+import android.text.Layout.Directions;
+import android.text.style.CharacterStyle;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+import android.text.style.TabStopSpan;
+import android.util.Log;
+
+/**
+ * Represents a line of styled text, for measuring in visual order and
+ * for rendering.
+ *
+ * <p>Get a new instance using obtain(), and when finished with it, return it
+ * to the pool using recycle().
+ *
+ * <p>Call set to prepare the instance for use, then either draw, measure,
+ * metrics, or caretToLeftRightOf.
+ *
+ * @hide
+ */
+class TextLine {
+ private TextPaint mPaint;
+ private CharSequence mText;
+ private int mStart;
+ private int mLen;
+ private int mDir;
+ private Directions mDirections;
+ private boolean mHasTabs;
+ private TabStopSpan[] mTabs;
+
+ private char[] mChars;
+ private boolean mCharsValid;
+ private Spanned mSpanned;
+ private TextPaint mWorkPaint = new TextPaint();
+ private int mPreppedIndex;
+ private int mPreppedLimit;
+
+ private static TextLine[] cached = new TextLine[3];
+
+ /**
+ * Returns a new TextLine from the shared pool.
+ *
+ * @return an uninitialized TextLine
+ */
+ static TextLine obtain() {
+ TextLine tl;
+ synchronized (cached) {
+ for (int i = cached.length; --i >= 0;) {
+ if (cached[i] != null) {
+ tl = cached[i];
+ cached[i] = null;
+ return tl;
+ }
+ }
+ }
+ tl = new TextLine();
+ Log.e("TLINE", "new: " + tl);
+ return tl;
+ }
+
+ /**
+ * Puts a TextLine back into the shared pool. Do not use this TextLine once
+ * it has been returned.
+ * @param tl the textLine
+ * @return null, as a convenience from clearing references to the provided
+ * TextLine
+ */
+ static TextLine recycle(TextLine tl) {
+ tl.mText = null;
+ tl.mPaint = null;
+ tl.mDirections = null;
+ if (tl.mLen < 250) {
+ synchronized(cached) {
+ for (int i = 0; i < cached.length; ++i) {
+ if (cached[i] == null) {
+ cached[i] = tl;
+ break;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Initializes a TextLine and prepares it for use.
+ *
+ * @param paint the base paint for the line
+ * @param text the text, can be Styled
+ * @param start the start of the line relative to the text
+ * @param limit the limit of the line relative to the text
+ * @param dir the paragraph direction of this line
+ * @param directions the directions information of this line
+ * @param hasTabs true if the line might contain tabs or emoji
+ * @param spans array of paragraph-level spans, of which only TabStopSpans
+ * are used. Can be null.
+ */
+ void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
+ Directions directions, boolean hasTabs, Object[] spans) {
+ mPaint = paint;
+ mText = text;
+ mStart = start;
+ mLen = limit - start;
+ mDir = dir;
+ mDirections = directions;
+ mHasTabs = hasTabs;
+ mSpanned = null;
+ mPreppedIndex = 0;
+ mPreppedLimit = 0;
+
+ boolean hasReplacement = false;
+ if (text instanceof Spanned) {
+ mSpanned = (Spanned) text;
+ hasReplacement = mSpanned.getSpans(start, limit,
+ ReplacementSpan.class).length > 0;
+ }
+
+ mCharsValid = hasReplacement || hasTabs ||
+ directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
+
+ if (mCharsValid) {
+ if (mChars == null || mChars.length < mLen) {
+ mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
+ }
+ TextUtils.getChars(text, start, limit, mChars, 0);
+
+ if (hasTabs) {
+ TabStopSpan[] tabs = mTabs;
+ int tabLen = 0;
+ if (mSpanned != null && spans == null) {
+ TabStopSpan[] newTabs = mSpanned.getSpans(start, limit,
+ TabStopSpan.class);
+ if (tabs == null || tabs.length < newTabs.length) {
+ tabs = newTabs;
+ } else {
+ for (int i = 0; i < newTabs.length; ++i) {
+ tabs[i] = newTabs[i];
+ }
+ }
+ tabLen = newTabs.length;
+ } else if (spans != null) {
+ if (tabs == null || tabs.length < spans.length) {
+ tabs = new TabStopSpan[spans.length];
+ }
+ for (int i = 0; i < spans.length; ++i) {
+ if (spans[i] instanceof TabStopSpan) {
+ tabs[tabLen++] = (TabStopSpan) spans[i];
+ }
+ }
+ }
+
+ if (tabs != null && tabLen < tabs.length){
+ tabs[tabLen] = null;
+ }
+ mTabs = tabs;
+ }
+ }
+ }
+
+ /**
+ * Renders the TextLine.
+ *
+ * @param c the canvas to render on
+ * @param x the leading margin position
+ * @param top the top of the line
+ * @param y the baseline
+ * @param bottom the bottom of the line
+ */
+ void draw(Canvas c, float x, int top, int y, int bottom) {
+ if (!mHasTabs) {
+ if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
+ drawRun(c, 0, 0, mLen, false, x, top, y, bottom, false);
+ return;
+ }
+ if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
+ drawRun(c, 0, 0, mLen, true, x, top, y, bottom, false);
+ return;
+ }
+ }
+
+ float h = 0;
+ int[] runs = mDirections.mDirections;
+ RectF emojiRect = null;
+
+ int lastRunIndex = runs.length - 2;
+ for (int i = 0; i < runs.length; i += 2) {
+ int runStart = runs[i];
+ int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
+ if (runLimit > mLen) {
+ runLimit = mLen;
+ }
+ boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
+
+ int segstart = runStart;
+ char[] chars = mChars;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ int codept = 0;
+ Bitmap bm = null;
+
+ if (mHasTabs && j < runLimit) {
+ codept = mChars[j];
+ if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
+ codept = Character.codePointAt(mChars, j);
+ if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
+ bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
+ } else if (codept > 0xffff) {
+ ++j;
+ continue;
+ }
+ }
+ }
+
+ if (j == runLimit || codept == '\t' || bm != null) {
+ h += drawRun(c, i, segstart, j, runIsRtl, x+h, top, y, bottom,
+ i != lastRunIndex || j != mLen);
+
+ if (codept == '\t') {
+ h = mDir * nextTab(h * mDir);
+ } else if (bm != null) {
+ float bmAscent = ascent(j);
+ float bitmapHeight = bm.getHeight();
+ float scale = -bmAscent / bitmapHeight;
+ float width = bm.getWidth() * scale;
+
+ if (emojiRect == null) {
+ emojiRect = new RectF();
+ }
+ emojiRect.set(x + h, y + bmAscent,
+ x + h + width, y);
+ c.drawBitmap(bm, null, emojiRect, mPaint);
+ h += width;
+ j++;
+ }
+ segstart = j + 1;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns metrics information for the entire line.
+ *
+ * @param fmi receives font metrics information, can be null
+ * @return the signed width of the line
+ */
+ float metrics(FontMetricsInt fmi) {
+ return measure(mLen, false, fmi);
+ }
+
+ /**
+ * Returns information about a position on the line.
+ *
+ * @param offset the line-relative character offset, between 0 and the
+ * line length, inclusive
+ * @param trailing true to measure the trailing edge of the character
+ * before offset, false to measure the leading edge of the character
+ * at offset.
+ * @param fmi receives metrics information about the requested
+ * character, can be null.
+ * @return the signed offset from the leading margin to the requested
+ * character edge.
+ */
+ float measure(int offset, boolean trailing, FontMetricsInt fmi) {
+ int target = trailing ? offset - 1 : offset;
+ if (target < 0) {
+ return 0;
+ }
+
+ float h = 0;
+
+ if (!mHasTabs) {
+ if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
+ return measureRun( 0, 0, target, mLen, false, fmi);
+ }
+ if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
+ return measureRun(0, 0, target, mLen, true, fmi);
+ }
+ }
+
+ char[] chars = mChars;
+ int[] runs = mDirections.mDirections;
+ for (int i = 0; i < runs.length; i += 2) {
+ int runStart = runs[i];
+ int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
+ if (runLimit > mLen) {
+ runLimit = mLen;
+ }
+ boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
+
+ int segstart = runStart;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ int codept = 0;
+ Bitmap bm = null;
+
+ if (mHasTabs && j < runLimit) {
+ codept = chars[j];
+ if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
+ codept = Character.codePointAt(chars, j);
+ if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
+ bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
+ } else if (codept > 0xffff) {
+ ++j;
+ continue;
+ }
+ }
+ }
+
+ if (j == runLimit || codept == '\t' || bm != null) {
+ boolean inSegment = target >= segstart && target < j;
+
+ boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+ if (inSegment && advance) {
+ return h += measureRun(i, segstart, offset, j, runIsRtl, fmi);
+ }
+
+ float w = measureRun(i, segstart, j, j, runIsRtl, fmi);
+ h += advance ? w : -w;
+
+ if (inSegment) {
+ return h += measureRun(i, segstart, offset, j, runIsRtl, null);
+ }
+
+ if (codept == '\t') {
+ if (offset == j) {
+ return h;
+ }
+ h = mDir * nextTab(h * mDir);
+ if (target == j) {
+ return h;
+ }
+ }
+
+ if (bm != null) {
+ float bmAscent = ascent(j);
+ float wid = bm.getWidth() * -bmAscent / bm.getHeight();
+ h += mDir * wid;
+ j++;
+ }
+
+ segstart = j + 1;
+ }
+ }
+ }
+
+ return h;
+ }
+
+ /**
+ * Draws a unidirectional (but possibly multi-styled) run of text.
+ *
+ * @param c the canvas to draw on
+ * @param runIndex the index of this directional run
+ * @param start the line-relative start
+ * @param limit the line-relative limit
+ * @param runIsRtl true if the run is right-to-left
+ * @param x the position of the run that is closest to the leading margin
+ * @param top the top of the line
+ * @param y the baseline
+ * @param bottom the bottom of the line
+ * @param needWidth true if the width value is required.
+ * @return the signed width of the run, based on the paragraph direction.
+ * Only valid if needWidth is true.
+ */
+ private float drawRun(Canvas c, int runIndex, int start,
+ int limit, boolean runIsRtl, float x, int top, int y, int bottom,
+ boolean needWidth) {
+
+ if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
+ float w = -measureRun(runIndex, start, limit, limit, runIsRtl, null);
+ handleRun(runIndex, start, limit, limit, runIsRtl, c, x + w, top,
+ y, bottom, null, false, PREP_NONE);
+ return w;
+ }
+
+ return handleRun(runIndex, start, limit, limit, runIsRtl, c, x, top,
+ y, bottom, null, needWidth, PREP_NEEDED);
+ }
+
+ /**
+ * Measures a unidirectional (but possibly multi-styled) run of text.
+ *
+ * @param runIndex the run index
+ * @param start the line-relative start of the run
+ * @param offset the offset to measure to, between start and limit inclusive
+ * @param limit the line-relative limit of the run
+ * @param runIsRtl true if the run is right-to-left
+ * @param fmi receives metrics information about the requested
+ * run, can be null.
+ * @return the signed width from the start of the run to the leading edge
+ * of the character at offset, based on the run (not paragraph) direction
+ */
+ private float measureRun(int runIndex, int start,
+ int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) {
+ return handleRun(runIndex, start, offset, limit, runIsRtl, null,
+ 0, 0, 0, 0, fmi, true, PREP_NEEDED);
+ }
+
+ /**
+ * Prepares a run for measurement or rendering. This ensures that any
+ * required shaping of the text in the run has been performed so that
+ * measurements reflect the shaped text.
+ *
+ * @param runIndex the run index
+ * @param start the line-relative start of the run
+ * @param limit the line-relative limit of the run
+ * @param runIsRtl true if the run is right-to-left
+ */
+ private void prepRun(int runIndex, int start, int limit,
+ boolean runIsRtl) {
+ handleRun(runIndex, start, limit, limit, runIsRtl, null, 0, 0, 0,
+ 0, null, false, PREP_ONLY);
+ }
+
+ /**
+ * Walk the cursor through this line, skipping conjuncts and
+ * zero-width characters.
+ *
+ * <p>This function cannot properly walk the cursor off the ends of the line
+ * since it does not know about any shaping on the previous/following line
+ * that might affect the cursor position. Callers must either avoid these
+ * situations or handle the result specially.
+ *
+ * <p>The paint is required because the region around the cursor might not
+ * have been formatted yet, and the valid positions can depend on the glyphs
+ * used to render the text, which in turn depends on the paint.
+ *
+ * @param paint the base paint of the line
+ * @param cursor the starting position of the cursor, between 0 and the
+ * length of the line, inclusive
+ * @param toLeft true if the caret is moving to the left.
+ * @return the new offset. If it is less than 0 or greater than the length
+ * of the line, the previous/following line should be examined to get the
+ * actual offset.
+ */
+ int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
+ // 1) The caret marks the leading edge of a character. The character
+ // logically before it might be on a different level, and the active caret
+ // position is on the character at the lower level. If that character
+ // was the previous character, the caret is on its trailing edge.
+ // 2) Take this character/edge and move it in the indicated direction.
+ // This gives you a new character and a new edge.
+ // 3) This position is between two visually adjacent characters. One of
+ // these might be at a lower level. The active position is on the
+ // character at the lower level.
+ // 4) If the active position is on the trailing edge of the character,
+ // the new caret position is the following logical character, else it
+ // is the character.
+
+ int lineStart = 0;
+ int lineEnd = mLen;
+ boolean paraIsRtl = mDir == -1;
+ int[] runs = mDirections.mDirections;
+
+ int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
+ boolean trailing = false;
+
+ if (cursor == lineStart) {
+ runIndex = -2;
+ } else if (cursor == lineEnd) {
+ runIndex = runs.length;
+ } else {
+ // First, get information about the run containing the character with
+ // the active caret.
+ for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
+ runStart = lineStart + runs[runIndex];
+ if (cursor >= runStart) {
+ runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
+ if (runLimit > lineEnd) {
+ runLimit = lineEnd;
+ }
+ if (cursor < runLimit) {
+ runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
+ Layout.RUN_LEVEL_MASK;
+ if (cursor == runStart) {
+ // The caret is on a run boundary, see if we should
+ // use the position on the trailing edge of the previous
+ // logical character instead.
+ int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
+ int pos = cursor - 1;
+ for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
+ prevRunStart = lineStart + runs[prevRunIndex];
+ if (pos >= prevRunStart) {
+ prevRunLimit = prevRunStart +
+ (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
+ if (prevRunLimit > lineEnd) {
+ prevRunLimit = lineEnd;
+ }
+ if (pos < prevRunLimit) {
+ prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
+ & Layout.RUN_LEVEL_MASK;
+ if (prevRunLevel < runLevel) {
+ // Start from logically previous character.
+ runIndex = prevRunIndex;
+ runLevel = prevRunLevel;
+ runStart = prevRunStart;
+ runLimit = prevRunLimit;
+ trailing = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ // caret might be == lineEnd. This is generally a space or paragraph
+ // separator and has an associated run, but might be the end of
+ // text, in which case it doesn't. If that happens, we ran off the
+ // end of the run list, and runIndex == runs.length. In this case,
+ // we are at a run boundary so we skip the below test.
+ if (runIndex != runs.length) {
+ boolean runIsRtl = (runLevel & 0x1) != 0;
+ boolean advance = toLeft == runIsRtl;
+ if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
+ // Moving within or into the run, so we can move logically.
+ prepRun(runIndex, runStart, runLimit, runIsRtl);
+ newCaret = getOffsetBeforeAfter(runIndex, cursor, advance);
+ // If the new position is internal to the run, we're at the strong
+ // position already so we're finished.
+ if (newCaret != (advance ? runLimit : runStart)) {
+ return newCaret;
+ }
+ }
+ }
+ }
+
+ // If newCaret is -1, we're starting at a run boundary and crossing
+ // into another run. Otherwise we've arrived at a run boundary, and
+ // need to figure out which character to attach to. Note we might
+ // need to run this twice, if we cross a run boundary and end up at
+ // another run boundary.
+ while (true) {
+ boolean advance = toLeft == paraIsRtl;
+ int otherRunIndex = runIndex + (advance ? 2 : -2);
+ if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
+ int otherRunStart = lineStart + runs[otherRunIndex];
+ int otherRunLimit = otherRunStart +
+ (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
+ if (otherRunLimit > lineEnd) {
+ otherRunLimit = lineEnd;
+ }
+ int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
+ Layout.RUN_LEVEL_MASK;
+ boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
+
+ advance = toLeft == otherRunIsRtl;
+ if (newCaret == -1) {
+ prepRun(otherRunIndex, otherRunStart, otherRunLimit,
+ otherRunIsRtl);
+ newCaret = getOffsetBeforeAfter(otherRunIndex,
+ advance ? otherRunStart : otherRunLimit, advance);
+ if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
+ // Crossed and ended up at a new boundary,
+ // repeat a second and final time.
+ runIndex = otherRunIndex;
+ runLevel = otherRunLevel;
+ continue;
+ }
+ break;
+ }
+
+ // The new caret is at a boundary.
+ if (otherRunLevel < runLevel) {
+ // The strong character is in the other run.
+ newCaret = advance ? otherRunStart : otherRunLimit;
+ }
+ break;
+ }
+
+ if (newCaret == -1) {
+ // We're walking off the end of the line. The paragraph
+ // level is always equal to or lower than any internal level, so
+ // the boundaries get the strong caret.
+ newCaret = getOffsetBeforeAfter(-1, cursor, advance);
+ break;
+ }
+
+ // Else we've arrived at the end of the line. That's a strong position.
+ // We might have arrived here by crossing over a run with no internal
+ // breaks and dropping out of the above loop before advancing one final
+ // time, so reset the caret.
+ // Note, we use '<=' below to handle a situation where the only run
+ // on the line is a counter-directional run. If we're not advancing,
+ // we can end up at the 'lineEnd' position but the caret we want is at
+ // the lineStart.
+ if (newCaret <= lineEnd) {
+ newCaret = advance ? lineEnd : lineStart;
+ }
+ break;
+ }
+
+ return newCaret;
+ }
+
+ /**
+ * Returns the next valid offset within this directional run, skipping
+ * conjuncts and zero-width characters. This should not be called to walk
+ * off the end of the run.
+ *
+ * @param runIndex the run index
+ * @param offset the offset
+ * @param after true if the new offset should logically follow the provided
+ * offset
+ * @return the new offset
+ */
+ private int getOffsetBeforeAfter(int runIndex, int offset, boolean after) {
+ // XXX note currently there is no special handling of zero-width
+ // combining marks, since the only analysis involves mock shaping.
+
+ boolean offEnd = offset == (after ? mLen : 0);
+ if (runIndex >= 0 && !offEnd && mCharsValid) {
+ char[] chars = mChars;
+ if (after) {
+ int cp = Character.codePointAt(chars, offset, mLen);
+ if (cp >= 0x10000) {
+ ++offset;
+ }
+ while (++offset < mLen && chars[offset] == '\ufeff'){}
+ } else {
+ while (--offset >= 0 && chars[offset] == '\ufeff'){}
+ int cp = Character.codePointBefore(chars, offset + 1);
+ if (cp >= 0x10000) {
+ --offset;
+ }
+ }
+ return offset;
+ }
+
+ if (after) {
+ return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
+ }
+ return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
+ }
+
+ /**
+ * Utility function for measuring and rendering text. The text must
+ * not include a tab or emoji.
+ *
+ * @param wp the working paint
+ * @param start the start of the text
+ * @param limit the limit of the text
+ * @param runIsRtl true if the run is right-to-left
+ * @param c the canvas, can be null if rendering is not needed
+ * @param x the edge of the run closest to the leading margin
+ * @param top the top of the line
+ * @param y the baseline
+ * @param bottom the bottom of the line
+ * @param fmi receives metrics information, can be null
+ * @param needWidth true if the width of the run is needed
+ * @return the signed width of the run based on the run direction; only
+ * valid if needWidth is true
+ */
+ private float handleText(TextPaint wp, int start, int limit,
+ boolean runIsRtl, Canvas c, float x, int top, int y, int bottom,
+ FontMetricsInt fmi, boolean needWidth) {
+
+ float ret = 0;
+
+ int runLen = limit - start;
+ if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
+ if (mCharsValid) {
+ ret = wp.measureText(mChars, start, runLen);
+ } else {
+ ret = wp.measureText(mText, mStart + start,
+ mStart + start + runLen);
+ }
+ }
+
+ if (fmi != null) {
+ wp.getFontMetricsInt(fmi);
+ }
+
+ if (c != null) {
+ if (runIsRtl) {
+ x -= ret;
+ }
+
+ if (wp.bgColor != 0) {
+ int color = wp.getColor();
+ Paint.Style s = wp.getStyle();
+ wp.setColor(wp.bgColor);
+ wp.setStyle(Paint.Style.FILL);
+
+ c.drawRect(x, top, x + ret, bottom, wp);
+
+ wp.setStyle(s);
+ wp.setColor(color);
+ }
+
+ drawTextRun(c, wp, start, limit, runIsRtl, x, y + wp.baselineShift);
+ }
+
+ return runIsRtl ? -ret : ret;
+ }
+
+ /**
+ * Utility function for measuring and rendering a replacement.
+ *
+ * @param replacement the replacement
+ * @param wp the work paint
+ * @param runIndex the run index
+ * @param start the start of the run
+ * @param limit the limit of the run
+ * @param runIsRtl true if the run is right-to-left
+ * @param c the canvas, can be null if not rendering
+ * @param x the edge of the replacement closest to the leading margin
+ * @param top the top of the line
+ * @param y the baseline
+ * @param bottom the bottom of the line
+ * @param fmi receives metrics information, can be null
+ * @param needWidth true if the width of the replacement is needed
+ * @param prepFlags one of PREP_NONE, PREP_REQUIRED, or PREP_ONLY
+ * @return the signed width of the run based on the run direction; only
+ * valid if needWidth is true
+ */
+ private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
+ int runIndex, int start, int limit, boolean runIsRtl, Canvas c,
+ float x, int top, int y, int bottom, FontMetricsInt fmi,
+ boolean needWidth, int prepFlags) {
+
+ float ret = 0;
+
+ // Preparation replaces the first character of the series with the
+ // object-replacement character and the remainder with zero width
+ // non-break space aka BOM. Cursor movement code skips over the BOMs
+ // so that the replacement character is the only character 'seen'.
+ if (prepFlags != PREP_NONE && limit > start &&
+ (runIndex > mPreppedIndex ||
+ (runIndex == mPreppedIndex && start >= mPreppedLimit))) {
+ char[] chars = mChars;
+ chars[start] = '\ufffc';
+ for (int i = start + 1; i < limit; ++i) {
+ chars[i] = '\ufeff'; // used as ZWNBS, marks positions to skip
+ }
+ mPreppedIndex = runIndex;
+ mPreppedLimit = limit;
+ }
+
+ if (prepFlags != PREP_ONLY) {
+ int textStart = mStart + start;
+ int textLimit = mStart + limit;
+
+ if (needWidth || (c != null && runIsRtl)) {
+ ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
+ }
+
+ if (c != null) {
+ if (runIsRtl) {
+ x -= ret;
+ }
+ replacement.draw(c, mText, textStart, textLimit,
+ x, top, y, bottom, wp);
+ }
+ }
+
+ return runIsRtl ? -ret : ret;
+ }
+
+ /**
+ * Utility function for handling a unidirectional run. The run must not
+ * contain tabs or emoji but can contain styles.
+ *
+ * @param p the base paint
+ * @param runIndex the run index
+ * @param start the line-relative start of the run
+ * @param offset the offset to measure to, between start and limit inclusive
+ * @param limit the limit of the run
+ * @param runIsRtl true if the run is right-to-left
+ * @param c the canvas, can be null
+ * @param x the end of the run closest to the leading margin
+ * @param top the top of the line
+ * @param y the baseline
+ * @param bottom the bottom of the line
+ * @param fmi receives metrics information, can be null
+ * @param needWidth true if the width is required
+ * @param prepFlags one of PREP_NONE, PREP_REQUIRED, or PREP_ONLY
+ * @return the signed width of the run based on the run direction; only
+ * valid if needWidth is true
+ */
+ private float handleRun(int runIndex, int start, int offset,
+ int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
+ int bottom, FontMetricsInt fmi, boolean needWidth, int prepFlags) {
+
+ // Shaping needs to take into account context up to metric boundaries,
+ // but rendering needs to take into account character style boundaries.
+ // So we iterate through metric runs, shape using the initial
+ // paint (the same typeface is used up to the next metric boundary),
+ // then within each metric run iterate through character style runs.
+ float ox = x;
+ for (int i = start, inext; i < offset; i = inext) {
+ TextPaint wp = mWorkPaint;
+ wp.set(mPaint);
+
+ int mnext;
+ if (mSpanned == null) {
+ inext = limit;
+ mnext = offset;
+ } else {
+ inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
+ MetricAffectingSpan.class) - mStart;
+
+ mnext = inext < offset ? inext : offset;
+ MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
+ mStart + mnext, MetricAffectingSpan.class);
+
+ if (spans.length > 0) {
+ ReplacementSpan replacement = null;
+ for (int j = 0; j < spans.length; j++) {
+ MetricAffectingSpan span = spans[j];
+ if (span instanceof ReplacementSpan) {
+ replacement = (ReplacementSpan)span;
+ } else {
+ span.updateDrawState(wp); // XXX or measureState?
+ }
+ }
+
+ if (replacement != null) {
+ x += handleReplacement(replacement, wp, runIndex, i,
+ mnext, runIsRtl, c, x, top, y, bottom, fmi,
+ needWidth || mnext < offset, prepFlags);
+ continue;
+ }
+ }
+ }
+
+ if (prepFlags != PREP_NONE) {
+ handlePrep(wp, runIndex, i, inext, runIsRtl);
+ }
+
+ if (prepFlags != PREP_ONLY) {
+ if (mSpanned == null || c == null) {
+ x += handleText(wp, i, mnext, runIsRtl, c, x, top,
+ y, bottom, fmi, needWidth || mnext < offset);
+ } else {
+ for (int j = i, jnext; j < mnext; j = jnext) {
+ jnext = mSpanned.nextSpanTransition(mStart + j,
+ mStart + mnext, CharacterStyle.class) - mStart;
+
+ CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
+ mStart + jnext, CharacterStyle.class);
+
+ wp.set(mPaint);
+ for (int k = 0; k < spans.length; k++) {
+ CharacterStyle span = spans[k];
+ span.updateDrawState(wp);
+ }
+
+ x += handleText(wp, j, jnext, runIsRtl, c, x,
+ top, y, bottom, fmi, needWidth || jnext < offset);
+ }
+ }
+ }
+ }
+
+ return x - ox;
+ }
+
+ private static final int PREP_NONE = 0;
+ private static final int PREP_NEEDED = 1;
+ private static final int PREP_ONLY = 2;
+
+ /**
+ * Prepares text for measuring or rendering.
+ *
+ * @param paint the paint used to shape the text
+ * @param runIndex the run index
+ * @param start the start of the text to prepare
+ * @param limit the limit of the text to prepare
+ * @param runIsRtl true if the run is right-to-left
+ */
+ private void handlePrep(TextPaint paint, int runIndex, int start, int limit,
+ boolean runIsRtl) {
+
+ // The current implementation 'prepares' text by manipulating the
+ // character array. In order to keep track of what ranges have
+ // already been prepared, it uses the runIndex and the limit of
+ // the prepared text within that run. This index is required
+ // since operations that prepare the text always proceed in visual
+ // order and the limit itself does not let us know which runs have
+ // been processed and which have not.
+ //
+ // This bookkeeping is an attempt to let us process a line partially,
+ // for example, by only shaping up to the cursor position. This may
+ // not make sense if we can reuse the line, say by caching repeated
+ // accesses to the same line for both measuring and drawing, since in
+ // those cases we'd always prepare the entire line. At the
+ // opposite extreme, we might shape and then immediately discard only
+ // the run of text we're working with at the moment, instead of retaining
+ // the results of shaping (as the chars array is). In this case as well
+ // we would not need to do the index/limit bookkeeping.
+ //
+ // Technically, the only reason for bookkeeping is so that we don't
+ // re-mirror already-mirrored glyphs, since the shaping and object
+ // replacement operations will not change already-processed text.
+
+ if (runIndex > mPreppedIndex ||
+ (runIndex == mPreppedIndex && start >= mPreppedLimit)) {
+ if (runIsRtl) {
+ int runLen = limit - start;
+ AndroidCharacter.mirror(mChars, start, runLen);
+ ArabicShaping.SHAPER.shape(mChars, start, runLen);
+
+ // Note: tweaked MockShaper to put '\ufeff' in place of
+ // alef when it forms lam-alef ligatures, so no extra
+ // processing is necessary here.
+ }
+ mPreppedIndex = runIndex;
+ mPreppedLimit = limit;
+ }
+ }
+
+ /**
+ * Render a text run with the set-up paint.
+ *
+ * @param c the canvas
+ * @param wp the paint used to render the text
+ * @param start the run start
+ * @param limit the run limit
+ * @param runIsRtl true if the run is right-to-left
+ * @param x the x position of the left edge of the run
+ * @param y the baseline of the run
+ */
+ private void drawTextRun(Canvas c, TextPaint wp, int start, int limit,
+ boolean runIsRtl, float x, int y) {
+
+ // Since currently skia only renders text left-to-right, we need to
+ // put the shaped characters into visual order before rendering.
+ // Since we might want to re-render the line again, we swap them
+ // back when we're done. If we left them swapped, measurement
+ // would be broken since it expects the characters in logical order.
+ if (runIsRtl) {
+ swapRun(start, limit);
+ }
+ if (mCharsValid) {
+ c.drawText(mChars, start, limit - start, x, y, wp);
+ } else {
+ c.drawText(mText, mStart + start, mStart + limit, x, y, wp);
+ }
+ if (runIsRtl) {
+ swapRun(start, limit);
+ }
+ }
+
+ /**
+ * Reverses the order of characters in the chars array between start and
+ * limit, used by drawTextRun.
+ * @param start the start of the run to reverse
+ * @param limit the limit of the run to reverse
+ */
+ private void swapRun(int start, int limit) {
+ // First we swap all the characters one for one, then we
+ // do another pass looking for surrogate pairs and swapping them
+ // back into their logical order.
+ char[] chars = mChars;
+ for (int s = start, e = limit - 1; s < e; ++s, --e) {
+ char ch = chars[s]; chars[s] = chars[e]; chars[e] = ch;
+ }
+
+ for (int s = start, e = limit - 1; s < e; ++s) {
+ char c1 = chars[s];
+ if (c1 >= 0xdc00 && c1 < 0xe000) {
+ char c2 = chars[s+1];
+ if (c2 >= 0xd800 && c2 < 0xdc00) {
+ chars[s++] = c2;
+ chars[s] = c1;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the ascent of the text at start. This is used for scaling
+ * emoji.
+ *
+ * @param pos the line-relative position
+ * @return the ascent of the text at start
+ */
+ float ascent(int pos) {
+ if (mSpanned == null) {
+ return mPaint.ascent();
+ }
+
+ pos += mStart;
+ MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1,
+ MetricAffectingSpan.class);
+ if (spans.length == 0) {
+ return mPaint.ascent();
+ }
+
+ TextPaint wp = mWorkPaint;
+ wp.set(mPaint);
+ for (MetricAffectingSpan span : spans) {
+ span.updateMeasureState(wp);
+ }
+ return wp.ascent();
+ }
+
+ /**
+ * Returns the next tab position.
+ *
+ * @param h the (unsigned) offset from the leading margin
+ * @return the (unsigned) tab position after this offset
+ */
+ float nextTab(float h) {
+ float nh = Float.MAX_VALUE;
+ boolean alltabs = false;
+
+ if (mHasTabs && mTabs != null) {
+ TabStopSpan[] tabs = mTabs;
+ for (int i = 0; i < tabs.length && tabs[i] != null; ++i) {
+ int where = tabs[i].getTabStop();
+ if (where < nh && where > h) {
+ nh = where;
+ }
+ }
+ if (nh != Float.MAX_VALUE) {
+ return nh;
+ }
+ }
+
+ return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
+ }
+
+ private static final int TAB_INCREMENT = 20;
+}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 9589bf3..2d6c7b6 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -17,12 +17,11 @@
package android.text;
import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
-import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.method.TextKeyListener.Capitalize;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
@@ -45,10 +44,8 @@
import android.text.style.UnderlineSpan;
import android.util.Printer;
-import com.android.internal.util.ArrayUtils;
-
-import java.util.regex.Pattern;
import java.util.Iterator;
+import java.util.regex.Pattern;
public class TextUtils {
private TextUtils() { /* cannot be instantiated */ }
@@ -983,7 +980,7 @@
/**
* Returns the original text if it fits in the specified width
* given the properties of the specified Paint,
- * or, if it does not fit, a copy with ellipsis character added
+ * or, if it does not fit, a copy with ellipsis character added
* at the specified edge or center.
* If <code>preserveLength</code> is specified, the returned copy
* will be padded with zero-width spaces to preserve the original
@@ -992,7 +989,7 @@
* report the start and end of the ellipsized range.
*/
public static CharSequence ellipsize(CharSequence text,
- TextPaint p,
+ TextPaint paint,
float avail, TruncateAt where,
boolean preserveLength,
EllipsizeCallback callback) {
@@ -1003,13 +1000,12 @@
int len = text.length();
- // Use Paint.breakText() for the non-Spanned case to avoid having
- // to allocate memory and accumulate the character widths ourselves.
+ MeasuredText mt = MeasuredText.obtain();
+ try {
+ float width = setPara(mt, paint, text, 0, text.length(),
+ Layout.DIR_REQUEST_DEFAULT_LTR);
- if (!(text instanceof Spanned)) {
- float wid = p.measureText(text, 0, len);
-
- if (wid <= avail) {
+ if (width <= avail) {
if (callback != null) {
callback.ellipsized(0, 0);
}
@@ -1017,250 +1013,69 @@
return text;
}
- float ellipsiswid = p.measureText(sEllipsis);
+ // XXX assumes ellipsis string does not require shaping and
+ // is unaffected by style
+ float ellipsiswid = paint.measureText(sEllipsis);
+ avail -= ellipsiswid;
- if (ellipsiswid > avail) {
- if (callback != null) {
- callback.ellipsized(0, len);
- }
-
- if (preserveLength) {
- char[] buf = obtain(len);
- for (int i = 0; i < len; i++) {
- buf[i] = '\uFEFF';
- }
- String ret = new String(buf, 0, len);
- recycle(buf);
- return ret;
- } else {
- return "";
- }
- }
-
- if (where == TruncateAt.START) {
- int fit = p.breakText(text, 0, len, false,
- avail - ellipsiswid, null);
-
- if (callback != null) {
- callback.ellipsized(0, len - fit);
- }
-
- if (preserveLength) {
- return blank(text, 0, len - fit);
- } else {
- return sEllipsis + text.toString().substring(len - fit, len);
- }
+ int left = 0;
+ int right = len;
+ if (avail < 0) {
+ // it all goes
+ } else if (where == TruncateAt.START) {
+ right = len - mt.breakText(0, len, false, avail);
} else if (where == TruncateAt.END) {
- int fit = p.breakText(text, 0, len, true,
- avail - ellipsiswid, null);
-
- if (callback != null) {
- callback.ellipsized(fit, len);
- }
-
- if (preserveLength) {
- return blank(text, fit, len);
- } else {
- return text.toString().substring(0, fit) + sEllipsis;
- }
- } else /* where == TruncateAt.MIDDLE */ {
- int right = p.breakText(text, 0, len, false,
- (avail - ellipsiswid) / 2, null);
- float used = p.measureText(text, len - right, len);
- int left = p.breakText(text, 0, len - right, true,
- avail - ellipsiswid - used, null);
-
- if (callback != null) {
- callback.ellipsized(left, len - right);
- }
-
- if (preserveLength) {
- return blank(text, left, len - right);
- } else {
- String s = text.toString();
- return s.substring(0, left) + sEllipsis +
- s.substring(len - right, len);
- }
- }
- }
-
- // But do the Spanned cases by hand, because it's such a pain
- // to iterate the span transitions backwards and getTextWidths()
- // will give us the information we need.
-
- // getTextWidths() always writes into the start of the array,
- // so measure each span into the first half and then copy the
- // results into the second half to use later.
-
- float[] wid = new float[len * 2];
- TextPaint temppaint = new TextPaint();
- Spanned sp = (Spanned) text;
-
- int next;
- for (int i = 0; i < len; i = next) {
- next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class);
-
- Styled.getTextWidths(p, temppaint, sp, i, next, wid, null);
- System.arraycopy(wid, 0, wid, len + i, next - i);
- }
-
- float sum = 0;
- for (int i = 0; i < len; i++) {
- sum += wid[len + i];
- }
-
- if (sum <= avail) {
- if (callback != null) {
- callback.ellipsized(0, 0);
- }
-
- return text;
- }
-
- float ellipsiswid = p.measureText(sEllipsis);
-
- if (ellipsiswid > avail) {
- if (callback != null) {
- callback.ellipsized(0, len);
- }
-
- if (preserveLength) {
- char[] buf = obtain(len);
- for (int i = 0; i < len; i++) {
- buf[i] = '\uFEFF';
- }
- SpannableString ss = new SpannableString(new String(buf, 0, len));
- recycle(buf);
- copySpansFrom(sp, 0, len, Object.class, ss, 0);
- return ss;
+ left = mt.breakText(0, len, true, avail);
} else {
- return "";
- }
- }
-
- if (where == TruncateAt.START) {
- sum = 0;
- int i;
-
- for (i = len; i >= 0; i--) {
- float w = wid[len + i - 1];
-
- if (w + sum + ellipsiswid > avail) {
- break;
- }
-
- sum += w;
- }
-
- if (callback != null) {
- callback.ellipsized(0, i);
- }
-
- if (preserveLength) {
- SpannableString ss = new SpannableString(blank(text, 0, i));
- copySpansFrom(sp, 0, len, Object.class, ss, 0);
- return ss;
- } else {
- SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
- out.insert(1, text, i, len);
-
- return out;
- }
- } else if (where == TruncateAt.END) {
- sum = 0;
- int i;
-
- for (i = 0; i < len; i++) {
- float w = wid[len + i];
-
- if (w + sum + ellipsiswid > avail) {
- break;
- }
-
- sum += w;
- }
-
- if (callback != null) {
- callback.ellipsized(i, len);
- }
-
- if (preserveLength) {
- SpannableString ss = new SpannableString(blank(text, i, len));
- copySpansFrom(sp, 0, len, Object.class, ss, 0);
- return ss;
- } else {
- SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
- out.insert(0, text, 0, i);
-
- return out;
- }
- } else /* where = TruncateAt.MIDDLE */ {
- float lsum = 0, rsum = 0;
- int left = 0, right = len;
-
- float ravail = (avail - ellipsiswid) / 2;
- for (right = len; right >= 0; right--) {
- float w = wid[len + right - 1];
-
- if (w + rsum > ravail) {
- break;
- }
-
- rsum += w;
- }
-
- float lavail = avail - ellipsiswid - rsum;
- for (left = 0; left < right; left++) {
- float w = wid[len + left];
-
- if (w + lsum > lavail) {
- break;
- }
-
- lsum += w;
+ right = len - mt.breakText(0, len, false, avail / 2);
+ avail -= mt.measure(right, len);
+ left = mt.breakText(0, right, true, avail);
}
if (callback != null) {
callback.ellipsized(left, right);
}
+ char[] buf = mt.mChars;
+ Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+
+ int remaining = len - (right - left);
if (preserveLength) {
- SpannableString ss = new SpannableString(blank(text, left, right));
+ if (remaining > 0) { // else eliminate the ellipsis too
+ buf[left++] = '\u2026';
+ }
+ for (int i = left; i < right; i++) {
+ buf[i] = '\uFEFF';
+ }
+ String s = new String(buf, 0, len);
+ if (sp == null) {
+ return s;
+ }
+ SpannableString ss = new SpannableString(s);
copySpansFrom(sp, 0, len, Object.class, ss, 0);
return ss;
- } else {
- SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
- out.insert(0, text, 0, left);
- out.insert(out.length(), text, right, len);
-
- return out;
}
- }
- }
- private static String blank(CharSequence source, int start, int end) {
- int len = source.length();
- char[] buf = obtain(len);
-
- if (start != 0) {
- getChars(source, 0, start, buf, 0);
- }
- if (end != len) {
- getChars(source, end, len, buf, end);
- }
-
- if (start != end) {
- buf[start] = '\u2026';
-
- for (int i = start + 1; i < end; i++) {
- buf[i] = '\uFEFF';
+ if (remaining == 0) {
+ return "";
}
- }
-
- String ret = new String(buf, 0, len);
- recycle(buf);
- return ret;
+ if (sp == null) {
+ StringBuilder sb = new StringBuilder(remaining + sEllipsis.length());
+ sb.append(buf, 0, left);
+ sb.append(sEllipsis);
+ sb.append(buf, right, len - right);
+ return sb.toString();
+ }
+
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+ ssb.append(text, 0, left);
+ ssb.append(sEllipsis);
+ ssb.append(text, right, len);
+ return ssb;
+ } finally {
+ MeasuredText.recycle(mt);
+ }
}
/**
@@ -1278,80 +1093,121 @@
TextPaint p, float avail,
String oneMore,
String more) {
- int len = text.length();
- char[] buf = new char[len];
- TextUtils.getChars(text, 0, len, buf, 0);
- int commaCount = 0;
- for (int i = 0; i < len; i++) {
- if (buf[i] == ',') {
- commaCount++;
- }
- }
-
- float[] wid;
-
- if (text instanceof Spanned) {
- Spanned sp = (Spanned) text;
- TextPaint temppaint = new TextPaint();
- wid = new float[len * 2];
-
- int next;
- for (int i = 0; i < len; i = next) {
- next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class);
-
- Styled.getTextWidths(p, temppaint, sp, i, next, wid, null);
- System.arraycopy(wid, 0, wid, len + i, next - i);
+ MeasuredText mt = MeasuredText.obtain();
+ try {
+ int len = text.length();
+ float width = setPara(mt, p, text, 0, len, Layout.DIR_REQUEST_DEFAULT_LTR);
+ if (width <= avail) {
+ return text;
}
- System.arraycopy(wid, len, wid, 0, len);
- } else {
- wid = new float[len];
- p.getTextWidths(text, 0, len, wid);
- }
+ char[] buf = mt.mChars;
- int ok = 0;
- int okRemaining = commaCount + 1;
- String okFormat = "";
-
- int w = 0;
- int count = 0;
-
- for (int i = 0; i < len; i++) {
- w += wid[i];
-
- if (buf[i] == ',') {
- count++;
-
- int remaining = commaCount - count + 1;
- float moreWid;
- String format;
-
- if (remaining == 1) {
- format = " " + oneMore;
- } else {
- format = " " + String.format(more, remaining);
- }
-
- moreWid = p.measureText(format);
-
- if (w + moreWid <= avail) {
- ok = i + 1;
- okRemaining = remaining;
- okFormat = format;
+ int commaCount = 0;
+ for (int i = 0; i < len; i++) {
+ if (buf[i] == ',') {
+ commaCount++;
}
}
- }
- if (w <= avail) {
- return text;
- } else {
+ int remaining = commaCount + 1;
+
+ int ok = 0;
+ int okRemaining = remaining;
+ String okFormat = "";
+
+ int w = 0;
+ int count = 0;
+ float[] widths = mt.mWidths;
+
+ int request = mt.mDir == 1 ? Layout.DIR_REQUEST_LTR :
+ Layout.DIR_REQUEST_RTL;
+
+ MeasuredText tempMt = MeasuredText.obtain();
+ for (int i = 0; i < len; i++) {
+ w += widths[i];
+
+ if (buf[i] == ',') {
+ count++;
+
+ String format;
+ // XXX should not insert spaces, should be part of string
+ // XXX should use plural rules and not assume English plurals
+ if (--remaining == 1) {
+ format = " " + oneMore;
+ } else {
+ format = " " + String.format(more, remaining);
+ }
+
+ // XXX this is probably ok, but need to look at it more
+ tempMt.setPara(format, 0, format.length(), request);
+ float moreWid = mt.addStyleRun(p, mt.mLen, null);
+
+ if (w + moreWid <= avail) {
+ ok = i + 1;
+ okRemaining = remaining;
+ okFormat = format;
+ }
+ }
+ }
+ MeasuredText.recycle(tempMt);
+
SpannableStringBuilder out = new SpannableStringBuilder(okFormat);
out.insert(0, text, 0, ok);
return out;
+ } finally {
+ MeasuredText.recycle(mt);
}
}
+ private static float setPara(MeasuredText mt, TextPaint paint,
+ CharSequence text, int start, int end, int bidiRequest) {
+
+ mt.setPara(text, start, end, bidiRequest);
+
+ float width;
+ Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+ int len = end - start;
+ if (sp == null) {
+ width = mt.addStyleRun(paint, len, null);
+ } else {
+ width = 0;
+ int spanEnd;
+ for (int spanStart = 0; spanStart < len; spanStart = spanEnd) {
+ spanEnd = sp.nextSpanTransition(spanStart, len,
+ MetricAffectingSpan.class);
+ MetricAffectingSpan[] spans = sp.getSpans(
+ spanStart, spanEnd, MetricAffectingSpan.class);
+ width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null);
+ }
+ }
+
+ return width;
+ }
+
+ private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
+
+ /* package */
+ static boolean doesNotNeedBidi(CharSequence s, int start, int end) {
+ for (int i = start; i < end; i++) {
+ if (s.charAt(i) >= FIRST_RIGHT_TO_LEFT) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /* package */
+ static boolean doesNotNeedBidi(char[] text, int start, int len) {
+ for (int i = start, e = i + len; i < e; i++) {
+ if (text[i] >= FIRST_RIGHT_TO_LEFT) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/* package */ static char[] obtain(int len) {
char[] buf;
@@ -1529,7 +1385,7 @@
*/
public static final int CAP_MODE_CHARACTERS
= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
-
+
/**
* Capitalization mode for {@link #getCapsMode}: capitalize the first
* character of all words. This value is explicitly defined to be the same as
@@ -1537,7 +1393,7 @@
*/
public static final int CAP_MODE_WORDS
= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
-
+
/**
* Capitalization mode for {@link #getCapsMode}: capitalize the first
* character of each sentence. This value is explicitly defined to be the same as
@@ -1545,13 +1401,13 @@
*/
public static final int CAP_MODE_SENTENCES
= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
-
+
/**
* Determine what caps mode should be in effect at the current offset in
* the text. Only the mode bits set in <var>reqModes</var> will be
* checked. Note that the caps mode flags here are explicitly defined
* to match those in {@link InputType}.
- *
+ *
* @param cs The text that should be checked for caps modes.
* @param off Location in the text at which to check.
* @param reqModes The modes to be checked: may be any combination of
@@ -1651,7 +1507,7 @@
return mode;
}
-
+
private static Object sLock = new Object();
private static char[] sTemp = null;
}
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index 2ee6e8a..5cbfd29 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -124,7 +124,7 @@
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ "|[1-9][0-9]|[0-9])))"
+ "(?:\\:\\d{1,5})?)" // plus option port number
- + "(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+ "(?:\\b|$)"); // and finally, a word boundary or end of
// input. This is to stop foo.sure from
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index c22f991..fc61700 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -622,6 +622,7 @@
mPackageName = null;
mContentDescription = null;
mBeforeText = null;
+ mParcelableData = null;
mText.clear();
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 0186270..f406da9 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -94,7 +94,9 @@
public static AccessibilityManager getInstance(Context context) {
synchronized (sInstanceSync) {
if (sInstance == null) {
- sInstance = new AccessibilityManager(context);
+ IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
+ sInstance = new AccessibilityManager(context, service);
}
}
return sInstance;
@@ -104,13 +106,16 @@
* Create an instance.
*
* @param context A {@link Context}.
+ * @param service An interface to the backing service.
+ *
+ * @hide
*/
- private AccessibilityManager(Context context) {
+ public AccessibilityManager(Context context, IAccessibilityManager service) {
mHandler = new MyHandler(context.getMainLooper());
- IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
- mService = IAccessibilityManager.Stub.asInterface(iBinder);
+ mService = service;
+
try {
- mService.addClient(mClient);
+ mIsEnabled = mService.addClient(mClient);
} catch (RemoteException re) {
Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
}
@@ -128,6 +133,18 @@
}
/**
+ * Returns the client interface this instance registers in
+ * the centralized accessibility manager service.
+ *
+ * @return The client.
+ *
+ * @hide
+ */
+ public IAccessibilityManagerClient getClient() {
+ return (IAccessibilityManagerClient) mClient.asBinder();
+ }
+
+ /**
* Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not
* enabled the call is a NOOP.
*
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 32788be..7633569 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -29,7 +29,7 @@
*/
interface IAccessibilityManager {
- void addClient(IAccessibilityManagerClient client);
+ boolean addClient(IAccessibilityManagerClient client);
boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent);
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index b767f11..6958f5c 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -296,13 +296,13 @@
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; en-us)"
+ " AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0"
+ " Safari/530.17";
- private static final String IPHONE_USERAGENT =
+ private static final String IPHONE_USERAGENT =
"Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)"
+ " AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0"
+ " Mobile/7A341 Safari/528.16";
private static Locale sLocale;
private static Object sLockForLocaleSettings;
-
+
/**
* Package constructor to prevent clients from creating a new settings
* instance.
@@ -327,6 +327,8 @@
android.os.Process.myUid()) != PackageManager.PERMISSION_GRANTED;
}
+ private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US";
+
/**
* Looks at sLocale and returns current AcceptLanguage String.
* @return Current AcceptLanguage String.
@@ -336,32 +338,53 @@
synchronized(sLockForLocaleSettings) {
locale = sLocale;
}
- StringBuffer buffer = new StringBuffer();
- final String language = locale.getLanguage();
- if (language != null) {
- buffer.append(language);
- final String country = locale.getCountry();
- if (country != null) {
- buffer.append("-");
- buffer.append(country);
+ StringBuilder buffer = new StringBuilder();
+ addLocaleToHttpAcceptLanguage(buffer, locale);
+
+ if (!Locale.US.equals(locale)) {
+ if (buffer.length() > 0) {
+ buffer.append(", ");
}
- }
- if (!locale.equals(Locale.US)) {
- buffer.append(", ");
- java.util.Locale us = Locale.US;
- if (us.getLanguage() != null) {
- buffer.append(us.getLanguage());
- final String country = us.getCountry();
- if (country != null) {
- buffer.append("-");
- buffer.append(country);
- }
- }
+ buffer.append(ACCEPT_LANG_FOR_US_LOCALE);
}
return buffer.toString();
}
-
+
+ /**
+ * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish,
+ * to new standard.
+ */
+ private static String convertObsoleteLanguageCodeToNew(String langCode) {
+ if (langCode == null) {
+ return null;
+ }
+ if ("iw".equals(langCode)) {
+ // Hebrew
+ return "he";
+ } else if ("in".equals(langCode)) {
+ // Indonesian
+ return "id";
+ } else if ("ji".equals(langCode)) {
+ // Yiddish
+ return "yi";
+ }
+ return langCode;
+ }
+
+ private static void addLocaleToHttpAcceptLanguage(StringBuilder builder,
+ Locale locale) {
+ String language = convertObsoleteLanguageCodeToNew(locale.getLanguage());
+ if (language != null) {
+ builder.append(language);
+ String country = locale.getCountry();
+ if (country != null) {
+ builder.append("-");
+ builder.append(country);
+ }
+ }
+ }
+
/**
* Looks at sLocale and mContext and returns current UserAgent String.
* @return Current UserAgent String.
@@ -379,11 +402,11 @@
} else {
// default to "1.0"
buffer.append("1.0");
- }
+ }
buffer.append("; ");
final String language = locale.getLanguage();
if (language != null) {
- buffer.append(language.toLowerCase());
+ buffer.append(convertObsoleteLanguageCodeToNew(language));
final String country = locale.getCountry();
if (country != null) {
buffer.append("-");
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 19abec1..c809b5a 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -300,6 +300,33 @@
return connection;
}
+ /**
+ * In general, TextView makes a call to InputMethodManager.updateSelection
+ * in onDraw. However, in the general case of WebTextView, we do not draw.
+ * This method is called by WebView.onDraw to take care of the part that
+ * needs to be called.
+ */
+ /* package */ void onDrawSubstitute() {
+ if (!willNotDraw()) {
+ // If the WebTextView is set to draw, such as in the case of a
+ // password, onDraw calls updateSelection(), so this code path is
+ // unnecessary.
+ return;
+ }
+ // This code is copied from TextView.onDraw(). That code does not get
+ // executed, however, because the WebTextView does not draw, allowing
+ // webkit's drawing to show through.
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && imm.isActive(this)) {
+ Spannable sp = (Spannable) getText();
+ int selStart = Selection.getSelectionStart(sp);
+ int selEnd = Selection.getSelectionEnd(sp);
+ int candStart = EditableInputConnection.getComposingSpanStart(sp);
+ int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
+ imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
+ }
+ }
+
@Override
protected void onDraw(Canvas canvas) {
// onDraw should only be called for password fields. If WebTextView is
@@ -360,19 +387,8 @@
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
- if (mInSetTextAndKeepSelection) return;
- // This code is copied from TextView.onDraw(). That code does not get
- // executed, however, because the WebTextView does not draw, allowing
- // webkit's drawing to show through.
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null && imm.isActive(this)) {
- Spannable sp = (Spannable) getText();
- int candStart = EditableInputConnection.getComposingSpanStart(sp);
- int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
- imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
- }
if (!mFromWebKit && !mFromFocusChange && !mFromSetInputType
- && mWebView != null) {
+ && mWebView != null && !mInSetTextAndKeepSelection) {
if (DebugFlags.WEB_TEXT_VIEW) {
Log.v(LOGTAG, "onSelectionChanged selStart=" + selStart
+ " selEnd=" + selEnd);
@@ -667,6 +683,7 @@
} else {
Selection.setSelection(text, selection, selection);
}
+ if (mWebView != null) mWebView.incrementTextGeneration();
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6f4c6ff..76dde76 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -312,6 +312,8 @@
// more key events.
private int mTextGeneration;
+ /* package */ void incrementTextGeneration() { mTextGeneration++; }
+
// Used by WebViewCore to create child views.
/* package */ final ViewManager mViewManager;
@@ -3154,6 +3156,7 @@
if (AUTO_REDRAW_HACK && mAutoRedraw) {
invalidate();
}
+ if (inEditingMode()) mWebTextView.onDrawSubstitute();
mWebViewCore.signalRepaintDone();
}
@@ -3978,6 +3981,14 @@
super.onDetachedFromWindow();
}
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (visibility != View.VISIBLE) {
+ dismissZoomControl();
+ }
+ }
+
/**
* @deprecated WebView no longer needs to implement
* ViewGroup.OnHierarchyChangeListener. This method does nothing now.
@@ -4683,16 +4694,13 @@
ted.mY = contentY;
ted.mMetaState = ev.getMetaState();
ted.mReprocess = mDeferTouchProcess;
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
if (mDeferTouchProcess) {
// still needs to set them for compute deltaX/Y
mLastTouchX = x;
mLastTouchY = y;
- ted.mViewX = x;
- ted.mViewY = y;
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
break;
}
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
if (!inFullScreenMode()) {
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(PREVENT_DEFAULT_TIMEOUT,
@@ -4718,20 +4726,17 @@
// pass the touch events from UI thread to WebCore thread
if (shouldForwardTouchEvent() && mConfirmMove && (firstMove
|| eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
- mLastSentTouchTime = eventTime;
TouchEventData ted = new TouchEventData();
ted.mAction = action;
ted.mX = contentX;
ted.mY = contentY;
ted.mMetaState = ev.getMetaState();
ted.mReprocess = mDeferTouchProcess;
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ mLastSentTouchTime = eventTime;
if (mDeferTouchProcess) {
- ted.mViewX = x;
- ted.mViewY = y;
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
break;
}
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
if (firstMove && !inFullScreenMode()) {
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(PREVENT_DEFAULT_TIMEOUT,
@@ -4762,9 +4767,11 @@
invalidate();
break;
}
+
if (!mConfirmMove) {
break;
}
+
if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES
|| mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
// track mLastTouchTime as we may need to do fling at
@@ -4888,6 +4895,7 @@
break;
}
case MotionEvent.ACTION_UP: {
+ if (!isFocused()) requestFocus();
// pass the touch events from UI thread to WebCore thread
if (shouldForwardTouchEvent()) {
TouchEventData ted = new TouchEventData();
@@ -4896,10 +4904,6 @@
ted.mY = contentY;
ted.mMetaState = ev.getMetaState();
ted.mReprocess = mDeferTouchProcess;
- if (mDeferTouchProcess) {
- ted.mViewX = x;
- ted.mViewY = y;
- }
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
}
mLastTouchUpTime = eventTime;
@@ -4914,10 +4918,6 @@
ted.mY = contentY;
ted.mMetaState = ev.getMetaState();
ted.mReprocess = mDeferTouchProcess;
- if (mDeferTouchProcess) {
- ted.mViewX = x;
- ted.mViewY = y;
- }
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
} else if (mPreventDefault != PREVENT_DEFAULT_YES){
doDoubleTap();
@@ -4939,9 +4939,17 @@
if (mPreventDefault != PREVENT_DEFAULT_YES
&& (computeMaxScrollX() > 0
|| computeMaxScrollY() > 0)) {
- // UI takes control back, cancel WebCore touch
- cancelWebCoreTouchEvent(contentX, contentY,
- true);
+ // If the user has performed a very quick touch
+ // sequence it is possible that we may get here
+ // before WebCore has had a chance to process the events.
+ // In this case, any call to preventDefault in the
+ // JS touch handler will not have been executed yet.
+ // Hence we will see both the UI (now) and WebCore
+ // (when context switches) handling the event,
+ // regardless of whether the web developer actually
+ // doeses preventDefault in their touch handler. This
+ // is the nature of our asynchronous touch model.
+
// we will not rewrite drag code here, but we
// will try fling if it applies.
WebViewCore.reducePriority();
@@ -6201,10 +6209,6 @@
// simplicity for now, we don't set it.
ted.mMetaState = 0;
ted.mReprocess = mDeferTouchProcess;
- if (mDeferTouchProcess) {
- ted.mViewX = mLastTouchX;
- ted.mViewY = mLastTouchY;
- }
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
} else if (mPreventDefault != PREVENT_DEFAULT_YES) {
mTouchMode = TOUCH_DONE_MODE;
@@ -6385,14 +6389,8 @@
break;
case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID:
displaySoftKeyboard(true);
- updateTextSelectionFromMessage(msg.arg1, msg.arg2,
- (WebViewCore.TextSelectionData) msg.obj);
- break;
+ // fall through to UPDATE_TEXT_SELECTION_MSG_ID
case UPDATE_TEXT_SELECTION_MSG_ID:
- // If no textfield was in focus, and the user touched one,
- // causing it to send this message, then WebTextView has not
- // been set up yet. Rebuild it so it can set its selection.
- rebuildWebTextView();
updateTextSelectionFromMessage(msg.arg1, msg.arg2,
(WebViewCore.TextSelectionData) msg.obj);
break;
@@ -6501,27 +6499,31 @@
TouchEventData ted = (TouchEventData) msg.obj;
switch (ted.mAction) {
case MotionEvent.ACTION_DOWN:
- mLastDeferTouchX = ted.mViewX;
- mLastDeferTouchY = ted.mViewY;
+ mLastDeferTouchX = contentToViewX(ted.mX)
+ - mScrollX;
+ mLastDeferTouchY = contentToViewY(ted.mY)
+ - mScrollY;
mDeferTouchMode = TOUCH_INIT_MODE;
break;
case MotionEvent.ACTION_MOVE: {
// no snapping in defer process
+ int x = contentToViewX(ted.mX) - mScrollX;
+ int y = contentToViewY(ted.mY) - mScrollY;
if (mDeferTouchMode != TOUCH_DRAG_MODE) {
mDeferTouchMode = TOUCH_DRAG_MODE;
- mLastDeferTouchX = ted.mViewX;
- mLastDeferTouchY = ted.mViewY;
+ mLastDeferTouchX = x;
+ mLastDeferTouchY = y;
startDrag();
}
int deltaX = pinLocX((int) (mScrollX
- + mLastDeferTouchX - ted.mViewX))
+ + mLastDeferTouchX - x))
- mScrollX;
int deltaY = pinLocY((int) (mScrollY
- + mLastDeferTouchY - ted.mViewY))
+ + mLastDeferTouchY - y))
- mScrollY;
doDrag(deltaX, deltaY);
- if (deltaX != 0) mLastDeferTouchX = ted.mViewX;
- if (deltaY != 0) mLastDeferTouchY = ted.mViewY;
+ if (deltaX != 0) mLastDeferTouchX = x;
+ if (deltaY != 0) mLastDeferTouchY = y;
break;
}
case MotionEvent.ACTION_UP:
@@ -6535,8 +6537,8 @@
break;
case WebViewCore.ACTION_DOUBLETAP:
// doDoubleTap() needs mLastTouchX/Y as anchor
- mLastTouchX = ted.mViewX;
- mLastTouchY = ted.mViewY;
+ mLastTouchX = contentToViewX(ted.mX) - mScrollX;
+ mLastTouchY = contentToViewY(ted.mY) - mScrollY;
doDoubleTap();
mDeferTouchMode = TOUCH_DONE_MODE;
break;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 4118119..28b5ae6 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -708,8 +708,6 @@
int mY;
int mMetaState;
boolean mReprocess;
- float mViewX;
- float mViewY;
}
static class GeolocationPermissionsData {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 48e7f79..0a39ab6 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -559,6 +559,16 @@
boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
setSmoothScrollbarEnabled(smoothScrollbar);
+
+ final int adapterId = a.getResourceId(R.styleable.AbsListView_adapter, 0);
+ if (adapterId != 0) {
+ final Context c = context;
+ post(new Runnable() {
+ public void run() {
+ setAdapter(Adapters.loadAdapter(c, adapterId));
+ }
+ });
+ }
a.recycle();
}
diff --git a/core/java/android/widget/Adapters.java b/core/java/android/widget/Adapters.java
new file mode 100644
index 0000000..05e501a
--- /dev/null
+++ b/core/java/android/widget/Adapters.java
@@ -0,0 +1,1191 @@
+/*
+ * 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.widget;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.View;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import static com.android.internal.R.*;
+
+/**
+ * <p>This class can be used to load {@link android.widget.Adapter adapters} defined in
+ * XML resources. XML-defined adapters can be used to easily create adapters in your
+ * own application or to pass adapters to other processes.</p>
+ *
+ * <h2>Types of adapters</h2>
+ * <p>Adapters defined using XML resources can only be one of the following supported
+ * types. Arbitrary adapters are not supported to guarantee the safety of the loaded
+ * code when adapters are loaded across packages.</p>
+ * <ul>
+ * <li><a href="#xml-cursor-adapter">Cursor adapter</a>: a cursor adapter can be used
+ * to display the content of a cursor, most often coming from a content provider</li>
+ * </ul>
+ * <p>The complete XML format definition of each adapter type is available below.</p>
+ *
+ * <a name="xml-cursor-adapter" />
+ * <h2>Cursor adapter</h2>
+ * <p>A cursor adapter XML definition starts with the
+ * <a href="#xml-cursor-adapter-tag"><code><cursor-adapter /></code></a>
+ * tag and may contain one or more instances of the following tags:</p>
+ * <ul>
+ * <li><a href="#xml-cursor-adapter-select-tag"><code><select /></code></a></li>
+ * <li><a href="#xml-cursor-adapter-bind-tag"><code><bind /></code></a></li>
+ * </ul>
+ *
+ * <a name="xml-cursor-adapter-tag" />
+ * <h3><cursor-adapter /></h3>
+ * <p>The <code><cursor-adapter /></code> element defines the beginning of the
+ * document and supports the following attributes:</p>
+ * <ul>
+ * <li><code>android:layout</code>: Reference to the XML layout to be inflated for
+ * each item of the adapter. This attribute is mandatory.</li>
+ * <li><code>android:selection</code>: Selection expression, used when the
+ * <code>android:uri</code> attribute is defined or when the adapter is loaded with
+ * {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}.
+ * This attribute is optional.</li>
+ * <li><code>android:sortOrder</code>: Sort expression, used when the
+ * <code>android:uri</code> attribute is defined or when the adapter is loaded with
+ * {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}.
+ * This attribute is optional.</li>
+ * <li><code>android:uri</code>: URI of the content provider to query to retrieve a cursor.
+ * Specifying this attribute is equivalent to calling {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}.
+ * If you call this method, the value of the XML attribute is ignored. This attribute is
+ * optional.</li>
+ * </ul>
+ * <p>In addition, you can specify one or more instances of
+ * <a href="#xml-cursor-adapter-select-tag"><code><select /></code></a> and
+ * <a href="#xml-cursor-adapter-bind-tag"><code><bind /></code></a> tags as children
+ * of <code><cursor-adapter /></code>.</p>
+ *
+ * <a name="xml-cursor-adapter-select-tag" />
+ * <h3><select /></h3>
+ * <p>The <code><select /></code> tag is used to select columns from the cursor
+ * when doing the query. This can be very useful when using transformations in the
+ * <code><bind /></code> elements. It can also be very useful if you are providing
+ * your own <a href="#xml-cursor-adapter-bind-data-types">binder</a> or
+ * <a href="#xml-cursor-adapter-bind-data-types">transformation</a> classes.
+ * <code><select /></code> elements are ignored if you supply the cursor yourself.</p>
+ * <p>The <code><select /></code> supports the following attributes:</p>
+ * <ul>
+ * <li><code>android:column</code>: Name of the column to select in the cursor during the
+ * query operation</li>
+ * </ul>
+ * <p><strong>Note:</strong> The column named <code>_id</code> is always implicitely
+ * selected.</p>
+ *
+ * <a name="xml-cursor-adapter-bind-tag" />
+ * <h3><bind /></h3>
+ * <p>The <code><bind /></code> tag is used to bind a column from the cursor to
+ * a {@link android.view.View}. A column bound using this tag is automatically selected
+ * during the query and a matching
+ * <a href="#xml-cursor-adapter-select-tag"><code><select /></code> tag is therefore
+ * not required.</p>
+ *
+ * <p>Each binding is declared as a one to one matching but
+ * custom binder classes or special
+ * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> can
+ * allow you to bind several columns to a single view. In this case you must use the
+ * <a href="#xml-cursor-adapter-select-tag"><code><select /></code> tag to make
+ * sure any required column is part of the query.</p>
+ *
+ * <p>The <code><bind /></code> tag supports the following attributes:</p>
+ * <ul>
+ * <li><code>android:from</code>: The name of the column to bind from.
+ * This attribute is mandatory.</li>
+ * <li><code>android:to</code>: The id of the view to bind to. This attribute is mandatory.</li>
+ * <li><code>android:as</code>: The <a href="#xml-cursor-adapter-bind-data-types">data type</a>
+ * of the binding. This attribute is mandatory.</li>
+ * </ul>
+ *
+ * <p>In addition, a <code><bind /></code> can contain zero or more instances of
+ * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> chilren
+ * tags.</p>
+ *
+ * <a name="xml-cursor-adapter-bind-data-types" />
+ * <h4>Binding data types</h4>
+ * <p>For a binding to occur the data type of the bound column/view pair must be specified.
+ * The following data types are currently supported:</p>
+ * <ul>
+ * <li><code>string</code>: The content of the column is interpreted as a string and must be
+ * bound to a {@link android.widget.TextView}</li>
+ * <li><code>image</code>: The content of the column is interpreted as a blob describing an
+ * image and must be bound to an {@link android.widget.ImageView}</li>
+ * <li><code>image-uri</code>: The content of the column is interpreted as a URI to an image
+ * and must be bound to an {@link android.widget.ImageView}</li>
+ * <li><code>drawable</code>: The content of the column is interpreted as a resource id to a
+ * drawable and must be bound to an {@link android.widget.ImageView}</li>
+ * <li>A fully qualified class name: The name of a class corresponding to an implementation of
+ * {@link android.widget.Adapters.CursorBinder}. Cursor binders can be used to provide
+ * bindings not supported by default. Custom binders cannot be used with
+ * {@link android.content.Context#isRestricted() restricted contexts}, for instance in an
+ * app widget</li>
+ * </ul>
+ *
+ * <a name="xml-cursor-adapter-bind-transformation" />
+ * <h4>Binding transformations</h4>
+ * <p>When defining a data binding you can specify an optional transformation by using one
+ * of the following tags as a child of a <code><bind /></code> elements:</p>
+ * <ul>
+ * <li><code><map /></code>: Maps a constant string to a string or a resource. Use
+ * one instance of this tag per value you want to map</li>
+ * <li><code><transform /></code>: Transforms a column's value using an expression
+ * or an instance of {@link android.widget.Adapters.CursorTransformation}</li>
+ * </ul>
+ * <p>While several <code><map /></code> tags can be used at the same time, you cannot
+ * mix <code><map /></code> and <code><transform /></code> tags. If several
+ * <code><transform /></code> tags are specified, only the last one is retained.</p>
+ *
+ * <a name="xml-cursor-adapter-bind-transformation-map" />
+ * <p><strong><map /></strong></p>
+ * <p>A map element simply specifies a value to match from and a value to match to. When
+ * a column's value equals the value to match from, it is replaced with the value to match
+ * to. The following attributes are supported:</p>
+ * <ul>
+ * <li><code>android:fromValue</code>: The value to match from. This attribute is mandatory</li>
+ * <li><code>android:toValue</code>: The value to match to. This value can be either a string
+ * or a resource identifier. This value is interpreted as a resource identifier when the
+ * data binding is of type <code>drawable</code>. This attribute is mandatory</li>
+ * </ul>
+ *
+ * <a name="xml-cursor-adapter-bind-transformation-transform" />
+ * <p><strong><transform /></strong></p>
+ * <p>A simple transform that occurs either by calling a specified class or by performing
+ * simple text substitution. The following attributes are supported:</p>
+ * <ul>
+ * <li><code>android:withExpression</code>: The transformation expression. The expression is
+ * a string containing column names surrounded with curly braces { and }. During the
+ * transformation each column name is replaced by its value. All columns must have been
+ * selected in the query. An example of expression is <code>"First name: {first_name},
+ * last name: {last_name}"</code>. This attribute is mandatory
+ * if <code>android:withClass</code> is not specified and ignored if <code>android:withClass</code>
+ * is specified</li>
+ * <li><code>android:withClass</code>: A fully qualified class name corresponding to an
+ * implementation of {@link android.widget.Adapters.CursorTransformation}. Custom
+ * transformationscannot be used with
+ * {@link android.content.Context#isRestricted() restricted contexts}, for instance in
+ * an app widget This attribute is mandatory if <code>android:withExpression</code> is
+ * not specified</li>
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * <p>The following example defines a cursor adapter that queries all the contacts with
+ * a phone number using the contacts content provider. Each contact is displayed with
+ * its display name, its favorite status and its photo. To display photos, a custom data
+ * binder is declared:</p>
+ *
+ * <pre class="prettyprint">
+ * <cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:uri="content://com.android.contacts/contacts"
+ * android:selection="has_phone_number=1"
+ * android:layout="@layout/contact_item">
+ *
+ * <bind android:from="display_name" android:to="@id/name" android:as="string" />
+ * <bind android:from="starred" android:to="@id/star" android:as="drawable">
+ * <map android:fromValue="0" android:toValue="@android:drawable/star_big_off" />
+ * <map android:fromValue="1" android:toValue="@android:drawable/star_big_on" />
+ * </bind>
+ * <bind android:from="_id" android:to="@id/name"
+ * android:as="com.google.android.test.adapters.ContactPhotoBinder" />
+ *
+ * </cursor-adapter>
+ * </pre>
+ *
+ * <h3>Related APIs</h3>
+ * <ul>
+ * <li>{@link android.widget.Adapters#loadAdapter(android.content.Context, int, Object[])}</li>
+ * <li>{@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, android.database.Cursor, Object[])}</li>
+ * <li>{@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}</li>
+ * <li>{@link android.widget.Adapters.CursorBinder}</li>
+ * <li>{@link android.widget.Adapters.CursorTransformation}</li>
+ * <li>{@link android.widget.CursorAdapter}</li>
+ * </ul>
+ *
+ * @see android.widget.Adapter
+ * @see android.content.ContentProvider
+ *
+ * @attr ref android.R.styleable#CursorAdapter_layout
+ * @attr ref android.R.styleable#CursorAdapter_selection
+ * @attr ref android.R.styleable#CursorAdapter_sortOrder
+ * @attr ref android.R.styleable#CursorAdapter_uri
+ * @attr ref android.R.styleable#CursorAdapter_BindItem_as
+ * @attr ref android.R.styleable#CursorAdapter_BindItem_from
+ * @attr ref android.R.styleable#CursorAdapter_BindItem_to
+ * @attr ref android.R.styleable#CursorAdapter_MapItem_fromValue
+ * @attr ref android.R.styleable#CursorAdapter_MapItem_toValue
+ * @attr ref android.R.styleable#CursorAdapter_SelectItem_column
+ * @attr ref android.R.styleable#CursorAdapter_TransformItem_withClass
+ * @attr ref android.R.styleable#CursorAdapter_TransformItem_withExpression
+ */
+@SuppressWarnings({"JavadocReference"})
+public class Adapters {
+ private static final String ADAPTER_CURSOR = "cursor-adapter";
+
+ /**
+ * <p>Interface used to bind a {@link android.database.Cursor} column to a View. This
+ * interface can be used to provide bindings for data types not supported by the
+ * standard implementation of {@link android.widget.Adapters}.</p>
+ *
+ * <p>A binder is provided with a cursor transformation which may or may not be used
+ * to transform the value retrieved from the cursor. The transformation is guaranteed
+ * to never be null so it's always safe to apply the transformation.</p>
+ *
+ * <p>The binder is associated with a Context but can be re-used with multiple cursors.
+ * As such, the implementation should make no assumption about the Cursor in use.</p>
+ *
+ * @see android.view.View
+ * @see android.database.Cursor
+ * @see android.widget.Adapters.CursorTransformation
+ */
+ public static abstract class CursorBinder {
+ /**
+ * <p>The context associated with this binder.</p>
+ */
+ protected final Context mContext;
+
+ /**
+ * <p>The transformation associated with this binder. This transformation is never
+ * null and may or may not be applied to the Cursor data during the
+ * {@link #bind(android.view.View, android.database.Cursor, int)} operation.</p>
+ *
+ * @see #bind(android.view.View, android.database.Cursor, int)
+ */
+ protected final CursorTransformation mTransformation;
+
+ /**
+ * <p>Creates a new Cursor binder.</p>
+ *
+ * @param context The context associated with this binder.
+ * @param transformation The transformation associated with this binder. This
+ * transformation may or may not be applied by the binder and is guaranteed
+ * to not be null.
+ */
+ public CursorBinder(Context context, CursorTransformation transformation) {
+ mContext = context;
+ mTransformation = transformation;
+ }
+
+ /**
+ * <p>Binds the specified Cursor column to the supplied View. The binding operation
+ * can query other Cursor columns as needed. During the binding operation, values
+ * retrieved from the Cursor may or may not be transformed using this binder's
+ * cursor transformation.</p>
+ *
+ * @param view The view to bind data to.
+ * @param cursor The cursor to bind data from.
+ * @param columnIndex The column index in the cursor where the data to bind resides.
+ *
+ * @see #mTransformation
+ *
+ * @return True if the column was successfully bound to the View, false otherwise.
+ */
+ public abstract boolean bind(View view, Cursor cursor, int columnIndex);
+ }
+
+ /**
+ * <p>Interface used to transform data coming out of a {@link android.database.Cursor}
+ * before it is bound to a {@link android.view.View}.</p>
+ *
+ * <p>Transformations are used to transform text-based data (in the form of a String),
+ * or to transform data into a resource identifier. A default implementation is provided
+ * to generate resource identifiers.</p>
+ *
+ * @see android.database.Cursor
+ * @see android.widget.Adapters.CursorBinder
+ */
+ public static abstract class CursorTransformation {
+ /**
+ * <p>The context associated with this transformation.</p>
+ */
+ protected final Context mContext;
+
+ /**
+ * <p>Creates a new Cursor transformation.</p>
+ *
+ * @param context The context associated with this transformation.
+ */
+ public CursorTransformation(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * <p>Transforms the specified Cursor column into a String. The transformation
+ * can simply return the content of the column as a String (this is known
+ * as the identity transformation) or manipulate the content. For instance,
+ * a transformation can perform text substitutions or concatenate other
+ * columns with the specified column.</p>
+ *
+ * @param cursor The cursor that contains the data to transform.
+ * @param columnIndex The index of the column to transform.
+ *
+ * @return A String containing the transformed value of the column.
+ */
+ public abstract String transform(Cursor cursor, int columnIndex);
+
+ /**
+ * <p>Transforms the specified Cursor column into a resource identifier.
+ * The default implementation simply interprets the content of the column
+ * as an integer.</p>
+ *
+ * @param cursor The cursor that contains the data to transform.
+ * @param columnIndex The index of the column to transform.
+ *
+ * @return A resource identifier.
+ */
+ public int transformToResource(Cursor cursor, int columnIndex) {
+ return cursor.getInt(columnIndex);
+ }
+ }
+
+ /**
+ * <p>Loads the {@link android.widget.CursorAdapter} defined in the specified
+ * XML resource. The content of the adapter is loaded from the content provider
+ * identified by the supplied URI.</p>
+ *
+ * <p><strong>Note:</strong> If the supplied {@link android.content.Context} is
+ * an {@link android.app.Activity}, the cursor returned by the content provider
+ * will be automatically managed. Otherwise, you are responsible for managing the
+ * cursor yourself.</p>
+ *
+ * <p>The format of the XML definition of the cursor adapter is documented at
+ * the top of this page.</p>
+ *
+ * @param context The context to load the XML resource from.
+ * @param id The identifier of the XML resource declaring the adapter.
+ * @param uri The URI of the content provider.
+ * @param parameters Optional parameters to pass to the CursorAdapter, used
+ * to substitute values in the selection expression.
+ *
+ * @return A {@link android.widget.CursorAdapter}
+ *
+ * @throws IllegalArgumentException If the XML resource does not contain
+ * a valid <cursor-adapter /> definition.
+ *
+ * @see android.content.ContentProvider
+ * @see android.widget.CursorAdapter
+ * @see #loadAdapter(android.content.Context, int, Object[])
+ */
+ public static CursorAdapter loadCursorAdapter(Context context, int id, String uri,
+ Object... parameters) {
+
+ XmlCursorAdapter adapter = (XmlCursorAdapter) loadAdapter(context, id, ADAPTER_CURSOR,
+ parameters);
+
+ if (uri != null) {
+ adapter.setUri(uri);
+ }
+ adapter.load();
+
+ return adapter;
+ }
+
+ /**
+ * <p>Loads the {@link android.widget.CursorAdapter} defined in the specified
+ * XML resource. The content of the adapter is loaded from the specified cursor.
+ * You are responsible for managing the supplied cursor.</p>
+ *
+ * <p>The format of the XML definition of the cursor adapter is documented at
+ * the top of this page.</p>
+ *
+ * @param context The context to load the XML resource from.
+ * @param id The identifier of the XML resource declaring the adapter.
+ * @param cursor The cursor containing the data for the adapter.
+ * @param parameters Optional parameters to pass to the CursorAdapter, used
+ * to substitute values in the selection expression.
+ *
+ * @return A {@link android.widget.CursorAdapter}
+ *
+ * @throws IllegalArgumentException If the XML resource does not contain
+ * a valid <cursor-adapter /> definition.
+ *
+ * @see android.content.ContentProvider
+ * @see android.widget.CursorAdapter
+ * @see android.database.Cursor
+ * @see #loadAdapter(android.content.Context, int, Object[])
+ */
+ public static CursorAdapter loadCursorAdapter(Context context, int id, Cursor cursor,
+ Object... parameters) {
+
+ XmlCursorAdapter adapter = (XmlCursorAdapter) loadAdapter(context, id, ADAPTER_CURSOR,
+ parameters);
+
+ if (cursor != null) {
+ adapter.changeCursor(cursor);
+ }
+
+ return adapter;
+ }
+
+ /**
+ * <p>Loads the adapter defined in the specified XML resource. The XML definition of
+ * the adapter must follow the format definition of one of the supported adapter
+ * types described at the top of this page.</p>
+ *
+ * <p><strong>Note:</strong> If the loaded adapter is a {@link android.widget.CursorAdapter}
+ * and the supplied {@link android.content.Context} is an {@link android.app.Activity},
+ * the cursor returned by the content provider will be automatically managed. Otherwise,
+ * you are responsible for managing the cursor yourself.</p>
+ *
+ * @param context The context to load the XML resource from.
+ * @param id The identifier of the XML resource declaring the adapter.
+ * @param parameters Optional parameters to pass to the adapter.
+ *
+ * @return An adapter instance.
+ *
+ * @see #loadCursorAdapter(android.content.Context, int, android.database.Cursor, Object[])
+ * @see #loadCursorAdapter(android.content.Context, int, String, Object[])
+ */
+ public static BaseAdapter loadAdapter(Context context, int id, Object... parameters) {
+ final BaseAdapter adapter = loadAdapter(context, id, null, parameters);
+ if (adapter instanceof ManagedAdapter) {
+ ((ManagedAdapter) adapter).load();
+ }
+ return adapter;
+ }
+
+ /**
+ * Loads an adapter from the specified XML resource. The optional assertName can
+ * be used to exit early if the adapter defined in the XML resource is not of the
+ * expected type.
+ *
+ * @param context The context to associate with the adapter.
+ * @param id The resource id of the XML document defining the adapter.
+ * @param assertName The mandatory name of the adapter in the XML document.
+ * Ignored if null.
+ * @param parameters Optional parameters passed to the adapter.
+ *
+ * @return An instance of {@link android.widget.BaseAdapter}.
+ */
+ private static BaseAdapter loadAdapter(Context context, int id, String assertName,
+ Object... parameters) {
+
+ XmlResourceParser parser = null;
+ try {
+ parser = context.getResources().getXml(id);
+ return createAdapterFromXml(context, parser, Xml.asAttributeSet(parser),
+ id, parameters, assertName);
+ } catch (XmlPullParserException ex) {
+ Resources.NotFoundException rnf = new Resources.NotFoundException(
+ "Can't load adapter resource ID " +
+ context.getResources().getResourceEntryName(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ Resources.NotFoundException rnf = new Resources.NotFoundException(
+ "Can't load adapter resource ID " +
+ context.getResources().getResourceEntryName(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ /**
+ * Generates an adapter using the specified XML parser. This method is responsible
+ * for choosing the type of the adapter to create based on the content of the
+ * XML parser.
+ *
+ * This method will generate an {@link IllegalArgumentException} if
+ * <code>assertName</code> is not null and does not match the root tag of the XML
+ * document.
+ */
+ private static BaseAdapter createAdapterFromXml(Context c,
+ XmlPullParser parser, AttributeSet attrs, int id, Object[] parameters,
+ String assertName) throws XmlPullParserException, IOException {
+
+ BaseAdapter adapter = null;
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) &&
+ type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+ if (assertName != null && !assertName.equals(name)) {
+ throw new IllegalArgumentException("The adapter defined in " +
+ c.getResources().getResourceEntryName(id) + " must be a <" + name + " />");
+ }
+
+ if (ADAPTER_CURSOR.equals(name)) {
+ adapter = createCursorAdapter(c, parser, attrs, id, parameters);
+ } else {
+ throw new IllegalArgumentException("Unknown adapter name " + parser.getName() +
+ " in " + c.getResources().getResourceEntryName(id));
+ }
+ }
+
+ return adapter;
+
+ }
+
+ /**
+ * Creates an XmlCursorAdapter using an XmlCursorAdapterParser.
+ */
+ private static XmlCursorAdapter createCursorAdapter(Context c, XmlPullParser parser,
+ AttributeSet attrs, int id, Object[] parameters)
+ throws IOException, XmlPullParserException {
+
+ return new XmlCursorAdapterParser(c, parser, attrs, id).parse(parameters);
+ }
+
+ /**
+ * Parser that can generate XmlCursorAdapter instances. This parser is responsible for
+ * handling all the attributes and child nodes for a <cursor-adapter />.
+ */
+ private static class XmlCursorAdapterParser {
+ private static final String ADAPTER_CURSOR_BIND = "bind";
+ private static final String ADAPTER_CURSOR_SELECT = "select";
+ private static final String ADAPTER_CURSOR_AS_STRING = "string";
+ private static final String ADAPTER_CURSOR_AS_IMAGE = "image";
+ private static final String ADAPTER_CURSOR_AS_IMAGE_URI = "image-uri";
+ private static final String ADAPTER_CURSOR_AS_DRAWABLE = "drawable";
+ private static final String ADAPTER_CURSOR_MAP = "map";
+ private static final String ADAPTER_CURSOR_TRANSFORM = "transform";
+
+ private final Context mContext;
+ private final XmlPullParser mParser;
+ private final AttributeSet mAttrs;
+ private final int mId;
+
+ private final HashMap<String, CursorBinder> mBinders;
+ private final ArrayList<String> mFrom;
+ private final ArrayList<Integer> mTo;
+ private final CursorTransformation mIdentity;
+ private final Resources mResources;
+
+ public XmlCursorAdapterParser(Context c, XmlPullParser parser, AttributeSet attrs, int id) {
+ mContext = c;
+ mParser = parser;
+ mAttrs = attrs;
+ mId = id;
+
+ mResources = mContext.getResources();
+ mBinders = new HashMap<String, CursorBinder>();
+ mFrom = new ArrayList<String>();
+ mTo = new ArrayList<Integer>();
+ mIdentity = new IdentityTransformation(mContext);
+ }
+
+ public XmlCursorAdapter parse(Object[] parameters)
+ throws IOException, XmlPullParserException {
+
+ Resources resources = mResources;
+ TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter);
+
+ String uri = a.getString(styleable.CursorAdapter_uri);
+ String selection = a.getString(styleable.CursorAdapter_selection);
+ String sortOrder = a.getString(styleable.CursorAdapter_sortOrder);
+ int layout = a.getResourceId(styleable.CursorAdapter_layout, 0);
+ if (layout == 0) {
+ throw new IllegalArgumentException("The layout specified in " +
+ resources.getResourceEntryName(mId) + " does not exist");
+ }
+
+ a.recycle();
+
+ XmlPullParser parser = mParser;
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) &&
+ type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if (ADAPTER_CURSOR_BIND.equals(name)) {
+ parseBindTag();
+ } else if (ADAPTER_CURSOR_SELECT.equals(name)) {
+ parseSelectTag();
+ } else {
+ throw new RuntimeException("Unknown tag name " + parser.getName() + " in " +
+ resources.getResourceEntryName(mId));
+ }
+ }
+
+ String[] fromArray = mFrom.toArray(new String[mFrom.size()]);
+ int[] toArray = new int[mTo.size()];
+ for (int i = 0; i < toArray.length; i++) {
+ toArray[i] = mTo.get(i);
+ }
+
+ String[] selectionArgs = null;
+ if (parameters != null) {
+ selectionArgs = new String[parameters.length];
+ for (int i = 0; i < selectionArgs.length; i++) {
+ selectionArgs[i] = (String) parameters[i];
+ }
+ }
+
+ return new XmlCursorAdapter(mContext, layout, uri, fromArray, toArray, selection,
+ selectionArgs, sortOrder, mBinders);
+ }
+
+ private void parseSelectTag() {
+ TypedArray a = mResources.obtainAttributes(mAttrs, styleable.CursorAdapter_SelectItem);
+
+ String fromName = a.getString(styleable.CursorAdapter_SelectItem_column);
+ if (fromName == null) {
+ throw new IllegalArgumentException("A select item in " +
+ mResources.getResourceEntryName(mId) + " does not have a 'column' attribute");
+ }
+
+ a.recycle();
+
+ mFrom.add(fromName);
+ mTo.add(View.NO_ID);
+ }
+
+ private void parseBindTag() throws IOException, XmlPullParserException {
+ Resources resources = mResources;
+ TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_BindItem);
+
+ String fromName = a.getString(styleable.CursorAdapter_BindItem_from);
+ if (fromName == null) {
+ throw new IllegalArgumentException("A bind item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'from' attribute");
+ }
+
+ int toName = a.getResourceId(styleable.CursorAdapter_BindItem_to, 0);
+ if (toName == 0) {
+ throw new IllegalArgumentException("A bind item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'to' attribute");
+ }
+
+ String asType = a.getString(styleable.CursorAdapter_BindItem_as);
+ if (asType == null) {
+ throw new IllegalArgumentException("A bind item in " +
+ resources.getResourceEntryName(mId) + " does not have an 'as' attribute");
+ }
+
+ mFrom.add(fromName);
+ mTo.add(toName);
+ mBinders.put(fromName, findBinder(asType));
+
+ a.recycle();
+ }
+
+ private CursorBinder findBinder(String type) throws IOException, XmlPullParserException {
+ final XmlPullParser parser = mParser;
+ final Context context = mContext;
+ CursorTransformation transformation = mIdentity;
+
+ int tagType;
+ int depth = parser.getDepth();
+
+ final boolean isDrawable = ADAPTER_CURSOR_AS_DRAWABLE.equals(type);
+
+ while (((tagType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && tagType != XmlPullParser.END_DOCUMENT) {
+
+ if (tagType != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if (ADAPTER_CURSOR_TRANSFORM.equals(name)) {
+ transformation = findTransformation();
+ } else if (ADAPTER_CURSOR_MAP.equals(name)) {
+ if (!(transformation instanceof MapTransformation)) {
+ transformation = new MapTransformation(context);
+ }
+ findMap(((MapTransformation) transformation), isDrawable);
+ } else {
+ throw new RuntimeException("Unknown tag name " + parser.getName() + " in " +
+ context.getResources().getResourceEntryName(mId));
+ }
+ }
+
+ if (ADAPTER_CURSOR_AS_STRING.equals(type)) {
+ return new StringBinder(context, transformation);
+ } else if (ADAPTER_CURSOR_AS_IMAGE.equals(type)) {
+ return new ImageBinder(context, transformation);
+ } else if (ADAPTER_CURSOR_AS_IMAGE_URI.equals(type)) {
+ return new ImageUriBinder(context, transformation);
+ } else if (isDrawable) {
+ return new DrawableBinder(context, transformation);
+ } else {
+ return createBinder(type, transformation);
+ }
+ }
+
+ private CursorBinder createBinder(String type, CursorTransformation transformation) {
+ if (mContext.isRestricted()) return null;
+
+ try {
+ final Class<?> klass = Class.forName(type, true, mContext.getClassLoader());
+ if (CursorBinder.class.isAssignableFrom(klass)) {
+ final Constructor<?> c = klass.getDeclaredConstructor(
+ Context.class, CursorTransformation.class);
+ return (CursorBinder) c.newInstance(mContext, transformation);
+ }
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ }
+
+ return null;
+ }
+
+ private void findMap(MapTransformation transformation, boolean drawable) {
+ Resources resources = mResources;
+
+ TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_MapItem);
+
+ String from = a.getString(styleable.CursorAdapter_MapItem_fromValue);
+ if (from == null) {
+ throw new IllegalArgumentException("A map item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'fromValue' attribute");
+ }
+
+ if (!drawable) {
+ String to = a.getString(styleable.CursorAdapter_MapItem_toValue);
+ if (to == null) {
+ throw new IllegalArgumentException("A map item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute");
+ }
+ transformation.addStringMapping(from, to);
+ } else {
+ int to = a.getResourceId(styleable.CursorAdapter_MapItem_toValue, 0);
+ if (to == 0) {
+ throw new IllegalArgumentException("A map item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute");
+ }
+ transformation.addResourceMapping(from, to);
+ }
+
+ a.recycle();
+ }
+
+ private CursorTransformation findTransformation() {
+ Resources resources = mResources;
+ CursorTransformation transformation = null;
+ TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_TransformItem);
+
+ String className = a.getString(styleable.CursorAdapter_TransformItem_withClass);
+ if (className == null) {
+ String expression = a.getString(
+ styleable.CursorAdapter_TransformItem_withExpression);
+ transformation = createExpressionTransformation(expression);
+ } else if (!mContext.isRestricted()) {
+ try {
+ final Class<?> klass = Class.forName(className, true, mContext.getClassLoader());
+ if (CursorTransformation.class.isAssignableFrom(klass)) {
+ final Constructor<?> c = klass.getDeclaredConstructor(Context.class);
+ transformation = (CursorTransformation) c.newInstance(mContext);
+ }
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ }
+ }
+
+ a.recycle();
+
+ if (transformation == null) {
+ throw new IllegalArgumentException("A transform item in " +
+ resources.getResourceEntryName(mId) + " must have a 'withClass' or " +
+ "'withExpression' attribute");
+ }
+
+ return transformation;
+ }
+
+ private CursorTransformation createExpressionTransformation(String expression) {
+ return new ExpressionTransformation(mContext, expression);
+ }
+ }
+
+ /**
+ * Interface used by adapters that require to be loaded after creation.
+ */
+ private static interface ManagedAdapter {
+ /**
+ * Loads the content of the adapter, asynchronously.
+ */
+ void load();
+ }
+
+ /**
+ * Implementation of a Cursor adapter defined in XML. This class is a thin wrapper
+ * of a SimpleCursorAdapter. The main difference is the ability to handle CursorBinders.
+ */
+ private static class XmlCursorAdapter extends SimpleCursorAdapter implements ManagedAdapter {
+ private final Context mContext;
+ private String mUri;
+ private final String mSelection;
+ private final String[] mSelectionArgs;
+ private final String mSortOrder;
+ private final String[] mColumns;
+ private final CursorBinder[] mBinders;
+ private AsyncTask<Void,Void,Cursor> mLoadTask;
+
+ XmlCursorAdapter(Context context, int layout, String uri, String[] from, int[] to,
+ String selection, String[] selectionArgs, String sortOrder,
+ HashMap<String, CursorBinder> binders) {
+
+ super(context, layout, null, from, to);
+ mContext = context;
+ mUri = uri;
+ mSelection = selection;
+ mSelectionArgs = selectionArgs;
+ mSortOrder = sortOrder;
+ mColumns = new String[from.length + 1];
+ // This is mandatory in CursorAdapter
+ mColumns[0] = "_id";
+ System.arraycopy(from, 0, mColumns, 1, from.length);
+
+ CursorBinder basic = new StringBinder(context, new IdentityTransformation(context));
+ final int count = from.length;
+ mBinders = new CursorBinder[count];
+
+ for (int i = 0; i < count; i++) {
+ CursorBinder binder = binders.get(from[i]);
+ if (binder == null) binder = basic;
+ mBinders[i] = binder;
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ final int count = mTo.length;
+ final int[] from = mFrom;
+ final int[] to = mTo;
+ final CursorBinder[] binders = mBinders;
+
+ for (int i = 0; i < count; i++) {
+ final View v = view.findViewById(to[i]);
+ if (v != null) {
+ binders[i].bind(v, cursor, from[i]);
+ }
+ }
+ }
+
+ public void load() {
+ if (mUri != null) {
+ mLoadTask = new QueryTask().execute();
+ }
+ }
+
+ void setUri(String uri) {
+ mUri = uri;
+ }
+
+ @Override
+ public void changeCursor(Cursor c) {
+ if (mLoadTask != null && mLoadTask.getStatus() != QueryTask.Status.FINISHED) {
+ mLoadTask.cancel(true);
+ mLoadTask = null;
+ }
+ super.changeCursor(c);
+ }
+
+ class QueryTask extends AsyncTask<Void, Void, Cursor> {
+ @Override
+ protected Cursor doInBackground(Void... params) {
+ if (mContext instanceof Activity) {
+ return ((Activity) mContext).managedQuery(
+ Uri.parse(mUri), mColumns, mSelection, mSelectionArgs, mSortOrder);
+ } else {
+ return mContext.getContentResolver().query(
+ Uri.parse(mUri), mColumns, mSelection, mSelectionArgs, mSortOrder);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Cursor cursor) {
+ if (!isCancelled()) {
+ XmlCursorAdapter.super.changeCursor(cursor);
+ }
+ }
+ }
+ }
+
+ /**
+ * Identity transformation, returns the content of the specified column as a String,
+ * without performing any manipulation. This is used when no transformation is specified.
+ */
+ private static class IdentityTransformation extends CursorTransformation {
+ public IdentityTransformation(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String transform(Cursor cursor, int columnIndex) {
+ return cursor.getString(columnIndex);
+ }
+ }
+
+ /**
+ * An expression transformation is a simple template based replacement utility.
+ * In an expression, each segment of the form <code>{(^[}]+)}</code> is replaced
+ * with the value of the column of name $1.
+ */
+ private static class ExpressionTransformation extends CursorTransformation {
+ private final ExpressionNode mFirstNode = new ConstantExpressionNode("");
+ private final StringBuilder mBuilder = new StringBuilder();
+
+ public ExpressionTransformation(Context context, String expression) {
+ super(context);
+
+ parse(expression);
+ }
+
+ private void parse(String expression) {
+ ExpressionNode node = mFirstNode;
+ int segmentStart;
+ int count = expression.length();
+
+ for (int i = 0; i < count; i++) {
+ char c = expression.charAt(i);
+ // Start a column name segment
+ segmentStart = i;
+ if (c == '{') {
+ while (i < count && (c = expression.charAt(i)) != '}') {
+ i++;
+ }
+ // We've reached the end, but the expression didn't close
+ if (c != '}') {
+ throw new IllegalStateException("The transform expression contains a " +
+ "non-closed column name: " +
+ expression.substring(segmentStart + 1, i));
+ }
+ node.next = new ColumnExpressionNode(expression.substring(segmentStart + 1, i));
+ } else {
+ while (i < count && (c = expression.charAt(i)) != '{') {
+ i++;
+ }
+ node.next = new ConstantExpressionNode(expression.substring(segmentStart, i));
+ // Rewind if we've reached a column expression
+ if (c == '{') i--;
+ }
+ node = node.next;
+ }
+ }
+
+ @Override
+ public String transform(Cursor cursor, int columnIndex) {
+ final StringBuilder builder = mBuilder;
+ builder.delete(0, builder.length());
+
+ ExpressionNode node = mFirstNode;
+ // Skip the first node
+ while ((node = node.next) != null) {
+ builder.append(node.asString(cursor));
+ }
+
+ return builder.toString();
+ }
+
+ static abstract class ExpressionNode {
+ public ExpressionNode next;
+
+ public abstract String asString(Cursor cursor);
+ }
+
+ static class ConstantExpressionNode extends ExpressionNode {
+ private final String mConstant;
+
+ ConstantExpressionNode(String constant) {
+ mConstant = constant;
+ }
+
+ @Override
+ public String asString(Cursor cursor) {
+ return mConstant;
+ }
+ }
+
+ static class ColumnExpressionNode extends ExpressionNode {
+ private final String mColumnName;
+ private Cursor mSignature;
+ private int mColumnIndex = -1;
+
+ ColumnExpressionNode(String columnName) {
+ mColumnName = columnName;
+ }
+
+ @Override
+ public String asString(Cursor cursor) {
+ if (cursor != mSignature || mColumnIndex == -1) {
+ mColumnIndex = cursor.getColumnIndex(mColumnName);
+ mSignature = cursor;
+ }
+
+ return cursor.getString(mColumnIndex);
+ }
+ }
+ }
+
+ /**
+ * A map transformation offers a simple mapping between specified String values
+ * to Strings or integers.
+ */
+ private static class MapTransformation extends CursorTransformation {
+ private final HashMap<String, String> mStringMappings;
+ private final HashMap<String, Integer> mResourceMappings;
+
+ public MapTransformation(Context context) {
+ super(context);
+ mStringMappings = new HashMap<String, String>();
+ mResourceMappings = new HashMap<String, Integer>();
+ }
+
+ void addStringMapping(String from, String to) {
+ mStringMappings.put(from, to);
+ }
+
+ void addResourceMapping(String from, int to) {
+ mResourceMappings.put(from, to);
+ }
+
+ @Override
+ public String transform(Cursor cursor, int columnIndex) {
+ final String value = cursor.getString(columnIndex);
+ final String transformed = mStringMappings.get(value);
+ return transformed == null ? value : transformed;
+ }
+
+ @Override
+ public int transformToResource(Cursor cursor, int columnIndex) {
+ final String value = cursor.getString(columnIndex);
+ final Integer transformed = mResourceMappings.get(value);
+ try {
+ return transformed == null ? Integer.parseInt(value) : transformed;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Binds a String to a TextView.
+ */
+ private static class StringBinder extends CursorBinder {
+ public StringBinder(Context context, CursorTransformation transformation) {
+ super(context, transformation);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ ((TextView) view).setText(mTransformation.transform(cursor, columnIndex));
+ return true;
+ }
+ }
+
+ /**
+ * Binds an image blob to an ImageView.
+ */
+ private static class ImageBinder extends CursorBinder {
+ public ImageBinder(Context context, CursorTransformation transformation) {
+ super(context, transformation);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ final byte[] data = cursor.getBlob(columnIndex);
+ ((ImageView) view).setImageBitmap(BitmapFactory.decodeByteArray(data, 0, data.length));
+ return true;
+ }
+ }
+
+ /**
+ * Binds an image URI to an ImageView.
+ */
+ private static class ImageUriBinder extends CursorBinder {
+ public ImageUriBinder(Context context, CursorTransformation transformation) {
+ super(context, transformation);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ ((ImageView) view).setImageURI(Uri.parse(
+ mTransformation.transform(cursor, columnIndex)));
+ return true;
+ }
+ }
+
+ /**
+ * Binds a drawable resource identifier to an ImageView.
+ */
+ private static class DrawableBinder extends CursorBinder {
+ public DrawableBinder(Context context, CursorTransformation transformation) {
+ super(context, transformation);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ final int resource = mTransformation.transformToResource(cursor, columnIndex);
+ if (resource == 0) return false;
+
+ ((ImageView) view).setImageResource(resource);
+ return true;
+ }
+ }
+}
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index e15a520..8611901 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -913,10 +913,10 @@
if (mItemClickListener != null) {
final DropDownListView list = mDropDownList;
- // Note that we don't have a View here, so we will need to
- // supply null. Hopefully no existing apps crash...
- mItemClickListener.onItemClick(list, null, completion.getPosition(),
- completion.getId());
+ final int position = completion.getPosition();
+ mItemClickListener.onItemClick(list,
+ list.getChildAt(position - list.getFirstVisiblePosition()),
+ position, completion.getId());
}
}
}
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index baa6833..4cf8785 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -80,6 +80,18 @@
protected FilterQueryProvider mFilterQueryProvider;
/**
+ * If set the adapter will call requery() on the cursor whenever a content change
+ * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}
+ */
+ public static final int FLAG_AUTO_REQUERY = 0x01;
+
+ /**
+ * If set the adapter will register a content observer on the cursor and will call
+ * {@link #onContentChanged()} when a notification comes in.
+ */
+ public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
+
+ /**
* Constructor. The adapter will call requery() on the cursor whenever
* it changes so that the most recent data is always displayed.
*
@@ -87,7 +99,7 @@
* @param context The context
*/
public CursorAdapter(Context context, Cursor c) {
- init(context, c, true);
+ init(context, c, FLAG_AUTO_REQUERY);
}
/**
@@ -99,19 +111,43 @@
* data is always displayed.
*/
public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
- init(context, c, autoRequery);
+ init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
+ }
+
+ /**
+ * Constructor
+ * @param c The cursor from which to get the data.
+ * @param context The context
+ * @param flags flags used to determine the behavior of the adapter
+ */
+ public CursorAdapter(Context context, Cursor c, int flags) {
+ init(context, c, flags);
}
protected void init(Context context, Cursor c, boolean autoRequery) {
+ init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
+ }
+
+ protected void init(Context context, Cursor c, int flags) {
+ if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
+ flags |= FLAG_REGISTER_CONTENT_OBSERVER;
+ mAutoRequery = true;
+ } else {
+ mAutoRequery = false;
+ }
boolean cursorPresent = c != null;
- mAutoRequery = autoRequery;
mCursor = c;
mDataValid = cursorPresent;
mContext = context;
mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
- mChangeObserver = new ChangeObserver();
+ if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
+ mChangeObserver = new ChangeObserver();
+ } else {
+ mChangeObserver = null;
+ }
+
if (cursorPresent) {
- c.registerContentObserver(mChangeObserver);
+ if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
c.registerDataSetObserver(mDataSetObserver);
}
}
@@ -246,13 +282,13 @@
return;
}
if (mCursor != null) {
- mCursor.unregisterContentObserver(mChangeObserver);
+ if (mChangeObserver != null) mCursor.unregisterContentObserver(mChangeObserver);
mCursor.unregisterDataSetObserver(mDataSetObserver);
mCursor.close();
}
mCursor = cursor;
if (cursor != null) {
- cursor.registerContentObserver(mChangeObserver);
+ if (mChangeObserver != null) cursor.registerContentObserver(mChangeObserver);
cursor.registerDataSetObserver(mDataSetObserver);
mRowIDColumn = cursor.getColumnIndexOrThrow("_id");
mDataValid = true;
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index f34823c..6775a94 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -1207,7 +1207,7 @@
// We unfocus the old child down here so the above hasFocus check
// returns true
- if (oldSelectedChild != null) {
+ if (oldSelectedChild != null && oldSelectedChild != child) {
// Make sure its drawable state doesn't contain 'selected'
oldSelectedChild.setSelected(false);
@@ -1263,6 +1263,7 @@
*/
if (gainFocus && mSelectedChild != null) {
mSelectedChild.requestFocus(direction);
+ mSelectedChild.setSelected(true);
}
}
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 07c3e4b..50fbb6b 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -48,6 +48,7 @@
private QueryHandler mQueryHandler;
private Drawable mBadgeBackground;
private Drawable mNoBadgeBackground;
+ private Drawable mDefaultAvatar;
protected String[] mExcludeMimes = null;
@@ -117,6 +118,16 @@
public void setMode(int size) {
mMode = size;
}
+
+ /**
+ * Resets the contact photo to the default state.
+ */
+ public void setImageToDefault() {
+ if (mDefaultAvatar == null) {
+ mDefaultAvatar = getResources().getDrawable(R.drawable.ic_contact_picture);
+ }
+ setImageDrawable(mDefaultAvatar);
+ }
/**
* Assign the contact uri that this QuickContactBadge should be associated
diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java
index 7d3459e..d1c2270 100644
--- a/core/java/android/widget/SimpleCursorAdapter.java
+++ b/core/java/android/widget/SimpleCursorAdapter.java
@@ -62,7 +62,8 @@
private int mStringConversionColumn = -1;
private CursorToStringConverter mCursorToStringConverter;
private ViewBinder mViewBinder;
- private String[] mOriginalFrom;
+
+ String[] mOriginalFrom;
/**
* Constructor.
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index 3df419a..450c966 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -66,8 +66,9 @@
* {@link #setZoomInEnabled(boolean)} and {@link #setZoomOutEnabled(boolean)}.
* <p>
* If you are using this with a custom View, please call
- * {@link #setVisible(boolean) setVisible(false)} from the
- * {@link View#onDetachedFromWindow}.
+ * {@link #setVisible(boolean) setVisible(false)} from
+ * {@link View#onDetachedFromWindow} and from {@link View#onVisibilityChanged}
+ * when <code>visibility != View.VISIBLE</code>.
*
*/
public class ZoomButtonsController implements View.OnTouchListener {
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 8d8df16..e00a853 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -26,6 +26,7 @@
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -284,6 +285,26 @@
out.endTag(null, "list");
}
+
+ public static final void writeSetXml(Set val, String name, XmlSerializer out)
+ throws XmlPullParserException, java.io.IOException {
+ if (val == null) {
+ out.startTag(null, "null");
+ out.endTag(null, "null");
+ return;
+ }
+
+ out.startTag(null, "set");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+
+ for (Object v : val) {
+ writeValueXml(v, null, out);
+ }
+
+ out.endTag(null, "set");
+ }
/**
* Flatten a byte[] into an XmlSerializer. The list can later be read back
@@ -426,6 +447,9 @@
} else if (v instanceof List) {
writeListXml((List)v, name, out);
return;
+ } else if (v instanceof Set) {
+ writeSetXml((Set)v, name, out);
+ return;
} else if (v instanceof CharSequence) {
// XXX This is to allow us to at least write something if
// we encounter styled text... but it means we will drop all
@@ -476,7 +500,7 @@
*
* @param in The InputStream from which to read.
*
- * @return HashMap The resulting list.
+ * @return ArrayList The resulting list.
*
* @see #readMapXml
* @see #readValueXml
@@ -490,6 +514,29 @@
parser.setInput(in, null);
return (ArrayList)readValueXml(parser, new String[1]);
}
+
+
+ /**
+ * Read a HashSet from an InputStream containing XML. The stream can
+ * previously have been written by writeSetXml().
+ *
+ * @param in The InputStream from which to read.
+ *
+ * @return HashSet The resulting set.
+ *
+ * @throws XmlPullParserException
+ * @throws java.io.IOException
+ *
+ * @see #readValueXml
+ * @see #readThisSetXml
+ * @see #writeSetXml
+ */
+ public static final HashSet readSetXml(InputStream in)
+ throws XmlPullParserException, java.io.IOException {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ return (HashSet) readValueXml(parser, new String[1]);
+ }
/**
* Read a HashMap object from an XmlPullParser. The XML data could
@@ -573,6 +620,47 @@
throw new XmlPullParserException(
"Document ended before " + endTag + " end tag");
}
+
+ /**
+ * Read a HashSet object from an XmlPullParser. The XML data could previously
+ * have been generated by writeSetXml(). The XmlPullParser must be positioned
+ * <em>after</em> the tag that begins the set.
+ *
+ * @param parser The XmlPullParser from which to read the set data.
+ * @param endTag Name of the tag that will end the set, usually "set".
+ * @param name An array of one string, used to return the name attribute
+ * of the set's tag.
+ *
+ * @return HashSet The newly generated set.
+ *
+ * @throws XmlPullParserException
+ * @throws java.io.IOException
+ *
+ * @see #readSetXml
+ */
+ public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name)
+ throws XmlPullParserException, java.io.IOException {
+ HashSet set = new HashSet();
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.START_TAG) {
+ Object val = readThisValueXml(parser, name);
+ set.add(val);
+ //System.out.println("Adding to set: " + val);
+ } else if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return set;
+ }
+ throw new XmlPullParserException(
+ "Expected " + endTag + " end tag at: " + parser.getName());
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException(
+ "Document ended before " + endTag + " end tag");
+ }
/**
* Read an int[] object from an XmlPullParser. The XML data could
@@ -740,6 +828,12 @@
name[0] = valueName;
//System.out.println("Returning value for " + valueName + ": " + res);
return res;
+ } else if (tagName.equals("set")) {
+ parser.next();
+ res = readThisSetXml(parser, "set", name);
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + res);
+ return res;
} else {
throw new XmlPullParserException(
"Unknown tag: " + tagName);
diff --git a/core/java/com/android/internal/widget/ContactHeaderWidget.java b/core/java/com/android/internal/widget/ContactHeaderWidget.java
index f421466..a514089 100644
--- a/core/java/com/android/internal/widget/ContactHeaderWidget.java
+++ b/core/java/com/android/internal/widget/ContactHeaderWidget.java
@@ -55,7 +55,7 @@
/**
* Header used across system for displaying a title bar with contact info. You
* can bind specific values on the header, or use helper methods like
- * {@link #bindFromContactId(long)} to populate asynchronously.
+ * {@link #bindFromContactLookupUri(Uri)} to populate asynchronously.
* <p>
* The parent must request the {@link Manifest.permission#READ_CONTACTS}
* permission to access contact data.
@@ -257,7 +257,7 @@
if (photoBitmap == null) {
photoBitmap = loadPlaceholderPhoto(null);
}
- mPhotoView.setImageBitmap(photoBitmap);
+ setPhoto(photoBitmap);
if (cookie != null && cookie instanceof Uri) {
mPhotoView.assignContactUri((Uri) cookie);
}
@@ -267,21 +267,13 @@
case TOKEN_CONTACT_INFO: {
if (cursor != null && cursor.moveToFirst()) {
bindContactInfo(cursor);
- Uri lookupUri = Contacts.getLookupUri(cursor.getLong(ContactQuery._ID),
+ final Uri lookupUri = Contacts.getLookupUri(
+ cursor.getLong(ContactQuery._ID),
cursor.getString(ContactQuery.LOOKUP_KEY));
final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
- if (photoId == 0) {
- mPhotoView.setImageBitmap(loadPlaceholderPhoto(null));
- if (cookie != null && cookie instanceof Uri) {
- mPhotoView.assignContactUri((Uri) cookie);
- }
- invalidate();
- } else {
- startPhotoQuery(photoId, lookupUri,
- false /* don't reset query handler */);
- }
+ setPhotoId(photoId, lookupUri);
} else {
// shouldn't really happen
setDisplayName(null, null);
@@ -361,18 +353,40 @@
}
/**
- * Manually set the contact uri
+ * Manually set the presence. If presence is null, it is hidden.
+ * This doesn't change the underlying {@link Contacts} value, only the UI state.
+ * @hide
+ */
+ public void setPresence(Integer presence) {
+ if (presence == null) {
+ showPresence(false);
+ } else {
+ showPresence(true);
+ setPresence(presence.intValue());
+ }
+ }
+
+ /**
+ * Turn on/off showing the presence.
+ * @hide this is here for consistency with setStared/showStar and should be public
+ */
+ public void showPresence(boolean showPresence) {
+ mPresenceView.setVisibility(showPresence ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Manually set the contact uri without loading any data
*/
public void setContactUri(Uri uri) {
setContactUri(uri, true);
}
/**
- * Manually set the contact uri
+ * Manually set the contact uri without loading any data
*/
- public void setContactUri(Uri uri, boolean sendToFastrack) {
+ public void setContactUri(Uri uri, boolean sendToQuickContact) {
mContactUri = uri;
- if (sendToFastrack) {
+ if (sendToQuickContact) {
mPhotoView.assignContactUri(uri);
}
}
@@ -386,6 +400,22 @@
}
/**
+ * Manually set the photo given its id. If the id is 0, a placeholder picture will
+ * be loaded. For any other Id, an async query is started
+ * @hide
+ */
+ public void setPhotoId(final long photoId, final Uri lookupUri) {
+ if (photoId == 0) {
+ setPhoto(loadPlaceholderPhoto(null));
+ mPhotoView.assignContactUri(lookupUri);
+ invalidate();
+ } else {
+ startPhotoQuery(photoId, lookupUri,
+ false /* don't reset query handler */);
+ }
+ }
+
+ /**
* Manually set the display name and phonetic name to show in the header.
* This doesn't change the underlying {@link Contacts}, only the UI state.
*/
@@ -400,7 +430,8 @@
}
/**
- * Manually set the social snippet text to display in the header.
+ * Manually set the social snippet text to display in the header. This doesn't change the
+ * underlying {@link Contacts}, only the UI state.
*/
public void setSocialSnippet(CharSequence snippet) {
if (snippet == null) {
@@ -413,6 +444,20 @@
}
/**
+ * Manually set the status attribution text to display in the header.
+ * This doesn't change the underlying {@link Contacts}, only the UI state.
+ * @hide
+ */
+ public void setStatusAttribution(CharSequence attribution) {
+ if (attribution != null) {
+ mStatusAttributionView.setText(attribution);
+ mStatusAttributionView.setVisibility(View.VISIBLE);
+ } else {
+ mStatusAttributionView.setVisibility(View.GONE);
+ }
+ }
+
+ /**
* Set a list of specific MIME-types to exclude and not display. For
* example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
* profile icon.
@@ -423,6 +468,88 @@
}
/**
+ * Manually set all the status values to display in the header.
+ * This doesn't change the underlying {@link Contacts}, only the UI state.
+ * @hide
+ * @param status The status of the contact. If this is either null or empty,
+ * the status is cleared and the other parameters are ignored.
+ * @param statusTimestamp The timestamp (retrieved via a call to
+ * {@link System#currentTimeMillis()}) of the last status update.
+ * This value can be null if it is not known.
+ * @param statusLabel The id of a resource string that specifies the current
+ * status. This value can be null if no Label should be used.
+ * @param statusResPackage The name of the resource package containing the resource string
+ * referenced in the parameter statusLabel.
+ */
+ public void setStatus(final String status, final Long statusTimestamp,
+ final Integer statusLabel, final String statusResPackage) {
+ if (TextUtils.isEmpty(status)) {
+ setSocialSnippet(null);
+ return;
+ }
+
+ setSocialSnippet(status);
+
+ final CharSequence timestampDisplayValue;
+
+ if (statusTimestamp != null) {
+ // Set the date/time field by mixing relative and absolute
+ // times.
+ int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
+
+ timestampDisplayValue = DateUtils.getRelativeTimeSpanString(
+ statusTimestamp.longValue(), System.currentTimeMillis(),
+ DateUtils.MINUTE_IN_MILLIS, flags);
+ } else {
+ timestampDisplayValue = null;
+ }
+
+
+ String labelDisplayValue = null;
+
+ if (statusLabel != null) {
+ Resources resources;
+ if (TextUtils.isEmpty(statusResPackage)) {
+ resources = getResources();
+ } else {
+ PackageManager pm = getContext().getPackageManager();
+ try {
+ resources = pm.getResourcesForApplication(statusResPackage);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Contact status update resource package not found: "
+ + statusResPackage);
+ resources = null;
+ }
+ }
+
+ if (resources != null) {
+ try {
+ labelDisplayValue = resources.getString(statusLabel.intValue());
+ } catch (NotFoundException e) {
+ Log.w(TAG, "Contact status update resource not found: " + statusResPackage + "@"
+ + statusLabel.intValue());
+ }
+ }
+ }
+
+ final CharSequence attribution;
+ if (timestampDisplayValue != null && labelDisplayValue != null) {
+ attribution = getContext().getString(
+ R.string.contact_status_update_attribution_with_date,
+ timestampDisplayValue, labelDisplayValue);
+ } else if (timestampDisplayValue == null && labelDisplayValue != null) {
+ attribution = getContext().getString(
+ R.string.contact_status_update_attribution,
+ labelDisplayValue);
+ } else if (timestampDisplayValue != null) {
+ attribution = timestampDisplayValue;
+ } else {
+ attribution = null;
+ }
+ setStatusAttribution(attribution);
+ }
+
+ /**
* Convenience method for binding all available data from an existing
* contact.
*
@@ -543,89 +670,28 @@
this.setDisplayName(displayName, phoneticName);
final boolean starred = c.getInt(ContactQuery.STARRED) != 0;
- mStarredView.setChecked(starred);
+ setStared(starred);
//Set the presence status
if (!c.isNull(ContactQuery.CONTACT_PRESENCE_STATUS)) {
int presence = c.getInt(ContactQuery.CONTACT_PRESENCE_STATUS);
- mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(presence));
- mPresenceView.setVisibility(View.VISIBLE);
+ setPresence(presence);
+ showPresence(true);
} else {
- mPresenceView.setVisibility(View.GONE);
+ showPresence(false);
}
//Set the status update
- String status = c.getString(ContactQuery.CONTACT_STATUS);
- if (!TextUtils.isEmpty(status)) {
- mStatusView.setText(status);
- mStatusView.setVisibility(View.VISIBLE);
+ final String status = c.getString(ContactQuery.CONTACT_STATUS);
+ final Long statusTimestamp = c.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
+ ? null
+ : c.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
+ final Integer statusLabel = c.isNull(ContactQuery.CONTACT_STATUS_LABEL)
+ ? null
+ : c.getInt(ContactQuery.CONTACT_STATUS_LABEL);
+ final String statusResPackage = c.getString(ContactQuery.CONTACT_STATUS_RES_PACKAGE);
- CharSequence timestamp = null;
-
- if (!c.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)) {
- long date = c.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
-
- // Set the date/time field by mixing relative and absolute
- // times.
- int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
-
- timestamp = DateUtils.getRelativeTimeSpanString(date,
- System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags);
- }
-
- String label = null;
-
- if (!c.isNull(ContactQuery.CONTACT_STATUS_LABEL)) {
- String resPackage = c.getString(ContactQuery.CONTACT_STATUS_RES_PACKAGE);
- int labelResource = c.getInt(ContactQuery.CONTACT_STATUS_LABEL);
- Resources resources;
- if (TextUtils.isEmpty(resPackage)) {
- resources = getResources();
- } else {
- PackageManager pm = getContext().getPackageManager();
- try {
- resources = pm.getResourcesForApplication(resPackage);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Contact status update resource package not found: "
- + resPackage);
- resources = null;
- }
- }
-
- if (resources != null) {
- try {
- label = resources.getString(labelResource);
- } catch (NotFoundException e) {
- Log.w(TAG, "Contact status update resource not found: " + resPackage + "@"
- + labelResource);
- }
- }
- }
-
- CharSequence attribution;
- if (timestamp != null && label != null) {
- attribution = getContext().getString(
- R.string.contact_status_update_attribution_with_date,
- timestamp, label);
- } else if (timestamp == null && label != null) {
- attribution = getContext().getString(
- R.string.contact_status_update_attribution,
- label);
- } else if (timestamp != null) {
- attribution = timestamp;
- } else {
- attribution = null;
- }
- if (attribution != null) {
- mStatusAttributionView.setText(attribution);
- mStatusAttributionView.setVisibility(View.VISIBLE);
- } else {
- mStatusAttributionView.setVisibility(View.GONE);
- }
- } else {
- mStatusView.setVisibility(View.GONE);
- mStatusAttributionView.setVisibility(View.GONE);
- }
+ setStatus(status, statusTimestamp, statusLabel, statusResPackage);
}
public void onClick(View view) {
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
index fa47ff6..23e2277 100644
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ b/core/java/com/android/internal/widget/DigitalClock.java
@@ -30,7 +30,7 @@
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import java.text.DateFormatSymbols;
@@ -39,7 +39,7 @@
/**
* Displays the time
*/
-public class DigitalClock extends LinearLayout {
+public class DigitalClock extends RelativeLayout {
private final static String M12 = "h:mm";
private final static String M24 = "kk:mm";
diff --git a/core/res/assets/images/combobox-disabled.png b/core/res/assets/images/combobox-disabled.png
deleted file mode 100644
index fe220e4..0000000
--- a/core/res/assets/images/combobox-disabled.png
+++ /dev/null
Binary files differ
diff --git a/core/res/assets/images/combobox-noHighlight.png b/core/res/assets/images/combobox-noHighlight.png
deleted file mode 100644
index abcdf72..0000000
--- a/core/res/assets/images/combobox-noHighlight.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/combobox_disabled.png b/core/res/res/drawable-hdpi/combobox_disabled.png
new file mode 100644
index 0000000..50eb45e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/combobox_disabled.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/combobox_nohighlight.png b/core/res/res/drawable-hdpi/combobox_nohighlight.png
new file mode 100644
index 0000000..9d60301
--- /dev/null
+++ b/core/res/res/drawable-hdpi/combobox_nohighlight.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_nobadge.9.png b/core/res/res/drawable-hdpi/quickcontact_nobadge.9.png
new file mode 100644
index 0000000..68d43c4
--- /dev/null
+++ b/core/res/res/drawable-hdpi/quickcontact_nobadge.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_action_call.png b/core/res/res/drawable-hdpi/sym_action_call.png
index 105f7d0..da8afee 100644
--- a/core/res/res/drawable-hdpi/sym_action_call.png
+++ b/core/res/res/drawable-hdpi/sym_action_call.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/combobox_disabled.png b/core/res/res/drawable-mdpi/combobox_disabled.png
new file mode 100644
index 0000000..c32db7e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/combobox_disabled.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/combobox_nohighlight.png b/core/res/res/drawable-mdpi/combobox_nohighlight.png
new file mode 100644
index 0000000..1963316
--- /dev/null
+++ b/core/res/res/drawable-mdpi/combobox_nohighlight.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/quickcontact_nobadge.9.png b/core/res/res/drawable-mdpi/quickcontact_nobadge.9.png
new file mode 100644
index 0000000..ebe747b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/quickcontact_nobadge.9.png
Binary files differ
diff --git a/core/res/res/drawable/ic_btn_back.png b/core/res/res/drawable/ic_btn_back.png
new file mode 100644
index 0000000..c9bff4c
--- /dev/null
+++ b/core/res/res/drawable/ic_btn_back.png
Binary files differ
diff --git a/core/res/res/drawable/ic_btn_next.png b/core/res/res/drawable/ic_btn_next.png
new file mode 100755
index 0000000..c6cf436
--- /dev/null
+++ b/core/res/res/drawable/ic_btn_next.png
Binary files differ
diff --git a/core/res/res/drawable/quickcontact_nobadge.xml b/core/res/res/drawable/quickcontact_nobadge.xml
deleted file mode 100644
index 922fa0e..0000000
--- a/core/res/res/drawable/quickcontact_nobadge.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* bubble_with_chats.xml
-**
-** Copyright 2009, Google Inc.
-**
-** 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.
-*/
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true" android:drawable="@drawable/quickcontact_nobadge_pressed" />
- <item android:state_selected="true" android:drawable="@drawable/quickcontact_nobadge_highlight" />
- <item android:state_focused="true" android:drawable="@drawable/quickcontact_nobadge_highlight" />
- <item android:state_enabled="false" android:drawable="@drawable/quickcontact_nobadge_normal" />
- <item android:drawable="@drawable/quickcontact_nobadge_normal" />
-</selector>
diff --git a/core/res/res/drawable/quickcontact_nobadge_highlight.9.png b/core/res/res/drawable/quickcontact_nobadge_highlight.9.png
deleted file mode 100644
index f0f50b3..0000000
--- a/core/res/res/drawable/quickcontact_nobadge_highlight.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/quickcontact_nobadge_normal.9.png b/core/res/res/drawable/quickcontact_nobadge_normal.9.png
deleted file mode 100644
index 01cc9dc..0000000
--- a/core/res/res/drawable/quickcontact_nobadge_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/quickcontact_nobadge_pressed.9.png b/core/res/res/drawable/quickcontact_nobadge_pressed.9.png
deleted file mode 100644
index 6e22c87..0000000
--- a/core/res/res/drawable/quickcontact_nobadge_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index c1b406f..83381a1 100644
--- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -58,18 +58,19 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
/>
+
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="72sp"
@@ -84,8 +85,9 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="bottom"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
android:ellipsize="none"
android:textSize="22sp"
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index 74a0eee..8dacfaf 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -55,12 +55,12 @@
android:layout_alignParentTop="true"
android:layout_marginTop="15dip"
android:layout_marginLeft="20dip"
+ android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="56sp"
@@ -74,8 +74,9 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="bottom"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
android:ellipsize="none"
android:textSize="18sp"
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
index 8f86981..844d338 100644
--- a/core/res/res/layout/preference_list_content.xml
+++ b/core/res/res/layout/preference_list_content.xml
@@ -4,22 +4,58 @@
**
** Copyright 2006, 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
+** 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
+** 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
+** 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.
*/
-->
-<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
- android:layout_width="match_parent"
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
android:layout_height="match_parent"
- android:drawSelectorOnTop="false"
- android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:layout_width="match_parent">
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
/>
+
+ <RelativeLayout android:id="@+id/button_bar"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:layout_weight="0"
+ android:background="@android:drawable/bottom_bar"
+ android:visibility="gone">
+
+ <Button android:id="@+id/back_button"
+ android:layout_width="150dip"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dip"
+ android:layout_alignParentLeft="true"
+ android:drawableLeft="@drawable/ic_btn_back"
+ android:drawablePadding="3dip"
+ android:text="@string/back_button_label"
+ />
+
+ <Button android:id="@+id/next_button"
+ android:layout_width="150dip"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dip"
+ android:layout_alignParentRight="true"
+ android:drawableRight="@drawable/ic_btn_next"
+ android:drawablePadding="3dip"
+ android:text="@string/next_button_label"
+ />
+ </RelativeLayout>
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a3ccaf7..67a88ae 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1518,6 +1518,8 @@
will use only the number of items in the adapter and the number of items visible
on screen to determine the scrollbar's properties. -->
<attr name="smoothScrollbar" format="boolean" />
+ <!-- A reference to an XML description of the adapter to attach to the list. -->
+ <attr name="adapter" format="reference" />
</declare-styleable>
<declare-styleable name="AbsSpinner">
<!-- Reference to an array resource that will populate the Spinner. For static content,
@@ -2504,6 +2506,10 @@
<attr name="drawable" />
</declare-styleable>
+ <declare-styleable name="MipmapDrawableItem">
+ <attr name="drawable" />
+ </declare-styleable>
+
<declare-styleable name="RotateDrawable">
<attr name="visible" />
<attr name="fromDegrees" format="float" />
@@ -3337,6 +3343,16 @@
<attr name="entryValues" format="reference" />
</declare-styleable>
+ <declare-styleable name="MultiSelectListPreference">
+ <!-- The human-readable array to present as a list. Each entry must have a corresponding
+ index in entryValues. -->
+ <attr name="entries" />
+ <!-- The array to find the value to save for a preference when an entry from
+ entries is selected. If a user clicks the second item in entries, the
+ second item in this array will be saved to the preference. -->
+ <attr name="entryValues" />
+ </declare-styleable>
+
<!-- Base attributes available to RingtonePreference. -->
<declare-styleable name="RingtonePreference">
<!-- Which ringtone type(s) to show in the picker. -->
@@ -3644,5 +3660,68 @@
<declare-styleable name="RecognitionService">
<attr name="settingsActivity" />
</declare-styleable>
+
+ <!-- =============================== -->
+ <!-- Adapters attributes -->
+ <!-- =============================== -->
+ <eat-comment />
+
+ <!-- Adapter used to bind cursors. -->
+ <declare-styleable name="CursorAdapter">
+ <!-- URI to get the cursor from. Optional. -->
+ <attr name="uri" format="string" />
+ <!-- Selection statement for the query. Optional. -->
+ <attr name="selection" format="string" />
+ <!-- Sort order statement for the query. Optional. -->
+ <attr name="sortOrder" format="string" />
+ <!-- Layout resource used to display each row from the cursor. Mandatory. -->
+ <attr name="layout" />
+ </declare-styleable>
+
+ <!-- Attributes used in bind items for XML cursor adapters. -->
+ <declare-styleable name="CursorAdapter_BindItem">
+ <!-- The name of the column to bind from. Mandatory. -->
+ <attr name="from" format="string" />
+ <!-- The resource id of the view to bind to. Mandatory. -->
+ <attr name="to" format="reference" />
+ <!-- The type of binding. If this value is not specified, the type will be
+ inferred from the type of the "to" target view. Mandatory.
+
+ The type can be one of:
+ <ul>
+ <li>string, The content of the column is interpreted as a string.</li>
+ <li>image, The content of the column is interpreted as a blob describing an image.</li>
+ <li>image-uri, The content of the column is interpreted as a URI to an image.</li>
+ <li>drawable, The content of the column is interpreted as a resource id to a drawable.</li>
+ <li>A fully qualified class name, corresponding to an implementation of
+ android.widget.Adapters.CursorBinder.</li>
+ </ul>
+ -->
+ <attr name="as" format="string" />
+ </declare-styleable>
+
+ <!-- Attributes used in select items for XML cursor adapters. -->
+ <declare-styleable name="CursorAdapter_SelectItem">
+ <!-- The name of the column to select. Mandatory. -->
+ <attr name="column" format="string" />
+ </declare-styleable>
+
+ <!-- Attributes used to map values to new values in XML cursor adapters' bind items. -->
+ <declare-styleable name="CursorAdapter_MapItem">
+ <!-- The original value from the column. Mandatory. -->
+ <attr name="fromValue" format="string" />
+ <!-- The new value from the column. Mandatory. -->
+ <attr name="toValue" format="string" />
+ </declare-styleable>
+
+ <!-- Attributes used to map values to new values in XML cursor adapters' bind items. -->
+ <declare-styleable name="CursorAdapter_TransformItem">
+ <!-- The transformation expression. Mandatory if "withClass" is not specified. -->
+ <attr name="withExpression" format="string" />
+ <!-- The transformation class, an implementation of
+ android.widget.Adapters.CursorTransformation. Mandatory if "withExpression"
+ is not specified. -->
+ <attr name="withClass" format="string" />
+ </declare-styleable>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 26d11cd..03152b1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1238,7 +1238,7 @@
<public type="id" name="custom" id="0x0102002b" />
<public type="anim" name="cycle_interpolator" id="0x010a000c" />
-
+
<!-- ===============================================================
Resources introduced in kraken.
=============================================================== -->
@@ -1257,4 +1257,21 @@
<public-padding type="color" name="kraken_resource_pad" end="0x01060020" />
<public-padding type="array" name="kraken_resource_pad" end="0x01070010" />
+<!-- ===============================================================
+ Resources proposed for Gingerbread.
+ =============================================================== -->
+ <eat-comment />
+ <public type="attr" name="adapter" />
+ <public type="attr" name="selection" />
+ <public type="attr" name="sortOrder" />
+ <public type="attr" name="uri" />
+ <public type="attr" name="from" />
+ <public type="attr" name="to" />
+ <public type="attr" name="as" />
+ <public type="attr" name="fromValue" />
+ <public type="attr" name="toValue" />
+ <public type="attr" name="column" />
+ <public type="attr" name="withExpression" />
+ <public type="attr" name="withClass" />
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a76c70c..f37a927 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2260,6 +2260,10 @@
<string name="tethered_notification_title">Tethering active</string>
<string name="tethered_notification_message">Touch to configure</string>
+ <!-- Strings for possible PreferenceActivity Back/Next buttons -->
+ <string name="back_button_label">Back</string>
+ <string name="next_button_label">Next</string>
+
<!-- Strings for throttling notification -->
<!-- Shown when the user is in danger of being throttled -->
<string name="throttle_warning_notification_title">High mobile data use</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index b5fff96..f9b0667 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -552,6 +552,7 @@
<item name="android:background">@android:drawable/quickcontact_badge</item>
<item name="android:clickable">true</item>
<item name="android:scaleType">fitCenter</item>
+ <item name="android:src">@android:drawable/ic_contact_picture</item>
</style>
<style name="Widget.QuickContactBadgeSmall">
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
index 656029d..34434d2 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -33,6 +33,7 @@
import junit.framework.Assert;
import java.io.File;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
@@ -1023,6 +1024,34 @@
}
}
+ @MediumTest
+ public void testUnionsWithBindArgs() {
+ /* make sure unions with bindargs work http://b/issue?id=1061291 */
+ mDatabase.execSQL("CREATE TABLE A (i int);");
+ mDatabase.execSQL("create table B (k int);");
+ mDatabase.execSQL("create table C (n int);");
+ mDatabase.execSQL("insert into A values(1);");
+ mDatabase.execSQL("insert into A values(2);");
+ mDatabase.execSQL("insert into A values(3);");
+ mDatabase.execSQL("insert into B values(201);");
+ mDatabase.execSQL("insert into B values(202);");
+ mDatabase.execSQL("insert into B values(203);");
+ mDatabase.execSQL("insert into C values(901);");
+ mDatabase.execSQL("insert into C values(902);");
+ String s = "select i from A where i > 2 " +
+ "UNION select k from B where k > 201 " +
+ "UNION select n from C where n !=900;";
+ Cursor c = mDatabase.rawQuery(s, null);
+ int n = c.getCount();
+ c.close();
+ String s1 = "select i from A where i > ? " +
+ "UNION select k from B where k > ? " +
+ "UNION select n from C where n != ?;";
+ Cursor c1 = mDatabase.rawQuery(s1, new String[]{"2", "201", "900"});
+ assertEquals(n, c1.getCount());
+ c1.close();
+ }
+
/**
* This test is available only when the platform has a locale with the language "ja".
* It finishes without failure when it is not available.
@@ -1108,5 +1137,44 @@
}
}
}
- }
+ }
+
+ @SmallTest
+ public void testLruCachingOfSqliteCompiledSqlObjs() {
+ mDatabase.execSQL("CREATE TABLE test (i int, j int);");
+ mDatabase.execSQL("insert into test values(1,1);");
+ // set cache size
+ int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
+ mDatabase.setMaxSqlCacheSize(N);
+
+ // do N+1 queries - and when the 0th entry is removed from LRU cache due to the
+ // insertion of (N+1)th entry, make sure 0th entry is closed
+ ArrayList<SQLiteStatement> stmtObjs = new ArrayList<SQLiteStatement>();
+ for (int i = 0; i < N+1; i++) {
+ SQLiteStatement c = mDatabase.compileStatement("select * from test where i = " + i);
+ c.close();
+ stmtObjs.add(i, c);
+ }
+
+ assertEquals(0, stmtObjs.get(0).getUniqueId());
+ for (int i = 1; i < N+1; i++) {
+ assertTrue(stmtObjs.get(i).getUniqueId() > 0);
+ }
+ }
+
+ @SmallTest
+ public void testSetMaxCahesize() {
+ mDatabase.execSQL("CREATE TABLE test (i int, j int);");
+ mDatabase.execSQL("insert into test values(1,1);");
+ // set cache size
+ int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
+ mDatabase.setMaxSqlCacheSize(N);
+
+ // try reduce cachesize
+ try {
+ mDatabase.setMaxSqlCacheSize(1);
+ } catch (IllegalStateException e) {
+ assertTrue(e.getMessage().contains("cannot set cacheSize to a value less than"));
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
index 004a197..0820a68 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
@@ -17,7 +17,9 @@
package android.pim.vcard;
import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.test_utils.ContactEntry;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -31,8 +33,6 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
-
import java.util.Arrays;
/**
@@ -275,6 +275,16 @@
testPhoneBasicCommon(V30);
}
+ public void testPhoneRefrainFormatting() {
+ mVerifier.initForExportTest(V21 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING);
+ mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1234567890(abcdefghijklmnopqrstuvwxyz)")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "1234567890(abcdefghijklmnopqrstuvwxyz)",
+ new TypeSet("HOME"));
+ }
+
/**
* Tests that vCard composer emits corresponding type param which we expect.
*/
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
index 21f2254..ea2ac6a 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
@@ -16,7 +16,9 @@
package android.pim.vcard;
import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.test_utils.ContentValuesVerifier;
+import android.pim.vcard.test_utils.ContentValuesVerifierElem;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
@@ -29,7 +31,6 @@
import android.provider.ContactsContract.CommonDataKinds.Website;
import com.android.frameworks.coretests.R;
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
import java.util.Arrays;
@@ -410,7 +411,7 @@
}
public void testV21SimpleCase1_Type_Generic() {
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, R.raw.v21_simple_1);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_simple_1);
mVerifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "Ando")
@@ -419,7 +420,7 @@
}
public void testV21SimpleCase1_Type_Japanese() {
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_1);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_1);
mVerifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "Ando")
@@ -431,7 +432,7 @@
}
public void testV21SimpleCase2() {
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_2);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_2);
mVerifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.DISPLAY_NAME, "Ando Roid");
@@ -717,8 +718,7 @@
// Though Japanese careers append ";;;;" at the end of the value of "SOUND",
// vCard 2.1/3.0 specification does not allow multiple values.
// Do not need to handle it as multiple values.
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
- R.raw.v21_japanese_1);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_1);
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
@@ -752,35 +752,34 @@
/**
* Verifies vCard with Japanese can be parsed correctly with
- * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC_UTF8}.
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC}.
*/
public void testV21Japanese1_Type_Generic_Utf8() {
testV21Japanese1Common(
- R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, false);
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC, false);
}
/**
* Verifies vCard with Japanese can be parsed correctly with
- * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_SJIS}.
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
*/
public void testV21Japanese1_Type_Japanese_Sjis() {
testV21Japanese1Common(
- R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, true);
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
}
/**
* Verifies vCard with Japanese can be parsed correctly with
- * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_UTF8}.
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
* since vCard 2.1 specifies the charset of each line if it contains non-Ascii.
*/
public void testV21Japanese1_Type_Japanese_Utf8() {
testV21Japanese1Common(
- R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8, true);
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
}
public void testV21Japanese2_Parsing() {
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
- R.raw.v21_japanese_2);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_2);
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
@@ -838,8 +837,7 @@
}
public void testV21MultipleEntryCase_Parse() {
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
- R.raw.v21_multiple_entry);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
@@ -882,8 +880,7 @@
}
public void testV21MultipleEntryCase() {
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
- R.raw.v21_multiple_entry);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
index 5b60342..17ee322 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
@@ -17,15 +17,16 @@
package android.pim.vcard;
import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.test_utils.ContactEntry;
+import android.pim.vcard.test_utils.ContentValuesBuilder;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
-
import java.util.Arrays;
public class VCardJapanizationTests extends VCardTestsBase {
@@ -50,15 +51,15 @@
}
public void testNameUtf8V21() {
- testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE);
}
public void testNameUtf8V30() {
- testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE);
}
public void testNameShiftJis() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
@@ -80,7 +81,7 @@
* DoCoMo phones require all name elements should be in "family name" field.
*/
public void testNameDoCoMo() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
@@ -105,8 +106,8 @@
.addExpectedNode("X-DCM-HMN-MODE", "");
}
- private void testPhoneticNameCommon(int vcardType) {
- mVerifier.initForExportTest(vcardType);
+ private void testPhoneticNameCommon(int vcardType, String charset) {
+ mVerifier.initForExportTest(vcardType, charset);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -114,7 +115,7 @@
.put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
final ContentValues contentValues =
- (VCardConfig.usesShiftJis(vcardType) ?
+ ("SHIFT_JIS".equalsIgnoreCase(charset) ?
(VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
mContentValuesForQPAndSJis) :
(VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8));
@@ -142,23 +143,23 @@
}
public void testPhoneticNameForJapaneseV21Utf8() {
- testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, null);
}
public void testPhoneticNameForJapaneseV21Sjis() {
- testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
}
public void testPhoneticNameForJapaneseV30Utf8() {
- testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, null);
}
public void testPhoneticNameForJapaneseV30SJis() {
- testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
}
public void testPhoneticNameForMobileV21_1() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -182,7 +183,7 @@
}
public void testPhoneticNameForMobileV21_2() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -198,8 +199,8 @@
.put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73");
}
- private void testPostalAddressWithJapaneseCommon(int vcardType) {
- mVerifier.initForExportTest(vcardType);
+ private void testPostalAddressWithJapaneseCommon(int vcardType, String charset) {
+ mVerifier.initForExportTest(vcardType, charset);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
@@ -214,7 +215,7 @@
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
.put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
- ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ?
+ ContentValues contentValues = ("UTF-8".equalsIgnoreCase(charset) ?
(VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
mContentValuesForQPAndSJis) :
(VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 :
@@ -240,7 +241,7 @@
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
}
public void testPostalAddresswithJapaneseV21() {
- testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
}
/**
@@ -248,7 +249,7 @@
* Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM
*/
public void testPostalAdrressForDoCoMo_1() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
@@ -276,7 +277,7 @@
}
public void testPostalAdrressForDoCoMo_2() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
@@ -301,7 +302,7 @@
}
public void testPostalAdrressForDoCoMo_3() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
@@ -329,7 +330,7 @@
* Verifies the vCard exporter tolerates null TYPE.
*/
public void testPostalAdrressForDoCoMo_4() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "1");
@@ -371,15 +372,15 @@
}
public void testJapanesePhoneNumberV21_1() {
- testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE);
}
public void testJapanesePhoneNumberV30() {
- testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE);
}
public void testJapanesePhoneNumberDoCoMo() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "0312341234")
@@ -399,7 +400,7 @@
}
public void testNoteDoCoMo() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Note.CONTENT_ITEM_TYPE)
.put(Note.NOTE, "note1");
@@ -421,7 +422,7 @@
}
public void testAndroidCustomV21() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC);
mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
.put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
index 0857e0c..383a9af 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
@@ -16,103 +16,17 @@
package android.pim.vcard;
-import android.content.ContentProvider;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
import android.content.ContentValues;
-import android.content.EntityIterator;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.database.CursorWindow;
-import android.database.IBulkCursor;
-import android.database.IContentObserver;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.test_utils.VCardVerifier;
import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * Almost a dead copy of android.test.mock.MockContentProvider, but different in that this
- * class extends ContentProvider, not implementing IContentProvider,
- * so that MockContentResolver is able to accept this class :(
- */
-class MockContentProvider extends ContentProvider {
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public int bulkInsert(Uri url, ContentValues[] initialValues) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @SuppressWarnings("unused")
- public IBulkCursor bulkQuery(Uri url, String[] projection, String selection,
- String[] selectionArgs, String sortOrder, IContentObserver observer,
- CursorWindow window) throws RemoteException {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- @SuppressWarnings("unused")
- public int delete(Uri url, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public String getType(Uri url) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public Uri insert(Uri url, ContentValues initialValues) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public ParcelFileDescriptor openFile(Uri url, String mode) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public AssetFileDescriptor openAssetFile(Uri uri, String mode) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- public IBinder asBinder() {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-}
/**
* BaseClass for vCard unit tests with utility classes.
* Please do not add each unit test here.
*/
/* package */ class VCardTestsBase extends AndroidTestCase {
- public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8;
- public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8;
+ public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC;
+ public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC;
// Do not modify these during tests.
protected final ContentValues mContentValuesForQP;
@@ -128,6 +42,7 @@
public VCardTestsBase() {
super();
+ // Not using constants in vCard code since it may be wrong.
mContentValuesForQP = new ContentValues();
mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
mContentValuesForSJis = new ContentValues();
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java
new file mode 100644
index 0000000..843750e
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java
@@ -0,0 +1,43 @@
+/*
+ * 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.pim.vcard.test_utils;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * The class representing one contact, which should contain multiple ContentValues like
+ * StructuredName, Email, etc.
+ * </p>
+ */
+public final class ContactEntry {
+ private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
+
+ public ContentValuesBuilder addContentValues(String mimeType) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Data.MIMETYPE, mimeType);
+ mContentValuesList.add(contentValues);
+ return new ContentValuesBuilder(contentValues);
+ }
+
+ public List<ContentValues> getList() {
+ return mContentValuesList;
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
similarity index 96%
rename from core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
index b3c0773..5c24186 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
@@ -22,7 +21,7 @@
* ContentValues-like class which enables users to chain put() methods and restricts
* the other methods.
*/
-/* package */ class ContentValuesBuilder {
+public class ContentValuesBuilder {
private final ContentValues mContentValues;
public ContentValuesBuilder(final ContentValues contentValues) {
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
similarity index 92%
rename from core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
index b9e9875..f2516ed 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.pim.vcard.VCardConfig;
import android.pim.vcard.VCardEntry;
@@ -30,7 +30,7 @@
import java.util.ArrayList;
import java.util.List;
-/* package */ class ContentValuesVerifier implements VCardEntryHandler {
+public class ContentValuesVerifier implements VCardEntryHandler {
private AndroidTestCase mTestCase;
private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
new ArrayList<ContentValuesVerifierElem>();
@@ -56,7 +56,7 @@
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
- vCardParser = new VCardParser_V30(true); // use StrictParsing
+ vCardParser = new VCardParser_V30();
} else {
vCardParser = new VCardParser_V21();
}
@@ -66,7 +66,7 @@
public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
VCardEntryConstructor builder =
- new VCardEntryConstructor(null, null, false, vCardType, null);
+ new VCardEntryConstructor(vCardType, null, null, false);
builder.addEntryHandler(this);
try {
vCardParser.parse(is, builder);
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
similarity index 92%
rename from core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
index 2edbb36..66d69b3 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
import android.pim.vcard.VCardConfig;
@@ -31,7 +31,7 @@
import java.io.IOException;
import java.io.InputStream;
-/* package */ class ContentValuesVerifierElem {
+public class ContentValuesVerifierElem {
private final AndroidTestCase mTestCase;
private final ImportTestResolver mResolver;
private final VCardEntryHandler mHandler;
@@ -57,12 +57,12 @@
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
- vCardParser = new VCardParser_V30(true); // use StrictParsing
+ vCardParser = new VCardParser_V30();
} else {
vCardParser = new VCardParser_V21();
}
VCardEntryConstructor builder =
- new VCardEntryConstructor(null, null, false, vCardType, null);
+ new VCardEntryConstructor(vCardType, null, null, false);
builder.addEntryHandler(mHandler);
try {
vCardParser.parse(is, builder);
diff --git a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
similarity index 63%
rename from core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
index 5968e83..0fbd9bb 100644
--- a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -21,10 +21,11 @@
import android.content.EntityIterator;
import android.database.Cursor;
import android.net.Uri;
+import android.pim.vcard.VCardComposer;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
-import android.test.mock.MockContentResolver;
+import android.test.mock.MockContentProvider;
import android.test.mock.MockCursor;
import junit.framework.TestCase;
@@ -33,76 +34,44 @@
import java.util.Iterator;
import java.util.List;
-/* package */ public class ExportTestResolver extends MockContentResolver {
- ExportTestProvider mProvider;
- public ExportTestResolver(TestCase testCase) {
- mProvider = new ExportTestProvider(testCase);
- addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
- addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
- }
-
- public ContactEntry addInputContactEntry() {
- return mProvider.buildInputEntry();
- }
-}
-
-/* package */ class MockEntityIterator implements EntityIterator {
- List<Entity> mEntityList;
- Iterator<Entity> mIterator;
-
- public MockEntityIterator(List<ContentValues> contentValuesList) {
- mEntityList = new ArrayList<Entity>();
- Entity entity = new Entity(new ContentValues());
- for (ContentValues contentValues : contentValuesList) {
- entity.addSubValue(Data.CONTENT_URI, contentValues);
- }
- mEntityList.add(entity);
- mIterator = mEntityList.iterator();
- }
-
- public boolean hasNext() {
- return mIterator.hasNext();
- }
-
- public Entity next() {
- return mIterator.next();
- }
-
- public void remove() {
- throw new UnsupportedOperationException("remove not supported");
- }
-
- public void reset() {
- mIterator = mEntityList.iterator();
- }
-
- public void close() {
- }
-}
-
-/**
- * Represents one contact, which should contain multiple ContentValues like
- * StructuredName, Email, etc.
- */
-/* package */ class ContactEntry {
- private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
-
- public ContentValuesBuilder addContentValues(String mimeType) {
- ContentValues contentValues = new ContentValues();
- contentValues.put(Data.MIMETYPE, mimeType);
- mContentValuesList.add(contentValues);
- return new ContentValuesBuilder(contentValues);
- }
-
- public List<ContentValues> getList() {
- return mContentValuesList;
- }
-}
-
/* package */ class ExportTestProvider extends MockContentProvider {
final private TestCase mTestCase;
final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
+ private static class MockEntityIterator implements EntityIterator {
+ List<Entity> mEntityList;
+ Iterator<Entity> mIterator;
+
+ public MockEntityIterator(List<ContentValues> contentValuesList) {
+ mEntityList = new ArrayList<Entity>();
+ Entity entity = new Entity(new ContentValues());
+ for (ContentValues contentValues : contentValuesList) {
+ entity.addSubValue(Data.CONTENT_URI, contentValues);
+ }
+ mEntityList.add(entity);
+ mIterator = mEntityList.iterator();
+ }
+
+ public boolean hasNext() {
+ return mIterator.hasNext();
+ }
+
+ public Entity next() {
+ return mIterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("remove not supported");
+ }
+
+ public void reset() {
+ mIterator = mEntityList.iterator();
+ }
+
+ public void close() {
+ }
+ }
+
public ExportTestProvider(TestCase testCase) {
mTestCase = testCase;
}
@@ -121,16 +90,6 @@
* We still keep using this method since we don't have a propeer way to know
* which value in the ContentValue corresponds to the entry in Contacts database.
* </p>
- * <p>
- * Detail:
- * There's an easy way to know which index "family name" corresponds to, via
- * {@link android.provider.ContactsContract}.
- * FAMILY_NAME equals DATA3, so the corresponding index
- * for "family name" should be 2 (note that index is 0-origin).
- * However, we cannot know what the index 2 corresponds to; it may be "family name",
- * "label" for now, but may be the other some column in the future. We don't have
- * convenient way to know the original data structure.
- * </p>
*/
public EntityIterator queryEntities(Uri uri,
String selection, String[] selectionArgs, String sortOrder) {
@@ -213,4 +172,4 @@
}
};
}
-}
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java
new file mode 100644
index 0000000..97e1e38
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2009 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.pim.vcard.test_utils;
+
+import android.pim.vcard.VCardComposer;
+import android.provider.ContactsContract.RawContacts;
+import android.test.mock.MockContentResolver;
+
+import junit.framework.TestCase;
+
+/* package */ class ExportTestResolver extends MockContentResolver {
+ private final ExportTestProvider mProvider;
+ public ExportTestResolver(TestCase testCase) {
+ mProvider = new ExportTestProvider(testCase);
+ addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
+ addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
+ }
+
+ public ContactEntry addInputContactEntry() {
+ return mProvider.buildInputEntry();
+ }
+
+ public ExportTestProvider getProvider() {
+ return mProvider;
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
similarity index 91%
rename from core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
index c3f6f79..ffbf95d 100644
--- a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
@@ -34,7 +34,7 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.test.mock.MockContentResolver;
+import android.test.mock.MockContentProvider;
import android.text.TextUtils;
import junit.framework.TestCase;
@@ -50,37 +50,6 @@
import java.util.TreeMap;
import java.util.Map.Entry;
-/* package */ class ImportTestResolver extends MockContentResolver {
- final ImportTestProvider mProvider;
-
- public ImportTestResolver(TestCase testCase) {
- mProvider = new ImportTestProvider(testCase);
- }
-
- @Override
- public ContentProviderResult[] applyBatch(String authority,
- ArrayList<ContentProviderOperation> operations) {
- equalsString(authority, RawContacts.CONTENT_URI.toString());
- return mProvider.applyBatch(operations);
- }
-
- public void addExpectedContentValues(ContentValues expectedContentValues) {
- mProvider.addExpectedContentValues(expectedContentValues);
- }
-
- public void verify() {
- mProvider.verify();
- }
-
- private static boolean equalsString(String a, String b) {
- if (a == null || a.length() == 0) {
- return b == null || b.length() == 0;
- } else {
- return a.equals(b);
- }
- }
-}
-
/* package */ class ImportTestProvider extends MockContentProvider {
private static final Set<String> sKnownMimeTypeSet =
new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE,
@@ -296,4 +265,4 @@
}
return true;
}
-}
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java
new file mode 100644
index 0000000..29738c6
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 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.pim.vcard.test_utils;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.provider.ContactsContract.RawContacts;
+import android.test.mock.MockContentResolver;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+
+/* package */ class ImportTestResolver extends MockContentResolver {
+ private final ImportTestProvider mProvider;
+
+ public ImportTestResolver(TestCase testCase) {
+ mProvider = new ImportTestProvider(testCase);
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(String authority,
+ ArrayList<ContentProviderOperation> operations) {
+ equalsString(authority, RawContacts.CONTENT_URI.toString());
+ return mProvider.applyBatch(operations);
+ }
+
+ public void addExpectedContentValues(ContentValues expectedContentValues) {
+ mProvider.addExpectedContentValues(expectedContentValues);
+ }
+
+ public void verify() {
+ mProvider.verify();
+ }
+
+ private static boolean equalsString(String a, String b) {
+ if (a == null || a.length() == 0) {
+ return b == null || b.length() == 0;
+ } else {
+ return a.equals(b);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
similarity index 94%
rename from core/tests/coretests/src/android/pim/vcard/LineVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
index cef15fd..3edec67 100644
--- a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.Context;
import android.pim.vcard.VCardComposer;
@@ -22,7 +22,7 @@
import java.util.ArrayList;
-class LineVerifier implements VCardComposer.OneEntryHandler {
+public class LineVerifier implements VCardComposer.OneEntryHandler {
private final TestCase mTestCase;
private final ArrayList<LineVerifierElem> mLineVerifierElemList;
private int mVCardType;
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
similarity index 97%
rename from core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
index b23b29b..4f7a9cf 100644
--- a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.pim.vcard.VCardConfig;
import android.text.TextUtils;
@@ -23,7 +23,7 @@
import java.util.ArrayList;
import java.util.List;
-class LineVerifierElem {
+public class LineVerifierElem {
private final TestCase mTestCase;
private final List<String> mExpectedLineList = new ArrayList<String>();
private final boolean mIsV30;
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
similarity index 95%
rename from core/tests/coretests/src/android/pim/vcard/PropertyNode.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
index 2c1f6d2..de7ad8e 100644
--- a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
import android.pim.vcard.VCardEntry;
-import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
@@ -26,12 +25,18 @@
import java.util.Set;
/**
+ * <p>
+ * The class representing one property (e.g. "N;ENCODING=UTF-8:family:given:middle:prefix:suffix").
+ * </p>
+ * <p>
* Previously used in main vCard handling code but now exists only for testing.
- *
+ * </p>
+ * <p>
* Especially useful for testing parser code (VCardParser), since all properties can be
* checked via this class unlike {@link VCardEntry}, which only emits the result of
* interpretation of the content of each vCard. We cannot know whether vCard parser or
- * ContactStruct is wrong withouth this class.
+ * {@link VCardEntry} is wrong without this class.
+ * </p>
*/
public class PropertyNode {
public String propName;
@@ -43,7 +48,8 @@
*/
public byte[] propValue_bytes;
- /** param store: key=paramType, value=paramValue
+ /**
+ * param store: key=paramType, value=paramValue
* Note that currently PropertyNode class does not support multiple param-values
* defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as
* one String value like "A,B", not ["A", "B"]...
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java
new file mode 100644
index 0000000..5c8215f
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2009 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.pim.vcard.test_utils;
+
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PropertyNodesVerifier extends VNodeBuilder {
+ private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
+ private final AndroidTestCase mAndroidTestCase;
+ private int mIndex;
+
+ public PropertyNodesVerifier(AndroidTestCase testCase) {
+ super();
+ mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
+ mAndroidTestCase = testCase;
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
+ PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
+ mPropertyNodesVerifierElemList.add(elem);
+ return elem;
+ }
+
+ public void verify(int resId, int vCardType)
+ throws IOException, VCardException {
+ verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
+ }
+
+ public void verify(int resId, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
+ vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+ final VCardParser vCardParser;
+ if (VCardConfig.isV30(vCardType)) {
+ vCardParser = new VCardParser_V30();
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ verify(is, vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ try {
+ vCardParser.parse(is, this);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void endEntry() {
+ super.endEntry();
+ mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
+ mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
+ mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
+ mIndex++;
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
similarity index 82%
rename from core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
index cfdd074..8c6c734 100644
--- a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -13,87 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardParser;
-import android.pim.vcard.VCardParser_V21;
-import android.pim.vcard.VCardParser_V30;
-import android.pim.vcard.exception.VCardException;
-import android.test.AndroidTestCase;
import junit.framework.TestCase;
-import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-/* package */ class PropertyNodesVerifier extends VNodeBuilder {
- private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
- private final AndroidTestCase mAndroidTestCase;
- private int mIndex;
-
- public PropertyNodesVerifier(AndroidTestCase testCase) {
- mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
- mAndroidTestCase = testCase;
- }
-
- public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
- PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
- mPropertyNodesVerifierElemList.add(elem);
- return elem;
- }
-
- public void verify(int resId, int vCardType)
- throws IOException, VCardException {
- verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
- }
-
- public void verify(int resId, int vCardType, final VCardParser vCardParser)
- throws IOException, VCardException {
- verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
- vCardType, vCardParser);
- }
-
- public void verify(InputStream is, int vCardType) throws IOException, VCardException {
- final VCardParser vCardParser;
- if (VCardConfig.isV30(vCardType)) {
- vCardParser = new VCardParser_V30(true); // Use StrictParsing.
- } else {
- vCardParser = new VCardParser_V21();
- }
- verify(is, vCardType, vCardParser);
- }
-
- public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
- throws IOException, VCardException {
- try {
- vCardParser.parse(is, this);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- }
- }
- }
- }
-
- @Override
- public void endEntry() {
- super.endEntry();
- mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
- mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
- mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
- mIndex++;
- }
-}
-
/**
* Utility class which verifies input VNode.
*
@@ -102,7 +33,7 @@
* If the node does not exist in the "ordered list", the class refers to
* "unorderd expected property set" and checks the node is expected somewhere.
*/
-/* package */ class PropertyNodesVerifierElem {
+public class PropertyNodesVerifierElem {
public static class TypeSet extends HashSet<String> {
public TypeSet(String ... array) {
super(Arrays.asList(array));
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
similarity index 79%
rename from core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
index bfc3158..5fb2942 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
@@ -13,9 +13,8 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
-import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.EntityIterator;
@@ -31,6 +30,8 @@
import android.pim.vcard.exception.VCardException;
import android.test.AndroidTestCase;
import android.test.mock.MockContext;
+import android.text.TextUtils;
+import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -38,19 +39,32 @@
import java.lang.reflect.Method;
import java.util.Arrays;
-/* package */ class CustomMockContext extends MockContext {
- final ContentResolver mResolver;
- public CustomMockContext(ContentResolver resolver) {
- mResolver = resolver;
+/**
+ * <p>
+ * The class lets users checks that given expected vCard data are same as given actual vCard data.
+ * Able to verify both vCard importer/exporter.
+ * </p>
+ * <p>
+ * First a user has to initialize the object by calling either
+ * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}.
+ * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported.
+ * </p>
+ */
+public class VCardVerifier {
+ private static final String LOG_TAG = "VCardVerifier";
+
+ private static class CustomMockContext extends MockContext {
+ final ContentResolver mResolver;
+ public CustomMockContext(ContentResolver resolver) {
+ mResolver = resolver;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
}
- @Override
- public ContentResolver getContentResolver() {
- return mResolver;
- }
-}
-
-/* package */ class VCardVerifier {
private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
public boolean onInit(Context context) {
return true;
@@ -80,9 +94,11 @@
private ContentValuesVerifier mContentValuesVerifier;
private boolean mInitialized;
private boolean mVerified = false;
+ private String mCharset;
- public VCardVerifier(AndroidTestCase androidTestCase) {
- mTestCase = androidTestCase;
+ // Called by VCardTestsBase
+ public VCardVerifier(AndroidTestCase testCase) {
+ mTestCase = testCase;
mVCardVerifierInternal = new VCardVerifierInternal();
mExportTestResolver = null;
mInputStream = null;
@@ -90,17 +106,7 @@
mVerified = false;
}
- public void initForExportTest(int vcardType) {
- if (mInitialized) {
- mTestCase.fail("Already initialized");
- }
- mExportTestResolver = new ExportTestResolver(mTestCase);
- mVCardType = vcardType;
- mIsV30 = VCardConfig.isV30(vcardType);
- mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- mInitialized = true;
- }
-
+ // Should be called at the beginning of each import test.
public void initForImportTest(int vcardType, int resId) {
if (mInitialized) {
mTestCase.fail("Already initialized");
@@ -112,6 +118,27 @@
mInitialized = true;
}
+ // Should be called at the beginning of each export test.
+ public void initForExportTest(int vcardType) {
+ initForExportTest(vcardType, "UTF-8");
+ }
+
+ public void initForExportTest(int vcardType, String charset) {
+ if (mInitialized) {
+ mTestCase.fail("Already initialized");
+ }
+ mExportTestResolver = new ExportTestResolver(mTestCase);
+ mVCardType = vcardType;
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ mInitialized = true;
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = "UTF-8";
+ } else {
+ mCharset = charset;
+ }
+ }
+
private void setInputResourceId(int resId) {
InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId);
if (inputStream == null) {
@@ -188,7 +215,7 @@
}
private void verifyOneVCard(final String vcard) {
- // Log.d("@@@", vcard);
+ Log.d(LOG_TAG, vcard);
final VCardInterpreter builder;
if (mContentValuesVerifier != null) {
final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
@@ -209,14 +236,15 @@
}
}
- final VCardParser parser =
- (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21());
InputStream is = null;
try {
- String charset =
- (VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8");
- is = new ByteArrayInputStream(vcard.getBytes(charset));
- mTestCase.assertEquals(true, parser.parse(is, null, builder));
+ // Note: we must not specify charset toward vCard parsers. This code checks whether
+ // those parsers are able to encode given binary without any extra information for
+ // charset.
+ final VCardParser parser = (mIsV30 ?
+ new VCardParser_V30(mVCardType) : new VCardParser_V21(mVCardType));
+ is = new ByteArrayInputStream(vcard.getBytes(mCharset));
+ parser.parse(is, builder);
} catch (IOException e) {
mTestCase.fail("Unexpected IOException: " + e.getMessage());
} catch (VCardException e) {
@@ -226,6 +254,7 @@
try {
is.close();
} catch (IOException e) {
+ mTestCase.fail("Unexpected IOException: " + e.getMessage());
}
}
}
@@ -267,10 +296,13 @@
final ContentResolver resolver,
final Uri uri, final String selection,
final String[] selectionArgs, final String sortOrder) {
- final ContentProvider provider =
- resolver.acquireContentProviderClient(uri).getLocalContentProvider();
- return ((ExportTestProvider)provider).queryEntities(
- uri, selection, selectionArgs, sortOrder);
+ if (ExportTestResolver.class.equals(resolver.getClass())) {
+ return ((ExportTestResolver)resolver).getProvider().queryEntities(
+ uri, selection, selectionArgs, sortOrder);
+ }
+
+ Log.e(LOG_TAG, "Unexpected provider given.");
+ return null;
}
private Method getMockGetEntityIteratorMethod()
@@ -281,7 +313,7 @@
private void verifyForExportTest() {
final VCardComposer composer =
- new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType);
+ new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType, mCharset);
composer.addHandler(mLineVerifier);
composer.addHandler(mVCardVerifierInternal);
if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
@@ -292,8 +324,8 @@
while (!composer.isAfterLast()) {
try {
final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
- mTestCase.assertTrue(
- composer.createOneEntry(getMockGetEntityIteratorMethod()));
+ mTestCase.assertNotNull(mockGetEntityIteratorMethod);
+ mTestCase.assertTrue(composer.createOneEntry(mockGetEntityIteratorMethod));
} catch (Exception e) {
e.printStackTrace();
mTestCase.fail();
diff --git a/core/tests/coretests/src/android/pim/vcard/VNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
similarity index 95%
rename from core/tests/coretests/src/android/pim/vcard/VNode.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
index 79f10dc..b890e2c 100644
--- a/core/tests/coretests/src/android/pim/vcard/VNode.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import java.util.ArrayList;
diff --git a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
similarity index 66%
rename from core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
index 0e6c325..25fb6ac 100644
--- a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
@@ -13,17 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
-import android.pim.vcard.VCardInterpreter;
import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardInterpreter;
+import android.pim.vcard.VCardUtils;
import android.util.CharsetUtils;
import android.util.Log;
-import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.net.QuotedPrintableCodec;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
@@ -32,34 +31,29 @@
import java.util.List;
/**
- * Store the parse result to custom datastruct: VNode, PropertyNode
+ * <p>
+ * The class storing the parse result to custom datastruct:
+ * {@link VNode}, and {@link PropertyNode}.
* Maybe several vcard instance, so use vNodeList to store.
- * VNode: standy by a vcard instance.
- * PropertyNode: standy by a property line of a card.
- *
- * Previously used in main vCard handling code but now exists only for testing.
+ * </p>
+ * <p>
+ * This is called VNode, not VCardNode, since it was used for expressing vCalendar (iCal).
+ * </p>
*/
-public class VNodeBuilder implements VCardInterpreter {
+/* package */ class VNodeBuilder implements VCardInterpreter {
static private String LOG_TAG = "VNodeBuilder";
- /**
- * If there's no other information available, this class uses this charset for encoding
- * byte arrays.
- */
- static public String TARGET_CHARSET = "UTF-8";
-
- /** type=VNode */
public List<VNode> vNodeList = new ArrayList<VNode>();
private int mNodeListPos = 0;
private VNode mCurrentVNode;
private PropertyNode mCurrentPropNode;
private String mCurrentParamType;
-
+
/**
* The charset using which VParser parses the text.
*/
private String mSourceCharset;
-
+
/**
* The charset with which byte array is encoded to String.
*/
@@ -68,11 +62,11 @@
private boolean mStrictLineBreakParsing;
public VNodeBuilder() {
- this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false);
+ this(VCardConfig.DEFAULT_INTERMEDIATE_CHARSET, VCardConfig.DEFAULT_IMPORT_CHARSET, false);
}
- public VNodeBuilder(String charset, boolean strictLineBreakParsing) {
- this(null, charset, strictLineBreakParsing);
+ public VNodeBuilder(String targetCharset, boolean strictLineBreakParsing) {
+ this(null, targetCharset, strictLineBreakParsing);
}
/**
@@ -83,12 +77,12 @@
if (sourceCharset != null) {
mSourceCharset = sourceCharset;
} else {
- mSourceCharset = VCardConfig.DEFAULT_CHARSET;
+ mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
}
if (targetCharset != null) {
mTargetCharset = targetCharset;
} else {
- mTargetCharset = TARGET_CHARSET;
+ mTargetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
}
mStrictLineBreakParsing = strictLineBreakParsing;
}
@@ -149,7 +143,6 @@
mCurrentPropNode.propName = name;
}
- // Used only in VCard.
public void propertyGroup(String group) {
mCurrentPropNode.propGroupSet.add(group);
}
@@ -196,67 +189,8 @@
Base64.decodeBase64(value.getBytes());
return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) {
- String quotedPrintable = value
- .replaceAll("= ", " ").replaceAll("=\t", "\t");
- String[] lines;
- if (mStrictLineBreakParsing) {
- lines = quotedPrintable.split("\r\n");
- } else {
- StringBuilder builder = new StringBuilder();
- int length = quotedPrintable.length();
- ArrayList<String> list = new ArrayList<String>();
- for (int i = 0; i < length; i++) {
- char ch = quotedPrintable.charAt(i);
- if (ch == '\n') {
- list.add(builder.toString());
- builder = new StringBuilder();
- } else if (ch == '\r') {
- list.add(builder.toString());
- builder = new StringBuilder();
- if (i < length - 1) {
- char nextCh = quotedPrintable.charAt(i + 1);
- if (nextCh == '\n') {
- i++;
- }
- }
- } else {
- builder.append(ch);
- }
- }
- String finalLine = builder.toString();
- if (finalLine.length() > 0) {
- list.add(finalLine);
- }
- lines = list.toArray(new String[0]);
- }
- StringBuilder builder = new StringBuilder();
- for (String line : lines) {
- if (line.endsWith("=")) {
- line = line.substring(0, line.length() - 1);
- }
- builder.append(line);
- }
- byte[] bytes;
- try {
- bytes = builder.toString().getBytes(mSourceCharset);
- } catch (UnsupportedEncodingException e1) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset);
- bytes = builder.toString().getBytes();
- }
-
- try {
- bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
- } catch (DecoderException e) {
- Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
- return "";
- }
-
- try {
- return new String(bytes, targetCharset);
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
- return new String(bytes);
- }
+ return VCardUtils.parseQuotedPrintable(
+ value, mStrictLineBreakParsing, mSourceCharset, targetCharset);
}
// Unknown encoding. Fall back to default.
}
@@ -309,6 +243,6 @@
}
public String getResult(){
- return null;
+ throw new RuntimeException("Not supported");
}
}
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 370ae78..b82e698 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -144,4 +144,51 @@
assertEquals(null, Settings.Bookmarks.getIntentForShortcut(r, '*'));
}
+
+ @MediumTest
+ public void testParseProviderList() {
+ ContentResolver r = getContext().getContentResolver();
+
+ // Make sure we get out what we put in.
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "test1,test2,test3");
+ assertEquals(Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED),
+ "test1,test2,test3");
+
+ // Test adding a value
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "");
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+test1");
+ assertEquals("test1",
+ Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+ // Test adding a second value
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+test2");
+ assertEquals("test1,test2",
+ Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+ // Test adding a third value
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+test3");
+ assertEquals("test1,test2,test3",
+ Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+ // Test deleting the first value in a 3 item list
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test1");
+ assertEquals("test2,test3",
+ Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+ // Test deleting the middle value in a 3 item list
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "test1,test2,test3");
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test2");
+ assertEquals("test1,test3",
+ Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+ // Test deleting the last value in a 3 item list
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "test1,test2,test3");
+ Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test3");
+ assertEquals("test1,test2",
+ Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+ }
}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index 8e7e63e..fb0f0c1 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -22,7 +22,7 @@
import junit.framework.TestCase;
/**
- * Tests StaticLayout bidi implementation.
+ * Quick check of native bidi implementation.
*/
public class StaticLayoutBidiTest extends TestCase {
@@ -41,73 +41,47 @@
//@SmallTest
public void testAllLtr() {
- expectBidi(REQ_DL, "a test", "000000", L);
+ expectNativeBidi(REQ_DL, "a test", "000000", L);
}
//@SmallTest
public void testLtrRtl() {
- expectBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
+ expectNativeBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
}
//@SmallTest
public void testAllRtl() {
- expectBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
+ expectNativeBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
}
//@SmallTest
public void testRtlLtr() {
- expectBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111000", R);
+ expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
}
//@SmallTest
public void testRAllLtr() {
- expectBidi(REQ_R, "a test", "000000", R);
+ expectNativeBidi(REQ_R, "a test", "222222", R);
}
//@SmallTest
public void testRLtrRtl() {
- expectBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "0001111", R);
+ expectNativeBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "2221111", R);
}
//@SmallTest
public void testLAllRtl() {
- expectBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
+ expectNativeBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
}
//@SmallTest
public void testLRtlLtr() {
- expectBidi(REQ_L, ALEF + BET + GIMEL + " abc", "1110000", L);
- }
-
- private void expectBidi(int dir, String text,
- String expectedLevels, int expectedDir) {
- char[] chs = text.toCharArray();
- int n = chs.length;
- byte[] chInfo = new byte[n];
-
- int resultDir = StaticLayout.bidi(dir, chs, chInfo, n, false);
-
- {
- StringBuilder sb = new StringBuilder("info:");
- for (int i = 0; i < n; ++i) {
- sb.append(" ").append(String.valueOf(chInfo[i]));
- }
- Log.i("BIDI", sb.toString());
- }
-
- char[] resultLevelChars = new char[n];
- for (int i = 0; i < n; ++i) {
- resultLevelChars[i] = (char)('0' + chInfo[i]);
- }
- String resultLevels = new String(resultLevelChars);
- assertEquals("direction", expectedDir, resultDir);
- assertEquals("levels", expectedLevels, resultLevels);
+ expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
}
//@SmallTest
public void testNativeBidi() {
- // native bidi returns levels, not simply directions
- expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
+ expectNativeBidi(REQ_L, ALEF + BET + GIMEL + " abc", "1110000", L);
}
private void expectNativeBidi(int dir, String text,
diff --git a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
new file mode 100644
index 0000000..4fde849
--- /dev/null
+++ b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.text;
+
+import android.text.Layout.Directions;
+import android.text.StaticLayoutTest.LayoutBuilder;
+
+import java.util.Arrays;
+import java.util.Formatter;
+
+import junit.framework.TestCase;
+
+public class StaticLayoutDirectionsTest extends TestCase {
+ private static final char ALEF = '\u05d0';
+
+ private static Directions dirs(int ... dirs) {
+ return new Directions(dirs);
+ }
+
+ // constants from Layout that are package-protected
+ private static final int RUN_LENGTH_MASK = 0x03ffffff;
+ private static final int RUN_LEVEL_SHIFT = 26;
+ private static final int RUN_LEVEL_MASK = 0x3f;
+ private static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
+
+ private static final Directions DIRS_ALL_LEFT_TO_RIGHT =
+ new Directions(new int[] { 0, RUN_LENGTH_MASK });
+ private static final Directions DIRS_ALL_RIGHT_TO_LEFT =
+ new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
+
+ private static final int LVL1_1 = 1 | (1 << RUN_LEVEL_SHIFT);
+ private static final int LVL2_1 = 1 | (2 << RUN_LEVEL_SHIFT);
+ private static final int LVL2_2 = 2 | (2 << RUN_LEVEL_SHIFT);
+
+ private static String[] texts = {
+ "",
+ " ",
+ "a",
+ "a1",
+ "aA",
+ "a1b",
+ "a1A",
+ "aA1",
+ "aAb",
+ "aA1B",
+ "aA1B2",
+
+ // rtl
+ "A",
+ "A1",
+ "Aa",
+ "A1B",
+ "A1a",
+ "Aa1",
+ "AaB"
+ };
+
+ // Expected directions are an array of start/length+level pairs,
+ // in visual order from the leading margin.
+ private static Directions[] expected = {
+ DIRS_ALL_LEFT_TO_RIGHT,
+ DIRS_ALL_LEFT_TO_RIGHT,
+ DIRS_ALL_LEFT_TO_RIGHT,
+ DIRS_ALL_LEFT_TO_RIGHT,
+ dirs(0, 1, 1, LVL1_1),
+ DIRS_ALL_LEFT_TO_RIGHT,
+ dirs(0, 2, 2, LVL1_1),
+ dirs(0, 1, 2, LVL2_1, 1, LVL1_1),
+ dirs(0, 1, 1, LVL1_1, 2, 1),
+ dirs(0, 1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
+ dirs(0, 1, 4, LVL2_1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
+
+ // rtl
+ DIRS_ALL_RIGHT_TO_LEFT,
+ dirs(0, LVL1_1, 1, LVL2_1),
+ dirs(0, LVL1_1, 1, LVL2_1),
+ dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
+ dirs(0, LVL1_1, 1, LVL2_2),
+ dirs(0, LVL1_1, 1, LVL2_2),
+ dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
+ };
+
+ private static String pseudoBidiToReal(String src) {
+ char[] chars = src.toCharArray();
+ for (int j = 0; j < chars.length; ++j) {
+ char c = chars[j];
+ if (c >= 'A' && c <= 'D') {
+ chars[j] = (char)(ALEF + c - 'A');
+ }
+ }
+
+ return new String(chars, 0, chars.length);
+ }
+
+ // @SmallTest
+ public void testDirections() {
+ StringBuilder buf = new StringBuilder("\n");
+ Formatter f = new Formatter(buf);
+
+ LayoutBuilder b = StaticLayoutTest.builder();
+ for (int i = 0; i < texts.length; ++i) {
+ b.setText(pseudoBidiToReal(texts[i]));
+ checkDirections(b.build(), i, b.text, expected, f);
+ }
+ if (buf.length() > 1) {
+ fail(buf.toString());
+ }
+ }
+
+ // @SmallTest
+ public void testTrailingWhitespace() {
+ LayoutBuilder b = StaticLayoutTest.builder();
+ b.setText(pseudoBidiToReal("Ab c"));
+ float width = b.paint.measureText(b.text, 0, 5); // exclude 'c'
+ b.setWidth(Math.round(width));
+ Layout l = b.build();
+ if (l.getLineCount() != 2) {
+ throw new RuntimeException("expected 2 lines, got: " + l.getLineCount());
+ }
+ Directions result = l.getLineDirections(0);
+ Directions expected = dirs(0, LVL1_1, 1, LVL2_1, 2, 3 | (1 << Layout.RUN_LEVEL_SHIFT));
+ expectDirections("split line", expected, result);
+ }
+
+ public void testNextToRightOf() {
+ LayoutBuilder b = StaticLayoutTest.builder();
+ b.setText(pseudoBidiToReal("aA1B2"));
+ // visual a2B1A positions 04321
+ // 0: |a2B1A, strong is sol, after -> 0
+ // 1: a|2B1A, strong is a, after ->, 1
+ // 2: a2|B1A, strong is B, after -> 4
+ // 3: a2B|1A, strong is B, before -> 3
+ // 4: a2B1|A, strong is A, after -> 2
+ // 5: a2B1A|, strong is eol, before -> 5
+ int[] expected = { 0, 1, 4, 3, 2, 5 };
+ Layout l = b.build();
+ int n = 0;
+ for (int i = 1; i < expected.length; ++i) {
+ int t = l.getOffsetToRightOf(n);
+ if (t != expected[i]) {
+ fail("offset[" + i + "] to right of: " + n + " expected: " +
+ expected[i] + " got: " + t);
+ }
+ n = t;
+ }
+ }
+
+ public void testNextToLeftOf() {
+ LayoutBuilder b = StaticLayoutTest.builder();
+ b.setText(pseudoBidiToReal("aA1B2"));
+ int[] expected = { 0, 1, 4, 3, 2, 5 };
+ Layout l = b.build();
+ int n = 5;
+ for (int i = expected.length - 1; --i >= 0;) {
+ int t = l.getOffsetToLeftOf(n);
+ if (t != expected[i]) {
+ fail("offset[" + i + "] to left of: " + n + " expected: " +
+ expected[i] + " got: " + t);
+ }
+ n = t;
+ }
+ }
+
+ // utility, not really a test
+ /*
+ public void testMeasureText1() {
+ LayoutBuilder b = StaticLayoutTest.builder();
+ String text = "ABC"; // "abAB"
+ b.setText(pseudoBidiToReal(text));
+ Layout l = b.build();
+ Directions directions = l.getLineDirections(0);
+
+ TextPaint workPaint = new TextPaint();
+
+ int dir = -1; // LEFT_TO_RIGHT
+ boolean trailing = true;
+ boolean alt = true;
+ do {
+ dir = -dir;
+ do {
+ trailing = !trailing;
+ for (int offset = 0, end = b.text.length(); offset <= end; ++offset) {
+ float width = Layout.measureText(b.paint,
+ workPaint,
+ b.text,
+ 0, offset, end,
+ dir, directions,
+ trailing, false,
+ null);
+ Log.i("BIDI", "dir: " + dir + " trail: " + trailing +
+ " offset: " + offset + " width: " + width);
+ }
+ } while (!trailing);
+ } while (dir > 0);
+ }
+ */
+
+ // utility for displaying arrays in hex
+ private static String hexArray(int[] array) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ for (int i : array) {
+ if (sb.length() > 1) {
+ sb.append(", ");
+ }
+ sb.append(Integer.toHexString(i));
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ private void checkDirections(Layout l, int i, String text,
+ Directions[] expectedDirs, Formatter f) {
+ Directions expected = expectedDirs[i];
+ Directions result = l.getLineDirections(0);
+ if (!Arrays.equals(expected.mDirections, result.mDirections)) {
+ f.format("%n[%2d] '%s', %s != %s", i, text,
+ hexArray(expected.mDirections),
+ hexArray(result.mDirections));
+ }
+ }
+
+ private void expectDirections(String msg, Directions expected, Directions result) {
+ if (!Arrays.equals(expected.mDirections, result.mDirections)) {
+ fail("expected: " + hexArray(expected.mDirections) +
+ " got: " + hexArray(result.mDirections));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 1f58a2c..d554a50 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -228,11 +228,11 @@
}
}
- private static LayoutBuilder builder() {
+ /* package */ static LayoutBuilder builder() {
return new LayoutBuilder();
}
- private static class LayoutBuilder {
+ /* package */ static class LayoutBuilder {
String text = "This is a test";
TextPaint paint = new TextPaint(); // default
int width = 100;
diff --git a/core/tests/coretests/src/android/util/ExpandableListScenario.java b/core/tests/coretests/src/android/util/ExpandableListScenario.java
deleted file mode 100644
index 4a12b0d..0000000
--- a/core/tests/coretests/src/android/util/ExpandableListScenario.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2007 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.util.ArrayList;
-import java.util.List;
-
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * Utility base class for creating various Expandable List scenarios.
- * <p>
- * WARNING: A lot of the features are mixed between ListView's expected position
- * (flat list position) and an ExpandableListView's expected position. You must add/change
- * features as you need them.
- *
- * @see ListScenario
- */
-public abstract class ExpandableListScenario extends ListScenario {
- protected ExpandableListAdapter mAdapter;
- protected List<MyGroup> mGroups;
-
- @Override
- protected ListView createListView() {
- return new ExpandableListView(this);
- }
-
- @Override
- protected Params createParams() {
- return new ExpandableParams();
- }
-
- @Override
- protected void setAdapter(ListView listView) {
- ((ExpandableListView) listView).setAdapter(mAdapter = createAdapter());
- }
-
- protected ExpandableListAdapter createAdapter() {
- return new MyAdapter();
- }
-
- @Override
- protected void readAndValidateParams(Params params) {
- ExpandableParams expandableParams = (ExpandableParams) params;
-
- int[] numChildren = expandableParams.mNumChildren;
-
- mGroups = new ArrayList<MyGroup>(numChildren.length);
- for (int i = 0; i < numChildren.length; i++) {
- mGroups.add(new MyGroup(numChildren[i]));
- }
-
- expandableParams.superSetNumItems();
-
- super.readAndValidateParams(params);
- }
-
- /**
- * Get the ExpandableListView widget.
- * @return The main widget.
- */
- public ExpandableListView getExpandableListView() {
- return (ExpandableListView) super.getListView();
- }
-
- public static class ExpandableParams extends Params {
- private int[] mNumChildren;
-
- /**
- * Sets the number of children per group.
- *
- * @param numChildrenPerGroup The number of children per group.
- */
- public ExpandableParams setNumChildren(int[] numChildren) {
- mNumChildren = numChildren;
- return this;
- }
-
- /**
- * Sets the number of items on the superclass based on the number of
- * groups and children per group.
- */
- private ExpandableParams superSetNumItems() {
- int numItems = 0;
-
- if (mNumChildren != null) {
- for (int i = mNumChildren.length - 1; i >= 0; i--) {
- numItems += mNumChildren[i];
- }
- }
-
- super.setNumItems(numItems);
-
- return this;
- }
-
- @Override
- public Params setNumItems(int numItems) {
- throw new IllegalStateException("Use setNumGroups and setNumChildren instead.");
- }
-
- @Override
- public ExpandableParams setFadingEdgeScreenSizeFactor(double fadingEdgeScreenSizeFactor) {
- return (ExpandableParams) super.setFadingEdgeScreenSizeFactor(fadingEdgeScreenSizeFactor);
- }
-
- @Override
- public ExpandableParams setItemScreenSizeFactor(double itemScreenSizeFactor) {
- return (ExpandableParams) super.setItemScreenSizeFactor(itemScreenSizeFactor);
- }
-
- @Override
- public ExpandableParams setItemsFocusable(boolean itemsFocusable) {
- return (ExpandableParams) super.setItemsFocusable(itemsFocusable);
- }
-
- @Override
- public ExpandableParams setMustFillScreen(boolean fillScreen) {
- return (ExpandableParams) super.setMustFillScreen(fillScreen);
- }
-
- @Override
- public ExpandableParams setPositionScreenSizeFactorOverride(int position, double itemScreenSizeFactor) {
- return (ExpandableParams) super.setPositionScreenSizeFactorOverride(position, itemScreenSizeFactor);
- }
-
- @Override
- public ExpandableParams setPositionUnselectable(int position) {
- return (ExpandableParams) super.setPositionUnselectable(position);
- }
-
- @Override
- public ExpandableParams setStackFromBottom(boolean stackFromBottom) {
- return (ExpandableParams) super.setStackFromBottom(stackFromBottom);
- }
-
- @Override
- public ExpandableParams setStartingSelectionPosition(int startingSelectionPosition) {
- return (ExpandableParams) super.setStartingSelectionPosition(startingSelectionPosition);
- }
-
- @Override
- public ExpandableParams setConnectAdapter(boolean connectAdapter) {
- return (ExpandableParams) super.setConnectAdapter(connectAdapter);
- }
- }
-
- /**
- * Gets a string for the value of some item.
- * @param packedPosition The position of the item.
- * @return The string.
- */
- public final String getValueAtPosition(long packedPosition) {
- final int type = ExpandableListView.getPackedPositionType(packedPosition);
-
- if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
- return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
- .children.get(ExpandableListView.getPackedPositionChild(packedPosition))
- .name;
- } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
- return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
- .name;
- } else {
- throw new IllegalStateException("packedPosition is not a valid position.");
- }
- }
-
- /**
- * Whether a particular position is out of bounds.
- *
- * @param packedPosition The packed position.
- * @return Whether it's out of bounds.
- */
- private boolean isOutOfBounds(long packedPosition) {
- final int type = ExpandableListView.getPackedPositionType(packedPosition);
-
- if (type == ExpandableListView.PACKED_POSITION_TYPE_NULL) {
- throw new IllegalStateException("packedPosition is not a valid position.");
- }
-
- final int group = ExpandableListView.getPackedPositionGroup(packedPosition);
- if (group >= mGroups.size() || group < 0) {
- return true;
- }
-
- if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
- final int child = ExpandableListView.getPackedPositionChild(packedPosition);
- if (child >= mGroups.get(group).children.size() || child < 0) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Gets a view for the packed position, possibly reusing the convertView.
- *
- * @param packedPosition The position to get a view for.
- * @param convertView Optional view to convert.
- * @param parent The future parent.
- * @return A view.
- */
- private View getView(long packedPosition, View convertView, ViewGroup parent) {
- if (isOutOfBounds(packedPosition)) {
- throw new IllegalStateException("position out of range for adapter!");
- }
-
- final ExpandableListView elv = getExpandableListView();
- final int flPos = elv.getFlatListPosition(packedPosition);
-
- if (convertView != null) {
- ((TextView) convertView).setText(getValueAtPosition(packedPosition));
- convertView.setId(flPos);
- return convertView;
- }
-
- int desiredHeight = getHeightForPosition(flPos);
- return createView(packedPosition, flPos, parent, desiredHeight);
- }
-
- /**
- * Create a view for a group or child position.
- *
- * @param packedPosition The packed position (has type, group pos, and optionally child pos).
- * @param flPos The flat list position (the position that the ListView goes by).
- * @param parent The parent view.
- * @param desiredHeight The desired height.
- * @return A view.
- */
- protected View createView(long packedPosition, int flPos, ViewGroup parent, int desiredHeight) {
- TextView result = new TextView(parent.getContext());
- result.setHeight(desiredHeight);
- result.setText(getValueAtPosition(packedPosition));
- final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- result.setLayoutParams(lp);
- result.setGravity(Gravity.CENTER_VERTICAL);
- result.setPadding(36, 0, 0, 0);
- result.setId(flPos);
- return result;
- }
-
- /**
- * Returns a group index containing either the number of children or at
- * least one child.
- *
- * @param numChildren The group must have this amount, or -1 if using
- * atLeastOneChild.
- * @param atLeastOneChild The group must have at least one child, or false
- * if using numChildren.
- * @return A group index with the requirements.
- */
- public int findGroupWithNumChildren(int numChildren, boolean atLeastOneChild) {
- final ExpandableListAdapter adapter = mAdapter;
-
- for (int i = adapter.getGroupCount() - 1; i >= 0; i--) {
- final int curNumChildren = adapter.getChildrenCount(i);
-
- if (numChildren == curNumChildren || atLeastOneChild && curNumChildren > 0) {
- return i;
- }
- }
-
- return -1;
- }
-
- public List<MyGroup> getGroups() {
- return mGroups;
- }
-
- public ExpandableListAdapter getAdapter() {
- return mAdapter;
- }
-
- /**
- * Simple expandable list adapter.
- */
- protected class MyAdapter extends BaseExpandableListAdapter {
- public Object getChild(int groupPosition, int childPosition) {
- return getValueAtPosition(ExpandableListView.getPackedPositionForChild(groupPosition,
- childPosition));
- }
-
- public long getChildId(int groupPosition, int childPosition) {
- return mGroups.get(groupPosition).children.get(childPosition).id;
- }
-
- public int getChildrenCount(int groupPosition) {
- return mGroups.get(groupPosition).children.size();
- }
-
- public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
- View convertView, ViewGroup parent) {
- return getView(ExpandableListView.getPackedPositionForChild(groupPosition,
- childPosition), convertView, parent);
- }
-
- public Object getGroup(int groupPosition) {
- return getValueAtPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
- }
-
- public int getGroupCount() {
- return mGroups.size();
- }
-
- public long getGroupId(int groupPosition) {
- return mGroups.get(groupPosition).id;
- }
-
- public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
- ViewGroup parent) {
- return getView(ExpandableListView.getPackedPositionForGroup(groupPosition),
- convertView, parent);
- }
-
- public boolean isChildSelectable(int groupPosition, int childPosition) {
- return true;
- }
-
- public boolean hasStableIds() {
- return true;
- }
-
- }
-
- public static class MyGroup {
- private static long mNextId = 1000;
-
- String name;
- long id = mNextId++;
- List<MyChild> children;
-
- public MyGroup(int numChildren) {
- name = "Group " + id;
- children = new ArrayList<MyChild>(numChildren);
- for (int i = 0; i < numChildren; i++) {
- children.add(new MyChild());
- }
- }
- }
-
- public static class MyChild {
- private static long mNextId = 2000;
-
- String name;
- long id = mNextId++;
-
- public MyChild() {
- name = "Child " + id;
- }
- }
-
- @Override
- protected final void init(Params params) {
- init((ExpandableParams) params);
- }
-
- /**
- * @see ListScenario#init
- */
- protected abstract void init(ExpandableParams params);
-}
diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java
index 957c593..b90c97b 100644
--- a/core/tests/coretests/src/android/util/PatternsTest.java
+++ b/core/tests/coretests/src/android/util/PatternsTest.java
@@ -74,6 +74,10 @@
t = Patterns.WEB_URL.matcher("\uD604\uAE08\uC601\uC218\uC99D.kr").matches();
assertTrue("Valid URL", t);
+ t = Patterns.WEB_URL.matcher("http://brainstormtech.blogs.fortune.cnn.com/2010/03/11/" +
+ "top-five-moments-from-eric-schmidt\u2019s-talk-in-abu-dhabi/").matches();
+ assertTrue("Valid URL", t);
+
t = Patterns.WEB_URL.matcher("ftp://www.example.com").matches();
assertFalse("Matched invalid protocol", t);
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java
deleted file mode 100644
index e23b516..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.util.ExpandableListScenario;
-import android.util.ListUtil;
-import android.util.ExpandableListScenario.MyGroup;
-import android.view.KeyEvent;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-
-import java.util.List;
-
-public class ExpandableListBasicTest extends ActivityInstrumentationTestCase2<ExpandableListSimple> {
- private ExpandableListScenario mActivity;
- private ExpandableListView mExpandableListView;
- private ExpandableListAdapter mAdapter;
- private ListUtil mListUtil;
-
- public ExpandableListBasicTest() {
- super(ExpandableListSimple.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mActivity = getActivity();
- mExpandableListView = mActivity.getExpandableListView();
- mAdapter = mExpandableListView.getExpandableListAdapter();
- mListUtil = new ListUtil(mExpandableListView, getInstrumentation());
- }
-
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mActivity);
- assertNotNull(mExpandableListView);
- }
-
- private int expandGroup(int numChildren, boolean atLeastOneChild) {
- final int groupPos = mActivity.findGroupWithNumChildren(numChildren, atLeastOneChild);
- assertTrue("Could not find group to expand", groupPos >= 0);
-
- assertFalse("Group is already expanded", mExpandableListView.isGroupExpanded(groupPos));
- mListUtil.arrowScrollToSelectedPosition(groupPos);
- getInstrumentation().waitForIdleSync();
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(groupPos));
-
- return groupPos;
- }
-
- @MediumTest
- public void testExpandGroup() {
- expandGroup(-1, true);
- }
-
- @MediumTest
- public void testCollapseGroup() {
- final int groupPos = expandGroup(-1, true);
-
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertFalse("Group did not collapse", mExpandableListView.isGroupExpanded(groupPos));
- }
-
- @MediumTest
- public void testExpandedGroupMovement() {
- // Expand the first group
- mListUtil.arrowScrollToSelectedPosition(0);
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
-
- // Ensure it expanded
- assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(0));
-
- // Wait until that's all good
- getInstrumentation().waitForIdleSync();
-
- // Make sure it expanded
- assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(0));
-
- // Insert a collapsed group in front of the one just expanded
- List<MyGroup> groups = mActivity.getGroups();
- MyGroup insertedGroup = new MyGroup(1);
- groups.add(0, insertedGroup);
-
- // Notify data change
- assertTrue("Adapter is not an instance of the base adapter",
- mAdapter instanceof BaseExpandableListAdapter);
- final BaseExpandableListAdapter adapter = (BaseExpandableListAdapter) mAdapter;
-
- mActivity.runOnUiThread(new Runnable() {
- public void run() {
- adapter.notifyDataSetChanged();
- }
- });
- getInstrumentation().waitForIdleSync();
-
- // Make sure the right group is expanded
- assertTrue("The expanded state didn't stay with the proper group",
- mExpandableListView.isGroupExpanded(1));
- assertFalse("The expanded state was given to the inserted group",
- mExpandableListView.isGroupExpanded(0));
- }
-
- @MediumTest
- public void testContextMenus() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testContextMenus();
- }
-
- @MediumTest
- public void testConvertionBetweenFlatAndPacked() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testConvertionBetweenFlatAndPackedOnGroups();
- tester.testConvertionBetweenFlatAndPackedOnChildren();
- }
-
- @MediumTest
- public void testSelectedPosition() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testSelectedPositionOnGroups();
- tester.testSelectedPositionOnChildren();
- }
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java
deleted file mode 100644
index 78db28c..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.widget.BaseExpandableListAdapter;
-
-import android.util.ExpandableListScenario;
-
-public class ExpandableListSimple extends ExpandableListScenario {
- private static final int[] NUM_CHILDREN = {4, 3, 2, 1, 0};
-
- @Override
- protected void init(ExpandableParams params) {
- params.setNumChildren(NUM_CHILDREN)
- .setItemScreenSizeFactor(0.14);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
-
- menu.add("Add item").setOnMenuItemClickListener(new OnMenuItemClickListener() {
- public boolean onMenuItemClick(MenuItem item) {
- mGroups.add(0, new MyGroup(2));
- ((BaseExpandableListAdapter) mAdapter).notifyDataSetChanged();
- return true;
- }
- });
-
- return true;
- }
-
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java
deleted file mode 100644
index dfb10fb..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.app.Instrumentation;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.ExpandableListScenario;
-import android.util.ListUtil;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-
-import junit.framework.Assert;
-
-public class ExpandableListTester {
- private final ExpandableListView mExpandableListView;
- private final ExpandableListAdapter mAdapter;
- private final ListUtil mListUtil;
-
- private final ActivityInstrumentationTestCase2<? extends ExpandableListScenario>
- mActivityInstrumentation;
-
- Instrumentation mInstrumentation;
-
- public ExpandableListTester(
- ExpandableListView expandableListView,
- ActivityInstrumentationTestCase2<? extends ExpandableListScenario>
- activityInstrumentation) {
- mExpandableListView = expandableListView;
- Instrumentation instrumentation = activityInstrumentation.getInstrumentation();
- mListUtil = new ListUtil(mExpandableListView, instrumentation);
- mAdapter = mExpandableListView.getExpandableListAdapter();
- mActivityInstrumentation = activityInstrumentation;
- mInstrumentation = mActivityInstrumentation.getInstrumentation();
- }
-
- private void expandGroup(final int groupIndex, int flatPosition) {
- Assert.assertFalse("Group is already expanded", mExpandableListView
- .isGroupExpanded(groupIndex));
- mListUtil.arrowScrollToSelectedPosition(flatPosition);
- mInstrumentation.waitForIdleSync();
- mActivityInstrumentation.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- mActivityInstrumentation.getInstrumentation().waitForIdleSync();
- Assert.assertTrue("Group did not expand " + groupIndex,
- mExpandableListView.isGroupExpanded(groupIndex));
- }
-
- void testContextMenus() {
- // Add a position tester ContextMenu listener to the ExpandableListView
- PositionTesterContextMenuListener menuListener = new PositionTesterContextMenuListener();
- mExpandableListView.setOnCreateContextMenuListener(menuListener);
-
- int index = 0;
-
- // Scrolling on header elements should trigger an AdapterContextMenu
- for (int i=0; i<mExpandableListView.getHeaderViewsCount(); i++) {
- // Check group index in context menu
- menuListener.expectAdapterContextMenu(i);
- // Make sure the group is visible so that getChild finds it
- mListUtil.arrowScrollToSelectedPosition(index);
- View headerChild = mExpandableListView.getChildAt(index
- - mExpandableListView.getFirstVisiblePosition());
- mExpandableListView.showContextMenuForChild(headerChild);
- mInstrumentation.waitForIdleSync();
- Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
- index++;
- }
-
- int groupCount = mAdapter.getGroupCount();
- for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
-
- // Expand group
- expandGroup(groupIndex, index);
-
- // Check group index in context menu
- menuListener.expectGroupContextMenu(groupIndex);
- // Make sure the group is visible so that getChild finds it
- mListUtil.arrowScrollToSelectedPosition(index);
- View groupChild = mExpandableListView.getChildAt(index
- - mExpandableListView.getFirstVisiblePosition());
- mExpandableListView.showContextMenuForChild(groupChild);
- mInstrumentation.waitForIdleSync();
- Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
- index++;
-
- final int childrenCount = mAdapter.getChildrenCount(groupIndex);
- for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
- // Check child index in context menu
- mListUtil.arrowScrollToSelectedPosition(index);
- menuListener.expectChildContextMenu(groupIndex, childIndex);
- View child = mExpandableListView.getChildAt(index
- - mExpandableListView.getFirstVisiblePosition());
- mExpandableListView.showContextMenuForChild(child);
- mInstrumentation.waitForIdleSync();
- Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
- index++;
- }
- }
-
- // Scrolling on footer elements should trigger an AdapterContextMenu
- for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
- // Check group index in context menu
- menuListener.expectAdapterContextMenu(index);
- // Make sure the group is visible so that getChild finds it
- mListUtil.arrowScrollToSelectedPosition(index);
- View footerChild = mExpandableListView.getChildAt(index
- - mExpandableListView.getFirstVisiblePosition());
- mExpandableListView.showContextMenuForChild(footerChild);
- mInstrumentation.waitForIdleSync();
- Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
- index++;
- }
-
- // Cleanup: remove the listener we added.
- mExpandableListView.setOnCreateContextMenuListener(null);
- }
-
- private int expandAGroup() {
- final int groupIndex = 2;
- final int headerCount = mExpandableListView.getHeaderViewsCount();
- Assert.assertTrue("Not enough groups", groupIndex < mAdapter.getGroupCount());
- expandGroup(groupIndex, groupIndex + headerCount);
- return groupIndex;
- }
-
- // This method assumes that NO group is expanded when called
- void testConvertionBetweenFlatAndPackedOnGroups() {
- final int headerCount = mExpandableListView.getHeaderViewsCount();
-
- for (int i=0; i<headerCount; i++) {
- Assert.assertEquals("Non NULL position for header item",
- ExpandableListView.PACKED_POSITION_VALUE_NULL,
- mExpandableListView.getExpandableListPosition(i));
- }
-
- // Test all (non expanded) groups
- final int groupCount = mAdapter.getGroupCount();
- for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
- int expectedFlatPosition = headerCount + groupIndex;
- long packedPositionForGroup = ExpandableListView.getPackedPositionForGroup(groupIndex);
- Assert.assertEquals("Group not found at flat position " + expectedFlatPosition,
- packedPositionForGroup,
- mExpandableListView.getExpandableListPosition(expectedFlatPosition));
-
- Assert.assertEquals("Wrong flat position for group " + groupIndex,
- expectedFlatPosition,
- mExpandableListView.getFlatListPosition(packedPositionForGroup));
- }
-
- for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
- Assert.assertEquals("Non NULL position for header item",
- ExpandableListView.PACKED_POSITION_VALUE_NULL,
- mExpandableListView.getExpandableListPosition(headerCount + groupCount + i));
- }
- }
-
- // This method assumes that NO group is expanded when called
- void testConvertionBetweenFlatAndPackedOnChildren() {
- // Test with an expanded group
- final int headerCount = mExpandableListView.getHeaderViewsCount();
- final int groupIndex = expandAGroup();
-
- final int childrenCount = mAdapter.getChildrenCount(groupIndex);
- for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
- int expectedFlatPosition = headerCount + groupIndex + 1 + childIndex;
- long childPos = ExpandableListView.getPackedPositionForChild(groupIndex, childIndex);
-
- Assert.assertEquals("Wrong flat position for child ",
- childPos,
- mExpandableListView.getExpandableListPosition(expectedFlatPosition));
-
- Assert.assertEquals("Wrong flat position for child ",
- expectedFlatPosition,
- mExpandableListView.getFlatListPosition(childPos));
- }
- }
-
- // This method assumes that NO group is expanded when called
- void testSelectedPositionOnGroups() {
- int index = 0;
-
- // Scrolling on header elements should not give a valid selected position.
- for (int i=0; i<mExpandableListView.getHeaderViewsCount(); i++) {
- mListUtil.arrowScrollToSelectedPosition(index);
- Assert.assertEquals("Header item is selected",
- ExpandableListView.PACKED_POSITION_VALUE_NULL,
- mExpandableListView.getSelectedPosition());
- index++;
- }
-
- // Check selection on group items
- final int groupCount = mAdapter.getGroupCount();
- for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
- mListUtil.arrowScrollToSelectedPosition(index);
- Assert.assertEquals("Group item is not selected",
- ExpandableListView.getPackedPositionForGroup(groupIndex),
- mExpandableListView.getSelectedPosition());
- index++;
- }
-
- // Scrolling on footer elements should not give a valid selected position.
- for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
- mListUtil.arrowScrollToSelectedPosition(index);
- Assert.assertEquals("Footer item is selected",
- ExpandableListView.PACKED_POSITION_VALUE_NULL,
- mExpandableListView.getSelectedPosition());
- index++;
- }
- }
-
- // This method assumes that NO group is expanded when called
- void testSelectedPositionOnChildren() {
- // Test with an expanded group
- final int headerCount = mExpandableListView.getHeaderViewsCount();
- final int groupIndex = expandAGroup();
-
- final int childrenCount = mAdapter.getChildrenCount(groupIndex);
- for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
- int childFlatPosition = headerCount + groupIndex + 1 + childIndex;
- mListUtil.arrowScrollToSelectedPosition(childFlatPosition);
- Assert.assertEquals("Group item is not selected",
- ExpandableListView.getPackedPositionForChild(groupIndex, childIndex),
- mExpandableListView.getSelectedPosition());
- }
- }
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java
deleted file mode 100644
index 2251c1d..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.os.Bundle;
-import android.util.ExpandableListScenario;
-import android.widget.Button;
-import android.widget.ExpandableListView;
-
-public class ExpandableListWithHeaders extends ExpandableListScenario {
- private static final int[] sNumChildren = {1, 4, 3, 2, 6};
- private static final int sNumOfHeadersAndFooters = 12;
-
- @Override
- protected void init(ExpandableParams params) {
- params.setStackFromBottom(false)
- .setStartingSelectionPosition(-1)
- .setNumChildren(sNumChildren)
- .setItemScreenSizeFactor(0.14)
- .setConnectAdapter(false);
- }
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final ExpandableListView expandableListView = getExpandableListView();
- expandableListView.setItemsCanFocus(true);
-
- for (int i = 0; i < sNumOfHeadersAndFooters; i++) {
- Button header = new Button(this);
- header.setText("Header View " + i);
- expandableListView.addHeaderView(header);
- }
-
- for (int i = 0; i < sNumOfHeadersAndFooters; i++) {
- Button footer = new Button(this);
- footer.setText("Footer View " + i);
- expandableListView.addFooterView(footer);
- }
-
- // Set adapter here AFTER we set header and footer views
- setAdapter(expandableListView);
- }
-
- /**
- * @return The number of headers (and the same number of footers)
- */
- public int getNumOfHeadersAndFooters() {
- return sNumOfHeadersAndFooters;
- }
-
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java
deleted file mode 100644
index 64a0fff..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.util.ListUtil;
-import android.view.KeyEvent;
-import android.widget.ExpandableListView;
-
-public class ExpandableListWithHeadersTest extends
- ActivityInstrumentationTestCase2<ExpandableListWithHeaders> {
- private ExpandableListView mExpandableListView;
- private ListUtil mListUtil;
-
- public ExpandableListWithHeadersTest() {
- super(ExpandableListWithHeaders.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mExpandableListView = getActivity().getExpandableListView();
- mListUtil = new ListUtil(mExpandableListView, getInstrumentation());
- }
-
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mExpandableListView);
- }
-
- @MediumTest
- public void testExpandOnFirstPosition() {
- // Should be a header, and hence the first group should NOT have expanded
- mListUtil.arrowScrollToSelectedPosition(0);
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertFalse(mExpandableListView.isGroupExpanded(0));
- }
-
- @LargeTest
- public void testExpandOnFirstGroup() {
- mListUtil.arrowScrollToSelectedPosition(getActivity().getNumOfHeadersAndFooters());
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertTrue(mExpandableListView.isGroupExpanded(0));
- }
-
- @MediumTest
- public void testContextMenus() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testContextMenus();
- }
-
- @MediumTest
- public void testConvertionBetweenFlatAndPacked() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testConvertionBetweenFlatAndPackedOnGroups();
- tester.testConvertionBetweenFlatAndPackedOnChildren();
- }
-
- @MediumTest
- public void testSelectedPosition() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testSelectedPositionOnGroups();
- tester.testSelectedPositionOnChildren();
- }
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java b/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java
deleted file mode 100644
index f4c9d56..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import com.android.frameworks.coretests.R;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.TextView;
-
-public class InflatedExpandableListView extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.inflated_expandablelistview);
-
- ExpandableListView elv = (ExpandableListView) findViewById(R.id.elv);
- elv.setAdapter(new MyExpandableListAdapter());
- }
-
- public class MyExpandableListAdapter extends BaseExpandableListAdapter {
- // Sample data set. children[i] contains the children (String[]) for groups[i].
- private String[] groups = { "People Names", "Dog Names", "Cat Names", "Fish Names" };
- private String[][] children = {
- { "Arnold", "Barry", "Chuck", "David" },
- { "Ace", "Bandit", "Cha-Cha", "Deuce" },
- { "Fluffy", "Snuggles" },
- { "Goldy", "Bubbles" }
- };
-
- public Object getChild(int groupPosition, int childPosition) {
- return children[groupPosition][childPosition];
- }
-
- public long getChildId(int groupPosition, int childPosition) {
- return childPosition;
- }
-
- public int getChildrenCount(int groupPosition) {
- return children[groupPosition].length;
- }
-
- public TextView getGenericView() {
- // Layout parameters for the ExpandableListView
- AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, 64);
-
- TextView textView = new TextView(InflatedExpandableListView.this);
- textView.setLayoutParams(lp);
- // Center the text vertically
- textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
- // Set the text starting position
- textView.setPadding(36, 0, 0, 0);
- return textView;
- }
-
- public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
- View convertView, ViewGroup parent) {
- TextView textView = getGenericView();
- textView.setText(getChild(groupPosition, childPosition).toString());
- return textView;
- }
-
- public Object getGroup(int groupPosition) {
- return groups[groupPosition];
- }
-
- public int getGroupCount() {
- return groups.length;
- }
-
- public long getGroupId(int groupPosition) {
- return groupPosition;
- }
-
- public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
- ViewGroup parent) {
- TextView textView = getGenericView();
- textView.setText(getGroup(groupPosition).toString());
- return textView;
- }
-
- public boolean isChildSelectable(int groupPosition, int childPosition) {
- return true;
- }
-
- public boolean hasStableIds() {
- return true;
- }
-
- }
-
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java b/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java
deleted file mode 100644
index 2dbdff8..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java
+++ /dev/null
@@ -1,111 +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.
- */
-
-package android.widget.expandablelistview;
-
-import android.view.ContextMenu;
-import android.view.View;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View.OnCreateContextMenuListener;
-import android.widget.ExpandableListView;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-
-public class PositionTesterContextMenuListener implements OnCreateContextMenuListener {
-
- private int groupPosition, childPosition;
-
- // Fake constant to store in testType a test type specific to headers and footers
- private static final int ADAPTER_TYPE = -1;
- private int testType; // as returned by getPackedPositionType
-
- // Will be set to null by each call to onCreateContextMenu, unless an error occurred.
- private String errorMessage;
-
- public void expectGroupContextMenu(int groupPosition) {
- this.groupPosition = groupPosition;
- testType = ExpandableListView.PACKED_POSITION_TYPE_GROUP;
- }
-
- public void expectChildContextMenu(int groupPosition, int childPosition) {
- this.groupPosition = groupPosition;
- this.childPosition = childPosition;
- testType = ExpandableListView.PACKED_POSITION_TYPE_CHILD;
- }
-
- public void expectAdapterContextMenu(int flatPosition) {
- this.groupPosition = flatPosition;
- testType = ADAPTER_TYPE;
- }
-
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- errorMessage = null;
- if (testType == ADAPTER_TYPE) {
- if (!isTrue("MenuInfo is not an AdapterContextMenuInfo",
- menuInfo instanceof AdapterContextMenuInfo)) {
- return;
- }
- AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) menuInfo;
- if (!areEqual("Wrong flat position", groupPosition, adapterContextMenuInfo.position)) {
- return;
- }
- } else {
- if (!isTrue("MenuInfo is not an ExpandableListContextMenuInfo",
- menuInfo instanceof ExpandableListView.ExpandableListContextMenuInfo)) {
- return;
- }
- ExpandableListView.ExpandableListContextMenuInfo elvMenuInfo =
- (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
- long packedPosition = elvMenuInfo.packedPosition;
-
- int packedPositionType = ExpandableListView.getPackedPositionType(packedPosition);
- if (!areEqual("Wrong packed position type", testType, packedPositionType)) {
- return;
- }
-
- int packedPositionGroup = ExpandableListView.getPackedPositionGroup(packedPosition);
- if (!areEqual("Wrong group position", groupPosition, packedPositionGroup)) {
- return;
- }
-
- if (testType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
- int packedPositionChild = ExpandableListView.getPackedPositionChild(packedPosition);
- if (!areEqual("Wrong child position", childPosition, packedPositionChild)) {
- return;
- }
- }
- }
- }
-
- private boolean areEqual(String message, int expected, int actual) {
- if (expected != actual) {
- errorMessage = String.format(message + " (%d vs %d", expected, actual);
- return false;
- }
- return true;
- }
-
- private boolean isTrue(String message, boolean value) {
- if (!value) {
- errorMessage = message;
- return false;
- }
- return true;
- }
-
- public String getErrorMessage() {
- return errorMessage;
- }
-}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3e3f87b..183c896 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -93,7 +93,9 @@
/**
* Geometry and text drawn with this style will be both filled and
* stroked at the same time, respecting the stroke-related fields on
- * the paint.
+ * the paint. This mode can give unexpected results if the geometry
+ * is oriented counter-clockwise. This restriction does not apply to
+ * either FILL or STROKE.
*/
FILL_AND_STROKE (2);
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index 3904234..0e405c2 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -53,7 +53,10 @@
/** [Sa * Da, Sc * Dc] */
MULTIPLY (14),
/** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
- SCREEN (15);
+ SCREEN (15),
+ /** Saturate(S + D) */
+ ADD (16),
+ OVERLAY (17);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 6a7b2d1..4c1d243 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -16,21 +16,30 @@
package android.graphics.drawable;
-import java.io.InputStream;
-import java.io.IOException;
-import java.util.Arrays;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.*;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.NinePatch;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Region;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.StateSet;
-import android.util.Xml;
import android.util.TypedValue;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
/**
* A Drawable is a general abstraction for "something that can be drawn." Most
@@ -645,6 +654,8 @@
* Calling this method on a mutable Drawable will have no effect.
*
* @return This drawable.
+ * @see ConstantState
+ * @see #getConstantState()
*/
public Drawable mutate() {
return this;
@@ -750,6 +761,8 @@
drawable = new StateListDrawable();
} else if (name.equals("level-list")) {
drawable = new LevelListDrawable();
+ } else if (name.equals("mipmap")) {
+ drawable = new MipmapDrawable();
} else if (name.equals("layer-list")) {
drawable = new LayerDrawable();
} else if (name.equals("transition")) {
@@ -771,7 +784,7 @@
} else if (name.equals("inset")) {
drawable = new InsetDrawable();
} else if (name.equals("bitmap")) {
- drawable = new BitmapDrawable();
+ drawable = new BitmapDrawable(r);
if (r != null) {
((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
}
@@ -806,6 +819,9 @@
return null;
}
+ /**
+ * Inflate this Drawable from an XML resource.
+ */
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
@@ -814,6 +830,12 @@
a.recycle();
}
+ /**
+ * Inflate a Drawable from an XML resource.
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
void inflateWithAttributes(Resources r, XmlPullParser parser,
TypedArray attrs, int visibleAttr)
throws XmlPullParserException, IOException {
@@ -821,12 +843,27 @@
mVisible = attrs.getBoolean(visibleAttr, mVisible);
}
+ /**
+ * This abstract class is used by {@link Drawable}s to store shared constant state and data
+ * between Drawables. {@link BitmapDrawable}s created from the same resource will for instance
+ * share a unique bitmap stored in their ConstantState.
+ *
+ * <p>
+ * {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances
+ * from this ConstantState.
+ * </p>
+ *
+ * Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling
+ * {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that
+ * Drawable.
+ */
public static abstract class ConstantState {
/**
* Create a new drawable without supplying resources the caller
* is running in. Note that using this means the density-dependent
* drawables (like bitmaps) will not be able to update their target
- * density correctly.
+ * density correctly. One should use {@link #newDrawable(Resources)}
+ * instead to provide a resource.
*/
public abstract Drawable newDrawable();
/**
@@ -845,6 +882,13 @@
public abstract int getChangingConfigurations();
}
+ /**
+ * Return a {@link ConstantState} instance that holds the shared state of this Drawable.
+ *q
+ * @return The ConstantState associated to that Drawable.
+ * @see ConstantState
+ * @see Drawable#mutate()
+ */
public ConstantState getConstantState() {
return null;
}
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index c6f57d4..124d907 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -17,8 +17,16 @@
package android.graphics.drawable;
import android.content.res.Resources;
-import android.graphics.*;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+/**
+ * A helper class that contains several {@link Drawable}s and selects which one to use.
+ *
+ * You can subclass it to create your own DrawableContainers or directly use one its child classes.
+ */
public class DrawableContainer extends Drawable implements Drawable.Callback {
/**
@@ -196,8 +204,7 @@
mDrawableContainerState.getOpacity();
}
- public boolean selectDrawable(int idx)
- {
+ public boolean selectDrawable(int idx) {
if (idx == mCurIndex) {
return false;
}
@@ -255,6 +262,12 @@
return this;
}
+ /**
+ * A ConstantState that can contain several {@link Drawable}s.
+ *
+ * This class was made public to enable testing, and its visibility may change in a future
+ * release.
+ */
public abstract static class DrawableContainerState extends ConstantState {
final DrawableContainer mOwner;
@@ -443,12 +456,12 @@
return mConstantMinimumHeight;
}
- private void computeConstantSize() {
+ protected void computeConstantSize() {
mComputedConstantSize = true;
final int N = getChildCount();
final Drawable[] drawables = mDrawables;
- mConstantWidth = mConstantHeight = 0;
+ mConstantWidth = mConstantHeight = -1;
mConstantMinimumWidth = mConstantMinimumHeight = 0;
for (int i = 0; i < N; i++) {
Drawable dr = drawables[i];
diff --git a/graphics/java/android/graphics/drawable/MipmapDrawable.java b/graphics/java/android/graphics/drawable/MipmapDrawable.java
new file mode 100644
index 0000000..75fdeed
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/MipmapDrawable.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+
+/**
+ * A resource that manages a number of alternate Drawables, and which actually draws the one which
+ * size matches the most closely the drawing bounds. Providing several pre-scaled version of the
+ * drawable helps minimizing the aliasing artifacts that can be introduced by the scaling.
+ *
+ * <p>
+ * Use {@link #addDrawable(Drawable)} to define the different Drawables that will represent the
+ * mipmap levels of this MipmapDrawable. The mipmap Drawable that will actually be used when this
+ * MipmapDrawable is drawn is the one which has the smallest intrinsic height greater or equal than
+ * the bounds' height. This selection ensures that the best available mipmap level is scaled down to
+ * draw this MipmapDrawable.
+ * </p>
+ *
+ * If the bounds' height is larger than the largest mipmap, the largest mipmap will be scaled up.
+ * Note that Drawables without intrinsic height (i.e. with a negative value, such as Color) will
+ * only be used if no other mipmap Drawable are provided. The Drawables' intrinsic heights should
+ * not be changed after the Drawable has been added to this MipmapDrawable.
+ *
+ * <p>
+ * The different mipmaps' parameters (opacity, padding, color filter, gravity...) should typically
+ * be similar to ensure a continuous visual appearance when the MipmapDrawable is scaled. The aspect
+ * ratio of the different mipmaps should especially be equal.
+ * </p>
+ *
+ * A typical example use of a MipmapDrawable would be for an image which is intended to be scaled at
+ * various sizes, and for which one wants to provide pre-scaled versions to precisely control its
+ * appearance.
+ *
+ * <p>
+ * The intrinsic size of a MipmapDrawable are inferred from those of the largest mipmap (in terms of
+ * {@link Drawable#getIntrinsicHeight()}). On the opposite, its minimum
+ * size is defined by the smallest provided mipmap.
+ * </p>
+
+ * It can be defined in an XML file with the <code><mipmap></code> element.
+ * Each mipmap Drawable is defined in a nested <code><item></code>. For example:
+ * <pre>
+ * <mipmap xmlns:android="http://schemas.android.com/apk/res/android">
+ * <item android:drawable="@drawable/my_image_8" />
+ * <item android:drawable="@drawable/my_image_32" />
+ * <item android:drawable="@drawable/my_image_128" />
+ * </mipmap>
+ *</pre>
+ * <p>
+ * With this XML saved into the res/drawable/ folder of the project, it can be referenced as
+ * the drawable for an {@link android.widget.ImageView}. Assuming that the heights of the provided
+ * drawables are respectively 8, 32 and 128 pixels, the first one will be scaled down when the
+ * bounds' height is lower or equal than 8 pixels. The second drawable will then be used up to a
+ * height of 32 pixels and the largest drawable will be used for greater heights.
+ * </p>
+ * @attr ref android.R.styleable#MipmapDrawableItem_drawable
+ */
+public class MipmapDrawable extends DrawableContainer {
+ private final MipmapContainerState mMipmapContainerState;
+ private boolean mMutated;
+
+ public MipmapDrawable() {
+ this(null, null);
+ }
+
+ /**
+ * Adds a Drawable to the list of available mipmap Drawables. The Drawable actually used when
+ * this MipmapDrawable is drawn is determined from its bounds.
+ *
+ * This method has no effect if drawable is null.
+ *
+ * @param drawable The Drawable that will be added to list of available mipmap Drawables.
+ */
+
+ public void addDrawable(Drawable drawable) {
+ if (drawable != null) {
+ mMipmapContainerState.addDrawable(drawable);
+ onDrawableAdded();
+ }
+ }
+
+ private void onDrawableAdded() {
+ // selectDrawable assumes that the container content does not change.
+ // When a Drawable is added, the same index can correspond to a new Drawable, and since
+ // selectDrawable has a fast exit case when oldIndex==newIndex, the new drawable could end
+ // up not being used in place of the previous one if they happen to share the same index.
+ // This make sure the new computed index can actually replace the previous one.
+ selectDrawable(-1);
+ onBoundsChange(getBounds());
+ }
+
+ // overrides from Drawable
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ final int index = mMipmapContainerState.indexForBounds(bounds);
+
+ // Will call invalidateSelf() if needed
+ selectDrawable(index);
+
+ super.onBoundsChange(bounds);
+ }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+
+ super.inflate(r, parser, attrs);
+
+ int type;
+
+ final int innerDepth = parser.getDepth() + 1;
+ int depth;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth
+ || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (depth > innerDepth || !parser.getName().equals("item")) {
+ continue;
+ }
+
+ TypedArray a = r.obtainAttributes(attrs,
+ com.android.internal.R.styleable.MipmapDrawableItem);
+
+ int drawableRes = a.getResourceId(
+ com.android.internal.R.styleable.MipmapDrawableItem_drawable, 0);
+
+ a.recycle();
+
+ Drawable dr;
+ if (drawableRes != 0) {
+ dr = r.getDrawable(drawableRes);
+ } else {
+ while ((type = parser.next()) == XmlPullParser.TEXT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription()
+ + ": <item> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ dr = Drawable.createFromXmlInner(r, parser, attrs);
+ }
+
+ mMipmapContainerState.addDrawable(dr);
+ }
+
+ onDrawableAdded();
+ }
+
+ @Override
+ public Drawable mutate() {
+ if (!mMutated && super.mutate() == this) {
+ mMipmapContainerState.mMipmapHeights = mMipmapContainerState.mMipmapHeights.clone();
+ mMutated = true;
+ }
+ return this;
+ }
+
+ private final static class MipmapContainerState extends DrawableContainerState {
+ private int[] mMipmapHeights;
+
+ MipmapContainerState(MipmapContainerState orig, MipmapDrawable owner, Resources res) {
+ super(orig, owner, res);
+
+ if (orig != null) {
+ mMipmapHeights = orig.mMipmapHeights;
+ } else {
+ mMipmapHeights = new int[getChildren().length];
+ }
+
+ // Change the default value
+ setConstantSize(true);
+ }
+
+ /**
+ * Returns the index of the child mipmap drawable that will best fit the provided bounds.
+ * This index is determined by comparing bounds' height and children intrinsic heights.
+ * The returned mipmap index is the smallest mipmap which height is greater or equal than
+ * the bounds' height. If the bounds' height is larger than the largest mipmap, the largest
+ * mipmap index is returned.
+ *
+ * @param bounds The bounds of the MipMapDrawable.
+ * @return The index of the child Drawable that will best fit these bounds, or -1 if there
+ * are no children mipmaps.
+ */
+ public int indexForBounds(Rect bounds) {
+ final int boundsHeight = bounds.height();
+ final int N = getChildCount();
+ for (int i = 0; i < N; i++) {
+ if (boundsHeight <= mMipmapHeights[i]) {
+ return i;
+ }
+ }
+
+ // No mipmap larger than bounds found. Use largest one which will be scaled up.
+ if (N > 0) {
+ return N - 1;
+ }
+ // No Drawable mipmap at all
+ return -1;
+ }
+
+ /**
+ * Adds a Drawable to the list of available mipmap Drawables. This list can be retrieved
+ * using {@link DrawableContainer.DrawableContainerState#getChildren()} and this method
+ * ensures that it is always sorted by increasing {@link Drawable#getIntrinsicHeight()}.
+ *
+ * @param drawable The Drawable that will be added to children list
+ */
+ public void addDrawable(Drawable drawable) {
+ // Insert drawable in last position, correctly resetting cached values and
+ // especially mComputedConstantSize
+ int pos = addChild(drawable);
+
+ // Bubble sort the last drawable to restore the sort by intrinsic height
+ final int drawableHeight = drawable.getIntrinsicHeight();
+
+ while (pos > 0) {
+ final Drawable previousDrawable = mDrawables[pos-1];
+ final int previousIntrinsicHeight = previousDrawable.getIntrinsicHeight();
+
+ if (drawableHeight < previousIntrinsicHeight) {
+ mDrawables[pos] = previousDrawable;
+ mMipmapHeights[pos] = previousIntrinsicHeight;
+
+ mDrawables[pos-1] = drawable;
+ mMipmapHeights[pos-1] = drawableHeight;
+ pos--;
+ } else {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Intrinsic sizes are those of the largest available mipmap.
+ * Minimum sizes are those of the smallest available mipmap.
+ */
+ @Override
+ protected void computeConstantSize() {
+ final int N = getChildCount();
+ if (N > 0) {
+ final Drawable smallestDrawable = mDrawables[0];
+ mConstantMinimumWidth = smallestDrawable.getMinimumWidth();
+ mConstantMinimumHeight = smallestDrawable.getMinimumHeight();
+
+ final Drawable largestDrawable = mDrawables[N-1];
+ mConstantWidth = largestDrawable.getIntrinsicWidth();
+ mConstantHeight = largestDrawable.getIntrinsicHeight();
+ } else {
+ mConstantWidth = mConstantHeight = -1;
+ mConstantMinimumWidth = mConstantMinimumHeight = 0;
+ }
+ mComputedConstantSize = true;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new MipmapDrawable(this, null);
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res) {
+ return new MipmapDrawable(this, res);
+ }
+
+ @Override
+ public void growArray(int oldSize, int newSize) {
+ super.growArray(oldSize, newSize);
+ int[] newInts = new int[newSize];
+ System.arraycopy(mMipmapHeights, 0, newInts, 0, oldSize);
+ mMipmapHeights = newInts;
+ }
+ }
+
+ private MipmapDrawable(MipmapContainerState state, Resources res) {
+ MipmapContainerState as = new MipmapContainerState(state, this, res);
+ mMipmapContainerState = as;
+ setConstantState(as);
+ onDrawableAdded();
+ }
+}
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 17c0778..d32a0b5 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -76,10 +76,30 @@
subData1D(0, mType.getElementCount(), d);
}
+ public void subData(int off, FieldPacker fp) {
+ int eSize = mType.mElement.getSizeBytes();
+ final byte[] data = fp.getData();
+
+ int count = data.length / eSize;
+ if ((eSize * count) != data.length) {
+ throw new IllegalArgumentException("Field packer length " + data.length +
+ " not divisible by element size " + eSize + ".");
+ }
+ data1DChecks(off, count, data.length, data.length);
+ mRS.nAllocationSubData1D(mID, off, count, data, data.length);
+ }
+
private void data1DChecks(int off, int count, int len, int dataSize) {
mRS.validate();
- if((off < 0) || (count < 1) || ((off + count) > mType.getElementCount())) {
- throw new IllegalArgumentException("Offset or Count out of bounds.");
+ if(off < 0) {
+ throw new IllegalArgumentException("Offset must be >= 0.");
+ }
+ if(count < 1) {
+ throw new IllegalArgumentException("Count must be >= 1.");
+ }
+ if((off + count) > mType.getElementCount()) {
+ throw new IllegalArgumentException("Overflow, Available count " + mType.getElementCount() +
+ ", got " + count + " at offset " + off + ".");
}
if((len) < dataSize) {
throw new IllegalArgumentException("Array too small for allocation type.");
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Byte2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Byte2.java
index 567d57fa..95cf88c 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Byte2.java
@@ -24,12 +24,12 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Byte2 {
+ public Byte2() {
}
- public float x;
- public float y;
+ public byte x;
+ public byte y;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Byte3.java
similarity index 87%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Byte3.java
index 567d57fa..a6c0ca9 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Byte3.java
@@ -24,12 +24,13 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Byte3 {
+ public Byte3() {
}
- public float x;
- public float y;
+ public byte x;
+ public byte y;
+ public byte z;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Byte4.java
similarity index 85%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Byte4.java
index 567d57fa..a5bfc61 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Byte4.java
@@ -24,14 +24,15 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Byte4 {
+ public Byte4() {
}
- public float x;
- public float y;
+ public byte x;
+ public byte y;
+ public byte z;
+ public byte w;
}
-
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index 10ef05a..7b155fe 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -126,6 +126,77 @@
return rs.mElement_USER_F32;
}
+ public static Element USER_ELEMENT(RenderScript rs) {
+ if(rs.mElement_USER_ELEMENT == null) {
+ rs.mElement_USER_ELEMENT = createUser(rs, DataType.RS_ELEMENT);
+ }
+ return rs.mElement_USER_ELEMENT;
+ }
+
+ public static Element USER_TYPE(RenderScript rs) {
+ if(rs.mElement_USER_TYPE == null) {
+ rs.mElement_USER_TYPE = createUser(rs, DataType.RS_TYPE);
+ }
+ return rs.mElement_USER_TYPE;
+ }
+
+ public static Element USER_ALLOCATION(RenderScript rs) {
+ if(rs.mElement_USER_ALLOCATION == null) {
+ rs.mElement_USER_ALLOCATION = createUser(rs, DataType.RS_ALLOCATION);
+ }
+ return rs.mElement_USER_ALLOCATION;
+ }
+
+ public static Element USER_SAMPLER(RenderScript rs) {
+ if(rs.mElement_USER_SAMPLER == null) {
+ rs.mElement_USER_SAMPLER = createUser(rs, DataType.RS_SAMPLER);
+ }
+ return rs.mElement_USER_SAMPLER;
+ }
+
+ public static Element USER_SCRIPT(RenderScript rs) {
+ if(rs.mElement_USER_SCRIPT == null) {
+ rs.mElement_USER_SCRIPT = createUser(rs, DataType.RS_SCRIPT);
+ }
+ return rs.mElement_USER_SCRIPT;
+ }
+
+ public static Element USER_MESH(RenderScript rs) {
+ if(rs.mElement_USER_MESH == null) {
+ rs.mElement_USER_MESH = createUser(rs, DataType.RS_MESH);
+ }
+ return rs.mElement_USER_MESH;
+ }
+
+ public static Element USER_PROGRAM_FRAGMENT(RenderScript rs) {
+ if(rs.mElement_USER_PROGRAM_FRAGMENT == null) {
+ rs.mElement_USER_PROGRAM_FRAGMENT = createUser(rs, DataType.RS_PROGRAM_FRAGMENT);
+ }
+ return rs.mElement_USER_PROGRAM_FRAGMENT;
+ }
+
+ public static Element USER_PROGRAM_VERTEX(RenderScript rs) {
+ if(rs.mElement_USER_PROGRAM_VERTEX == null) {
+ rs.mElement_USER_PROGRAM_VERTEX = createUser(rs, DataType.RS_PROGRAM_VERTEX);
+ }
+ return rs.mElement_USER_PROGRAM_VERTEX;
+ }
+
+ public static Element USER_PROGRAM_RASTER(RenderScript rs) {
+ if(rs.mElement_USER_PROGRAM_RASTER == null) {
+ rs.mElement_USER_PROGRAM_RASTER = createUser(rs, DataType.RS_PROGRAM_RASTER);
+ }
+ return rs.mElement_USER_PROGRAM_RASTER;
+ }
+
+ public static Element USER_PROGRAM_STORE(RenderScript rs) {
+ if(rs.mElement_USER_PROGRAM_STORE == null) {
+ rs.mElement_USER_PROGRAM_STORE = createUser(rs, DataType.RS_PROGRAM_STORE);
+ }
+ return rs.mElement_USER_PROGRAM_STORE;
+ }
+
+
public static Element A_8(RenderScript rs) {
if(rs.mElement_A_8 == null) {
rs.mElement_A_8 = createPixel(rs, DataType.UNSIGNED_8, DataKind.PIXEL_A);
diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java
index b26e47d..6d55c7e 100644
--- a/graphics/java/android/renderscript/FieldPacker.java
+++ b/graphics/java/android/renderscript/FieldPacker.java
@@ -33,21 +33,24 @@
}
}
- void reset() {
+ public void reset() {
mPos = 0;
}
+ public void reset(int i) {
+ mPos = i;
+ }
- void addI8(byte v) {
+ public void addI8(byte v) {
mData[mPos++] = v;
}
- void addI16(short v) {
+ public void addI16(short v) {
align(2);
mData[mPos++] = (byte)(v & 0xff);
mData[mPos++] = (byte)(v >> 8);
}
- void addI32(int v) {
+ public void addI32(int v) {
align(4);
mData[mPos++] = (byte)(v & 0xff);
mData[mPos++] = (byte)((v >> 8) & 0xff);
@@ -55,7 +58,7 @@
mData[mPos++] = (byte)((v >> 24) & 0xff);
}
- void addI64(long v) {
+ public void addI64(long v) {
align(8);
mData[mPos++] = (byte)(v & 0xff);
mData[mPos++] = (byte)((v >> 8) & 0xff);
@@ -67,14 +70,14 @@
mData[mPos++] = (byte)((v >> 56) & 0xff);
}
- void addU8(short v) {
+ public void addU8(short v) {
if ((v < 0) || (v > 0xff)) {
throw new IllegalArgumentException("Saving value out of range for type");
}
mData[mPos++] = (byte)v;
}
- void addU16(int v) {
+ public void addU16(int v) {
if ((v < 0) || (v > 0xffff)) {
throw new IllegalArgumentException("Saving value out of range for type");
}
@@ -83,7 +86,7 @@
mData[mPos++] = (byte)(v >> 8);
}
- void addU32(long v) {
+ public void addU32(long v) {
if ((v < 0) || (v > 0xffffffff)) {
throw new IllegalArgumentException("Saving value out of range for type");
}
@@ -94,7 +97,7 @@
mData[mPos++] = (byte)((v >> 24) & 0xff);
}
- void addU64(long v) {
+ public void addU64(long v) {
if (v < 0) {
throw new IllegalArgumentException("Saving value out of range for type");
}
@@ -109,15 +112,135 @@
mData[mPos++] = (byte)((v >> 56) & 0xff);
}
- void addF32(float v) {
+ public void addF32(float v) {
addI32(Float.floatToRawIntBits(v));
}
- void addF64(float v) {
+ public void addF64(float v) {
addI64(Double.doubleToRawLongBits(v));
}
- final byte[] getData() {
+ public void addObj(BaseObj obj) {
+ if (obj != null) {
+ addI32(obj.getID());
+ } else {
+ addI32(0);
+ }
+ }
+
+ public void addF32(Float2 v) {
+ addF32(v.x);
+ addF32(v.y);
+ }
+ public void addF32(Float3 v) {
+ addF32(v.x);
+ addF32(v.y);
+ addF32(v.z);
+ }
+ public void addF32(Float4 v) {
+ addF32(v.x);
+ addF32(v.y);
+ addF32(v.z);
+ addF32(v.w);
+ }
+
+ public void addI8(Byte2 v) {
+ addI8(v.x);
+ addI8(v.y);
+ }
+ public void addI8(Byte3 v) {
+ addI8(v.x);
+ addI8(v.y);
+ addI8(v.z);
+ }
+ public void addI8(Byte4 v) {
+ addI8(v.x);
+ addI8(v.y);
+ addI8(v.z);
+ addI8(v.w);
+ }
+
+ public void addU8(Short2 v) {
+ addU8(v.x);
+ addU8(v.y);
+ }
+ public void addU8(Short3 v) {
+ addU8(v.x);
+ addU8(v.y);
+ addU8(v.z);
+ }
+ public void addU8(Short4 v) {
+ addU8(v.x);
+ addU8(v.y);
+ addU8(v.z);
+ addU8(v.w);
+ }
+
+ public void addI16(Short2 v) {
+ addI16(v.x);
+ addI16(v.y);
+ }
+ public void addI16(Short3 v) {
+ addI16(v.x);
+ addI16(v.y);
+ addI16(v.z);
+ }
+ public void addI16(Short4 v) {
+ addI16(v.x);
+ addI16(v.y);
+ addI16(v.z);
+ addI16(v.w);
+ }
+
+ public void addU16(Int2 v) {
+ addU16(v.x);
+ addU16(v.y);
+ }
+ public void addU16(Int3 v) {
+ addU16(v.x);
+ addU16(v.y);
+ addU16(v.z);
+ }
+ public void addU16(Int4 v) {
+ addU16(v.x);
+ addU16(v.y);
+ addU16(v.z);
+ addU16(v.w);
+ }
+
+ public void addI32(Int2 v) {
+ addI32(v.x);
+ addI32(v.y);
+ }
+ public void addI32(Int3 v) {
+ addI32(v.x);
+ addI32(v.y);
+ addI32(v.z);
+ }
+ public void addI32(Int4 v) {
+ addI32(v.x);
+ addI32(v.y);
+ addI32(v.z);
+ addI32(v.w);
+ }
+
+ public void addU32(Int2 v) {
+ addU32(v.x);
+ addU32(v.y);
+ }
+ public void addU32(Int3 v) {
+ addU32(v.x);
+ addU32(v.y);
+ addU32(v.z);
+ }
+ public void addU32(Int4 v) {
+ addU32(v.x);
+ addU32(v.y);
+ addU32(v.z);
+ addU32(v.w);
+ }
+
+ public final byte[] getData() {
return mData;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Float2.java
similarity index 93%
rename from graphics/java/android/renderscript/Vector2f.java
rename to graphics/java/android/renderscript/Float2.java
index 567d57fa..8fea91f 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Float2.java
@@ -24,8 +24,8 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Float2 {
+ public Float2() {
}
public float x;
diff --git a/graphics/java/android/renderscript/Vector3f.java b/graphics/java/android/renderscript/Float3.java
similarity index 94%
rename from graphics/java/android/renderscript/Vector3f.java
rename to graphics/java/android/renderscript/Float3.java
index f2842f3..9d9e406 100644
--- a/graphics/java/android/renderscript/Vector3f.java
+++ b/graphics/java/android/renderscript/Float3.java
@@ -24,8 +24,8 @@
* @hide
*
**/
-public class Vector3f {
- public Vector3f() {
+public class Float3 {
+ public Float3() {
}
public float x;
diff --git a/graphics/java/android/renderscript/Vector4f.java b/graphics/java/android/renderscript/Float4.java
similarity index 94%
rename from graphics/java/android/renderscript/Vector4f.java
rename to graphics/java/android/renderscript/Float4.java
index fabd959..a703e80 100644
--- a/graphics/java/android/renderscript/Vector4f.java
+++ b/graphics/java/android/renderscript/Float4.java
@@ -24,8 +24,8 @@
* @hide
*
**/
-public class Vector4f {
- public Vector4f() {
+public class Float4 {
+ public Float4() {
}
public float x;
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Int2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Int2.java
index 567d57fa..56e2fe9 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Int2.java
@@ -24,12 +24,12 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Int2 {
+ public Int2() {
}
- public float x;
- public float y;
+ public int x;
+ public int y;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Int3.java
similarity index 88%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Int3.java
index 567d57fa..1b27509 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Int3.java
@@ -24,12 +24,13 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Int3 {
+ public Int3() {
}
- public float x;
- public float y;
+ public int x;
+ public int y;
+ public int z;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Int4.java
similarity index 86%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Int4.java
index 567d57fa..3d6f3f5 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Int4.java
@@ -24,14 +24,15 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Int4 {
+ public Int4() {
}
- public float x;
- public float y;
+ public int x;
+ public int y;
+ public int z;
+ public int w;
}
-
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Long2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Long2.java
index 567d57fa..11ead2f 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Long2.java
@@ -24,12 +24,12 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Long2 {
+ public Long2() {
}
- public float x;
- public float y;
+ public long x;
+ public long y;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Long3.java
similarity index 87%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Long3.java
index 567d57fa..1604532 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Long3.java
@@ -24,12 +24,13 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Long3 {
+ public Long3() {
}
- public float x;
- public float y;
+ public long x;
+ public long y;
+ public long z;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Long4.java
similarity index 85%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Long4.java
index 567d57fa..2fd2747 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Long4.java
@@ -24,14 +24,15 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Long4 {
+ public Long4() {
}
- public float x;
- public float y;
+ public long x;
+ public long y;
+ public long z;
+ public long w;
}
-
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index a935243..db2a3fd 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -203,6 +203,17 @@
Element mElement_USER_I32;
Element mElement_USER_F32;
+ Element mElement_USER_ELEMENT;
+ Element mElement_USER_TYPE;
+ Element mElement_USER_ALLOCATION;
+ Element mElement_USER_SAMPLER;
+ Element mElement_USER_SCRIPT;
+ Element mElement_USER_MESH;
+ Element mElement_USER_PROGRAM_FRAGMENT;
+ Element mElement_USER_PROGRAM_VERTEX;
+ Element mElement_USER_PROGRAM_RASTER;
+ Element mElement_USER_PROGRAM_STORE;
+
Element mElement_A_8;
Element mElement_RGB_565;
Element mElement_RGB_888;
diff --git a/graphics/java/android/renderscript/Script.java b/graphics/java/android/renderscript/Script.java
index 57ccfa3..0d21368 100644
--- a/graphics/java/android/renderscript/Script.java
+++ b/graphics/java/android/renderscript/Script.java
@@ -42,6 +42,10 @@
}
}
+ protected void invoke(int slot) {
+ mRS.nScriptInvoke(mID, slot);
+ }
+
Script(int id, RenderScript rs) {
super(rs);
mID = id;
@@ -145,5 +149,48 @@
}
+
+ public static class FieldBase {
+ protected Element mElement;
+ protected Type mType;
+ protected Allocation mAllocation;
+
+ protected void init(RenderScript rs, int dimx) {
+ mAllocation = Allocation.createSized(rs, mElement, dimx);
+ mType = mAllocation.getType();
+ }
+
+ protected FieldBase() {
+ }
+
+ public Element getElement() {
+ return mElement;
+ }
+
+ public Type getType() {
+ return mType;
+ }
+
+ public Allocation getAllocation() {
+ return mAllocation;
+ }
+
+ //@Override
+ public void updateAllocation() {
+ }
+
+
+ //
+ /*
+ public class ScriptField_UserField
+ extends android.renderscript.Script.FieldBase {
+
+ protected
+
+ }
+
+ */
+
+ }
}
diff --git a/graphics/java/android/renderscript/ScriptC.java b/graphics/java/android/renderscript/ScriptC.java
index bb99e23..f5d5b2f 100644
--- a/graphics/java/android/renderscript/ScriptC.java
+++ b/graphics/java/android/renderscript/ScriptC.java
@@ -37,6 +37,47 @@
super(id, rs);
}
+ protected ScriptC(RenderScript rs, Resources resources, int resourceID, boolean isRoot) {
+ super(0, rs);
+ mID = internalCreate(rs, resources, resourceID, isRoot);
+ }
+
+
+ private static synchronized int internalCreate(RenderScript rs, Resources resources, int resourceID, boolean isRoot) {
+ byte[] pgm;
+ int pgmLength;
+ InputStream is = resources.openRawResource(resourceID);
+ try {
+ try {
+ pgm = new byte[1024];
+ pgmLength = 0;
+ while(true) {
+ int bytesLeft = pgm.length - pgmLength;
+ if (bytesLeft == 0) {
+ byte[] buf2 = new byte[pgm.length * 2];
+ System.arraycopy(pgm, 0, buf2, 0, pgm.length);
+ pgm = buf2;
+ bytesLeft = pgm.length - pgmLength;
+ }
+ int bytesRead = is.read(pgm, pgmLength, bytesLeft);
+ if (bytesRead <= 0) {
+ break;
+ }
+ pgmLength += bytesRead;
+ }
+ } finally {
+ is.close();
+ }
+ } catch(IOException e) {
+ throw new Resources.NotFoundException();
+ }
+
+ rs.nScriptCBegin();
+ rs.nScriptCSetScript(pgm, 0, pgmLength);
+ rs.nScriptSetRoot(isRoot);
+ return rs.nScriptCCreate();
+ }
+
public static class Builder extends Script.Builder {
byte[] mProgram;
int mProgramLength;
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Short2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Short2.java
index 567d57fa..426801f 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Short2.java
@@ -24,12 +24,12 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Short2 {
+ public Short2() {
}
- public float x;
- public float y;
+ public short x;
+ public short y;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Short3.java
similarity index 87%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Short3.java
index 567d57fa..7b9c305 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Short3.java
@@ -24,12 +24,13 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Short3 {
+ public Short3() {
}
- public float x;
- public float y;
+ public short x;
+ public short y;
+ public short z;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Short4.java
similarity index 85%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Short4.java
index 567d57fa..9a474e2 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Short4.java
@@ -24,14 +24,15 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Short4 {
+ public Short4() {
}
- public float x;
- public float y;
+ public short x;
+ public short y;
+ public short z;
+ public short w;
}
-
diff --git a/icu4j/java/android/icu/text/ArabicShaping.java b/icu4j/java/android/icu/text/ArabicShaping.java
new file mode 100644
index 0000000..13e2175
--- /dev/null
+++ b/icu4j/java/android/icu/text/ArabicShaping.java
@@ -0,0 +1,1947 @@
+/*
+*******************************************************************************
+* Copyright (C) 2001-2009, International Business Machines
+* Corporation and others. All Rights Reserved.
+*******************************************************************************
+*/
+
+/*
+ * Ported with minor modifications from ICU4J 4.2's
+ * com.ibm.icu.text.ArabicShaping class.
+ */
+
+package android.icu.text;
+
+
+/**
+ * Shape Arabic text on a character basis.
+ *
+ * <p>ArabicShaping performs basic operations for "shaping" Arabic text. It is most
+ * useful for use with legacy data formats and legacy display technology
+ * (simple terminals). All operations are performed on Unicode characters.</p>
+ *
+ * <p>Text-based shaping means that some character code points in the text are
+ * replaced by others depending on the context. It transforms one kind of text
+ * into another. In comparison, modern displays for Arabic text select
+ * appropriate, context-dependent font glyphs for each text element, which means
+ * that they transform text into a glyph vector.</p>
+ *
+ * <p>Text transformations are necessary when modern display technology is not
+ * available or when text needs to be transformed to or from legacy formats that
+ * use "shaped" characters. Since the Arabic script is cursive, connecting
+ * adjacent letters to each other, computers select images for each letter based
+ * on the surrounding letters. This usually results in four images per Arabic
+ * letter: initial, middle, final, and isolated forms. In Unicode, on the other
+ * hand, letters are normally stored abstract, and a display system is expected
+ * to select the necessary glyphs. (This makes searching and other text
+ * processing easier because the same letter has only one code.) It is possible
+ * to mimic this with text transformations because there are characters in
+ * Unicode that are rendered as letters with a specific shape
+ * (or cursive connectivity). They were included for interoperability with
+ * legacy systems and codepages, and for unsophisticated display systems.</p>
+ *
+ * <p>A second kind of text transformations is supported for Arabic digits:
+ * For compatibility with legacy codepages that only include European digits,
+ * it is possible to replace one set of digits by another, changing the
+ * character code points. These operations can be performed for either
+ * Arabic-Indic Digits (U+0660...U+0669) or Eastern (Extended) Arabic-Indic
+ * digits (U+06f0...U+06f9).</p>
+ *
+ * <p>Some replacements may result in more or fewer characters (code points).
+ * By default, this means that the destination buffer may receive text with a
+ * length different from the source length. Some legacy systems rely on the
+ * length of the text to be constant. They expect extra spaces to be added
+ * or consumed either next to the affected character or at the end of the
+ * text.</p>
+ * @stable ICU 2.0
+ *
+ * @hide
+ */
+public class ArabicShaping {
+ private final int options;
+ private boolean isLogical; // convenience
+ private boolean spacesRelativeToTextBeginEnd;
+ private char tailChar;
+
+ public static final ArabicShaping SHAPER = new ArabicShaping(
+ ArabicShaping.TEXT_DIRECTION_LOGICAL |
+ ArabicShaping.LENGTH_FIXED_SPACES_NEAR |
+ ArabicShaping.LETTERS_SHAPE |
+ ArabicShaping.DIGITS_NOOP);
+
+ /**
+ * Convert a range of text in the source array, putting the result
+ * into a range of text in the destination array, and return the number
+ * of characters written.
+ *
+ * @param source An array containing the input text
+ * @param sourceStart The start of the range of text to convert
+ * @param sourceLength The length of the range of text to convert
+ * @param dest The destination array that will receive the result.
+ * It may be <code>NULL</code> only if <code>destSize</code> is 0.
+ * @param destStart The start of the range of the destination buffer to use.
+ * @param destSize The size (capacity) of the destination buffer.
+ * If <code>destSize</code> is 0, then no output is produced,
+ * but the necessary buffer size is returned ("preflighting"). This
+ * does not validate the text against the options, for example,
+ * if letters are being unshaped, and spaces are being consumed
+ * following lamalef, this will not detect a lamalef without a
+ * corresponding space. An error will be thrown when the actual
+ * conversion is attempted.
+ * @return The number of chars written to the destination buffer.
+ * If an error occurs, then no output was written, or it may be
+ * incomplete.
+ * @throws ArabicShapingException if the text cannot be converted according to the options.
+ * @stable ICU 2.0
+ */
+ public int shape(char[] source, int sourceStart, int sourceLength,
+ char[] dest, int destStart, int destSize) throws ArabicShapingException {
+ if (source == null) {
+ throw new IllegalArgumentException("source can not be null");
+ }
+ if (sourceStart < 0 || sourceLength < 0 || sourceStart + sourceLength > source.length) {
+ throw new IllegalArgumentException("bad source start (" + sourceStart +
+ ") or length (" + sourceLength +
+ ") for buffer of length " + source.length);
+ }
+ if (dest == null && destSize != 0) {
+ throw new IllegalArgumentException("null dest requires destSize == 0");
+ }
+ if ((destSize != 0) &&
+ (destStart < 0 || destSize < 0 || destStart + destSize > dest.length)) {
+ throw new IllegalArgumentException("bad dest start (" + destStart +
+ ") or size (" + destSize +
+ ") for buffer of length " + dest.length);
+ }
+ /* Validate input options */
+ if ( ((options&TASHKEEL_MASK) > 0) &&
+ !(((options & TASHKEEL_MASK)==TASHKEEL_BEGIN) ||
+ ((options & TASHKEEL_MASK)==TASHKEEL_END ) ||
+ ((options & TASHKEEL_MASK)==TASHKEEL_RESIZE )||
+ ((options & TASHKEEL_MASK)==TASHKEEL_REPLACE_BY_TATWEEL)) ){
+ throw new IllegalArgumentException("Wrong Tashkeel argument");
+ }
+
+ ///CLOVER:OFF
+ //According to Steven Loomis, the code is unreachable when you OR all the constants within the if statements
+ if(((options&LAMALEF_MASK) > 0)&&
+ !(((options & LAMALEF_MASK)==LAMALEF_BEGIN) ||
+ ((options & LAMALEF_MASK)==LAMALEF_END ) ||
+ ((options & LAMALEF_MASK)==LAMALEF_RESIZE )||
+ ((options & LAMALEF_MASK)==LAMALEF_AUTO) ||
+ ((options & LAMALEF_MASK)==LAMALEF_NEAR))){
+ throw new IllegalArgumentException("Wrong Lam Alef argument");
+ }
+ ///CLOVER:ON
+
+ /* Validate Tashkeel (Tashkeel replacement options should be enabled in shaping mode only)*/
+ if(((options&TASHKEEL_MASK) > 0) && (options&LETTERS_MASK) == LETTERS_UNSHAPE) {
+ throw new IllegalArgumentException("Tashkeel replacement should not be enabled in deshaping mode ");
+ }
+ return internalShape(source, sourceStart, sourceLength, dest, destStart, destSize);
+ }
+
+ /**
+ * Convert a range of text in place. This may only be used if the Length option
+ * does not grow or shrink the text.
+ *
+ * @param source An array containing the input text
+ * @param start The start of the range of text to convert
+ * @param length The length of the range of text to convert
+ * @throws ArabicShapingException if the text cannot be converted according to the options.
+ * @stable ICU 2.0
+ */
+ public void shape(char[] source, int start, int length) throws ArabicShapingException {
+ if ((options & LAMALEF_MASK) == LAMALEF_RESIZE) {
+ throw new ArabicShapingException("Cannot shape in place with length option resize.");
+ }
+ shape(source, start, length, source, start, length);
+ }
+
+ /**
+ * Convert a string, returning the new string.
+ *
+ * @param text the string to convert
+ * @return the converted string
+ * @throws ArabicShapingException if the string cannot be converted according to the options.
+ * @stable ICU 2.0
+ */
+ public String shape(String text) throws ArabicShapingException {
+ char[] src = text.toCharArray();
+ char[] dest = src;
+ if (((options & LAMALEF_MASK) == LAMALEF_RESIZE) &&
+ ((options & LETTERS_MASK) == LETTERS_UNSHAPE)) {
+
+ dest = new char[src.length * 2]; // max
+ }
+ int len = shape(src, 0, src.length, dest, 0, dest.length);
+
+ return new String(dest, 0, len);
+ }
+
+ /**
+ * Construct ArabicShaping using the options flags.
+ * The flags are as follows:<br>
+ * 'LENGTH' flags control whether the text can change size, and if not,
+ * how to maintain the size of the text when LamAlef ligatures are
+ * formed or broken.<br>
+ * 'TEXT_DIRECTION' flags control whether the text is read and written
+ * in visual order or in logical order.<br>
+ * 'LETTERS_SHAPE' flags control whether conversion is to or from
+ * presentation forms.<br>
+ * 'DIGITS' flags control whether digits are shaped, and whether from
+ * European to Arabic-Indic or vice-versa.<br>
+ * 'DIGIT_TYPE' flags control whether standard or extended Arabic-Indic
+ * digits are used when performing digit conversion.
+ * @stable ICU 2.0
+ */
+ public ArabicShaping(int options) {
+ this.options = options;
+ if ((options & DIGITS_MASK) > 0x80) {
+ throw new IllegalArgumentException("bad DIGITS options");
+ }
+
+ isLogical = ( (options & TEXT_DIRECTION_MASK) == TEXT_DIRECTION_LOGICAL );
+ /* Validate options */
+ spacesRelativeToTextBeginEnd = ( (options & SPACES_RELATIVE_TO_TEXT_MASK) == SPACES_RELATIVE_TO_TEXT_BEGIN_END );
+ if ( (options&SHAPE_TAIL_TYPE_MASK) == SHAPE_TAIL_NEW_UNICODE){
+ tailChar = NEW_TAIL_CHAR;
+ } else {
+ tailChar = OLD_TAIL_CHAR;
+ }
+ }
+
+ /* Seen Tail options */
+ /**
+ * Memory option: the result must have the same length as the source.
+ * Shaping mode: The SEEN family character will expand into two characters using space near
+ * the SEEN family character(i.e. the space after the character).
+ * if there are no spaces found, ArabicShapingException will be thrown
+ *
+ * De-shaping mode: Any Seen character followed by Tail character will be
+ * replaced by one cell Seen and a space will replace the Tail.
+ * Affects: Seen options
+ */
+ public static final int SEEN_TWOCELL_NEAR = 0x200000;
+
+ /** Bit mask for Seen memory options. */
+ public static final int SEEN_MASK = 0x700000;
+
+ /* YehHamza options */
+ /**
+ * Memory option: the result must have the same length as the source.
+ * Shaping mode: The YEHHAMZA character will expand into two characters using space near it
+ * (i.e. the space after the character)
+ * if there are no spaces found, ArabicShapingException will be thrown
+ *
+ * De-shaping mode: Any Yeh (final or isolated) character followed by Hamza character will be
+ * replaced by one cell YehHamza and space will replace the Hamza.
+ * Affects: YehHamza options
+ */
+ public static final int YEHHAMZA_TWOCELL_NEAR = 0x1000000;
+
+
+ /** Bit mask for YehHamza memory options. */
+ public static final int YEHHAMZA_MASK = 0x3800000;
+
+ /* New Tashkeel options */
+ /**
+ * Memory option: the result must have the same length as the source.
+ * Shaping mode: Tashkeel characters will be replaced by spaces.
+ * Spaces will be placed at beginning of the buffer
+ *
+ * De-shaping mode: N/A
+ * Affects: Tashkeel options
+ */
+ public static final int TASHKEEL_BEGIN = 0x40000;
+
+ /**
+ * Memory option: the result must have the same length as the source.
+ * Shaping mode: Tashkeel characters will be replaced by spaces.
+ * Spaces will be placed at end of the buffer
+ *
+ * De-shaping mode: N/A
+ * Affects: Tashkeel options
+ */
+ public static final int TASHKEEL_END = 0x60000;
+
+ /**
+ * Memory option: allow the result to have a different length than the source.
+ * Shaping mode: Tashkeel characters will be removed, buffer length will shrink.
+ * De-shaping mode: N/A
+ *
+ * Affects: Tashkeel options
+ */
+ public static final int TASHKEEL_RESIZE = 0x80000;
+
+ /**
+ * Memory option: the result must have the same length as the source.
+ * Shaping mode: Tashkeel characters will be replaced by Tatweel if it is connected to adjacent
+ * characters (i.e. shaped on Tatweel) or replaced by space if it is not connected.
+ *
+ * De-shaping mode: N/A
+ * Affects: YehHamza options
+ */
+ public static final int TASHKEEL_REPLACE_BY_TATWEEL = 0xC0000;
+
+ /** Bit mask for Tashkeel replacement with Space or Tatweel memory options. */
+ public static final int TASHKEEL_MASK = 0xE0000;
+
+ /* Space location Control options */
+ /**
+ * This option effects the meaning of BEGIN and END options. if this option is not used the default
+ * for BEGIN and END will be as following:
+ * The Default (for both Visual LTR, Visual RTL and Logical Text)
+ * 1. BEGIN always refers to the start address of physical memory.
+ * 2. END always refers to the end address of physical memory.
+ *
+ * If this option is used it will swap the meaning of BEGIN and END only for Visual LTR text.
+ *
+ * The affect on BEGIN and END Memory Options will be as following:
+ * A. BEGIN For Visual LTR text: This will be the beginning (right side) of the visual text
+ * (corresponding to the physical memory address end, same as END in default behavior)
+ * B. BEGIN For Logical text: Same as BEGIN in default behavior.
+ * C. END For Visual LTR text: This will be the end (left side) of the visual text. (corresponding to
+ * the physical memory address beginning, same as BEGIN in default behavior)
+ * D. END For Logical text: Same as END in default behavior.
+ * Affects: All LamAlef BEGIN, END and AUTO options.
+ */
+ public static final int SPACES_RELATIVE_TO_TEXT_BEGIN_END = 0x4000000;
+
+ /** Bit mask for swapping BEGIN and END for Visual LTR text */
+ public static final int SPACES_RELATIVE_TO_TEXT_MASK = 0x4000000;
+
+ /**
+ * If this option is used, shaping will use the new Unicode code point for TAIL (i.e. 0xFE73).
+ * If this option is not specified (Default), old unofficial Unicode TAIL code point is used (i.e. 0x200B)
+ * De-shaping will not use this option as it will always search for both the new Unicode code point for the
+ * TAIL (i.e. 0xFE73) or the old unofficial Unicode TAIL code point (i.e. 0x200B) and de-shape the
+ * Seen-Family letter accordingly.
+ *
+ * Shaping Mode: Only shaping.
+ * De-shaping Mode: N/A.
+ * Affects: All Seen options
+ */
+ public static final int SHAPE_TAIL_NEW_UNICODE = 0x8000000;
+
+ /** Bit mask for new Unicode Tail option */
+ public static final int SHAPE_TAIL_TYPE_MASK = 0x8000000;
+
+ /**
+ * Memory option: allow the result to have a different length than the source.
+ * @stable ICU 2.0
+ */
+ public static final int LENGTH_GROW_SHRINK = 0;
+
+ /**
+ * Memory option: allow the result to have a different length than the source.
+ * Affects: LamAlef options
+ * This option is an alias to LENGTH_GROW_SHRINK
+ */
+ public static final int LAMALEF_RESIZE = 0;
+
+ /**
+ * Memory option: the result must have the same length as the source.
+ * If more room is necessary, then try to consume spaces next to modified characters.
+ * @stable ICU 2.0
+ */
+ public static final int LENGTH_FIXED_SPACES_NEAR = 1;
+
+ /**
+ * Memory option: the result must have the same length as the source.
+ * If more room is necessary, then try to consume spaces next to modified characters.
+ * Affects: LamAlef options
+ * This option is an alias to LENGTH_FIXED_SPACES_NEAR
+ */
+ public static final int LAMALEF_NEAR = 1 ;
+
+ /**
+ * Memory option: the result must have the same length as the source.
+ * If more room is necessary, then try to consume spaces at the end of the text.
+ * @stable ICU 2.0
+ */
+ public static final int LENGTH_FIXED_SPACES_AT_END = 2;
+
+
+ /**
+ * Memory option: the result must have the same length as the source.
+ * If more room is necessary, then try to consume spaces at the end of the text.
+ * Affects: LamAlef options
+ * This option is an alias to LENGTH_FIXED_SPACES_AT_END
+ */
+ public static final int LAMALEF_END = 2;
+
+ /**
+ * Memory option: the result must have the same length as the source.
+ * If more room is necessary, then try to consume spaces at the beginning of the text.
+ * @stable ICU 2.0
+ */
+ public static final int LENGTH_FIXED_SPACES_AT_BEGINNING = 3;
+
+ /**
+ * Memory option: the result must have the same length as the source.
+ * If more room is necessary, then try to consume spaces at the beginning of the text.
+ * Affects: LamAlef options
+ * This option is an alias to LENGTH_FIXED_SPACES_AT_BEGINNING
+ */
+ public static final int LAMALEF_BEGIN = 3;
+
+ /**
+ * Memory option: the result must have the same length as the source.
+ * Shaping Mode: For each LAMALEF character found, expand LAMALEF using space at end.
+ * If there is no space at end, use spaces at beginning of the buffer. If there
+ * is no space at beginning of the buffer, use spaces at the near (i.e. the space
+ * after the LAMALEF character).
+ *
+ * Deshaping Mode: Perform the same function as the flag equals LAMALEF_END.
+ * Affects: LamAlef options
+ */
+ public static final int LAMALEF_AUTO = 0x10000;
+
+ /**
+ * Bit mask for memory options.
+ * @stable ICU 2.0
+ */
+ public static final int LENGTH_MASK = 0x10003;
+
+ /** Bit mask for LamAlef memory options. */
+
+ public static final int LAMALEF_MASK = 0x10003;
+
+ /**
+ * Direction indicator: the source is in logical (keyboard) order.
+ * @stable ICU 2.0
+ */
+ public static final int TEXT_DIRECTION_LOGICAL = 0;
+
+ /**
+ * Direction indicator:the source is in visual RTL order,
+ * the rightmost displayed character stored first.
+ * This option is an alias to U_SHAPE_TEXT_DIRECTION_LOGICAL
+ */
+ public static final int TEXT_DIRECTION_VISUAL_RTL = 0;
+
+ /**
+ * Direction indicator: the source is in visual (display) order, that is,
+ * the leftmost displayed character is stored first.
+ * @stable ICU 2.0
+ */
+ public static final int TEXT_DIRECTION_VISUAL_LTR = 4;
+
+ /**
+ * Bit mask for direction indicators.
+ * @stable ICU 2.0
+ */
+ public static final int TEXT_DIRECTION_MASK = 4;
+
+
+ /**
+ * Letter shaping option: do not perform letter shaping.
+ * @stable ICU 2.0
+ */
+ public static final int LETTERS_NOOP = 0;
+
+ /**
+ * Letter shaping option: replace normative letter characters in the U+0600 (Arabic) block,
+ * by shaped ones in the U+FE70 (Presentation Forms B) block. Performs Lam-Alef ligature
+ * substitution.
+ * @stable ICU 2.0
+ */
+ public static final int LETTERS_SHAPE = 8;
+
+ /**
+ * Letter shaping option: replace shaped letter characters in the U+FE70 (Presentation Forms B) block
+ * by normative ones in the U+0600 (Arabic) block. Converts Lam-Alef ligatures to pairs of Lam and
+ * Alef characters, consuming spaces if required.
+ * @stable ICU 2.0
+ */
+ public static final int LETTERS_UNSHAPE = 0x10;
+
+ /**
+ * Letter shaping option: replace normative letter characters in the U+0600 (Arabic) block,
+ * except for the TASHKEEL characters at U+064B...U+0652, by shaped ones in the U+Fe70
+ * (Presentation Forms B) block. The TASHKEEL characters will always be converted to
+ * the isolated forms rather than to their correct shape.
+ * @stable ICU 2.0
+ */
+ public static final int LETTERS_SHAPE_TASHKEEL_ISOLATED = 0x18;
+
+ /**
+ * Bit mask for letter shaping options.
+ * @stable ICU 2.0
+ */
+ public static final int LETTERS_MASK = 0x18;
+
+
+ /**
+ * Digit shaping option: do not perform digit shaping.
+ * @stable ICU 2.0
+ */
+ public static final int DIGITS_NOOP = 0;
+
+ /**
+ * Digit shaping option: Replace European digits (U+0030...U+0039) by Arabic-Indic digits.
+ * @stable ICU 2.0
+ */
+ public static final int DIGITS_EN2AN = 0x20;
+
+ /**
+ * Digit shaping option: Replace Arabic-Indic digits by European digits (U+0030...U+0039).
+ * @stable ICU 2.0
+ */
+ public static final int DIGITS_AN2EN = 0x40;
+
+ /**
+ * Digit shaping option:
+ * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
+ * if the most recent strongly directional character
+ * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
+ * The initial state at the start of the text is assumed to be not an Arabic,
+ * letter, so European digits at the start of the text will not change.
+ * Compare to DIGITS_ALEN2AN_INIT_AL.
+ * @stable ICU 2.0
+ */
+ public static final int DIGITS_EN2AN_INIT_LR = 0x60;
+
+ /**
+ * Digit shaping option:
+ * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
+ * if the most recent strongly directional character
+ * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
+ * The initial state at the start of the text is assumed to be an Arabic,
+ * letter, so European digits at the start of the text will change.
+ * Compare to DIGITS_ALEN2AN_INT_LR.
+ * @stable ICU 2.0
+ */
+ public static final int DIGITS_EN2AN_INIT_AL = 0x80;
+
+ /** Not a valid option value. */
+ //private static final int DIGITS_RESERVED = 0xa0;
+
+ /**
+ * Bit mask for digit shaping options.
+ * @stable ICU 2.0
+ */
+ public static final int DIGITS_MASK = 0xe0;
+
+ /**
+ * Digit type option: Use Arabic-Indic digits (U+0660...U+0669).
+ * @stable ICU 2.0
+ */
+ public static final int DIGIT_TYPE_AN = 0;
+
+ /**
+ * Digit type option: Use Eastern (Extended) Arabic-Indic digits (U+06f0...U+06f9).
+ * @stable ICU 2.0
+ */
+ public static final int DIGIT_TYPE_AN_EXTENDED = 0x100;
+
+ /**
+ * Bit mask for digit type options.
+ * @stable ICU 2.0
+ */
+ public static final int DIGIT_TYPE_MASK = 0x0100; // 0x3f00?
+
+ /**
+ * some constants
+ */
+ private static final char HAMZAFE_CHAR = '\ufe80';
+ private static final char HAMZA06_CHAR = '\u0621';
+ private static final char YEH_HAMZA_CHAR = '\u0626';
+ private static final char YEH_HAMZAFE_CHAR = '\uFE89';
+ private static final char LAMALEF_SPACE_SUB = '\uffff';
+ private static final char TASHKEEL_SPACE_SUB = '\ufffe';
+ private static final char LAM_CHAR = '\u0644';
+ private static final char SPACE_CHAR = '\u0020';
+ private static final char SPACE_CHAR_FOR_LAMALEF = '\ufeff'; // XXX: tweak for TextLine use
+ private static final char SHADDA_CHAR = '\uFE7C';
+ private static final char TATWEEL_CHAR = '\u0640';
+ private static final char SHADDA_TATWEEL_CHAR = '\uFE7D';
+ private static final char NEW_TAIL_CHAR = '\uFE73';
+ private static final char OLD_TAIL_CHAR = '\u200B';
+ private static final int SHAPE_MODE = 0;
+ private static final int DESHAPE_MODE = 1;
+
+ /**
+ * @stable ICU 2.0
+ */
+ public boolean equals(Object rhs) {
+ return rhs != null &&
+ rhs.getClass() == ArabicShaping.class &&
+ options == ((ArabicShaping)rhs).options;
+ }
+
+ /**
+ * @stable ICU 2.0
+ */
+ ///CLOVER:OFF
+ public int hashCode() {
+ return options;
+ }
+
+ /**
+ * @stable ICU 2.0
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer(super.toString());
+ buf.append('[');
+
+ switch (options & LAMALEF_MASK) {
+ case LAMALEF_RESIZE: buf.append("LamAlef resize"); break;
+ case LAMALEF_NEAR: buf.append("LamAlef spaces at near"); break;
+ case LAMALEF_BEGIN: buf.append("LamAlef spaces at begin"); break;
+ case LAMALEF_END: buf.append("LamAlef spaces at end"); break;
+ case LAMALEF_AUTO: buf.append("lamAlef auto"); break;
+ }
+ switch (options & TEXT_DIRECTION_MASK) {
+ case TEXT_DIRECTION_LOGICAL: buf.append(", logical"); break;
+ case TEXT_DIRECTION_VISUAL_LTR: buf.append(", visual"); break;
+ }
+ switch (options & LETTERS_MASK) {
+ case LETTERS_NOOP: buf.append(", no letter shaping"); break;
+ case LETTERS_SHAPE: buf.append(", shape letters"); break;
+ case LETTERS_SHAPE_TASHKEEL_ISOLATED: buf.append(", shape letters tashkeel isolated"); break;
+ case LETTERS_UNSHAPE: buf.append(", unshape letters"); break;
+ }
+ switch (options & SEEN_MASK) {
+ case SEEN_TWOCELL_NEAR: buf.append(", Seen at near"); break;
+ }
+ switch (options & YEHHAMZA_MASK) {
+ case YEHHAMZA_TWOCELL_NEAR: buf.append(", Yeh Hamza at near"); break;
+ }
+ switch (options & TASHKEEL_MASK) {
+ case TASHKEEL_BEGIN: buf.append(", Tashkeel at begin"); break;
+ case TASHKEEL_END: buf.append(", Tashkeel at end"); break;
+ case TASHKEEL_REPLACE_BY_TATWEEL: buf.append(", Tashkeel replace with tatweel"); break;
+ case TASHKEEL_RESIZE: buf.append(", Tashkeel resize"); break;
+ }
+
+ switch (options & DIGITS_MASK) {
+ case DIGITS_NOOP: buf.append(", no digit shaping"); break;
+ case DIGITS_EN2AN: buf.append(", shape digits to AN"); break;
+ case DIGITS_AN2EN: buf.append(", shape digits to EN"); break;
+ case DIGITS_EN2AN_INIT_LR: buf.append(", shape digits to AN contextually: default EN"); break;
+ case DIGITS_EN2AN_INIT_AL: buf.append(", shape digits to AN contextually: default AL"); break;
+ }
+ switch (options & DIGIT_TYPE_MASK) {
+ case DIGIT_TYPE_AN: buf.append(", standard Arabic-Indic digits"); break;
+ case DIGIT_TYPE_AN_EXTENDED: buf.append(", extended Arabic-Indic digits"); break;
+ }
+ buf.append("]");
+
+ return buf.toString();
+ }
+ ///CLOVER:ON
+
+ //
+ // ported api
+ //
+
+ private static final int IRRELEVANT = 4;
+ private static final int LAMTYPE = 16;
+ private static final int ALEFTYPE = 32;
+
+ private static final int LINKR = 1;
+ private static final int LINKL = 2;
+ private static final int LINK_MASK = 3;
+
+ private static final int irrelevantPos[] = {
+ 0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xC, 0xE
+ };
+
+/*
+ private static final char convertLamAlef[] = {
+ '\u0622', // FEF5
+ '\u0622', // FEF6
+ '\u0623', // FEF7
+ '\u0623', // FEF8
+ '\u0625', // FEF9
+ '\u0625', // FEFA
+ '\u0627', // FEFB
+ '\u0627' // FEFC
+ };
+*/
+
+ private static final int tailFamilyIsolatedFinal[] = {
+ /* FEB1 */ 1,
+ /* FEB2 */ 1,
+ /* FEB3 */ 0,
+ /* FEB4 */ 0,
+ /* FEB5 */ 1,
+ /* FEB6 */ 1,
+ /* FEB7 */ 0,
+ /* FEB8 */ 0,
+ /* FEB9 */ 1,
+ /* FEBA */ 1,
+ /* FEBB */ 0,
+ /* FEBC */ 0,
+ /* FEBD */ 1,
+ /* FEBE */ 1
+ };
+
+ private static final int tashkeelMedial[] = {
+ /* FE70 */ 0,
+ /* FE71 */ 1,
+ /* FE72 */ 0,
+ /* FE73 */ 0,
+ /* FE74 */ 0,
+ /* FE75 */ 0,
+ /* FE76 */ 0,
+ /* FE77 */ 1,
+ /* FE78 */ 0,
+ /* FE79 */ 1,
+ /* FE7A */ 0,
+ /* FE7B */ 1,
+ /* FE7C */ 0,
+ /* FE7D */ 1,
+ /* FE7E */ 0,
+ /* FE7F */ 1
+ };
+
+ private static final char yehHamzaToYeh[] =
+ {
+ /* isolated*/ 0xFEEF,
+ /* final */ 0xFEF0
+ };
+
+ private static final char convertNormalizedLamAlef[] = {
+ '\u0622', // 065C
+ '\u0623', // 065D
+ '\u0625', // 065E
+ '\u0627', // 065F
+ };
+
+ private static final int[] araLink = {
+ 1 + 32 + 256 * 0x11, /*0x0622*/
+ 1 + 32 + 256 * 0x13, /*0x0623*/
+ 1 + 256 * 0x15, /*0x0624*/
+ 1 + 32 + 256 * 0x17, /*0x0625*/
+ 1 + 2 + 256 * 0x19, /*0x0626*/
+ 1 + 32 + 256 * 0x1D, /*0x0627*/
+ 1 + 2 + 256 * 0x1F, /*0x0628*/
+ 1 + 256 * 0x23, /*0x0629*/
+ 1 + 2 + 256 * 0x25, /*0x062A*/
+ 1 + 2 + 256 * 0x29, /*0x062B*/
+ 1 + 2 + 256 * 0x2D, /*0x062C*/
+ 1 + 2 + 256 * 0x31, /*0x062D*/
+ 1 + 2 + 256 * 0x35, /*0x062E*/
+ 1 + 256 * 0x39, /*0x062F*/
+ 1 + 256 * 0x3B, /*0x0630*/
+ 1 + 256 * 0x3D, /*0x0631*/
+ 1 + 256 * 0x3F, /*0x0632*/
+ 1 + 2 + 256 * 0x41, /*0x0633*/
+ 1 + 2 + 256 * 0x45, /*0x0634*/
+ 1 + 2 + 256 * 0x49, /*0x0635*/
+ 1 + 2 + 256 * 0x4D, /*0x0636*/
+ 1 + 2 + 256 * 0x51, /*0x0637*/
+ 1 + 2 + 256 * 0x55, /*0x0638*/
+ 1 + 2 + 256 * 0x59, /*0x0639*/
+ 1 + 2 + 256 * 0x5D, /*0x063A*/
+ 0, 0, 0, 0, 0, /*0x063B-0x063F*/
+ 1 + 2, /*0x0640*/
+ 1 + 2 + 256 * 0x61, /*0x0641*/
+ 1 + 2 + 256 * 0x65, /*0x0642*/
+ 1 + 2 + 256 * 0x69, /*0x0643*/
+ 1 + 2 + 16 + 256 * 0x6D, /*0x0644*/
+ 1 + 2 + 256 * 0x71, /*0x0645*/
+ 1 + 2 + 256 * 0x75, /*0x0646*/
+ 1 + 2 + 256 * 0x79, /*0x0647*/
+ 1 + 256 * 0x7D, /*0x0648*/
+ 1 + 256 * 0x7F, /*0x0649*/
+ 1 + 2 + 256 * 0x81, /*0x064A*/
+ 4, 4, 4, 4, /*0x064B-0x064E*/
+ 4, 4, 4, 4, /*0x064F-0x0652*/
+ 4, 4, 4, 0, 0, /*0x0653-0x0657*/
+ 0, 0, 0, 0, /*0x0658-0x065B*/
+ 1 + 256 * 0x85, /*0x065C*/
+ 1 + 256 * 0x87, /*0x065D*/
+ 1 + 256 * 0x89, /*0x065E*/
+ 1 + 256 * 0x8B, /*0x065F*/
+ 0, 0, 0, 0, 0, /*0x0660-0x0664*/
+ 0, 0, 0, 0, 0, /*0x0665-0x0669*/
+ 0, 0, 0, 0, 0, 0, /*0x066A-0x066F*/
+ 4, /*0x0670*/
+ 0, /*0x0671*/
+ 1 + 32, /*0x0672*/
+ 1 + 32, /*0x0673*/
+ 0, /*0x0674*/
+ 1 + 32, /*0x0675*/
+ 1, 1, /*0x0676-0x0677*/
+ 1+2, 1+2, 1+2, 1+2, 1+2, 1+2, /*0x0678-0x067D*/
+ 1+2, 1+2, 1+2, 1+2, 1+2, 1+2, /*0x067E-0x0683*/
+ 1+2, 1+2, 1+2, 1+2, /*0x0684-0x0687*/
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*0x0688-0x0691*/
+ 1, 1, 1, 1, 1, 1, 1, 1, /*0x0692-0x0699*/
+ 1+2, 1+2, 1+2, 1+2, 1+2, 1+2, /*0x069A-0x06A3*/
+ 1+2, 1+2, 1+2, 1+2, /*0x069A-0x06A3*/
+ 1+2, 1+2, 1+2, 1+2, 1+2, 1+2, /*0x06A4-0x06AD*/
+ 1+2, 1+2, 1+2, 1+2, /*0x06A4-0x06AD*/
+ 1+2, 1+2, 1+2, 1+2, 1+2, 1+2, /*0x06AE-0x06B7*/
+ 1+2, 1+2, 1+2, 1+2, /*0x06AE-0x06B7*/
+ 1+2, 1+2, 1+2, 1+2, 1+2, 1+2, /*0x06B8-0x06BF*/
+ 1+2, 1+2, /*0x06B8-0x06BF*/
+ 1, /*0x06C0*/
+ 1+2, /*0x06C1*/
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*0x06C2-0x06CB*/
+ 1+2, /*0x06CC*/
+ 1, /*0x06CD*/
+ 1+2, 1+2, 1+2, 1+2, /*0x06CE-0x06D1*/
+ 1, 1 /*0x06D2-0x06D3*/
+ };
+
+ private static final int[] presLink = {
+ 1 + 2, /*0xFE70*/
+ 1 + 2, /*0xFE71*/
+ 1 + 2, 0, 1+ 2, 0, 1+ 2, /*0xFE72-0xFE76*/
+ 1 + 2, /*0xFE77*/
+ 1+ 2, 1 + 2, 1+2, 1 + 2, /*0xFE78-0xFE81*/
+ 1+ 2, 1 + 2, 1+2, 1 + 2, /*0xFE82-0xFE85*/
+ 0, 0 + 32, 1 + 32, 0 + 32, /*0xFE86-0xFE89*/
+ 1 + 32, 0, 1, 0 + 32, /*0xFE8A-0xFE8D*/
+ 1 + 32, 0, 2, 1 + 2, /*0xFE8E-0xFE91*/
+ 1, 0 + 32, 1 + 32, 0, /*0xFE92-0xFE95*/
+ 2, 1 + 2, 1, 0, /*0xFE96-0xFE99*/
+ 1, 0, 2, 1 + 2, /*0xFE9A-0xFE9D*/
+ 1, 0, 2, 1 + 2, /*0xFE9E-0xFEA1*/
+ 1, 0, 2, 1 + 2, /*0xFEA2-0xFEA5*/
+ 1, 0, 2, 1 + 2, /*0xFEA6-0xFEA9*/
+ 1, 0, 2, 1 + 2, /*0xFEAA-0xFEAD*/
+ 1, 0, 1, 0, /*0xFEAE-0xFEB1*/
+ 1, 0, 1, 0, /*0xFEB2-0xFEB5*/
+ 1, 0, 2, 1+2, /*0xFEB6-0xFEB9*/
+ 1, 0, 2, 1+2, /*0xFEBA-0xFEBD*/
+ 1, 0, 2, 1+2, /*0xFEBE-0xFEC1*/
+ 1, 0, 2, 1+2, /*0xFEC2-0xFEC5*/
+ 1, 0, 2, 1+2, /*0xFEC6-0xFEC9*/
+ 1, 0, 2, 1+2, /*0xFECA-0xFECD*/
+ 1, 0, 2, 1+2, /*0xFECE-0xFED1*/
+ 1, 0, 2, 1+2, /*0xFED2-0xFED5*/
+ 1, 0, 2, 1+2, /*0xFED6-0xFED9*/
+ 1, 0, 2, 1+2, /*0xFEDA-0xFEDD*/
+ 1, 0, 2, 1+2, /*0xFEDE-0xFEE1*/
+ 1, 0 + 16, 2 + 16, 1 + 2 +16, /*0xFEE2-0xFEE5*/
+ 1 + 16, 0, 2, 1+2, /*0xFEE6-0xFEE9*/
+ 1, 0, 2, 1+2, /*0xFEEA-0xFEED*/
+ 1, 0, 2, 1+2, /*0xFEEE-0xFEF1*/
+ 1, 0, 1, 0, /*0xFEF2-0xFEF5*/
+ 1, 0, 2, 1+2, /*0xFEF6-0xFEF9*/
+ 1, 0, 1, 0, /*0xFEFA-0xFEFD*/
+ 1, 0, 1, 0,
+ 1
+ };
+
+ private static int[] convertFEto06 = {
+ /***********0******1******2******3******4******5******6******7******8******9******A******B******C******D******E******F***/
+ /*FE7*/ 0x64B, 0x64B, 0x64C, 0x64C, 0x64D, 0x64D, 0x64E, 0x64E, 0x64F, 0x64F, 0x650, 0x650, 0x651, 0x651, 0x652, 0x652,
+ /*FE8*/ 0x621, 0x622, 0x622, 0x623, 0x623, 0x624, 0x624, 0x625, 0x625, 0x626, 0x626, 0x626, 0x626, 0x627, 0x627, 0x628,
+ /*FE9*/ 0x628, 0x628, 0x628, 0x629, 0x629, 0x62A, 0x62A, 0x62A, 0x62A, 0x62B, 0x62B, 0x62B, 0x62B, 0x62C, 0x62C, 0x62C,
+ /*FEA*/ 0x62C, 0x62D, 0x62D, 0x62D, 0x62D, 0x62E, 0x62E, 0x62E, 0x62E, 0x62F, 0x62F, 0x630, 0x630, 0x631, 0x631, 0x632,
+ /*FEB*/ 0x632, 0x633, 0x633, 0x633, 0x633, 0x634, 0x634, 0x634, 0x634, 0x635, 0x635, 0x635, 0x635, 0x636, 0x636, 0x636,
+ /*FEC*/ 0x636, 0x637, 0x637, 0x637, 0x637, 0x638, 0x638, 0x638, 0x638, 0x639, 0x639, 0x639, 0x639, 0x63A, 0x63A, 0x63A,
+ /*FED*/ 0x63A, 0x641, 0x641, 0x641, 0x641, 0x642, 0x642, 0x642, 0x642, 0x643, 0x643, 0x643, 0x643, 0x644, 0x644, 0x644,
+ /*FEE*/ 0x644, 0x645, 0x645, 0x645, 0x645, 0x646, 0x646, 0x646, 0x646, 0x647, 0x647, 0x647, 0x647, 0x648, 0x648, 0x649,
+ /*FEF*/ 0x649, 0x64A, 0x64A, 0x64A, 0x64A, 0x65C, 0x65C, 0x65D, 0x65D, 0x65E, 0x65E, 0x65F, 0x65F
+ };
+
+ private static final int shapeTable[][][] = {
+ { {0,0,0,0}, {0,0,0,0}, {0,1,0,3}, {0,1,0,1} },
+ { {0,0,2,2}, {0,0,1,2}, {0,1,1,2}, {0,1,1,3} },
+ { {0,0,0,0}, {0,0,0,0}, {0,1,0,3}, {0,1,0,3} },
+ { {0,0,1,2}, {0,0,1,2}, {0,1,1,2}, {0,1,1,3} }
+ };
+
+ /*
+ * This function shapes European digits to Arabic-Indic digits
+ * in-place, writing over the input characters. Data is in visual
+ * order.
+ */
+ private void shapeToArabicDigitsWithContext(char[] dest,
+ int start,
+ int length,
+ char digitBase,
+ boolean lastStrongWasAL) {
+ digitBase -= '0'; // move common adjustment out of loop
+
+ for(int i = start + length; --i >= start;) {
+ char ch = dest[i];
+ switch (Character.getDirectionality(ch)) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ lastStrongWasAL = false;
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ lastStrongWasAL = true;
+ break;
+ case Character.DIRECTIONALITY_EUROPEAN_NUMBER:
+ if (lastStrongWasAL && ch <= '\u0039') {
+ dest[i] = (char)(ch + digitBase);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /*
+ * Name : invertBuffer
+ * Function: This function inverts the buffer, it's used
+ * in case the user specifies the buffer to be
+ * TEXT_DIRECTION_LOGICAL
+ */
+ private static void invertBuffer(char[] buffer,
+ int start,
+ int length) {
+
+ for(int i = start, j = start + length - 1; i < j; i++, --j) {
+ char temp = buffer[i];
+ buffer[i] = buffer[j];
+ buffer[j] = temp;
+ }
+ }
+
+ /*
+ * Name : changeLamAlef
+ * Function: Converts the Alef characters into an equivalent
+ * LamAlef location in the 0x06xx Range, this is an
+ * intermediate stage in the operation of the program
+ * later it'll be converted into the 0xFExx LamAlefs
+ * in the shaping function.
+ */
+ private static char changeLamAlef(char ch) {
+ switch(ch) {
+ case '\u0622': return '\u065C';
+ case '\u0623': return '\u065D';
+ case '\u0625': return '\u065E';
+ case '\u0627': return '\u065F';
+ default: return '\u0000'; // not a lamalef
+ }
+ }
+
+ /*
+ * Name : specialChar
+ * Function: Special Arabic characters need special handling in the shapeUnicode
+ * function, this function returns 1 or 2 for these special characters
+ */
+ private static int specialChar(char ch) {
+ if ((ch > '\u0621' && ch < '\u0626') ||
+ (ch == '\u0627') ||
+ (ch > '\u062E' && ch < '\u0633') ||
+ (ch > '\u0647' && ch < '\u064A') ||
+ (ch == '\u0629')) {
+ return 1;
+ } else if (ch >= '\u064B' && ch<= '\u0652') {
+ return 2;
+ } else if (ch >= 0x0653 && ch <= 0x0655 ||
+ ch == 0x0670 ||
+ ch >= 0xFE70 && ch <= 0xFE7F) {
+ return 3;
+ } else {
+ return 0;
+ }
+ }
+
+ /*
+ * Name : getLink
+ * Function: Resolves the link between the characters as
+ * Arabic characters have four forms :
+ * Isolated, Initial, Middle and Final Form
+ */
+ private static int getLink(char ch) {
+ if (ch >= '\u0622' && ch <= '\u06D3') {
+ return araLink[ch - '\u0622'];
+ } else if (ch == '\u200D') {
+ return 3;
+ } else if (ch >= '\u206D' && ch <= '\u206F') {
+ return 4;
+ } else if (ch >= '\uFE70' && ch <= '\uFEFC') {
+ return presLink[ch - '\uFE70'];
+ } else {
+ return 0;
+ }
+ }
+
+ /*
+ * Name : countSpaces
+ * Function: Counts the number of spaces
+ * at each end of the logical buffer
+ */
+ private static int countSpacesLeft(char[] dest,
+ int start,
+ int count) {
+ for (int i = start, e = start + count; i < e; ++i) {
+ if (dest[i] != SPACE_CHAR) {
+ return i - start;
+ }
+ }
+ return count;
+ }
+
+ private static int countSpacesRight(char[] dest,
+ int start,
+ int count) {
+
+ for (int i = start + count; --i >= start;) {
+ if (dest[i] != SPACE_CHAR) {
+ return start + count - 1 - i;
+ }
+ }
+ return count;
+ }
+
+ /*
+ * Name : isTashkeelChar
+ * Function: Returns true for Tashkeel characters else return false
+ */
+ private static boolean isTashkeelChar(char ch) {
+ return ( ch >='\u064B' && ch <= '\u0652' );
+ }
+
+ /*
+ *Name : isSeenTailFamilyChar
+ *Function : returns 1 if the character is a seen family isolated character
+ * in the FE range otherwise returns 0
+ */
+
+ private static int isSeenTailFamilyChar(char ch) {
+ if (ch >= 0xfeb1 && ch < 0xfebf){
+ return tailFamilyIsolatedFinal [ch - 0xFEB1];
+ } else {
+ return 0;
+ }
+ }
+
+ /* Name : isSeenFamilyChar
+ * Function : returns 1 if the character is a seen family character in the Unicode
+ * 06 range otherwise returns 0
+ */
+
+ private static int isSeenFamilyChar(char ch){
+ if (ch >= 0x633 && ch <= 0x636){
+ return 1;
+ }else {
+ return 0;
+ }
+ }
+
+ /*
+ *Name : isTailChar
+ *Function : returns true if the character matches one of the tail characters
+ * (0xfe73 or 0x200b) otherwise returns false
+ */
+
+ private static boolean isTailChar(char ch) {
+ if(ch == OLD_TAIL_CHAR || ch == NEW_TAIL_CHAR){
+ return true;
+ }else{
+ return false;
+ }
+ }
+
+ /*
+ *Name : isAlefMaksouraChar
+ *Function : returns true if the character is a Alef Maksoura Final or isolated
+ * otherwise returns false
+ */
+ private static boolean isAlefMaksouraChar(char ch) {
+ return ( (ch == 0xFEEF) || ( ch == 0xFEF0) || (ch == 0x0649));
+ }
+
+ /*
+ * Name : isYehHamzaChar
+ * Function : returns true if the character is a yehHamza isolated or yehhamza
+ * final is found otherwise returns false
+ */
+ private static boolean isYehHamzaChar(char ch) {
+ if((ch==0xFE89)||(ch==0xFE8A)){
+ return true;
+ }else{
+ return false;
+ }
+ }
+
+ /*
+ *Name : isTashkeelCharFE
+ *Function : Returns true for Tashkeel characters in FE range else return false
+ */
+
+ private static boolean isTashkeelCharFE(char ch) {
+ return ( ch!=0xFE75 &&(ch>=0xFE70 && ch<= 0xFE7F) );
+ }
+
+ /*
+ * Name: isTashkeelOnTatweelChar
+ * Function: Checks if the Tashkeel Character is on Tatweel or not,if the
+ * Tashkeel on tatweel (FE range), it returns 1 else if the
+ * Tashkeel with shadda on tatweel (FC range)return 2 otherwise
+ * returns 0
+ */
+ private static int isTashkeelOnTatweelChar(char ch){
+ if (ch >= 0xfe70 && ch <= 0xfe7f && ch != NEW_TAIL_CHAR && ch != 0xFE75 && ch != SHADDA_TATWEEL_CHAR)
+ {
+ return tashkeelMedial [ch - 0xFE70];
+ } else if( (ch >= 0xfcf2 && ch <= 0xfcf4) || (ch == SHADDA_TATWEEL_CHAR)) {
+ return 2;
+ } else {
+ return 0;
+ }
+ }
+
+ /*
+ * Name: isIsolatedTashkeelChar
+ * Function: Checks if the Tashkeel Character is in the isolated form
+ * (i.e. Unicode FE range) returns 1 else if the Tashkeel
+ * with shadda is in the isolated form (i.e. Unicode FC range)
+ * returns 1 otherwise returns 0
+ */
+ private static int isIsolatedTashkeelChar(char ch){
+ if (ch >= 0xfe70 && ch <= 0xfe7f && ch != NEW_TAIL_CHAR && ch != 0xFE75){
+ return (1 - tashkeelMedial [ch - 0xFE70]);
+ } else if(ch >= 0xfc5e && ch <= 0xfc63){
+ return 1;
+ } else{
+ return 0;
+ }
+ }
+
+ /*
+ * Name : isAlefChar
+ * Function: Returns 1 for Alef characters else return 0
+ */
+ private static boolean isAlefChar(char ch) {
+ return ch == '\u0622' || ch == '\u0623' || ch == '\u0625' || ch == '\u0627';
+ }
+
+ /*
+ * Name : isLamAlefChar
+ * Function: Returns true for LamAlef characters else return false
+ */
+ private static boolean isLamAlefChar(char ch) {
+ return ch >= '\uFEF5' && ch <= '\uFEFC';
+ }
+
+ private static boolean isNormalizedLamAlefChar(char ch) {
+ return ch >= '\u065C' && ch <= '\u065F';
+ }
+
+ /*
+ * Name : calculateSize
+ * Function: This function calculates the destSize to be used in preflighting
+ * when the destSize is equal to 0
+ */
+ private int calculateSize(char[] source,
+ int sourceStart,
+ int sourceLength) {
+
+ int destSize = sourceLength;
+
+ switch (options & LETTERS_MASK) {
+ case LETTERS_SHAPE:
+ case LETTERS_SHAPE_TASHKEEL_ISOLATED:
+ if (isLogical) {
+ for (int i = sourceStart, e = sourceStart + sourceLength - 1; i < e; ++i) {
+ if ((source[i] == LAM_CHAR && isAlefChar(source[i+1])) || isTashkeelCharFE(source[i])){
+ --destSize;
+ }
+ }
+ } else { // visual
+ for(int i = sourceStart + 1, e = sourceStart + sourceLength; i < e; ++i) {
+ if ((source[i] == LAM_CHAR && isAlefChar(source[i-1])) || isTashkeelCharFE(source[i])) {
+ --destSize;
+ }
+ }
+ }
+ break;
+
+ case LETTERS_UNSHAPE:
+ for(int i = sourceStart, e = sourceStart + sourceLength; i < e; ++i) {
+ if (isLamAlefChar(source[i])) {
+ destSize++;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return destSize;
+ }
+
+
+ /*
+ * Name : countSpaceSub
+ * Function: Counts number of times the subChar appears in the array
+ */
+ public static int countSpaceSub(char [] dest,int length, char subChar){
+ int i = 0;
+ int count = 0;
+ while (i < length) {
+ if (dest[i] == subChar) {
+ count++;
+ }
+ i++;
+ }
+ return count;
+ }
+
+ /*
+ * Name : shiftArray
+ * Function: Shifts characters to replace space sub characters
+ */
+ public static void shiftArray(char [] dest,int start, int e, char subChar){
+ int w = e;
+ int r = e;
+ while (--r >= start) {
+ char ch = dest[r];
+ if (ch != subChar) {
+ --w;
+ if (w != r) {
+ dest[w] = ch;
+ }
+ }
+ }
+ }
+
+ /*
+ * Name : flipArray
+ * Function: inverts array, so that start becomes end and vice versa
+ */
+ public static int flipArray(char [] dest, int start, int e, int w){
+ int r;
+ if (w > start) {
+ // shift, assume small buffer size so don't use arraycopy
+ r = w;
+ w = start;
+ while (r < e) {
+ dest[w++] = dest[r++];
+ }
+ } else {
+ w = e;
+ }
+ return w;
+ }
+
+ /*
+ * Name : handleTashkeelWithTatweel
+ * Function : Replaces Tashkeel as following:
+ * Case 1 :if the Tashkeel on tatweel, replace it with Tatweel.
+ * Case 2 :if the Tashkeel aggregated with Shadda on Tatweel, replace
+ * it with Shadda on Tatweel.
+ * Case 3: if the Tashkeel is isolated replace it with Space.
+ *
+ */
+ private static int handleTashkeelWithTatweel(char[] dest, int sourceLength) {
+ int i;
+ for(i = 0; i < sourceLength; i++){
+ if((isTashkeelOnTatweelChar(dest[i]) == 1)){
+ dest[i] = TATWEEL_CHAR;
+ }else if((isTashkeelOnTatweelChar(dest[i]) == 2)){
+ dest[i] = SHADDA_TATWEEL_CHAR;
+ }else if((isIsolatedTashkeelChar(dest[i])==1) && dest[i] != SHADDA_CHAR){
+ dest[i] = SPACE_CHAR;
+ }
+ }
+ return sourceLength;
+ }
+
+ /*
+ *Name : handleGeneratedSpaces
+ *Function : The shapeUnicode function converts Lam + Alef into LamAlef + space,
+ * and Tashkeel to space.
+ * handleGeneratedSpaces function puts these generated spaces
+ * according to the options the user specifies. LamAlef and Tashkeel
+ * spaces can be replaced at begin, at end, at near or decrease the
+ * buffer size.
+ *
+ * There is also Auto option for LamAlef and tashkeel, which will put
+ * the spaces at end of the buffer (or end of text if the user used
+ * the option SPACES_RELATIVE_TO_TEXT_BEGIN_END).
+ *
+ * If the text type was visual_LTR and the option
+ * SPACES_RELATIVE_TO_TEXT_BEGIN_END was selected the END
+ * option will place the space at the beginning of the buffer and
+ * BEGIN will place the space at the end of the buffer.
+ */
+ private int handleGeneratedSpaces(char[] dest,
+ int start,
+ int length) {
+
+ int lenOptionsLamAlef = options & LAMALEF_MASK;
+ int lenOptionsTashkeel = options & TASHKEEL_MASK;
+ boolean lamAlefOn = false;
+ boolean tashkeelOn = false;
+
+ if (!isLogical & !spacesRelativeToTextBeginEnd) {
+ switch (lenOptionsLamAlef) {
+ case LAMALEF_BEGIN: lenOptionsLamAlef = LAMALEF_END; break;
+ case LAMALEF_END: lenOptionsLamAlef = LAMALEF_BEGIN; break;
+ default: break;
+ }
+ switch (lenOptionsTashkeel){
+ case TASHKEEL_BEGIN: lenOptionsTashkeel = TASHKEEL_END; break;
+ case TASHKEEL_END: lenOptionsTashkeel = TASHKEEL_BEGIN; break;
+ default: break;
+ }
+ }
+
+
+ if (lenOptionsLamAlef == LAMALEF_NEAR) {
+ for (int i = start, e = i + length; i < e; ++i) {
+ if (dest[i] == LAMALEF_SPACE_SUB) {
+ dest[i] = SPACE_CHAR_FOR_LAMALEF;
+ }
+ }
+
+ } else {
+
+ final int e = start + length;
+ int wL = countSpaceSub(dest, length, LAMALEF_SPACE_SUB);
+ int wT = countSpaceSub(dest, length, TASHKEEL_SPACE_SUB);
+
+ if (lenOptionsLamAlef == LAMALEF_END){
+ lamAlefOn = true;
+ }
+ if (lenOptionsTashkeel == TASHKEEL_END){
+ tashkeelOn = true;
+ }
+
+
+ if (lamAlefOn && (lenOptionsLamAlef == LAMALEF_END)) {
+ shiftArray(dest, start, e, LAMALEF_SPACE_SUB);
+ while (wL > start) {
+ dest[--wL] = SPACE_CHAR;
+ }
+ }
+
+ if (tashkeelOn && (lenOptionsTashkeel == TASHKEEL_END)){
+ shiftArray(dest, start, e, TASHKEEL_SPACE_SUB);
+ while (wT > start) {
+ dest[--wT] = SPACE_CHAR;
+ }
+ }
+
+ lamAlefOn = false;
+ tashkeelOn = false;
+
+ if (lenOptionsLamAlef == LAMALEF_RESIZE){
+ lamAlefOn = true;
+ }
+ if (lenOptionsTashkeel == TASHKEEL_RESIZE){
+ tashkeelOn = true;
+ }
+
+ if (lamAlefOn && (lenOptionsLamAlef == LAMALEF_RESIZE)){
+ shiftArray(dest, start, e, LAMALEF_SPACE_SUB);
+ wL = flipArray(dest,start,e, wL);
+ length = wL - start;
+ }
+ if (tashkeelOn && (lenOptionsTashkeel == TASHKEEL_RESIZE)) {
+ shiftArray(dest, start, e, TASHKEEL_SPACE_SUB);
+ wT = flipArray(dest,start,e, wT);
+ length = wT - start;
+ }
+
+ lamAlefOn = false;
+ tashkeelOn = false;
+
+ if ((lenOptionsLamAlef == LAMALEF_BEGIN) ||
+ (lenOptionsLamAlef == LAMALEF_AUTO)){
+ lamAlefOn = true;
+ }
+ if (lenOptionsTashkeel == TASHKEEL_BEGIN){
+ tashkeelOn = true;
+ }
+
+ if (lamAlefOn && ((lenOptionsLamAlef == LAMALEF_BEGIN)||
+ (lenOptionsLamAlef == LAMALEF_AUTO))) { // spaces at beginning
+ shiftArray(dest, start, e, LAMALEF_SPACE_SUB);
+ wL = flipArray(dest,start,e, wL);
+ while (wL < e) {
+ dest[wL++] = SPACE_CHAR;
+ }
+ }
+ if(tashkeelOn && (lenOptionsTashkeel == TASHKEEL_BEGIN)){
+ shiftArray(dest, start, e, TASHKEEL_SPACE_SUB);
+ wT = flipArray(dest,start,e, wT);
+ while (wT < e) {
+ dest[wT++] = SPACE_CHAR;
+ }
+ }
+ }
+
+ return length;
+ }
+
+
+ /*
+ *Name :expandCompositCharAtBegin
+ *Function :Expands the LamAlef character to Lam and Alef consuming the required
+ * space from beginning of the buffer. If the text type was visual_LTR
+ * and the option SPACES_RELATIVE_TO_TEXT_BEGIN_END was selected
+ * the spaces will be located at end of buffer.
+ * If there are no spaces to expand the LamAlef, an exception is thrown.
+*/
+ private boolean expandCompositCharAtBegin(char[] dest,int start, int length,
+ int lacount) {
+ boolean spaceNotFound = false;
+
+ if (lacount > countSpacesRight(dest, start, length)) {
+ spaceNotFound = true;
+ return spaceNotFound;
+ }
+ for (int r = start + length - lacount, w = start + length; --r >= start;) {
+ char ch = dest[r];
+ if (isNormalizedLamAlefChar(ch)) {
+ dest[--w] = LAM_CHAR;
+ dest[--w] = convertNormalizedLamAlef[ch - '\u065C'];
+ } else {
+ dest[--w] = ch;
+ }
+ }
+ return spaceNotFound;
+
+ }
+
+ /*
+ *Name : expandCompositCharAtEnd
+ *Function : Expands the LamAlef character to Lam and Alef consuming the
+ * required space from end of the buffer. If the text type was
+ * Visual LTR and the option SPACES_RELATIVE_TO_TEXT_BEGIN_END
+ * was used, the spaces will be consumed from begin of buffer. If
+ * there are no spaces to expand the LamAlef, an exception is thrown.
+ */
+
+ private boolean expandCompositCharAtEnd(char[] dest,int start, int length,
+ int lacount){
+ boolean spaceNotFound = false;
+
+ if (lacount > countSpacesLeft(dest, start, length)) {
+ spaceNotFound = true;
+ return spaceNotFound;
+ }
+ for (int r = start + lacount, w = start, e = start + length; r < e; ++r) {
+ char ch = dest[r];
+ if (isNormalizedLamAlefChar(ch)) {
+ dest[w++] = convertNormalizedLamAlef[ch - '\u065C'];
+ dest[w++] = LAM_CHAR;
+ } else {
+ dest[w++] = ch;
+ }
+ }
+ return spaceNotFound;
+ }
+
+ /*
+ *Name : expandCompositCharAtNear
+ *Function : Expands the LamAlef character into Lam + Alef, YehHamza character
+ * into Yeh + Hamza, SeenFamily character into SeenFamily character
+ * + Tail, while consuming the space next to the character.
+ */
+
+ private boolean expandCompositCharAtNear(char[] dest,int start, int length,
+ int yehHamzaOption, int seenTailOption, int lamAlefOption){
+
+ boolean spaceNotFound = false;
+
+
+
+ if (isNormalizedLamAlefChar(dest[start])) {
+ spaceNotFound = true;
+ return spaceNotFound;
+ }
+ for (int i = start + length; --i >=start;) {
+ char ch = dest[i];
+ if (lamAlefOption == 1 && isNormalizedLamAlefChar(ch)) {
+ if (i>start &&dest[i-1] == SPACE_CHAR) {
+ dest[i] = LAM_CHAR;
+ dest[--i] = convertNormalizedLamAlef[ch - '\u065C'];
+ } else {
+ spaceNotFound = true;
+ return spaceNotFound;
+ }
+ }else if(seenTailOption == 1 && isSeenTailFamilyChar(ch) == 1){
+ if(i>start &&dest[i-1] == SPACE_CHAR){
+ dest[i-1] = tailChar;
+ } else{
+ spaceNotFound = true;
+ return spaceNotFound;
+ }
+ }else if(yehHamzaOption == 1 && isYehHamzaChar(ch)){
+
+ if(i>start &&dest[i-1] == SPACE_CHAR){
+ dest[i] = yehHamzaToYeh[ch - YEH_HAMZAFE_CHAR];
+ dest[i-1] = HAMZAFE_CHAR;
+ }else{
+ spaceNotFound = true;
+ return spaceNotFound;
+ }
+
+
+ }
+ }
+ return false;
+
+ }
+
+ /*
+ * Name : expandCompositChar
+ * Function: LamAlef needs special handling as the LamAlef is
+ * one character while expanding it will give two
+ * characters Lam + Alef, so we need to expand the LamAlef
+ * in near or far spaces according to the options the user
+ * specifies or increase the buffer size.
+ * Dest has enough room for the expansion if we are growing.
+ * lamalef are normalized to the 'special characters'
+ */
+ private int expandCompositChar(char[] dest,
+ int start,
+ int length,
+ int lacount,
+ int shapingMode) throws ArabicShapingException {
+
+ int lenOptionsLamAlef = options & LAMALEF_MASK;
+ int lenOptionsSeen = options & SEEN_MASK;
+ int lenOptionsYehHamza = options & YEHHAMZA_MASK;
+ boolean spaceNotFound = false;
+
+ if (!isLogical && !spacesRelativeToTextBeginEnd) {
+ switch (lenOptionsLamAlef) {
+ case LAMALEF_BEGIN: lenOptionsLamAlef = LAMALEF_END; break;
+ case LAMALEF_END: lenOptionsLamAlef = LAMALEF_BEGIN; break;
+ default: break;
+ }
+ }
+
+ if(shapingMode == 1){
+ if(lenOptionsLamAlef == LAMALEF_AUTO){
+ if(isLogical){
+ spaceNotFound = expandCompositCharAtEnd(dest, start, length, lacount);
+ if(spaceNotFound){
+ spaceNotFound = expandCompositCharAtBegin(dest, start, length, lacount);
+ }
+ if(spaceNotFound){
+ spaceNotFound = expandCompositCharAtNear(dest, start, length,0,0,1);
+ }
+ if(spaceNotFound){
+ throw new ArabicShapingException("No spacefor lamalef");
+ }
+ }else{
+ spaceNotFound = expandCompositCharAtBegin(dest, start, length, lacount);
+ if(spaceNotFound){
+ spaceNotFound = expandCompositCharAtEnd(dest, start, length, lacount);
+ }
+ if(spaceNotFound){
+ spaceNotFound = expandCompositCharAtNear(dest, start, length,0,0,1);
+ }
+ if(spaceNotFound){
+ throw new ArabicShapingException("No spacefor lamalef");
+ }
+ }
+ }else if(lenOptionsLamAlef == LAMALEF_END){
+ spaceNotFound = expandCompositCharAtEnd(dest, start, length, lacount);
+ if(spaceNotFound){
+ throw new ArabicShapingException("No spacefor lamalef");
+ }
+ }else if(lenOptionsLamAlef == LAMALEF_BEGIN){
+ spaceNotFound = expandCompositCharAtBegin(dest, start, length, lacount);
+ if(spaceNotFound){
+ throw new ArabicShapingException("No spacefor lamalef");
+ }
+ }else if(lenOptionsLamAlef == LAMALEF_NEAR){
+ spaceNotFound = expandCompositCharAtNear(dest, start, length,0,0,1);
+ if(spaceNotFound){
+ throw new ArabicShapingException("No spacefor lamalef");
+ }
+ }else if(lenOptionsLamAlef == LAMALEF_RESIZE){
+ for (int r = start + length, w = r + lacount; --r >= start;) {
+ char ch = dest[r];
+ if (isNormalizedLamAlefChar(ch)) {
+ dest[--w] = '\u0644';
+ dest[--w] = convertNormalizedLamAlef[ch - '\u065C'];
+ } else {
+ dest[--w] = ch;
+ }
+ }
+ length += lacount;
+ }
+ }else{
+ if(lenOptionsSeen == SEEN_TWOCELL_NEAR){
+ spaceNotFound = expandCompositCharAtNear(dest, start, length,0,1,0);
+ if(spaceNotFound){
+ throw new ArabicShapingException("No space for Seen tail expansion");
+ }
+ }
+ if(lenOptionsYehHamza == YEHHAMZA_TWOCELL_NEAR){
+ spaceNotFound = expandCompositCharAtNear(dest, start, length,1,0,0);
+ if(spaceNotFound){
+ throw new ArabicShapingException("No space for YehHamza expansion");
+ }
+ }
+ }
+ return length;
+ }
+
+
+ /* Convert the input buffer from FExx Range into 06xx Range
+ * to put all characters into the 06xx range
+ * even the lamalef is converted to the special region in
+ * the 06xx range. Return the number of lamalef chars found.
+ */
+ private int normalize(char[] dest, int start, int length) {
+ int lacount = 0;
+ for (int i = start, e = i + length; i < e; ++i) {
+ char ch = dest[i];
+ if (ch >= '\uFE70' && ch <= '\uFEFC') {
+ if (isLamAlefChar(ch)) {
+ ++lacount;
+ }
+ dest[i] = (char)convertFEto06[ch - '\uFE70'];
+ }
+ }
+ return lacount;
+ }
+
+ /*
+ * Name : deshapeNormalize
+ * Function: Convert the input buffer from FExx Range into 06xx Range
+ * even the lamalef is converted to the special region in the 06xx range.
+ * According to the options the user enters, all seen family characters
+ * followed by a tail character are merged to seen tail family character and
+ * any yeh followed by a hamza character are merged to yehhamza character.
+ * Method returns the number of lamalef chars found.
+ */
+ private int deshapeNormalize(char[] dest, int start, int length) {
+ int lacount = 0;
+ int yehHamzaComposeEnabled = 0;
+ int seenComposeEnabled = 0;
+
+ yehHamzaComposeEnabled = ((options&YEHHAMZA_MASK) == YEHHAMZA_TWOCELL_NEAR) ? 1 : 0;
+ seenComposeEnabled = ((options&SEEN_MASK) == SEEN_TWOCELL_NEAR)? 1 : 0;
+
+ for (int i = start, e = i + length; i < e; ++i) {
+ char ch = dest[i];
+
+ if( (yehHamzaComposeEnabled == 1) && ((ch == HAMZA06_CHAR) || (ch == HAMZAFE_CHAR))
+ && (i < (length - 1)) && isAlefMaksouraChar(dest[i+1] )) {
+ dest[i] = SPACE_CHAR;
+ dest[i+1] = YEH_HAMZA_CHAR;
+ } else if ( (seenComposeEnabled == 1) && (isTailChar(ch)) && (i< (length - 1))
+ && (isSeenTailFamilyChar(dest[i+1])==1) ) {
+ dest[i] = SPACE_CHAR;
+ }
+ else if (ch >= '\uFE70' && ch <= '\uFEFC') {
+ if (isLamAlefChar(ch)) {
+ ++lacount;
+ }
+ dest[i] = (char)convertFEto06[ch - '\uFE70'];
+ }
+ }
+ return lacount;
+ }
+
+ /*
+ * Name : shapeUnicode
+ * Function: Converts an Arabic Unicode buffer in 06xx Range into a shaped
+ * arabic Unicode buffer in FExx Range
+ */
+ private int shapeUnicode(char[] dest,
+ int start,
+ int length,
+ int destSize,
+ int tashkeelFlag)throws ArabicShapingException {
+
+ int lamalef_count = normalize(dest, start, length);
+
+ // resolve the link between the characters.
+ // Arabic characters have four forms: Isolated, Initial, Medial and Final.
+ // Tashkeel characters have two, isolated or medial, and sometimes only isolated.
+ // tashkeelFlag == 0: shape normally, 1: shape isolated, 2: don't shape
+
+ boolean lamalef_found = false, seenfam_found = false;
+ boolean yehhamza_found = false, tashkeel_found = false;
+ int i = start + length - 1;
+ int currLink = getLink(dest[i]);
+ int nextLink = 0;
+ int prevLink = 0;
+ int lastLink = 0;
+ //int prevPos = i;
+ int lastPos = i;
+ int nx = -2;
+ int nw = 0;
+
+ while (i >= 0) {
+ // If high byte of currLink > 0 then there might be more than one shape
+ if ((currLink & '\uFF00') > 0 || isTashkeelChar(dest[i])) {
+ nw = i - 1;
+ nx = -2;
+ while (nx < 0) { // we need to know about next char
+ if (nw == -1) {
+ nextLink = 0;
+ nx = Integer.MAX_VALUE;
+ } else {
+ nextLink = getLink(dest[nw]);
+ if ((nextLink & IRRELEVANT) == 0) {
+ nx = nw;
+ } else {
+ --nw;
+ }
+ }
+ }
+
+ if (((currLink & ALEFTYPE) > 0) && ((lastLink & LAMTYPE) > 0)) {
+ lamalef_found = true;
+ char wLamalef = changeLamAlef(dest[i]); // get from 0x065C-0x065f
+ if (wLamalef != '\u0000') {
+ // replace alef by marker, it will be removed later
+ dest[i] = '\uffff';
+ dest[lastPos] = wLamalef;
+ i = lastPos;
+ }
+
+ lastLink = prevLink;
+ currLink = getLink(wLamalef); // requires '\u0000', unfortunately
+ }
+ if ((i > 0) && (dest[i-1] == SPACE_CHAR))
+ {
+ if ( isSeenFamilyChar(dest[i]) == 1){
+ seenfam_found = true;
+ } else if (dest[i] == YEH_HAMZA_CHAR) {
+ yehhamza_found = true;
+ }
+ }
+ else if(i==0){
+ if ( isSeenFamilyChar(dest[i]) == 1){
+ seenfam_found = true;
+ } else if (dest[i] == YEH_HAMZA_CHAR) {
+ yehhamza_found = true;
+ }
+ }
+
+
+ // get the proper shape according to link ability of neighbors
+ // and of character; depends on the order of the shapes
+ // (isolated, initial, middle, final) in the compatibility area
+
+ int flag = specialChar(dest[i]);
+
+ int shape = shapeTable[nextLink & LINK_MASK]
+ [lastLink & LINK_MASK]
+ [currLink & LINK_MASK];
+
+ if (flag == 1) {
+ shape &= 0x1;
+ } else if (flag == 2) {
+ if (tashkeelFlag == 0 &&
+ ((lastLink & LINKL) != 0) &&
+ ((nextLink & LINKR) != 0) &&
+ dest[i] != '\u064C' &&
+ dest[i] != '\u064D' &&
+ !((nextLink & ALEFTYPE) == ALEFTYPE &&
+ (lastLink & LAMTYPE) == LAMTYPE)) {
+
+ shape = 1;
+ } else {
+ shape = 0;
+ }
+ }
+ if (flag == 2) {
+ if (tashkeelFlag == 2) {
+ dest[i] = TASHKEEL_SPACE_SUB;
+ tashkeel_found = true;
+ }
+ else{
+ dest[i] = (char)('\uFE70' + irrelevantPos[dest[i] - '\u064B'] + shape);
+ }
+ // else leave tashkeel alone
+ } else {
+ dest[i] = (char)('\uFE70' + (currLink >> 8) + shape);
+ }
+ }
+
+ // move one notch forward
+ if ((currLink & IRRELEVANT) == 0) {
+ prevLink = lastLink;
+ lastLink = currLink;
+ //prevPos = lastPos;
+ lastPos = i;
+ }
+
+ --i;
+ if (i == nx) {
+ currLink = nextLink;
+ nx = -2;
+ } else if (i != -1) {
+ currLink = getLink(dest[i]);
+ }
+ }
+
+ // If we found a lam/alef pair in the buffer
+ // call handleGeneratedSpaces to remove the spaces that were added
+
+ destSize = length;
+ if (lamalef_found || tashkeel_found) {
+ destSize = handleGeneratedSpaces(dest, start, length);
+ }
+ if (seenfam_found || yehhamza_found){
+ destSize = expandCompositChar(dest, start, destSize, lamalef_count, SHAPE_MODE);
+ }
+ return destSize;
+ }
+
+ /*
+ * Name : deShapeUnicode
+ * Function: Converts an Arabic Unicode buffer in FExx Range into unshaped
+ * arabic Unicode buffer in 06xx Range
+ */
+ private int deShapeUnicode(char[] dest,
+ int start,
+ int length,
+ int destSize) throws ArabicShapingException {
+
+ int lamalef_count = deshapeNormalize(dest, start, length);
+
+ // If there was a lamalef in the buffer call expandLamAlef
+ if (lamalef_count != 0) {
+ // need to adjust dest to fit expanded buffer... !!!
+ destSize = expandCompositChar(dest, start, length, lamalef_count,DESHAPE_MODE);
+ } else {
+ destSize = length;
+ }
+
+ return destSize;
+ }
+
+ private int internalShape(char[] source,
+ int sourceStart,
+ int sourceLength,
+ char[] dest,
+ int destStart,
+ int destSize) throws ArabicShapingException {
+
+ if (sourceLength == 0) {
+ return 0;
+ }
+
+ if (destSize == 0) {
+ if (((options & LETTERS_MASK) != LETTERS_NOOP) &&
+ ((options & LAMALEF_MASK) == LAMALEF_RESIZE)) {
+
+ return calculateSize(source, sourceStart, sourceLength);
+ } else {
+ return sourceLength; // by definition
+ }
+ }
+
+ // always use temp buffer
+ char[] temp = new char[sourceLength * 2]; // all lamalefs requiring expansion
+ System.arraycopy(source, sourceStart, temp, 0, sourceLength);
+
+ if (isLogical) {
+ invertBuffer(temp, 0, sourceLength);
+ }
+
+ int outputSize = sourceLength;
+
+ switch (options & LETTERS_MASK) {
+ case LETTERS_SHAPE_TASHKEEL_ISOLATED:
+ outputSize = shapeUnicode(temp, 0, sourceLength, destSize, 1);
+ break;
+
+ case LETTERS_SHAPE:
+ if( ((options&TASHKEEL_MASK)> 0) &&
+ ((options&TASHKEEL_MASK) !=TASHKEEL_REPLACE_BY_TATWEEL)) {
+ /* Call the shaping function with tashkeel flag == 2 for removal of tashkeel */
+ outputSize = shapeUnicode(temp, 0, sourceLength, destSize, 2);
+ }else {
+ //default Call the shaping function with tashkeel flag == 1 */
+ outputSize = shapeUnicode(temp, 0, sourceLength, destSize, 0);
+
+ /*After shaping text check if user wants to remove tashkeel and replace it with tatweel*/
+ if( (options&TASHKEEL_MASK) == TASHKEEL_REPLACE_BY_TATWEEL){
+ outputSize = handleTashkeelWithTatweel(temp,sourceLength);
+ }
+ }
+ break;
+
+ case LETTERS_UNSHAPE:
+ outputSize = deShapeUnicode(temp, 0, sourceLength, destSize);
+ break;
+
+ default:
+ break;
+ }
+
+ if (outputSize > destSize) {
+ throw new ArabicShapingException("not enough room for result data");
+ }
+
+ if ((options & DIGITS_MASK) != DIGITS_NOOP) {
+ char digitBase = '\u0030'; // European digits
+ switch (options & DIGIT_TYPE_MASK) {
+ case DIGIT_TYPE_AN:
+ digitBase = '\u0660'; // Arabic-Indic digits
+ break;
+
+ case DIGIT_TYPE_AN_EXTENDED:
+ digitBase = '\u06f0'; // Eastern Arabic-Indic digits (Persian and Urdu)
+ break;
+
+ default:
+ break;
+ }
+
+ switch (options & DIGITS_MASK) {
+ case DIGITS_EN2AN:
+ {
+ int digitDelta = digitBase - '\u0030';
+ for (int i = 0; i < outputSize; ++i) {
+ char ch = temp[i];
+ if (ch <= '\u0039' && ch >= '\u0030') {
+ temp[i] += digitDelta;
+ }
+ }
+ }
+ break;
+
+ case DIGITS_AN2EN:
+ {
+ char digitTop = (char)(digitBase + 9);
+ int digitDelta = '\u0030' - digitBase;
+ for (int i = 0; i < outputSize; ++i) {
+ char ch = temp[i];
+ if (ch <= digitTop && ch >= digitBase) {
+ temp[i] += digitDelta;
+ }
+ }
+ }
+ break;
+
+ case DIGITS_EN2AN_INIT_LR:
+ shapeToArabicDigitsWithContext(temp, 0, outputSize, digitBase, false);
+ break;
+
+ case DIGITS_EN2AN_INIT_AL:
+ shapeToArabicDigitsWithContext(temp, 0, outputSize, digitBase, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (isLogical) {
+ invertBuffer(temp, 0, outputSize);
+ }
+
+ System.arraycopy(temp, 0, dest, destStart, outputSize);
+
+ return outputSize;
+ }
+
+ private static class ArabicShapingException extends RuntimeException {
+ ArabicShapingException(String msg) {
+ super(msg);
+ }
+ }
+}
diff --git a/icu4j/license.html b/icu4j/license.html
new file mode 100644
index 0000000..b905ddf
--- /dev/null
+++ b/icu4j/license.html
@@ -0,0 +1,51 @@
+<html>
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii"></meta>
+<title>ICU License - ICU 1.8.1 and later</title>
+</head>
+
+<body BGCOLOR="#ffffff">
+<h2>ICU License - ICU 1.8.1 and later</h2>
+
+<p>COPYRIGHT AND PERMISSION NOTICE</p>
+
+<p>
+Copyright (c) 1995-2006 International Business Machines Corporation and others
+</p>
+<p>
+All rights reserved.
+</p>
+<p>
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Software, and to permit persons
+to whom the Software is furnished to do so, provided that the above
+copyright notice(s) and this permission notice appear in all copies
+of the Software and that both the above copyright notice(s) and this
+permission notice appear in supporting documentation.
+</p>
+<p>
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL
+THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM,
+OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+USE OR PERFORMANCE OF THIS SOFTWARE.
+</p>
+<p>
+Except as contained in this notice, the name of a copyright holder shall not be
+used in advertising or otherwise to promote the sale, use or other dealings in
+this Software without prior written authorization of the copyright holder.
+</p>
+
+<hr>
+<p><small>
+All trademarks and registered trademarks mentioned herein are the property of their respective owners.
+</small></p>
+</body>
+</html>
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 98464a0..0b06022 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -76,16 +76,18 @@
LOCAL_SRC_FILES:= \
rsAdapter.cpp \
rsAllocation.cpp \
+ rsAnimation.cpp \
rsComponent.cpp \
rsContext.cpp \
rsDevice.cpp \
rsElement.cpp \
- rsFileA3D.cpp \
+ rsFileA3D.cpp \
rsLight.cpp \
rsLocklessFifo.cpp \
rsObjectBase.cpp \
rsMatrix.cpp \
- rsMesh.cpp \
+ rsMesh.cpp \
+ rsMutex.cpp \
rsNoise.cpp \
rsProgram.cpp \
rsProgramFragment.cpp \
@@ -96,7 +98,8 @@
rsScript.cpp \
rsScriptC.cpp \
rsScriptC_Lib.cpp \
- rsShaderCache.cpp \
+ rsShaderCache.cpp \
+ rsSignal.cpp \
rsSimpleMesh.cpp \
rsThreadIO.cpp \
rsType.cpp \
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index d280f50..7415ba9 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -30,6 +30,7 @@
typedef void * RsAdapter1D;
typedef void * RsAdapter2D;
typedef void * RsAllocation;
+typedef void * RsAnimation;
typedef void * RsContext;
typedef void * RsDevice;
typedef void * RsElement;
@@ -205,7 +206,27 @@
enum RsError {
RS_ERROR_NONE,
RS_ERROR_BAD_SHADER,
- RS_ERROR_BAD_SCRIPT
+ RS_ERROR_BAD_SCRIPT,
+ RS_ERROR_BAD_VALUE,
+ RS_ERROR_OUT_OF_MEMORY
+};
+
+enum RsAnimationInterpolation {
+ RS_ANIMATION_INTERPOLATION_STEP,
+ RS_ANIMATION_INTERPOLATION_LINEAR,
+ RS_ANIMATION_INTERPOLATION_BEZIER,
+ RS_ANIMATION_INTERPOLATION_CARDINAL,
+ RS_ANIMATION_INTERPOLATION_HERMITE,
+ RS_ANIMATION_INTERPOLATION_BSPLINE
+};
+
+enum RsAnimationEdge {
+ RS_ANIMATION_EDGE_UNDEFINED,
+ RS_ANIMATION_EDGE_CONSTANT,
+ RS_ANIMATION_EDGE_GRADIENT,
+ RS_ANIMATION_EDGE_CYCLE,
+ RS_ANIMATION_EDGE_OSCILLATE,
+ RS_ANIMATION_EDGE_CYLE_RELATIVE
};
#ifndef NO_RS_FUNCS
diff --git a/libs/rs/java/Fountain/res/raw/fountain2.rs b/libs/rs/java/Fountain/res/raw/fountain2.rs
index 3301140..5d36e35 100644
--- a/libs/rs/java/Fountain/res/raw/fountain2.rs
+++ b/libs/rs/java/Fountain/res/raw/fountain2.rs
@@ -1,9 +1,9 @@
// Fountain test script
#pragma version(1)
-#include "rs_types.rsh"
-#include "rs_math.rsh"
-#include "rs_graphics.rsh"
+#include "../../../../scriptc/rs_types.rsh"
+#include "../../../../scriptc/rs_math.rsh"
+#include "../../../../scriptc/rs_graphics.rsh"
static int newPart = 0;
@@ -12,15 +12,15 @@
int rate;
int count;
float r, g, b;
- rs_allocation partBuffer;
rs_mesh partMesh;
+ rs_allocation partBuffer;
} Control_t;
Control_t *Control;
typedef struct Point_s{
float2 delta;
- float2 position;
- unsigned int color;
+ rs_position2 pos;
+ rs_color4u color;
} Point_t;
Point_t *point;
@@ -33,8 +33,6 @@
if (rate) {
float rMax = ((float)rate) * 0.005f;
- int x = Control->x;
- int y = Control->y;
int color = ((int)(Control->r * 255.f)) |
((int)(Control->g * 255.f)) << 8 |
((int)(Control->b * 255.f)) << 16 |
@@ -42,9 +40,11 @@
Point_t * np = &p[newPart];
while (rate--) {
- np->delta = vec2Rand(rMax);
- np->position.x = x;
- np->position.y = y;
+ np->delta.x = rand(rMax);
+ np->delta.y = rand(rMax);
+ //np->delta = vec2Rand(rMax);
+ np->pos.x = Control->x;
+ np->pos.y = Control->y;
np->color = color;
newPart++;
np++;
@@ -57,13 +57,13 @@
for (ct=0; ct < count; ct++) {
float dy = p->delta.y + 0.15f;
- float posy = p->position.y + dy;
+ float posy = p->pos.y + dy;
if ((posy > height) && (dy > 0)) {
dy *= -0.3f;
}
p->delta.y = dy;
- p->position.x += p->delta.x;
- p->position.y = posy;
+ p->pos.x += p->delta.x;
+ p->pos.y = posy;
p++;
}
diff --git a/libs/rs/java/ImageProcessing/res/raw/threshold2.rs b/libs/rs/java/ImageProcessing/res/raw/threshold2.rs
new file mode 100644
index 0000000..9f687b5
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/res/raw/threshold2.rs
@@ -0,0 +1,49 @@
+#pragma version(1)
+
+#include "../../../../scriptc/rs_types.rsh"
+#include "../../../../scriptc/rs_math.rsh"
+#include "../../../../scriptc/rs_graphics.rsh"
+
+typedef struct Params_s{
+ int inHeight;
+ int inWidth;
+ int outHeight;
+ int outWidth;
+ float threshold;
+} Params_t;
+
+Params_t * Params;
+rs_color4u * InPixel;
+rs_color4u * OutPixel;
+
+
+int main() {
+ int t = uptimeMillis();
+
+ rs_color4u *in = InPixel;
+ rs_color4u *out = OutPixel;
+
+ int count = Params->inWidth * Params->inHeight;
+ int i;
+ float threshold = Params->threshold * 255.f;
+
+ for (i = 0; i < count; i++) {
+ float luminance = 0.2125f * in->x +
+ 0.7154f * in->y +
+ 0.0721f * in->z;
+ if (luminance > threshold) {
+ *out = *in;
+ } else {
+ *((int *)out) = *((int *)in) & 0xff000000;
+ }
+
+ in++;
+ out++;
+ }
+
+ t= uptimeMillis() - t;
+ debugI32("Filter time", t);
+
+ sendToClient(&count, 1, 4, 0);
+ return 0;
+}
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index cb9937c..08aa369 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -480,3 +480,13 @@
param uint32_t slot
}
+AnimationCreate {
+ param const float *inValues
+ param const float *outValues
+ param uint32_t valueCount
+ param RsAnimationInterpolation interp
+ param RsAnimationEdge pre
+ param RsAnimationEdge post
+ ret RsAnimation
+ }
+
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 4e8278d..e5ff1d7 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -170,6 +170,7 @@
glGenerateMipmap(GL_TEXTURE_2D);
}
+ rsc->checkError("Allocation::uploadToTexture");
}
void Allocation::deferedUploadToBufferObject(const Context *rsc)
@@ -201,6 +202,7 @@
glBindBuffer(GL_ARRAY_BUFFER, mBufferID);
glBufferData(GL_ARRAY_BUFFER, mType->getSizeBytes(), getPtr(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
+ rsc->checkError("Allocation::uploadToBufferObject");
}
void Allocation::uploadCheck(const Context *rsc)
diff --git a/libs/rs/rsAnimation.cpp b/libs/rs/rsAnimation.cpp
new file mode 100644
index 0000000..48c9334
--- /dev/null
+++ b/libs/rs/rsAnimation.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2009 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 "rsContext.h"
+#include "rsAnimation.h"
+
+
+using namespace android;
+using namespace android::renderscript;
+
+/*
+Animation::Animation(Context *rsc) : ObjectBase(rsc)
+{
+ mAllocFile = __FILE__;
+ mAllocLine = __LINE__;
+
+ mValuesInput = NULL;
+ mValuesOutput = NULL;
+ mValueCount = 0;
+ mInterpolation = RS_ANIMATION_INTERPOLATION_STEP;
+ mEdgePre = RS_ANIMATION_EDGE_UNDEFINED;
+ mEdgePost = RS_ANIMATION_EDGE_UNDEFINED;
+ mInputMin = 0;
+ mInputMax = 0;
+}
+
+Animation * Animation::create(Context *rsc,
+ const float *inValues, const float *outValues,
+ uint32_t valueCount, RsAnimationInterpolation interp,
+ RsAnimationEdge pre, RsAnimationEdge post)
+{
+ if (valueCount < 2) {
+ rsc->setError(RS_ERROR_BAD_VALUE, "Animations require more than 2 values.");
+ return NULL;
+ }
+ Animation *a = new Animation(rsc);
+ if (!a) {
+ rsc->setError(RS_ERROR_OUT_OF_MEMORY);
+ return NULL;
+ }
+
+ float *vin = (float *)malloc(valueCount * sizeof(float));
+ float *vout = (float *)malloc(valueCount * sizeof(float));
+ a->mValuesInput = vin;
+ a->mValuesOutput = vout;
+ if (a->mValuesInput == NULL || a->mValuesOutput == NULL) {
+ delete a;
+ rsc->setError(RS_ERROR_OUT_OF_MEMORY);
+ return NULL;
+ }
+
+ a->mEdgePre = pre;
+ a->mEdgePost = post;
+ a->mInterpolation = interp;
+ a->mValueCount = valueCount;
+
+ memcpy(vin, inValues, valueCount * sizeof(float));
+ memcpy(vout, outValues, valueCount * sizeof(float));
+ a->mInputMin = inValues[0];
+ a->mInputMax = inValues[0];
+
+ bool needSort = false;
+ for (uint32_t ct=1; ct < valueCount; ct++) {
+ if (a->mInputMin > vin[ct]) {
+ needSort = true;
+ a->mInputMin = vin[ct];
+ }
+ if (a->mInputMax < vin[ct]) {
+ a->mInputMax = vin[ct];
+ } else {
+ needSort = true;
+ }
+ }
+
+ while (1) {
+ bool changed = false;
+ for (uint32_t ct=1; ct < valueCount; ct++) {
+ if (vin[ct-1] > vin[ct]) {
+ float t = vin[ct-1];
+ vin[ct-1] = vin[ct];
+ vin[ct] = t;
+ t = vout[ct-1];
+ vout[ct-1] = vout[ct];
+ vout[ct] = t;
+ changed = true;
+ }
+ }
+ if (!changed) break;
+ }
+
+ return a;
+}
+*/
+
+
+/////////////////////////////////////////
+//
+
+namespace android {
+namespace renderscript {
+
+RsAnimation rsi_AnimationCreate(Context *rsc,
+ const float *inValues,
+ const float *outValues,
+ uint32_t valueCount,
+ RsAnimationInterpolation interp,
+ RsAnimationEdge pre,
+ RsAnimationEdge post)
+{
+ //LOGE("rsi_ElementCreate %i %i %i %i", dt, dk, norm, vecSize);
+ Animation *a = NULL;//Animation::create(rsc, inValues, outValues, valueCount, interp, pre, post);
+ if (a != NULL) {
+ a->incUserRef();
+ }
+ return (RsAnimation)a;
+}
+
+
+}
+}
+
diff --git a/libs/rs/rsAnimation.h b/libs/rs/rsAnimation.h
new file mode 100644
index 0000000..b8db661
--- /dev/null
+++ b/libs/rs/rsAnimation.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#ifndef ANDROID_RS_ANIMATION_H
+#define ANDROID_RS_ANIMATION_H
+
+#include "rsUtils.h"
+#include "rsObjectBase.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+namespace renderscript {
+
+
+class Animation : public ObjectBase
+{
+public:
+ ~Animation();
+
+ static Animation * create(Context *rsc,
+ const float *inValues, const float *outValues,
+ uint32_t valueCount, RsAnimationInterpolation,
+ RsAnimationEdge pre, RsAnimationEdge post);
+
+ float eval(float) const;
+
+
+protected:
+ Animation(Context *rsc);
+
+
+
+ float evalInRange(float) const;
+
+
+
+ const float *mValuesInput;
+ const float *mValuesOutput;
+ uint32_t mValueCount;
+ RsAnimationInterpolation mInterpolation;
+ RsAnimationEdge mEdgePre;
+ RsAnimationEdge mEdgePost;
+
+ // derived
+ float mInputMin;
+ float mInputMax;
+};
+
+
+
+
+}
+}
+#endif //ANDROID_STRUCTURED_ELEMENT_H
+
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index d8a9a99..4107229 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -664,8 +664,7 @@
bool Context::objDestroyOOBInit()
{
- int status = pthread_mutex_init(&mObjDestroy.mMutex, NULL);
- if (status) {
+ if (!mObjDestroy.mMutex.init()) {
LOGE("Context::ObjDestroyOOBInit mutex init failure");
return false;
}
@@ -675,9 +674,8 @@
void Context::objDestroyOOBRun()
{
if (mObjDestroy.mNeedToEmpty) {
- int status = pthread_mutex_lock(&mObjDestroy.mMutex);
- if (status) {
- LOGE("Context::ObjDestroyOOBRun: error %i locking for OOBRun.", status);
+ if (!mObjDestroy.mMutex.lock()) {
+ LOGE("Context::ObjDestroyOOBRun: error locking for OOBRun.");
return;
}
@@ -686,35 +684,25 @@
}
mObjDestroy.mDestroyList.clear();
mObjDestroy.mNeedToEmpty = false;
-
- status = pthread_mutex_unlock(&mObjDestroy.mMutex);
- if (status) {
- LOGE("Context::ObjDestroyOOBRun: error %i unlocking for set condition.", status);
- }
+ mObjDestroy.mMutex.unlock();
}
}
void Context::objDestroyOOBDestroy()
{
rsAssert(!mObjDestroy.mNeedToEmpty);
- pthread_mutex_destroy(&mObjDestroy.mMutex);
}
void Context::objDestroyAdd(ObjectBase *obj)
{
- int status = pthread_mutex_lock(&mObjDestroy.mMutex);
- if (status) {
- LOGE("Context::ObjDestroyOOBRun: error %i locking for OOBRun.", status);
+ if (!mObjDestroy.mMutex.lock()) {
+ LOGE("Context::ObjDestroyOOBRun: error locking for OOBRun.");
return;
}
mObjDestroy.mNeedToEmpty = true;
mObjDestroy.mDestroyList.add(obj);
-
- status = pthread_mutex_unlock(&mObjDestroy.mMutex);
- if (status) {
- LOGE("Context::ObjDestroyOOBRun: error %i unlocking for set condition.", status);
- }
+ mObjDestroy.mMutex.unlock();
}
uint32_t Context::getMessageToClient(void *data, size_t *receiveLen, size_t bufferLen, bool wait)
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 82c3687..8e755a9 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -18,6 +18,7 @@
#define ANDROID_RS_CONTEXT_H
#include "rsUtils.h"
+#include "rsMutex.h"
#include "rsThreadIO.h"
#include "rsType.h"
@@ -161,7 +162,7 @@
void dumpDebug() const;
void checkError(const char *) const;
const char * getError(RsError *);
- void setError(RsError e, const char *msg);
+ void setError(RsError e, const char *msg = NULL);
mutable const ObjectBase * mObjHead;
@@ -227,7 +228,7 @@
struct ObjDestroyOOB {
- pthread_mutex_t mMutex;
+ Mutex mMutex;
Vector<ObjectBase *> mDestroyList;
bool mNeedToEmpty;
};
diff --git a/libs/rs/rsLocklessFifo.cpp b/libs/rs/rsLocklessFifo.cpp
index c796520..76ca32e 100644
--- a/libs/rs/rsLocklessFifo.cpp
+++ b/libs/rs/rsLocklessFifo.cpp
@@ -17,7 +17,7 @@
#include "rsLocklessFifo.h"
using namespace android;
-
+using namespace android::renderscript;
LocklessCommandFifo::LocklessCommandFifo()
{
@@ -128,15 +128,19 @@
//dumpState("flush 2");
}
+void LocklessCommandFifo::wait()
+{
+ while(isEmpty() && !mInShutdown) {
+ mSignalToControl.set();
+ mSignalToWorker.wait();
+ }
+}
+
const void * LocklessCommandFifo::get(uint32_t *command, uint32_t *bytesData)
{
while(1) {
//dumpState("get");
- while(isEmpty() && !mInShutdown) {
- mSignalToControl.set();
- mSignalToWorker.wait();
- }
-
+ wait();
if (mInShutdown) {
*command = 0;
*bytesData = 0;
@@ -192,79 +196,3 @@
LOGV("%s put %p, get %p, buf %p, end %p", s, mPut, mGet, mBuffer, mEnd);
}
-LocklessCommandFifo::Signal::Signal()
-{
- mSet = true;
-}
-
-LocklessCommandFifo::Signal::~Signal()
-{
- pthread_mutex_destroy(&mMutex);
- pthread_cond_destroy(&mCondition);
-}
-
-bool LocklessCommandFifo::Signal::init()
-{
- int status = pthread_mutex_init(&mMutex, NULL);
- if (status) {
- LOGE("LocklessFifo mutex init failure");
- return false;
- }
-
- status = pthread_cond_init(&mCondition, NULL);
- if (status) {
- LOGE("LocklessFifo condition init failure");
- pthread_mutex_destroy(&mMutex);
- return false;
- }
-
- return true;
-}
-
-void LocklessCommandFifo::Signal::set()
-{
- int status;
-
- status = pthread_mutex_lock(&mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i locking for set condition.", status);
- return;
- }
-
- mSet = true;
-
- status = pthread_cond_signal(&mCondition);
- if (status) {
- LOGE("LocklessCommandFifo: error %i on set condition.", status);
- }
-
- status = pthread_mutex_unlock(&mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i unlocking for set condition.", status);
- }
-}
-
-void LocklessCommandFifo::Signal::wait()
-{
- int status;
-
- status = pthread_mutex_lock(&mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i locking for condition.", status);
- return;
- }
-
- if (!mSet) {
- status = pthread_cond_wait(&mCondition, &mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i waiting on condition.", status);
- }
- }
- mSet = false;
-
- status = pthread_mutex_unlock(&mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i unlocking for condition.", status);
- }
-}
-
diff --git a/libs/rs/rsLocklessFifo.h b/libs/rs/rsLocklessFifo.h
index d0a4356..ae906ca 100644
--- a/libs/rs/rsLocklessFifo.h
+++ b/libs/rs/rsLocklessFifo.h
@@ -19,8 +19,10 @@
#include "rsUtils.h"
+#include "rsSignal.h"
namespace android {
+namespace renderscript {
// A simple FIFO to be used as a producer / consumer between two
@@ -37,24 +39,7 @@
LocklessCommandFifo();
~LocklessCommandFifo();
-
protected:
- class Signal {
- public:
- Signal();
- ~Signal();
-
- bool init();
-
- void set();
- void wait();
-
- protected:
- bool mSet;
- pthread_mutex_t mMutex;
- pthread_cond_t mCondition;
- };
-
uint8_t * volatile mPut;
uint8_t * volatile mGet;
uint8_t * mBuffer;
@@ -65,14 +50,14 @@
Signal mSignalToWorker;
Signal mSignalToControl;
-
-
public:
void * reserve(uint32_t bytes);
void commit(uint32_t command, uint32_t bytes);
void commitSync(uint32_t command, uint32_t bytes);
void flush();
+ void wait();
+
const void * get(uint32_t *command, uint32_t *bytesData);
void next();
@@ -88,4 +73,5 @@
}
+}
#endif
diff --git a/libs/rs/rsMutex.cpp b/libs/rs/rsMutex.cpp
new file mode 100644
index 0000000..37752f2
--- /dev/null
+++ b/libs/rs/rsMutex.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2009 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 "rsMutex.h"
+
+using namespace android;
+using namespace android::renderscript;
+
+
+Mutex::Mutex()
+{
+}
+
+Mutex::~Mutex()
+{
+ pthread_mutex_destroy(&mMutex);
+}
+
+bool Mutex::init()
+{
+ int status = pthread_mutex_init(&mMutex, NULL);
+ if (status) {
+ LOGE("Mutex::Mutex init failure");
+ return false;
+ }
+ return true;
+}
+
+bool Mutex::lock()
+{
+ int status;
+ status = pthread_mutex_lock(&mMutex);
+ if (status) {
+ LOGE("Mutex: error %i locking.", status);
+ return false;
+ }
+ return true;
+}
+
+bool Mutex::unlock()
+{
+ int status;
+ status = pthread_mutex_unlock(&mMutex);
+ if (status) {
+ LOGE("Mutex error %i unlocking.", status);
+ return false;
+ }
+ return true;
+}
+
+
diff --git a/graphics/java/android/renderscript/Vector2f.java b/libs/rs/rsMutex.h
similarity index 69%
copy from graphics/java/android/renderscript/Vector2f.java
copy to libs/rs/rsMutex.h
index 567d57fa..47725d7 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/libs/rs/rsMutex.h
@@ -14,24 +14,30 @@
* limitations under the License.
*/
-package android.renderscript;
-
-import java.lang.Math;
-import android.util.Log;
+#ifndef ANDROID_RS_MUTEX_H
+#define ANDROID_RS_MUTEX_H
-/**
- * @hide
- *
- **/
-public class Vector2f {
- public Vector2f() {
- }
+#include "rsUtils.h"
- public float x;
- public float y;
+namespace android {
+namespace renderscript {
+
+class Mutex {
+public:
+ Mutex();
+ ~Mutex();
+
+ bool init();
+ bool lock();
+ bool unlock();
+
+protected:
+ pthread_mutex_t mMutex;
+};
+
+}
}
-
-
+#endif
diff --git a/libs/rs/rsSignal.cpp b/libs/rs/rsSignal.cpp
new file mode 100644
index 0000000..9239bfd
--- /dev/null
+++ b/libs/rs/rsSignal.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2009 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 "rsSignal.h"
+
+using namespace android;
+using namespace android::renderscript;
+
+
+Signal::Signal()
+{
+ mSet = true;
+}
+
+Signal::~Signal()
+{
+ pthread_mutex_destroy(&mMutex);
+ pthread_cond_destroy(&mCondition);
+}
+
+bool Signal::init()
+{
+ int status = pthread_mutex_init(&mMutex, NULL);
+ if (status) {
+ LOGE("LocklessFifo mutex init failure");
+ return false;
+ }
+
+ status = pthread_cond_init(&mCondition, NULL);
+ if (status) {
+ LOGE("LocklessFifo condition init failure");
+ pthread_mutex_destroy(&mMutex);
+ return false;
+ }
+
+ return true;
+}
+
+void Signal::set()
+{
+ int status;
+
+ status = pthread_mutex_lock(&mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i locking for set condition.", status);
+ return;
+ }
+
+ mSet = true;
+
+ status = pthread_cond_signal(&mCondition);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i on set condition.", status);
+ }
+
+ status = pthread_mutex_unlock(&mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i unlocking for set condition.", status);
+ }
+}
+
+void Signal::wait()
+{
+ int status;
+
+ status = pthread_mutex_lock(&mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i locking for condition.", status);
+ return;
+ }
+
+ if (!mSet) {
+ status = pthread_cond_wait(&mCondition, &mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i waiting on condition.", status);
+ }
+ }
+ mSet = false;
+
+ status = pthread_mutex_unlock(&mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i unlocking for condition.", status);
+ }
+}
+
diff --git a/graphics/java/android/renderscript/Vector2f.java b/libs/rs/rsSignal.h
similarity index 65%
copy from graphics/java/android/renderscript/Vector2f.java
copy to libs/rs/rsSignal.h
index 567d57fa..2e760f1 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/libs/rs/rsSignal.h
@@ -14,24 +14,33 @@
* limitations under the License.
*/
-package android.renderscript;
-
-import java.lang.Math;
-import android.util.Log;
+#ifndef ANDROID_RS_SIGNAL_H
+#define ANDROID_RS_SIGNAL_H
-/**
- * @hide
- *
- **/
-public class Vector2f {
- public Vector2f() {
- }
+#include "rsUtils.h"
- public float x;
- public float y;
+namespace android {
+namespace renderscript {
+
+class Signal {
+public:
+ Signal();
+ ~Signal();
+
+ bool init();
+
+ void set();
+ void wait();
+
+protected:
+ bool mSet;
+ pthread_mutex_t mMutex;
+ pthread_cond_t mCondition;
+};
+
+}
}
-
-
+#endif
diff --git a/libs/rs/rsg_ScriptJavaClass.cpp b/libs/rs/rsg_ScriptJavaClass.cpp
index cee9f52..0169b98 100644
--- a/libs/rs/rsg_ScriptJavaClass.cpp
+++ b/libs/rs/rsg_ScriptJavaClass.cpp
@@ -7,8 +7,12 @@
struct Element;
struct ElementField {
+ // An Element Field is a combination of an Element with a name assigned.
+
const char *name;
Element *e;
+
+
ElementField(const char *n, Element *_e) {
name = n;
e = _e;
@@ -20,12 +24,21 @@
};
struct Element {
+ // An Element can take one of two forms.
+ // 1: Basic. It contains a single basic type and vector size.
+ // 2: Complex. It contains a list of fields with names. Each field
+ // will in turn be another element.
+
ElementField *fields;
- size_t fieldCount;
+ size_t fieldCount; // If field count is 0, the element is a Basic type.
const char *name;
bool generated;
+ // The basic data type from RenderScript.h
RsDataType compType;
+
+ // The vector size of the data type for float2, float3, ....
+ // Allowed sizes are 2,3,4,8,16
uint32_t compVectorSize;
Element() {
diff --git a/libs/rs/scriptc/rs_geom.rsh b/libs/rs/scriptc/rs_geom.rsh
new file mode 100644
index 0000000..6e9e9fc
--- /dev/null
+++ b/libs/rs/scriptc/rs_geom.rsh
@@ -0,0 +1,26 @@
+
+extern float3 __attribute__((overloadable)) cross(float3, float3);
+extern float4 __attribute__((overloadable)) cross(float4, float4);
+
+//extern float __attribute__((overloadable)) dot(float, float);
+extern float __attribute__((overloadable)) dot(float2, float2);
+extern float __attribute__((overloadable)) dot(float3, float3);
+extern float __attribute__((overloadable)) dot(float4, float4);
+
+//extern float __attribute__((overloadable)) distance(float, float);
+extern float __attribute__((overloadable)) distance(float2, float2);
+extern float __attribute__((overloadable)) distance(float3, float3);
+extern float __attribute__((overloadable)) distance(float4, float4);
+
+//extern float __attribute__((overloadable)) length(float);
+extern float __attribute__((overloadable)) length(float2);
+extern float __attribute__((overloadable)) length(float3);
+extern float __attribute__((overloadable)) length(float4);
+
+extern float2 __attribute__((overloadable)) normalize(float2);
+extern float3 __attribute__((overloadable)) normalize(float3);
+extern float4 __attribute__((overloadable)) normalize(float4);
+
+
+
+
diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh
index 70cd562..0f03732 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -1,5 +1,7 @@
+extern float rand(float max);
+
extern float2 vec2Rand(float len);
extern float3 float3Norm(float3);
diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh
index 613c7ca..fba0f8f 100644
--- a/libs/rs/scriptc/rs_math.rsh
+++ b/libs/rs/scriptc/rs_math.rsh
@@ -1,264 +1,262 @@
// Float ops
extern float __attribute__((overloadable)) abs(float);
-extern float2 __attribute__((overloadable)) abs(float2);
-extern float3 __attribute__((overloadable)) abs(float3);
-extern float4 __attribute__((overloadable)) abs(float4);
-extern float8 __attribute__((overloadable)) abs(float8);
-extern float16 __attribute__((overloadable)) abs(float16);
+//extern float2 __attribute__((overloadable)) abs(float2);
+//extern float3 __attribute__((overloadable)) abs(float3);
+//extern float4 __attribute__((overloadable)) abs(float4);
+//extern float8 __attribute__((overloadable)) abs(float8);
+//extern float16 __attribute__((overloadable)) abs(float16);
extern float __attribute__((overloadable)) acos(float);
-extern float2 __attribute__((overloadable)) acos(float2);
-extern float3 __attribute__((overloadable)) acos(float3);
-extern float4 __attribute__((overloadable)) acos(float4);
-extern float8 __attribute__((overloadable)) acos(float8);
-extern float16 __attribute__((overloadable)) acos(float16);
+//extern float2 __attribute__((overloadable)) acos(float2);
+//extern float3 __attribute__((overloadable)) acos(float3);
+//extern float4 __attribute__((overloadable)) acos(float4);
+//extern float8 __attribute__((overloadable)) acos(float8);
+//extern float16 __attribute__((overloadable)) acos(float16);
extern float __attribute__((overloadable)) asin(float);
-extern float2 __attribute__((overloadable)) asin(float2);
-extern float3 __attribute__((overloadable)) asin(float3);
-extern float4 __attribute__((overloadable)) asin(float4);
-extern float8 __attribute__((overloadable)) asin(float8);
-extern float16 __attribute__((overloadable)) asin(float16);
+//extern float2 __attribute__((overloadable)) asin(float2);
+//extern float3 __attribute__((overloadable)) asin(float3);
+//extern float4 __attribute__((overloadable)) asin(float4);
+//extern float8 __attribute__((overloadable)) asin(float8);
+//extern float16 __attribute__((overloadable)) asin(float16);
extern float __attribute__((overloadable)) atan(float);
-extern float2 __attribute__((overloadable)) atan(float2);
-extern float3 __attribute__((overloadable)) atan(float3);
-extern float4 __attribute__((overloadable)) atan(float4);
-extern float8 __attribute__((overloadable)) atan(float8);
-extern float16 __attribute__((overloadable)) atan(float16);
+//extern float2 __attribute__((overloadable)) atan(float2);
+//extern float3 __attribute__((overloadable)) atan(float3);
+//extern float4 __attribute__((overloadable)) atan(float4);
+//extern float8 __attribute__((overloadable)) atan(float8);
+//extern float16 __attribute__((overloadable)) atan(float16);
extern float __attribute__((overloadable)) atan2(float, float);
-extern float2 __attribute__((overloadable)) atan2(float2, float2);
-extern float3 __attribute__((overloadable)) atan2(float3, float3);
-extern float4 __attribute__((overloadable)) atan2(float4, float4);
-extern float8 __attribute__((overloadable)) atan2(float8, float8);
-extern float16 __attribute__((overloadable)) atan2(float16, float16);
+//extern float2 __attribute__((overloadable)) atan2(float2, float2);
+//extern float3 __attribute__((overloadable)) atan2(float3, float3);
+//extern float4 __attribute__((overloadable)) atan2(float4, float4);
+//extern float8 __attribute__((overloadable)) atan2(float8, float8);
+//extern float16 __attribute__((overloadable)) atan2(float16, float16);
extern float __attribute__((overloadable)) ceil(float);
-extern float2 __attribute__((overloadable)) ceil(float2);
-extern float3 __attribute__((overloadable)) ceil(float3);
-extern float4 __attribute__((overloadable)) ceil(float4);
-extern float8 __attribute__((overloadable)) ceil(float8);
-extern float16 __attribute__((overloadable)) ceil(float16);
+//extern float2 __attribute__((overloadable)) ceil(float2);
+//extern float3 __attribute__((overloadable)) ceil(float3);
+//extern float4 __attribute__((overloadable)) ceil(float4);
+//extern float8 __attribute__((overloadable)) ceil(float8);
+//extern float16 __attribute__((overloadable)) ceil(float16);
extern float __attribute__((overloadable)) clamp(float, float, float);
-extern float2 __attribute__((overloadable)) clamp(float2, float2, float2);
-extern float3 __attribute__((overloadable)) clamp(float3, float3, float3);
-extern float4 __attribute__((overloadable)) clamp(float4, float4, float4);
-extern float8 __attribute__((overloadable)) clamp(float8, float8, float8);
-extern float16 __attribute__((overloadable)) clamp(float16, float16, float16);
-extern float __attribute__((overloadable)) clamp(float, float, float);
-extern float2 __attribute__((overloadable)) clamp(float2, float, float);
-extern float3 __attribute__((overloadable)) clamp(float3, float, float);
-extern float4 __attribute__((overloadable)) clamp(float4, float, float);
-extern float8 __attribute__((overloadable)) clamp(float8, float, float);
-extern float16 __attribute__((overloadable)) clamp(float16, float, float);
+//extern float2 __attribute__((overloadable)) clamp(float2, float2, float2);
+//extern float3 __attribute__((overloadable)) clamp(float3, float3, float3);
+//extern float4 __attribute__((overloadable)) clamp(float4, float4, float4);
+//extern float8 __attribute__((overloadable)) clamp(float8, float8, float8);
+//extern float16 __attribute__((overloadable)) clamp(float16, float16, float16);
+//extern float2 __attribute__((overloadable)) clamp(float2, float, float);
+//extern float3 __attribute__((overloadable)) clamp(float3, float, float);
+//extern float4 __attribute__((overloadable)) clamp(float4, float, float);
+//extern float8 __attribute__((overloadable)) clamp(float8, float, float);
+//extern float16 __attribute__((overloadable)) clamp(float16, float, float);
extern float __attribute__((overloadable)) copysign(float, float);
-extern float2 __attribute__((overloadable)) copysign(float2, float2);
-extern float3 __attribute__((overloadable)) copysign(float3, float3);
-extern float4 __attribute__((overloadable)) copysign(float4, float4);
-extern float8 __attribute__((overloadable)) copysign(float8, float8);
-extern float16 __attribute__((overloadable)) copysign(float16, float16);
+//extern float2 __attribute__((overloadable)) copysign(float2, float2);
+//extern float3 __attribute__((overloadable)) copysign(float3, float3);
+//extern float4 __attribute__((overloadable)) copysign(float4, float4);
+//extern float8 __attribute__((overloadable)) copysign(float8, float8);
+//extern float16 __attribute__((overloadable)) copysign(float16, float16);
extern float __attribute__((overloadable)) cos(float);
-extern float2 __attribute__((overloadable)) cos(float2);
-extern float3 __attribute__((overloadable)) cos(float3);
-extern float4 __attribute__((overloadable)) cos(float4);
-extern float8 __attribute__((overloadable)) cos(float8);
-extern float16 __attribute__((overloadable)) cos(float16);
+//extern float2 __attribute__((overloadable)) cos(float2);
+//extern float3 __attribute__((overloadable)) cos(float3);
+//extern float4 __attribute__((overloadable)) cos(float4);
+//extern float8 __attribute__((overloadable)) cos(float8);
+//extern float16 __attribute__((overloadable)) cos(float16);
extern float __attribute__((overloadable)) degrees(float);
-extern float2 __attribute__((overloadable)) degrees(float2);
-extern float3 __attribute__((overloadable)) degrees(float3);
-extern float4 __attribute__((overloadable)) degrees(float4);
-extern float8 __attribute__((overloadable)) degrees(float8);
-extern float16 __attribute__((overloadable)) degrees(float16);
+//extern float2 __attribute__((overloadable)) degrees(float2);
+//extern float3 __attribute__((overloadable)) degrees(float3);
+//extern float4 __attribute__((overloadable)) degrees(float4);
+//extern float8 __attribute__((overloadable)) degrees(float8);
+//extern float16 __attribute__((overloadable)) degrees(float16);
extern float __attribute__((overloadable)) exp(float);
-extern float2 __attribute__((overloadable)) exp(float2);
-extern float3 __attribute__((overloadable)) exp(float3);
-extern float4 __attribute__((overloadable)) exp(float4);
-extern float8 __attribute__((overloadable)) exp(float8);
-extern float16 __attribute__((overloadable)) exp(float16);
+//extern float2 __attribute__((overloadable)) exp(float2);
+//extern float3 __attribute__((overloadable)) exp(float3);
+//extern float4 __attribute__((overloadable)) exp(float4);
+//extern float8 __attribute__((overloadable)) exp(float8);
+//extern float16 __attribute__((overloadable)) exp(float16);
extern float __attribute__((overloadable)) exp2(float);
-extern float2 __attribute__((overloadable)) exp2(float2);
-extern float3 __attribute__((overloadable)) exp2(float3);
-extern float4 __attribute__((overloadable)) exp2(float4);
-extern float8 __attribute__((overloadable)) exp2(float8);
-extern float16 __attribute__((overloadable)) exp2(float16);
+//extern float2 __attribute__((overloadable)) exp2(float2);
+//extern float3 __attribute__((overloadable)) exp2(float3);
+//extern float4 __attribute__((overloadable)) exp2(float4);
+//extern float8 __attribute__((overloadable)) exp2(float8);
+//extern float16 __attribute__((overloadable)) exp2(float16);
extern float __attribute__((overloadable)) exp10(float);
-extern float2 __attribute__((overloadable)) exp10(float2);
-extern float3 __attribute__((overloadable)) exp10(float3);
-extern float4 __attribute__((overloadable)) exp10(float4);
-extern float8 __attribute__((overloadable)) exp10(float8);
-extern float16 __attribute__((overloadable)) exp10(float16);
+//extern float2 __attribute__((overloadable)) exp10(float2);
+//extern float3 __attribute__((overloadable)) exp10(float3);
+//extern float4 __attribute__((overloadable)) exp10(float4);
+//extern float8 __attribute__((overloadable)) exp10(float8);
+//extern float16 __attribute__((overloadable)) exp10(float16);
extern float __attribute__((overloadable)) fabs(float);
-extern float2 __attribute__((overloadable)) fabs(float2);
-extern float3 __attribute__((overloadable)) fabs(float3);
-extern float4 __attribute__((overloadable)) fabs(float4);
-extern float8 __attribute__((overloadable)) fabs(float8);
-extern float16 __attribute__((overloadable)) fabs(float16);
+//extern float2 __attribute__((overloadable)) fabs(float2);
+//extern float3 __attribute__((overloadable)) fabs(float3);
+//extern float4 __attribute__((overloadable)) fabs(float4);
+//extern float8 __attribute__((overloadable)) fabs(float8);
+//extern float16 __attribute__((overloadable)) fabs(float16);
extern float __attribute__((overloadable)) floor(float);
-extern float2 __attribute__((overloadable)) floor(float2);
-extern float3 __attribute__((overloadable)) floor(float3);
-extern float4 __attribute__((overloadable)) floor(float4);
-extern float8 __attribute__((overloadable)) floor(float8);
-extern float16 __attribute__((overloadable)) floor(float16);
+//extern float2 __attribute__((overloadable)) floor(float2);
+//extern float3 __attribute__((overloadable)) floor(float3);
+//extern float4 __attribute__((overloadable)) floor(float4);
+//extern float8 __attribute__((overloadable)) floor(float8);
+//extern float16 __attribute__((overloadable)) floor(float16);
extern float __attribute__((overloadable)) fmax(float, float);
-extern float2 __attribute__((overloadable)) fmax(float2, float2);
-extern float3 __attribute__((overloadable)) fmax(float3, float3);
-extern float4 __attribute__((overloadable)) fmax(float4, float4);
-extern float8 __attribute__((overloadable)) fmax(float8, float8);
-extern float16 __attribute__((overloadable)) fmax(float16, float16);
-extern float2 __attribute__((overloadable)) fmax(float2, float);
-extern float3 __attribute__((overloadable)) fmax(float3, float);
-extern float4 __attribute__((overloadable)) fmax(float4, float);
-extern float8 __attribute__((overloadable)) fmax(float8, float);
-extern float16 __attribute__((overloadable)) fmax(float16, float);
+//extern float2 __attribute__((overloadable)) fmax(float2, float2);
+//extern float3 __attribute__((overloadable)) fmax(float3, float3);
+//extern float4 __attribute__((overloadable)) fmax(float4, float4);
+//extern float8 __attribute__((overloadable)) fmax(float8, float8);
+//extern float16 __attribute__((overloadable)) fmax(float16, float16);
+//extern float2 __attribute__((overloadable)) fmax(float2, float);
+//extern float3 __attribute__((overloadable)) fmax(float3, float);
+//extern float4 __attribute__((overloadable)) fmax(float4, float);
+//extern float8 __attribute__((overloadable)) fmax(float8, float);
+//extern float16 __attribute__((overloadable)) fmax(float16, float);
extern float __attribute__((overloadable)) fmin(float, float);
-extern float2 __attribute__((overloadable)) fmin(float2, float2);
-extern float3 __attribute__((overloadable)) fmin(float3, float3);
-extern float4 __attribute__((overloadable)) fmin(float4, float4);
-extern float8 __attribute__((overloadable)) fmin(float8, float8);
-extern float16 __attribute__((overloadable)) fmin(float16, float16);
-extern float2 __attribute__((overloadable)) fmin(float2, float);
-extern float3 __attribute__((overloadable)) fmin(float3, float);
-extern float4 __attribute__((overloadable)) fmin(float4, float);
-extern float8 __attribute__((overloadable)) fmin(float8, float);
-extern float16 __attribute__((overloadable)) fmin(float16, float);
+//extern float2 __attribute__((overloadable)) fmin(float2, float2);
+//extern float3 __attribute__((overloadable)) fmin(float3, float3);
+//extern float4 __attribute__((overloadable)) fmin(float4, float4);
+//extern float8 __attribute__((overloadable)) fmin(float8, float8);
+//extern float16 __attribute__((overloadable)) fmin(float16, float16);
+//extern float2 __attribute__((overloadable)) fmin(float2, float);
+//extern float3 __attribute__((overloadable)) fmin(float3, float);
+//extern float4 __attribute__((overloadable)) fmin(float4, float);
+//extern float8 __attribute__((overloadable)) fmin(float8, float);
+//extern float16 __attribute__((overloadable)) fmin(float16, float);
extern float __attribute__((overloadable)) fmod(float, float);
-extern float2 __attribute__((overloadable)) fmod(float2, float2);
-extern float3 __attribute__((overloadable)) fmod(float3, float3);
-extern float4 __attribute__((overloadable)) fmod(float4, float4);
-extern float8 __attribute__((overloadable)) fmod(float8, float8);
-extern float16 __attribute__((overloadable)) fmod(float16, float16);
+//extern float2 __attribute__((overloadable)) fmod(float2, float2);
+//extern float3 __attribute__((overloadable)) fmod(float3, float3);
+//extern float4 __attribute__((overloadable)) fmod(float4, float4);
+//extern float8 __attribute__((overloadable)) fmod(float8, float8);
+//extern float16 __attribute__((overloadable)) fmod(float16, float16);
extern float __attribute__((overloadable)) log(float);
-extern float2 __attribute__((overloadable)) log(float2);
-extern float3 __attribute__((overloadable)) log(float3);
-extern float4 __attribute__((overloadable)) log(float4);
-extern float8 __attribute__((overloadable)) log(float8);
-extern float16 __attribute__((overloadable)) log(float16);
+//extern float2 __attribute__((overloadable)) log(float2);
+//extern float3 __attribute__((overloadable)) log(float3);
+//extern float4 __attribute__((overloadable)) log(float4);
+//extern float8 __attribute__((overloadable)) log(float8);
+//extern float16 __attribute__((overloadable)) log(float16);
extern float __attribute__((overloadable)) log2(float);
-extern float2 __attribute__((overloadable)) log2(float2);
-extern float3 __attribute__((overloadable)) log2(float3);
-extern float4 __attribute__((overloadable)) log2(float4);
-extern float8 __attribute__((overloadable)) log2(float8);
-extern float16 __attribute__((overloadable)) log2(float16);
+//extern float2 __attribute__((overloadable)) log2(float2);
+//extern float3 __attribute__((overloadable)) log2(float3);
+//extern float4 __attribute__((overloadable)) log2(float4);
+//extern float8 __attribute__((overloadable)) log2(float8);
+//extern float16 __attribute__((overloadable)) log2(float16);
extern float __attribute__((overloadable)) log10(float);
-extern float2 __attribute__((overloadable)) log10(float2);
-extern float3 __attribute__((overloadable)) log10(float3);
-extern float4 __attribute__((overloadable)) log10(float4);
-extern float8 __attribute__((overloadable)) log10(float8);
-extern float16 __attribute__((overloadable)) log10(float16);
+//extern float2 __attribute__((overloadable)) log10(float2);
+//extern float3 __attribute__((overloadable)) log10(float3);
+//extern float4 __attribute__((overloadable)) log10(float4);
+//extern float8 __attribute__((overloadable)) log10(float8);
+//extern float16 __attribute__((overloadable)) log10(float16);
extern float __attribute__((overloadable)) max(float, float);
-extern float2 __attribute__((overloadable)) max(float2, float2);
-extern float3 __attribute__((overloadable)) max(float3, float3);
-extern float4 __attribute__((overloadable)) max(float4, float4);
-extern float8 __attribute__((overloadable)) max(float8, float8);
-extern float16 __attribute__((overloadable)) max(float16, float16);
+//extern float2 __attribute__((overloadable)) max(float2, float2);
+//extern float3 __attribute__((overloadable)) max(float3, float3);
+//extern float4 __attribute__((overloadable)) max(float4, float4);
+//extern float8 __attribute__((overloadable)) max(float8, float8);
+//extern float16 __attribute__((overloadable)) max(float16, float16);
extern float __attribute__((overloadable)) min(float, float);
-extern float2 __attribute__((overloadable)) min(float2, float2);
-extern float3 __attribute__((overloadable)) min(float3, float3);
-extern float4 __attribute__((overloadable)) min(float4, float4);
-extern float8 __attribute__((overloadable)) min(float8, float8);
-extern float16 __attribute__((overloadable)) min(float16, float16);
+//extern float2 __attribute__((overloadable)) min(float2, float2);
+//extern float3 __attribute__((overloadable)) min(float3, float3);
+//extern float4 __attribute__((overloadable)) min(float4, float4);
+//extern float8 __attribute__((overloadable)) min(float8, float8);
+//extern float16 __attribute__((overloadable)) min(float16, float16);
extern float __attribute__((overloadable)) mix(float, float, float);
-extern float2 __attribute__((overloadable)) mix(float2, float2, float2);
-extern float3 __attribute__((overloadable)) mix(float3, float3, float3);
-extern float4 __attribute__((overloadable)) mix(float4, float4, float4);
-extern float8 __attribute__((overloadable)) mix(float8, float8, float8);
-extern float16 __attribute__((overloadable)) mix(float16, float16, float16);
-extern float __attribute__((overloadable)) mix(float, float, float);
-extern float2 __attribute__((overloadable)) mix(float2, float2, float);
-extern float3 __attribute__((overloadable)) mix(float3, float3, float);
-extern float4 __attribute__((overloadable)) mix(float4, float4, float);
-extern float8 __attribute__((overloadable)) mix(float8, float8, float);
-extern float16 __attribute__((overloadable)) mix(float16, float16, float);
+//extern float2 __attribute__((overloadable)) mix(float2, float2, float2);
+//extern float3 __attribute__((overloadable)) mix(float3, float3, float3);
+//extern float4 __attribute__((overloadable)) mix(float4, float4, float4);
+//extern float8 __attribute__((overloadable)) mix(float8, float8, float8);
+//extern float16 __attribute__((overloadable)) mix(float16, float16, float16);
+//extern float2 __attribute__((overloadable)) mix(float2, float2, float);
+//extern float3 __attribute__((overloadable)) mix(float3, float3, float);
+//extern float4 __attribute__((overloadable)) mix(float4, float4, float);
+//extern float8 __attribute__((overloadable)) mix(float8, float8, float);
+//extern float16 __attribute__((overloadable)) mix(float16, float16, float);
extern float __attribute__((overloadable)) pow(float, float);
-extern float2 __attribute__((overloadable)) pow(float2, float2);
-extern float3 __attribute__((overloadable)) pow(float3, float3);
-extern float4 __attribute__((overloadable)) pow(float4, float4);
-extern float8 __attribute__((overloadable)) pow(float8, float8);
-extern float16 __attribute__((overloadable)) pow(float16, float16);
+//extern float2 __attribute__((overloadable)) pow(float2, float2);
+//extern float3 __attribute__((overloadable)) pow(float3, float3);
+//extern float4 __attribute__((overloadable)) pow(float4, float4);
+//extern float8 __attribute__((overloadable)) pow(float8, float8);
+//extern float16 __attribute__((overloadable)) pow(float16, float16);
extern float __attribute__((overloadable)) radians(float);
-extern float2 __attribute__((overloadable)) radians(float2);
-extern float3 __attribute__((overloadable)) radians(float3);
-extern float4 __attribute__((overloadable)) radians(float4);
-extern float8 __attribute__((overloadable)) radians(float8);
-extern float16 __attribute__((overloadable)) radians(float16);
+//extern float2 __attribute__((overloadable)) radians(float2);
+//extern float3 __attribute__((overloadable)) radians(float3);
+//extern float4 __attribute__((overloadable)) radians(float4);
+//extern float8 __attribute__((overloadable)) radians(float8);
+//extern float16 __attribute__((overloadable)) radians(float16);
extern float __attribute__((overloadable)) rint(float);
-extern float2 __attribute__((overloadable)) rint(float2);
-extern float3 __attribute__((overloadable)) rint(float3);
-extern float4 __attribute__((overloadable)) rint(float4);
-extern float8 __attribute__((overloadable)) rint(float8);
-extern float16 __attribute__((overloadable)) rint(float16);
+//extern float2 __attribute__((overloadable)) rint(float2);
+//extern float3 __attribute__((overloadable)) rint(float3);
+//extern float4 __attribute__((overloadable)) rint(float4);
+//extern float8 __attribute__((overloadable)) rint(float8);
+//extern float16 __attribute__((overloadable)) rint(float16);
extern float __attribute__((overloadable)) round(float);
-extern float2 __attribute__((overloadable)) round(float2);
-extern float3 __attribute__((overloadable)) round(float3);
-extern float4 __attribute__((overloadable)) round(float4);
-extern float8 __attribute__((overloadable)) round(float8);
-extern float16 __attribute__((overloadable)) round(float16);
+//extern float2 __attribute__((overloadable)) round(float2);
+//extern float3 __attribute__((overloadable)) round(float3);
+//extern float4 __attribute__((overloadable)) round(float4);
+//extern float8 __attribute__((overloadable)) round(float8);
+//extern float16 __attribute__((overloadable)) round(float16);
extern float __attribute__((overloadable)) rsqrt(float);
-extern float2 __attribute__((overloadable)) rsqrt(float2);
-extern float3 __attribute__((overloadable)) rsqrt(float3);
-extern float4 __attribute__((overloadable)) rsqrt(float4);
-extern float8 __attribute__((overloadable)) rsqrt(float8);
-extern float16 __attribute__((overloadable)) rsqrt(float16);
+//extern float2 __attribute__((overloadable)) rsqrt(float2);
+//extern float3 __attribute__((overloadable)) rsqrt(float3);
+//extern float4 __attribute__((overloadable)) rsqrt(float4);
+//extern float8 __attribute__((overloadable)) rsqrt(float8);
+//extern float16 __attribute__((overloadable)) rsqrt(float16);
extern float __attribute__((overloadable)) sign(float);
-extern float2 __attribute__((overloadable)) sign(float2);
-extern float3 __attribute__((overloadable)) sign(float3);
-extern float4 __attribute__((overloadable)) sign(float4);
-extern float8 __attribute__((overloadable)) sign(float8);
-extern float16 __attribute__((overloadable)) sign(float16);
+//extern float2 __attribute__((overloadable)) sign(float2);
+//extern float3 __attribute__((overloadable)) sign(float3);
+//extern float4 __attribute__((overloadable)) sign(float4);
+//extern float8 __attribute__((overloadable)) sign(float8);
+//extern float16 __attribute__((overloadable)) sign(float16);
extern float __attribute__((overloadable)) sin(float);
-extern float2 __attribute__((overloadable)) sin(float2);
-extern float3 __attribute__((overloadable)) sin(float3);
-extern float4 __attribute__((overloadable)) sin(float4);
-extern float8 __attribute__((overloadable)) sin(float8);
-extern float16 __attribute__((overloadable)) sin(float16);
+//extern float2 __attribute__((overloadable)) sin(float2);
+//extern float3 __attribute__((overloadable)) sin(float3);
+//extern float4 __attribute__((overloadable)) sin(float4);
+//extern float8 __attribute__((overloadable)) sin(float8);
+//extern float16 __attribute__((overloadable)) sin(float16);
extern float __attribute__((overloadable)) sqrt(float);
-extern float2 __attribute__((overloadable)) sqrt(float2);
-extern float3 __attribute__((overloadable)) sqrt(float3);
-extern float4 __attribute__((overloadable)) sqrt(float4);
-extern float8 __attribute__((overloadable)) sqrt(float8);
-extern float16 __attribute__((overloadable)) sqrt(float16);
+//extern float2 __attribute__((overloadable)) sqrt(float2);
+//extern float3 __attribute__((overloadable)) sqrt(float3);
+//extern float4 __attribute__((overloadable)) sqrt(float4);
+//extern float8 __attribute__((overloadable)) sqrt(float8);
+//extern float16 __attribute__((overloadable)) sqrt(float16);
extern float __attribute__((overloadable)) tan(float);
-extern float2 __attribute__((overloadable)) tan(float2);
-extern float3 __attribute__((overloadable)) tan(float3);
-extern float4 __attribute__((overloadable)) tan(float4);
-extern float8 __attribute__((overloadable)) tan(float8);
-extern float16 __attribute__((overloadable)) tan(float16);
+//extern float2 __attribute__((overloadable)) tan(float2);
+//extern float3 __attribute__((overloadable)) tan(float3);
+//extern float4 __attribute__((overloadable)) tan(float4);
+//extern float8 __attribute__((overloadable)) tan(float8);
+//extern float16 __attribute__((overloadable)) tan(float16);
extern float __attribute__((overloadable)) trunc(float);
-extern float2 __attribute__((overloadable)) trunc(float2);
-extern float3 __attribute__((overloadable)) trunc(float3);
-extern float4 __attribute__((overloadable)) trunc(float4);
-extern float8 __attribute__((overloadable)) trunc(float8);
-extern float16 __attribute__((overloadable)) trunc(float16);
+//extern float2 __attribute__((overloadable)) trunc(float2);
+//extern float3 __attribute__((overloadable)) trunc(float3);
+//extern float4 __attribute__((overloadable)) trunc(float4);
+//extern float8 __attribute__((overloadable)) trunc(float8);
+//extern float16 __attribute__((overloadable)) trunc(float16);
@@ -268,11 +266,11 @@
// Int ops
extern int __attribute__((overloadable)) abs(int);
-extern int2 __attribute__((overloadable)) abs(int2);
-extern int3 __attribute__((overloadable)) abs(int3);
-extern int4 __attribute__((overloadable)) abs(int4);
-extern int8 __attribute__((overloadable)) abs(int8);
-extern int16 __attribute__((overloadable)) abs(int16);
+//extern int2 __attribute__((overloadable)) abs(int2);
+//extern int3 __attribute__((overloadable)) abs(int3);
+//extern int4 __attribute__((overloadable)) abs(int4);
+//extern int8 __attribute__((overloadable)) abs(int8);
+//extern int16 __attribute__((overloadable)) abs(int16);
diff --git a/libs/rs/scriptc/rs_types.rsh b/libs/rs/scriptc/rs_types.rsh
index 4198a74..b710146 100644
--- a/libs/rs/scriptc/rs_types.rsh
+++ b/libs/rs/scriptc/rs_types.rsh
@@ -68,4 +68,29 @@
typedef int int16 __attribute__((ext_vector_type(16)));
+// RS_KIND_POSITION
+typedef float rs_position1;
+typedef float2 rs_position2;
+typedef float3 rs_position3;
+typedef float4 rs_position4;
+
+// RS_KIND_COLOR
+typedef float3 rs_color3f;
+typedef float4 rs_color4f;
+typedef uchar4 rs_color4u;
+
+// RS_KIND_NORMAL
+typedef float3 rs_normal;
+
+// RS_KIND_POINT_SIZE
+typedef float rs_point_size;
+
+// RS_KIND_TEXTURE
+typedef float rs_texture_coord1;
+typedef float2 rs_texture_coord2;
+typedef float3 rs_texture_coord3;
+typedef float4 rs_texture_coord4;
+
+// RS_KIND_INDEX
+typedef ushort rs_index;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 47e2da2..2e3eae0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1523,4 +1523,22 @@
* {@hide}
*/
private IBinder mICallBack = new Binder();
+
+ /**
+ * Checks whether the phone is in silent mode, with or without vibrate.
+ *
+ * @return true if phone is in silent mode, with or without vibrate.
+ *
+ * @see #getRingerMode()
+ *
+ * @hide pending API Council approval
+ */
+ public boolean isSilentMode() {
+ int ringerMode = getRingerMode();
+ boolean silentMode =
+ (ringerMode == RINGER_MODE_SILENT) ||
+ (ringerMode == RINGER_MODE_VIBRATE);
+ return silentMode;
+ }
+
}
diff --git a/opengl/tests/testViewport/Android.mk b/opengl/tests/testViewport/Android.mk
new file mode 100644
index 0000000..3da59b5
--- /dev/null
+++ b/opengl/tests/testViewport/Android.mk
@@ -0,0 +1,22 @@
+#########################################################################
+# OpenGL ES JNI sample
+# This makefile builds both an activity and a shared library.
+#########################################################################
+ifneq ($(TARGET_SIMULATOR),true) # not 64 bit clean
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build activity
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TestViewport
+
+include $(BUILD_PACKAGE)
+
+endif # TARGET_SIMULATOR
diff --git a/opengl/tests/testViewport/AndroidManifest.xml b/opengl/tests/testViewport/AndroidManifest.xml
new file mode 100644
index 0000000..f4a493e
--- /dev/null
+++ b/opengl/tests/testViewport/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, 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.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test">
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <application
+ android:label="@string/test_activity">
+ <activity android:name="TestActivity"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+ android:configChanges="orientation|keyboardHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/opengl/tests/testViewport/README b/opengl/tests/testViewport/README
new file mode 100644
index 0000000..c06abc9
--- /dev/null
+++ b/opengl/tests/testViewport/README
@@ -0,0 +1,28 @@
+Repro steps:
+
+build, install and run the attached test program TestViewport.apk
+
+Run on Sapphire with Froyo.
+
+The program clears the screen to blue, then draws a full screen white quad that
+is alligned to the screen.
+(Therefore the whole screen should appear to be white.)
+
+
+Note that screen is all white.
+
+Rotate screen 90 degrees.
+
+Expected: screen is still all white.
+
+Actual: screen is blue with offset white rectangle.
+
+This bug only happens on Sapphire, it works correctly on Passion.
+
+What happens:
+
+I think the bug is that the gl.glViewport() call in onSurfaceChanged() is
+being ignored by the OpenGL driver.
+
+NOTE: If a gl.glViewport call is added at the beginning of the onDrawFrame()
+call (which means it is called before every draw), the program runs correctly.
diff --git a/opengl/tests/testViewport/res/values/strings.xml b/opengl/tests/testViewport/res/values/strings.xml
new file mode 100644
index 0000000..f4b8bbb
--- /dev/null
+++ b/opengl/tests/testViewport/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2006, 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.
+*/
+-->
+
+<!-- This file contains resource definitions for displayed strings, allowing
+ them to be changed based on the locale and options. -->
+
+<resources>
+ <!-- Simple strings. -->
+ <string name="test_activity">Test Viewport</string>
+
+</resources>
+
diff --git a/opengl/tests/testViewport/src/com/android/test/TestActivity.java b/opengl/tests/testViewport/src/com/android/test/TestActivity.java
new file mode 100644
index 0000000..cc7e450
--- /dev/null
+++ b/opengl/tests/testViewport/src/com/android/test/TestActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007 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 com.android.test;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestActivity extends Activity {
+ private final static String TAG = "TestActivity";
+ TestView mView;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mView = new TestView(getApplication());
+ mView.setFocusableInTouchMode(true);
+ setContentView(mView);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mView.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mView.onResume();
+ }
+}
diff --git a/opengl/tests/testViewport/src/com/android/test/TestView.java b/opengl/tests/testViewport/src/com/android/test/TestView.java
new file mode 100644
index 0000000..23cc37d
--- /dev/null
+++ b/opengl/tests/testViewport/src/com/android/test/TestView.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2009 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 com.android.test;
+/*
+ * Copyright (C) 2008 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.
+ */
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.FloatBuffer;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying an OpenGL animation. This allows the animation to run in a
+ * separate thread, without requiring that it be driven by the update mechanism
+ * of the view hierarchy.
+ *
+ * The application-specific rendering code is delegated to a GLView.Renderer
+ * instance.
+ */
+class TestView extends GLSurfaceView {
+ TestView(Context context) {
+ super(context);
+ init();
+ }
+
+ public TestView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ setRenderer(new Renderer());
+ setRenderMode(RENDERMODE_WHEN_DIRTY);
+ }
+
+ /** A grid is a topologically rectangular array of vertices.
+ *
+ * The vertex and index data are held in VBO objects because on most
+ * GPUs VBO objects are the fastest way of rendering static vertex
+ * and index data.
+ *
+ */
+
+ private static class Grid {
+ // Size of vertex data elements in bytes:
+ final static int FLOAT_SIZE = 4;
+ final static int CHAR_SIZE = 2;
+
+ // Vertex structure:
+ // float x, y, z;
+
+ final static int VERTEX_SIZE = 3 * FLOAT_SIZE;
+
+ private int mVertexBufferObjectId;
+ private int mElementBufferObjectId;
+
+ // These buffers are used to hold the vertex and index data while
+ // constructing the grid. Once createBufferObjects() is called
+ // the buffers are nulled out to save memory.
+
+ private ByteBuffer mVertexByteBuffer;
+ private FloatBuffer mVertexBuffer;
+ private CharBuffer mIndexBuffer;
+
+ private int mW;
+ private int mH;
+ private int mIndexCount;
+
+ public Grid(int w, int h) {
+ if (w < 0 || w >= 65536) {
+ throw new IllegalArgumentException("w");
+ }
+ if (h < 0 || h >= 65536) {
+ throw new IllegalArgumentException("h");
+ }
+ if (w * h >= 65536) {
+ throw new IllegalArgumentException("w * h >= 65536");
+ }
+
+ mW = w;
+ mH = h;
+ int size = w * h;
+
+ mVertexByteBuffer = ByteBuffer.allocateDirect(VERTEX_SIZE * size)
+ .order(ByteOrder.nativeOrder());
+ mVertexBuffer = mVertexByteBuffer.asFloatBuffer();
+
+ int quadW = mW - 1;
+ int quadH = mH - 1;
+ int quadCount = quadW * quadH;
+ int indexCount = quadCount * 6;
+ mIndexCount = indexCount;
+ mIndexBuffer = ByteBuffer.allocateDirect(CHAR_SIZE * indexCount)
+ .order(ByteOrder.nativeOrder()).asCharBuffer();
+
+ /*
+ * Initialize triangle list mesh.
+ *
+ * [0]-----[ 1] ...
+ * | / |
+ * | / |
+ * | / |
+ * [w]-----[w+1] ...
+ * | |
+ *
+ */
+
+ {
+ int i = 0;
+ for (int y = 0; y < quadH; y++) {
+ for (int x = 0; x < quadW; x++) {
+ char a = (char) (y * mW + x);
+ char b = (char) (y * mW + x + 1);
+ char c = (char) ((y + 1) * mW + x);
+ char d = (char) ((y + 1) * mW + x + 1);
+
+ mIndexBuffer.put(i++, a);
+ mIndexBuffer.put(i++, c);
+ mIndexBuffer.put(i++, b);
+
+ mIndexBuffer.put(i++, b);
+ mIndexBuffer.put(i++, c);
+ mIndexBuffer.put(i++, d);
+ }
+ }
+ }
+
+ }
+
+ public void set(int i, int j, float x, float y, float z) {
+ if (i < 0 || i >= mW) {
+ throw new IllegalArgumentException("i");
+ }
+ if (j < 0 || j >= mH) {
+ throw new IllegalArgumentException("j");
+ }
+
+ int index = mW * j + i;
+
+ mVertexBuffer.position(index * VERTEX_SIZE / FLOAT_SIZE);
+ mVertexBuffer.put(x);
+ mVertexBuffer.put(y);
+ mVertexBuffer.put(z);
+ }
+
+ public void createBufferObjects(GL gl) {
+ // Generate a the vertex and element buffer IDs
+ int[] vboIds = new int[2];
+ GL11 gl11 = (GL11) gl;
+ gl11.glGenBuffers(2, vboIds, 0);
+ mVertexBufferObjectId = vboIds[0];
+ mElementBufferObjectId = vboIds[1];
+
+ // Upload the vertex data
+ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
+ mVertexByteBuffer.position(0);
+ gl11.glBufferData(GL11.GL_ARRAY_BUFFER, mVertexByteBuffer.capacity(), mVertexByteBuffer, GL11.GL_STATIC_DRAW);
+
+ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
+ mIndexBuffer.position(0);
+ gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer.capacity() * CHAR_SIZE, mIndexBuffer, GL11.GL_STATIC_DRAW);
+
+ // We don't need the in-memory data any more
+ mVertexBuffer = null;
+ mVertexByteBuffer = null;
+ mIndexBuffer = null;
+ }
+
+ public void draw(GL10 gl) {
+ GL11 gl11 = (GL11) gl;
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+
+ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
+ gl11.glVertexPointer(3, GL10.GL_FLOAT, VERTEX_SIZE, 0);
+
+ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
+ gl11.glDrawElements(GL10.GL_TRIANGLES, mIndexCount, GL10.GL_UNSIGNED_SHORT, 0);
+ gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
+ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
+ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+ }
+
+
+ private class Renderer implements GLSurfaceView.Renderer {
+ private static final String TAG = "Renderer";
+ private Grid mGrid;
+
+ public void onDrawFrame(GL10 gl) {
+ gl.glClearColor(0,0,1,1);
+ gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
+ mGrid.draw(gl);
+ }
+
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ gl.glViewport(0, 0, width, height);
+ gl.glMatrixMode(GL11.GL_PROJECTION);
+ gl.glLoadIdentity();
+ gl.glOrthof(0, width, height, 0, -1, 1);
+ gl.glMatrixMode(GL11.GL_MODELVIEW);
+ createGrid(gl, width, height);
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ }
+
+ private void createGrid(GL10 gl, float w, float h) {
+ mGrid = new Grid(2, 2);
+ for (int j = 0; j < 2; j++) {
+ for (int i = 0; i < 2; i++) {
+ float x = w * i;
+ float y = h * j;
+ float z = 0.0f;
+ mGrid.set(i,j, x, y, z);
+ }
+ }
+ mGrid.createBufferObjects(gl);
+ }
+ }
+}
+
diff --git a/policy/com/android/internal/policy/impl/LockScreen.java b/policy/com/android/internal/policy/impl/LockScreen.java
index a5ef1fa..b3707b0 100644
--- a/policy/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/com/android/internal/policy/impl/LockScreen.java
@@ -220,7 +220,6 @@
}
});
-
setFocusable(true);
setFocusableInTouchMode(true);
setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
@@ -518,7 +517,7 @@
mScreenLocked.setText("");
// layout
- mScreenLocked.setVisibility(View.VISIBLE);
+ mScreenLocked.setVisibility(View.INVISIBLE);
mSelector.setVisibility(View.VISIBLE);
mEmergencyCallText.setVisibility(View.GONE);
break;
@@ -658,7 +657,6 @@
/** {@inheritDoc} */
public void onResume() {
resetStatusInfo(mUpdateMonitor);
- mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
}
/** {@inheritDoc} */
@@ -676,6 +674,5 @@
}
public void onPhoneStateChanged(String newState) {
- mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
}
}
diff --git a/services/java/Android.mk b/services/java/Android.mk
index 934712c..c756d29 100644
--- a/services/java/Android.mk
+++ b/services/java/Android.mk
@@ -13,7 +13,9 @@
LOCAL_JAVA_LIBRARIES := android.policy
+LOCAL_NO_EMMA_INSTRUMENT := true
+LOCAL_NO_EMMA_COMPILE := true
+
include $(BUILD_JAVA_LIBRARY)
include $(BUILD_DROIDDOC)
-
diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java
index 87de79a..83ce3e3 100644
--- a/services/java/com/android/server/AccessibilityManagerService.java
+++ b/services/java/com/android/server/AccessibilityManagerService.java
@@ -269,14 +269,10 @@
});
}
- public void addClient(IAccessibilityManagerClient client) {
+ public boolean addClient(IAccessibilityManagerClient client) {
synchronized (mLock) {
- try {
- client.setEnabled(mIsEnabled);
- mClients.add(client);
- } catch (RemoteException re) {
- Slog.w(LOG_TAG, "Dead AccessibilityManagerClient: " + client, re);
- }
+ mClients.add(client);
+ return mIsEnabled;
}
}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index f9c1a93..b92480f 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -462,10 +462,11 @@
mEnabledProviders.add(passiveProvider.getName());
// initialize external network location and geocoder services
+ PackageManager pm = mContext. getPackageManager();
Resources resources = mContext.getResources();
String serviceName = resources.getString(
com.android.internal.R.string.config_networkLocationProvider);
- if (serviceName != null) {
+ if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
mNetworkLocationProvider =
new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
serviceName, mLocationHandler);
@@ -473,7 +474,7 @@
}
serviceName = resources.getString(com.android.internal.R.string.config_geocodeProvider);
- if (serviceName != null) {
+ if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
mGeocodeProvider = new GeocoderProxy(mContext, serviceName);
}
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 205e308..02ba36d 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -148,7 +148,6 @@
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_REORDER = false;
static final boolean DEBUG_WALLPAPER = false;
- static final boolean DEBUG_FREEZE = false;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean HIDE_STACK_CRAWLS = true;
static final boolean MEASURE_LATENCY = false;
@@ -4419,8 +4418,7 @@
final int N = mWindows.size();
for (int i=0; i<N; i++) {
WindowState w = (WindowState)mWindows.get(i);
- if (w.isVisibleLw() && !w.mObscured
- && (w.mOrientationChanging || !w.isDrawnLw())) {
+ if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
return;
}
}
@@ -7929,7 +7927,7 @@
final AppWindowToken atoken = mAppToken;
return mSurface != null && !mAttachedHidden
&& (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
- && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending))
+ && !mDrawPending && !mCommitDrawPending
&& !mExiting && !mDestroying;
}
@@ -8033,14 +8031,12 @@
/**
* Returns true if the window has a surface that it has drawn a
- * complete UI in to. Note that this returns true if the orientation
- * is changing even if the window hasn't redrawn because we don't want
- * to stop things from executing during that time.
+ * complete UI in to.
*/
public boolean isDrawnLw() {
final AppWindowToken atoken = mAppToken;
return mSurface != null && !mDestroying
- && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending));
+ && !mDrawPending && !mCommitDrawPending;
}
public boolean fillsScreenLw(int screenWidth, int screenHeight,
@@ -10298,12 +10294,6 @@
if (w.mAttachedHidden || !w.isReadyForDisplay()) {
if (!w.mLastHidden) {
//dump();
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Window hiding: waitingToShow="
- + w.mRootToken.waitingToShow + " polvis="
- + w.mPolicyVisibility + " atthid="
- + w.mAttachedHidden + " tokhid="
- + w.mRootToken.hidden + " vis="
- + w.mViewVisibility);
w.mLastHidden = true;
if (SHOW_TRANSACTIONS) logSurface(w,
"HIDE (performLayout)", null);
@@ -10699,28 +10689,23 @@
} else if (animating) {
requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis());
}
-
- if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen
- + " holdScreen=" + holdScreen);
- if (!mDisplayFrozen) {
- mQueue.setHoldScreenLocked(holdScreen != null);
- if (screenBrightness < 0 || screenBrightness > 1.0f) {
- mPowerManager.setScreenBrightnessOverride(-1);
- } else {
- mPowerManager.setScreenBrightnessOverride((int)
- (screenBrightness * Power.BRIGHTNESS_ON));
- }
- if (buttonBrightness < 0 || buttonBrightness > 1.0f) {
- mPowerManager.setButtonBrightnessOverride(-1);
- } else {
- mPowerManager.setButtonBrightnessOverride((int)
- (buttonBrightness * Power.BRIGHTNESS_ON));
- }
- if (holdScreen != mHoldingScreenOn) {
- mHoldingScreenOn = holdScreen;
- Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen);
- mH.sendMessage(m);
- }
+ mQueue.setHoldScreenLocked(holdScreen != null);
+ if (screenBrightness < 0 || screenBrightness > 1.0f) {
+ mPowerManager.setScreenBrightnessOverride(-1);
+ } else {
+ mPowerManager.setScreenBrightnessOverride((int)
+ (screenBrightness * Power.BRIGHTNESS_ON));
+ }
+ if (buttonBrightness < 0 || buttonBrightness > 1.0f) {
+ mPowerManager.setButtonBrightnessOverride(-1);
+ } else {
+ mPowerManager.setButtonBrightnessOverride((int)
+ (buttonBrightness * Power.BRIGHTNESS_ON));
+ }
+ if (holdScreen != mHoldingScreenOn) {
+ mHoldingScreenOn = holdScreen;
+ Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen);
+ mH.sendMessage(m);
}
if (mTurnOnScreen) {
@@ -10997,8 +10982,6 @@
mFreezeGcPending = now;
}
- if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException());
-
mDisplayFrozen = true;
if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
@@ -11022,8 +11005,6 @@
return;
}
- if (DEBUG_FREEZE) Slog.v(TAG, "*** UNFREEZING DISPLAY", new RuntimeException());
-
mDisplayFrozen = false;
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
if (PROFILE_ORIENTATION) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 8383ca3..e9e3866 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -6887,8 +6887,6 @@
enforceCallingPermission(android.Manifest.permission.GET_TASKS,
"getRecentTasks()");
- IPackageManager pm = ActivityThread.getPackageManager();
-
final int N = mRecentTasks.size();
ArrayList<ActivityManager.RecentTaskInfo> res
= new ArrayList<ActivityManager.RecentTaskInfo>(
@@ -6905,25 +6903,6 @@
rti.baseIntent = new Intent(
tr.intent != null ? tr.intent : tr.affinityIntent);
rti.origActivity = tr.origActivity;
-
- if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) {
- // Check whether this activity is currently available.
- try {
- if (rti.origActivity != null) {
- if (pm.getActivityInfo(rti.origActivity, 0) == null) {
- continue;
- }
- } else if (rti.baseIntent != null) {
- if (pm.queryIntentActivities(rti.baseIntent,
- null, 0) == null) {
- continue;
- }
- }
- } catch (RemoteException e) {
- // Will never happen.
- }
- }
-
res.add(rti);
maxNum--;
}
diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
index afd6ca8..28cefd4 100755
--- a/services/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -276,9 +276,9 @@
sAGpsInterface->init(&sAGpsCallbacks);
if (!sGpsNiInterface)
- sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+ sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
if (sGpsNiInterface)
- sGpsNiInterface->init(&sGpsNiCallbacks);
+ sGpsNiInterface->init(&sGpsNiCallbacks);
if (!sGpsDebugInterface)
sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
@@ -533,12 +533,10 @@
static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
jint notifId, jint response)
{
- if (!sGpsNiInterface) {
+ if (!sGpsNiInterface)
sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
- }
- if (sGpsNiInterface) {
+ if (sGpsNiInterface)
sGpsNiInterface->respond(notifId, response);
- }
}
static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj)
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 32e7176..089a84b 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -747,10 +747,10 @@
if (prependPlus) {
// This is an "international number" and should have
// a plus prepended to the dialing number. But there
- // can also be Gsm MMI codes as defined in TS 22.030 6.5.2
+ // can also be GSM MMI codes as defined in TS 22.030 6.5.2
// so we need to handle those also.
//
- // http://web.telia.com/~u47904776/gsmkode.htm is a
+ // http://web.telia.com/~u47904776/gsmkode.htm
// has a nice list of some of these GSM codes.
//
// Examples are:
@@ -838,10 +838,10 @@
// FIXME(mkf) TS 23.040 9.1.2.3 says
// "if a mobile receives 1111 in a position prior to
- // the last semi-octet then processing shall commense with
+ // the last semi-octet then processing shall commence with
// the next semi-octet and the intervening
// semi-octet shall be ignored"
- // How does this jive with 24,008 10.5.4.7
+ // How does this jive with 24.008 10.5.4.7
b = (byte)((bytes[i] >> 4) & 0xf);
@@ -972,7 +972,7 @@
* Convert a dialing number to BCD byte array
*
* @param number dialing number string
- * if the dialing number starts with '+', set to internationl TOA
+ * if the dialing number starts with '+', set to international TOA
* @return BCD byte array
*/
public static byte[]
@@ -1076,10 +1076,10 @@
*
* @param source the phone number to format
* @param defaultFormattingType The default formatting rules to apply if the number does
- * not begin with +<country_code>
+ * not begin with +[country_code]
* @return The phone number formatted with the given formatting type.
*
- * @hide TODO:Shuold be unhidden.
+ * @hide TODO: Should be unhidden.
*/
public static String formatNumber(String source, int defaultFormattingType) {
SpannableStringBuilder text = new SpannableStringBuilder(source);
@@ -1106,7 +1106,7 @@
*
* @param text The number to be formatted, will be modified with the formatting
* @param defaultFormattingType The default formatting rules to apply if the number does
- * not begin with +<country_code>
+ * not begin with +[country_code]
*/
public static void formatNumber(Editable text, int defaultFormattingType) {
int formatType = defaultFormattingType;
@@ -1502,7 +1502,7 @@
* @hide
*/
public static String
- cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormt) {
+ cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat) {
String retStr = dialStr;
// Checks if the plus sign character is in the passed-in dial string
@@ -1510,7 +1510,7 @@
dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) {
// Format the string based on the rules for the country the number is from,
// and the current country the phone is camped on.
- if ((currFormat == defaultFormt) && (currFormat == FORMAT_NANP)) {
+ if ((currFormat == defaultFormat) && (currFormat == FORMAT_NANP)) {
// Handle case where default and current telephone numbering plans are NANP.
String postDialStr = null;
String tempDialStr = dialStr;
@@ -1688,7 +1688,7 @@
return -1;
}
- // This function appends the non-diablable P/W character to the original
+ // This function appends the non-dialable P/W character to the original
// dial string based on the dialable index passed in
private static String
appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) {
@@ -1708,7 +1708,7 @@
return retStr;
}
- //===== Begining of utility methods used in compareLoosely() =====
+ //===== Beginning of utility methods used in compareLoosely() =====
/**
* Phone numbers are stored in "lookup" form in the database
@@ -1830,12 +1830,12 @@
//===== End of utility methods used only in compareLoosely() =====
- //===== Beggining of utility methods used only in compareStrictly() ====
+ //===== Beginning of utility methods used only in compareStrictly() ====
/*
* If true, the number is country calling code.
*/
- private static final boolean COUNTLY_CALLING_CALL[] = {
+ private static final boolean COUNTRY_CALLING_CALL[] = {
true, true, false, false, false, false, false, true, false, false,
false, false, false, false, false, false, false, false, false, false,
true, false, false, false, false, false, false, true, true, false,
@@ -1847,18 +1847,18 @@
false, true, true, true, true, false, true, false, false, true,
true, true, true, true, true, true, false, false, true, false,
};
- private static final int CCC_LENGTH = COUNTLY_CALLING_CALL.length;
+ private static final int CCC_LENGTH = COUNTRY_CALLING_CALL.length;
/**
* @return true when input is valid Country Calling Code.
*/
private static boolean isCountryCallingCode(int countryCallingCodeCandidate) {
return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH &&
- COUNTLY_CALLING_CALL[countryCallingCodeCandidate];
+ COUNTRY_CALLING_CALL[countryCallingCodeCandidate];
}
/**
- * Returns interger corresponding to the input if input "ch" is
+ * Returns integer corresponding to the input if input "ch" is
* ISO-LATIN characters 0-9.
* Returns -1 otherwise
*/
@@ -1993,7 +1993,7 @@
/**
* Return true if the prefix of "str" is "ignorable". Here, "ignorable" means
- * that "str" has only one digit and separater characters. The one digit is
+ * that "str" has only one digit and separator characters. The one digit is
* assumed to be trunk prefix.
*/
private static boolean checkPrefixIsIgnorable(final String str,
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 3122722..2a969bb 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -21,7 +21,6 @@
import android.os.ServiceManager;
import android.text.TextUtils;
-import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.ISms;
import com.android.internal.telephony.IccConstants;
import com.android.internal.telephony.SmsRawData;
@@ -33,7 +32,7 @@
/*
* TODO(code review): Curious question... Why are a lot of these
* methods not declared as static, since they do not seem to require
- * any local object state? Assumedly this cannot be changed without
+ * any local object state? Presumably this cannot be changed without
* interfering with the API...
*/
@@ -42,7 +41,8 @@
* Get this object by calling the static method SmsManager.getDefault().
*/
public final class SmsManager {
- private static SmsManager sInstance;
+ /** Singleton object constructed during class initialization. */
+ private static final SmsManager sInstance = new SmsManager();
/**
* Send a text based SMS.
@@ -52,8 +52,8 @@
* the current default SMSC
* @param text the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
- * broadcast when the message is sucessfully sent, or failed.
- * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
@@ -116,7 +116,7 @@
* @param sentIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been sent.
- * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
@@ -125,7 +125,7 @@
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
- * is NULL the caller will be checked against all unknown applicaitons,
+ * is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
@@ -178,8 +178,8 @@
* @param destinationPort the port to deliver the message to
* @param data the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
- * broadcast when the message is sucessfully sent, or failed.
- * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
@@ -188,7 +188,7 @@
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
- * is NULL the caller will be checked against all unknown applicaitons,
+ * is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is delivered to the recipient. The
@@ -224,9 +224,6 @@
* @return the default instance of the SmsManager
*/
public static SmsManager getDefault() {
- if (sInstance == null) {
- sInstance = new SmsManager();
- }
return sInstance;
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index a284ea5..d899430 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -20,7 +20,6 @@
import android.util.Log;
import com.android.internal.telephony.GsmAlphabet;
-import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java
index d90c305..9055203 100644
--- a/telephony/java/com/android/internal/telephony/CommandsInterface.java
+++ b/telephony/java/com/android/internal/telephony/CommandsInterface.java
@@ -27,8 +27,8 @@
*/
public interface CommandsInterface {
enum RadioState {
- RADIO_OFF, /* Radio explictly powered off (eg CFUN=0) */
- RADIO_UNAVAILABLE, /* Radio unavailable (eg, resetting or not booted) */
+ RADIO_OFF, /* Radio explicitly powered off (e.g. CFUN=0) */
+ RADIO_UNAVAILABLE, /* Radio unavailable (e.g. resetting or not booted) */
SIM_NOT_READY, /* Radio is on, but the SIM interface is not ready */
SIM_LOCKED_OR_ABSENT, /* SIM PIN locked, PUK required, network
personalization, or SIM absent */
@@ -121,7 +121,7 @@
// See 27.007 +CCFC or +CLCK
static final int SERVICE_CLASS_NONE = 0; // no user input
static final int SERVICE_CLASS_VOICE = (1 << 0);
- static final int SERVICE_CLASS_DATA = (1 << 1); //synoym for 16+32+64+128
+ static final int SERVICE_CLASS_DATA = (1 << 1); //synonym for 16+32+64+128
static final int SERVICE_CLASS_FAX = (1 << 2);
static final int SERVICE_CLASS_SMS = (1 << 3);
static final int SERVICE_CLASS_DATA_SYNC = (1 << 4);
@@ -939,19 +939,19 @@
void writeSmsToRuim(int status, String pdu, Message response);
/**
- * @deprecated
* @param apn
* @param user
* @param password
* @param response
*/
+ @Deprecated
void setupDefaultPDP(String apn, String user, String password, Message response);
/**
- * @deprecated
* @param cid
* @param response
*/
+ @Deprecated
void deactivateDefaultPDP(int cid, Message response);
void setRadioPower(boolean on, Message response);
@@ -961,7 +961,7 @@
void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message response);
/**
- * parameters equivilient to 27.007 AT+CRSM command
+ * parameters equivalent to 27.007 AT+CRSM command
* response.obj will be an AsyncResult
* response.obj.userObj will be a IccIoResult on success
*/
@@ -1066,7 +1066,7 @@
/**
* (AsyncResult)response.obj).result will be an Integer representing
- * the sum of enabled serivice classes (sum of SERVICE_CLASS_*)
+ * the sum of enabled service classes (sum of SERVICE_CLASS_*)
*
* @param facility one of CB_FACILTY_*
* @param password password or "" if not required
@@ -1139,7 +1139,7 @@
/**
* Request to enable/disable network state change notifications when
- * location informateion (lac and/or cid) has changed.
+ * location information (lac and/or cid) has changed.
*
* @param enable true to enable, false to disable
* @param response callback message
@@ -1170,7 +1170,7 @@
/**
* Indicates to the vendor ril that StkService is running
- * rand is eady to receive RIL_UNSOL_STK_XXXX commands.
+ * and is ready to receive RIL_UNSOL_STK_XXXX commands.
*
* @param result callback message
*/
diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
index ebdd220..cebcf5d 100644
--- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
@@ -16,14 +16,13 @@
package com.android.internal.telephony;
-import android.telephony.SmsMessage;
import android.util.SparseIntArray;
import android.util.Log;
/**
* This class implements the character set mapping between
- * the GSM SMS 7-bit alphabet specifed in TS 23.038 6.2.1
+ * the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1
* and UTF-16
*
* {@hide}
@@ -171,7 +170,7 @@
* array cannot contain more than 255 septets.
*
* @param data The text string to encode.
- * @param header Optional header (includeing length byte) that precedes
+ * @param header Optional header (including length byte) that precedes
* the encoded data, padded to septet boundary.
* @return Byte array containing header and encoded data.
*/
@@ -204,7 +203,7 @@
* the packed septets. The returned array cannot contain more than 255
* septets.
*
- * @param data the data string to endcode
+ * @param data the data string to encode
* @throws EncodeException if String is too large to encode
*/
public static byte[] stringToGsm7BitPacked(String data)
@@ -223,7 +222,7 @@
*
* @param data the text to convert to septets
* @param startingSeptetOffset the number of padding septets to put before
- * the character data at the begining of the array
+ * the character data at the beginning of the array
* @param throwException If true, throws EncodeException on invalid char.
* If false, replaces unencodable char with GSM alphabet space char.
*
@@ -257,7 +256,7 @@
}
/**
- * Pack a 7-bit char into its appropirate place in a byte array
+ * Pack a 7-bit char into its appropriate place in a byte array
*
* @param bitOffset the bit offset that the septet should be packed at
* (septet index * 7)
diff --git a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 8a5a6ae..5fef6de 100644
--- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -57,7 +57,7 @@
* @param destPort the port to deliver the message to
* @param data the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
- * broadcast when the message is sucessfully sent, or failed.
+ * broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -67,7 +67,7 @@
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
- * is NULL the caller will be checked against all unknown applicaitons,
+ * is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is delivered to the recipient. The
@@ -94,7 +94,7 @@
* the current default SMSC
* @param text the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
- * broadcast when the message is sucessfully sent, or failed.
+ * broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -140,7 +140,7 @@
* <code>RESULT_ERROR_RADIO_OFF</code>
* <code>RESULT_ERROR_NULL_PDU</code>.
* The per-application based SMS control checks sentIntent. If sentIntent
- * is NULL the caller will be checked against all unknown applicaitons,
+ * is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index d8e313a..b942ad7 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -285,9 +285,8 @@
}
- //***** Handler implemementation
-
- public void
+ //***** Handler implementation
+ @Override public void
handleMessage(Message msg) {
RILRequest rr = (RILRequest)(msg.obj);
RILRequest req = null;
@@ -780,7 +779,7 @@
send(rr);
}
- public void
+ @Deprecated public void
getPDPContextList(Message result) {
getDataCallList(result);
}
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index 764d12e..fb58e14 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -142,7 +142,7 @@
private SmsCounter mCounter;
- private ArrayList mSTrackers = new ArrayList(MO_MSG_QUEUE_LIMIT);
+ private ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT);
/** Wake lock to ensure device stays awake while dispatching the SMS intent. */
private PowerManager.WakeLock mWakeLock;
@@ -264,6 +264,7 @@
mCm.unregisterForOn(this);
}
+ @Override
protected void finalize() {
Log.d(TAG, "SMSDispatcher finalized");
}
@@ -344,7 +345,7 @@
msg.obj = null;
if (mSTrackers.isEmpty() == false) {
try {
- SmsTracker sTracker = (SmsTracker)mSTrackers.remove(0);
+ SmsTracker sTracker = mSTrackers.remove(0);
sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
} catch (CanceledException ex) {
Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
@@ -357,7 +358,7 @@
case EVENT_SEND_CONFIRMED_SMS:
if (mSTrackers.isEmpty() == false) {
- SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
+ SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
if (isMultipartTracker(sTracker)) {
sendMultipartSms(sTracker);
} else {
@@ -371,7 +372,7 @@
if (mSTrackers.isEmpty() == false) {
// Remove the latest one.
try {
- SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
+ SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
} catch (CanceledException ex) {
Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
@@ -671,7 +672,7 @@
* @param destPort the port to deliver the message to
* @param data the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
- * broadcast when the message is sucessfully sent, or failed.
+ * broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -681,7 +682,7 @@
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
- * is NULL the caller will be checked against all unknown applicaitons,
+ * is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is delivered to the recipient. The
@@ -698,7 +699,7 @@
* the current default SMSC
* @param text the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
- * broadcast when the message is sucessfully sent, or failed.
+ * broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -734,7 +735,7 @@
* <code>RESULT_ERROR_RADIO_OFF</code>
* <code>RESULT_ERROR_NULL_PDU</code>.
* The per-application based SMS control checks sentIntent. If sentIntent
- * is NULL the caller will be checked against all unknown applicaitons,
+ * is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
@@ -750,17 +751,17 @@
* Send a SMS
*
* @param smsc the SMSC to send the message through, or NULL for the
- * defatult SMSC
+ * default SMSC
* @param pdu the raw PDU to send
* @param sentIntent if not NULL this <code>Intent</code> is
- * broadcast when the message is sucessfully sent, or failed.
+ * broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:
* <code>RESULT_ERROR_GENERIC_FAILURE</code>
* <code>RESULT_ERROR_RADIO_OFF</code>
* <code>RESULT_ERROR_NULL_PDU</code>.
* The per-application based SMS control checks sentIntent. If sentIntent
- * is NULL the caller will be checked against all unknown applicaitons,
+ * is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntent if not NULL this <code>Intent</code> is
* broadcast when the message is delivered to the recipient. The
@@ -929,14 +930,14 @@
*/
static protected class SmsTracker {
// fields need to be public for derived SmsDispatchers
- public HashMap mData;
+ public HashMap<String, Object> mData;
public int mRetryCount;
public int mMessageRef;
public PendingIntent mSentIntent;
public PendingIntent mDeliveryIntent;
- SmsTracker(HashMap data, PendingIntent sentIntent,
+ SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
PendingIntent deliveryIntent) {
mData = data;
mSentIntent = sentIntent;
@@ -945,7 +946,7 @@
}
}
- protected SmsTracker SmsTrackerFactory(HashMap data, PendingIntent sentIntent,
+ protected SmsTracker SmsTrackerFactory(HashMap<String, Object> data, PendingIntent sentIntent,
PendingIntent deliveryIntent) {
return new SmsTracker(data, sentIntent, deliveryIntent);
}
diff --git a/telephony/java/com/android/internal/telephony/SmsHeader.java b/telephony/java/com/android/internal/telephony/SmsHeader.java
index 7872eec..7a65162 100644
--- a/telephony/java/com/android/internal/telephony/SmsHeader.java
+++ b/telephony/java/com/android/internal/telephony/SmsHeader.java
@@ -30,7 +30,7 @@
*/
public class SmsHeader {
- // TODO(cleanup): this datastructure is generally referred to as
+ // TODO(cleanup): this data structure is generally referred to as
// the 'user data header' or UDH, and so the class name should
// change to reflect this...
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
index af6c5f8..cbd8606 100644
--- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -16,7 +16,6 @@
package com.android.internal.telephony;
-import android.util.Log;
import com.android.internal.telephony.SmsHeader;
import java.util.Arrays;
@@ -366,13 +365,13 @@
/**
* Try to parse this message as an email gateway message
* There are two ways specified in TS 23.040 Section 3.8 :
- * - SMS message "may have its TP-PID set for internet electronic mail - MT
+ * - SMS message "may have its TP-PID set for Internet electronic mail - MT
* SMS format: [<from-address><space>]<message> - "Depending on the
* nature of the gateway, the destination/origination address is either
* derived from the content of the SMS TP-OA or TP-DA field, or the
* TP-OA/TP-DA field contains a generic gateway address and the to/from
* address is added at the beginning as shown above." (which is supported here)
- * - Multiple addreses separated by commas, no spaces, Subject field delimited
+ * - Multiple addresses separated by commas, no spaces, Subject field delimited
* by '()' or '##' and '#' Section 9.2.3.24.11 (which are NOT supported here)
*/
protected void extractEmailAddressFromMessageBody() {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index ed93aea..1b08aed 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -51,7 +51,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.lang.Boolean;
final class CdmaSMSDispatcher extends SMSDispatcher {
@@ -75,6 +74,7 @@
* @param ar AsyncResult passed into the message handler. ar.result should
* be a String representing the status report PDU, as ASCII hex.
*/
+ @Override
protected void handleStatusReport(AsyncResult ar) {
Log.d(TAG, "handleStatusReport is a special GSM function, should never be called in CDMA!");
}
@@ -97,6 +97,7 @@
}
/** {@inheritDoc} */
+ @Override
protected int dispatchMessage(SmsMessageBase smsb) {
// If sms is null, means there was a parsing error.
@@ -176,7 +177,7 @@
* TODO(cleanup): Why are we using a getter method for this
* (and for so many other sms fields)? Trivial getters and
* setters like this are direct violations of the style guide.
- * If the purpose is to protect agaist writes (by not
+ * If the purpose is to protect against writes (by not
* providing a setter) then any protection is illusory (and
* hence bad) for cases where the values are not primitives,
* such as this call for the header. Since this is an issue
@@ -340,6 +341,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void sendData(String destAddr, String scAddr, int destPort,
byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -348,6 +350,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -356,6 +359,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void sendMultipartText(String destAddr, String scAddr,
ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents) {
@@ -364,7 +368,7 @@
* TODO(cleanup): There is no real code difference between
* this and the GSM version, and hence it should be moved to
* the base class or consolidated somehow, provided calling
- * the proper submitpdu stuff can be arranged.
+ * the proper submit pdu stuff can be arranged.
*/
int refNumber = getNextConcatenatedRef() & 0x00FF;
@@ -437,8 +441,9 @@
}
/** {@inheritDoc} */
+ @Override
protected void sendSms(SmsTracker tracker) {
- HashMap map = tracker.mData;
+ HashMap<String, Object> map = tracker.mData;
byte smsc[] = (byte[]) map.get("smsc");
byte pdu[] = (byte[]) map.get("pdu");
@@ -449,11 +454,13 @@
}
/** {@inheritDoc} */
+ @Override
protected void sendMultipartSms (SmsTracker tracker) {
Log.d(TAG, "TODO: CdmaSMSDispatcher.sendMultipartSms not implemented");
}
/** {@inheritDoc} */
+ @Override
protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
// FIXME unit test leaves cm == null. this should change
@@ -474,16 +481,19 @@
}
/** {@inheritDoc} */
+ @Override
protected void activateCellBroadcastSms(int activate, Message response) {
mCm.setCdmaBroadcastActivation((activate == 0), response);
}
/** {@inheritDoc} */
+ @Override
protected void getCellBroadcastSmsConfig(Message response) {
mCm.getCdmaBroadcastConfig(response);
}
/** {@inheritDoc} */
+ @Override
protected void setCellBroadcastConfig(int[] configValuesArray, Message response) {
mCm.setCdmaBroadcastConfig(configValuesArray, response);
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index c7032ac..a0b5390 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -21,7 +21,6 @@
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
import android.util.Log;
-import android.util.SparseIntArray;
import android.telephony.SmsMessage;
@@ -33,7 +32,6 @@
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
-import com.android.internal.util.HexDump;
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.BitwiseOutputStream;
@@ -45,7 +43,7 @@
private final static String LOG_TAG = "SMS";
/**
- * Bearer Data Subparameter Indentifiers
+ * Bearer Data Subparameter Identifiers
* (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
* NOTE: Commented subparameter types are not implemented.
*/
@@ -802,9 +800,9 @@
* Create serialized representation for BearerData object.
* (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
*
- * @param bearerData an instance of BearerData.
+ * @param bData an instance of BearerData.
*
- * @return data byta array of raw encoded SMS bearer data.
+ * @return data byte array of raw encoded SMS bearer data.
*/
public static byte[] encode(BearerData bData) {
bData.hasUserDataHeader = ((bData.userData != null) &&
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 6ae316d..fbc3d84 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -57,6 +57,7 @@
* @param ar AsyncResult passed into the message handler. ar.result should
* be a String representing the status report PDU, as ASCII hex.
*/
+ @Override
protected void handleStatusReport(AsyncResult ar) {
String pduString = (String) ar.result;
SmsMessage sms = SmsMessage.newFromCDS(pduString);
@@ -85,6 +86,7 @@
/** {@inheritDoc} */
+ @Override
protected int dispatchMessage(SmsMessageBase smsb) {
// If sms is null, means there was a parsing error.
@@ -145,6 +147,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void sendData(String destAddr, String scAddr, int destPort,
byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -153,6 +156,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -161,6 +165,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void sendMultipartText(String destinationAddress, String scAddress,
ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents) {
@@ -300,8 +305,9 @@
}
/** {@inheritDoc} */
+ @Override
protected void sendSms(SmsTracker tracker) {
- HashMap map = tracker.mData;
+ HashMap<String, Object> map = tracker.mData;
byte smsc[] = (byte[]) map.get("smsc");
byte pdu[] = (byte[]) map.get("pdu");
@@ -316,12 +322,13 @@
*
* @param tracker holds the multipart Sms tracker ready to be sent
*/
+ @Override
protected void sendMultipartSms (SmsTracker tracker) {
ArrayList<String> parts;
ArrayList<PendingIntent> sentIntents;
ArrayList<PendingIntent> deliveryIntents;
- HashMap map = tracker.mData;
+ HashMap<String, Object> map = tracker.mData;
String destinationAddress = (String) map.get("destination");
String scAddress = (String) map.get("scaddress");
@@ -336,6 +343,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
// FIXME unit test leaves cm == null. this should change
if (mCm != null) {
@@ -344,6 +352,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void activateCellBroadcastSms(int activate, Message response) {
// Unless CBS is implemented for GSM, this point should be unreachable.
Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
@@ -351,6 +360,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void getCellBroadcastSmsConfig(Message response){
// Unless CBS is implemented for GSM, this point should be unreachable.
Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
@@ -358,6 +368,7 @@
}
/** {@inheritDoc} */
+ @Override
protected void setCellBroadcastConfig(int[] configValuesArray, Message response) {
// Unless CBS is implemented for GSM, this point should be unreachable.
Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
index 2028ca4..f000d79 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -86,6 +86,7 @@
public void dispose() {
}
+ @Override
protected void finalize() {
try {
super.finalize();
@@ -191,6 +192,7 @@
return mSms;
}
+ @Override
protected void log(String msg) {
Log.d(LOG_TAG, "[SimSmsInterfaceManager] " + msg);
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index d627baf..4eb667c 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -26,7 +26,6 @@
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
-import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
@@ -45,7 +44,7 @@
* A Short Message Service message.
*
*/
-public class SmsMessage extends SmsMessageBase{
+public class SmsMessage extends SmsMessageBase {
static final String LOG_TAG = "GSM";
private MessageClass messageClass;
@@ -303,7 +302,7 @@
// the receiver's SIM card. You can then send messages to yourself
// (on a phone with this change) and they'll end up on the SIM card.
bo.write(0x00);
- } else { //assume UCS-2
+ } else { // assume UCS-2
if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
// Message too long
return null;
@@ -369,7 +368,7 @@
* @param destinationAddress the address of the destination for the message
* @param destinationPort the port to deliver the message to at the
* destination
- * @param data the dat for the message
+ * @param data the data for the message
* @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message.
* Returns null on encode error.
@@ -474,7 +473,7 @@
return bo;
}
- static class PduParser {
+ private static class PduParser {
byte pdu[];
int cur;
SmsHeader userDataHeader;
@@ -482,10 +481,6 @@
int mUserDataSeptetPadding;
int mUserDataSize;
- PduParser(String s) {
- this(IccUtils.hexStringToBytes(s));
- }
-
PduParser(byte[] pdu) {
this.pdu = pdu;
cur = 0;
@@ -537,7 +532,7 @@
GsmSmsAddress ret;
// "The Address-Length field is an integer representation of
- // the number field, i.e. excludes any semi octet containing only
+ // the number field, i.e. excludes any semi-octet containing only
// fill bits."
// The TOA field is not included as part of this
int addressLength = pdu[cur] & 0xff;
@@ -565,7 +560,7 @@
int second = IccUtils.gsmBcdByteToInt(pdu[cur++]);
// For the timezone, the most significant bit of the
- // least signficant nibble is the sign byte
+ // least significant nibble is the sign byte
// (meaning the max range of this field is 79 quarter-hours,
// which is more than enough)
@@ -624,7 +619,7 @@
/*
* Here we just create the user data length to be the remainder of
* the pdu minus the user data header, since userDataLength means
- * the number of uncompressed sepets.
+ * the number of uncompressed septets.
*/
bufferLen = pdu.length - offset;
} else {
@@ -663,10 +658,10 @@
}
/**
- * Returns the number of padding bits at the begining of the user data
+ * Returns the number of padding bits at the beginning of the user data
* array before the start of the septets.
*
- * @return the number of padding bits at the begining of the user data
+ * @return the number of padding bits at the beginning of the user data
* array before the start of the septets
*/
int getUserDataSeptetPadding() {
@@ -686,7 +681,7 @@
XXX Not sure what this one is supposed to be doing, and no one is using
it.
String getUserDataGSM8bit() {
- // System.out.println("remainder of pud:" +
+ // System.out.println("remainder of pdu:" +
// HexDump.dumpHexString(pdu, cur, pdu.length - cur));
int count = pdu[cur++] & 0xff;
int size = pdu[cur++];
@@ -817,11 +812,13 @@
}
/** {@inheritDoc} */
+ @Override
public int getProtocolIdentifier() {
return protocolIdentifier;
}
/** {@inheritDoc} */
+ @Override
public boolean isReplace() {
return (protocolIdentifier & 0xc0) == 0x40
&& (protocolIdentifier & 0x3f) > 0
@@ -829,12 +826,14 @@
}
/** {@inheritDoc} */
+ @Override
public boolean isCphsMwiMessage() {
return ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear()
|| ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
}
/** {@inheritDoc} */
+ @Override
public boolean isMWIClearMessage() {
if (isMwi && (mwiSense == false)) {
return true;
@@ -845,6 +844,7 @@
}
/** {@inheritDoc} */
+ @Override
public boolean isMWISetMessage() {
if (isMwi && (mwiSense == true)) {
return true;
@@ -855,6 +855,7 @@
}
/** {@inheritDoc} */
+ @Override
public boolean isMwiDontStore() {
if (isMwi && mwiDontStore) {
return true;
@@ -874,31 +875,34 @@
}
/** {@inheritDoc} */
+ @Override
public int getStatus() {
return status;
}
/** {@inheritDoc} */
+ @Override
public boolean isStatusReportMessage() {
return isStatusReportMessage;
}
/** {@inheritDoc} */
+ @Override
public boolean isReplyPathPresent() {
return replyPathPresent;
}
/**
- * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
+ * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
* SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
* ME/TA converts each octet of TP data unit into two IRA character long
- * hexad number (e.g. octet with integer value 42 is presented to TE as two
+ * hex number (e.g. octet with integer value 42 is presented to TE as two
* characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
* something else...
*/
private void parsePdu(byte[] pdu) {
mPdu = pdu;
- // Log.d(LOG_TAG, "raw sms mesage:");
+ // Log.d(LOG_TAG, "raw sms message:");
// Log.d(LOG_TAG, s);
PduParser p = new PduParser(pdu);
@@ -1150,6 +1154,7 @@
/**
* {@inheritDoc}
*/
+ @Override
public MessageClass getMessageClass() {
return messageClass;
}
diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
index 11b3fd6..3675e78 100644
--- a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -16,7 +16,6 @@
package com.android.internal.telephony.test;
-
import android.os.AsyncResult;
import android.os.HandlerThread;
import android.os.Looper;
@@ -27,7 +26,6 @@
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.DataCallState;
-import com.android.internal.telephony.IccCard;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.gsm.CallFailCause;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
@@ -334,7 +332,7 @@
/**
* (AsyncResult)response.obj).result will be an Integer representing
- * the sum of enabled serivice classes (sum of SERVICE_CLASS_*)
+ * the sum of enabled service classes (sum of SERVICE_CLASS_*)
*
* @param facility one of CB_FACILTY_*
* @param pin password or "" if not required
@@ -440,7 +438,7 @@
* returned message
* retMsg.obj = AsyncResult ar
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result contains a List of DriverCall
* The ar.result List is sorted by DriverCall.index
*/
@@ -467,7 +465,7 @@
* returned message
* retMsg.obj = AsyncResult ar
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result contains a List of DataCallState
*/
public void getDataCallList(Message result) {
@@ -478,7 +476,7 @@
* returned message
* retMsg.obj = AsyncResult ar
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*
* CLIR_DEFAULT == on "use subscription default value"
@@ -495,7 +493,7 @@
* returned message
* retMsg.obj = AsyncResult ar
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is String containing IMSI on success
*/
public void getIMSI(Message result) {
@@ -506,7 +504,7 @@
* returned message
* retMsg.obj = AsyncResult ar
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is String containing IMEI on success
*/
public void getIMEI(Message result) {
@@ -517,7 +515,7 @@
* returned message
* retMsg.obj = AsyncResult ar
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is String containing IMEISV on success
*/
public void getIMEISV(Message result) {
@@ -529,7 +527,7 @@
* returned message
* retMsg.obj = AsyncResult ar
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*
* 3GPP 22.030 6.5.5
@@ -554,7 +552,7 @@
* "Releases all held calls or sets User Determined User Busy (UDUB)
* for a waiting call."
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void hangupWaitingOrBackground (Message result) {
@@ -575,7 +573,7 @@
* the other (held or waiting) call."
*
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void hangupForegroundResumeBackground (Message result) {
@@ -596,7 +594,7 @@
* the other (held or waiting) call."
*
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void switchWaitingOrHoldingAndActive (Message result) {
@@ -616,7 +614,7 @@
* "Adds a held call to the conversation"
*
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void conference (Message result) {
@@ -636,7 +634,7 @@
* "Connects the two calls and disconnects the subscriber from both calls"
*
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void explicitCallTransfer (Message result) {
@@ -672,7 +670,7 @@
/**
*
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void acceptCall (Message result) {
@@ -690,7 +688,7 @@
/**
* also known as UDUB
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void rejectCall (Message result) {
@@ -767,7 +765,7 @@
*
* @param result is callback message
* ((AsyncResult)response.obj).result is an int[] with every
- * element representing one avialable BM_*_BAND
+ * element representing one available BM_*_BAND
*/
public void queryAvailableBandMode (Message result) {
int ret[] = new int [4];
@@ -826,7 +824,6 @@
ret[11] = null;
ret[12] = null;
ret[13] = null;
- ret[14] = null;
resultSuccess(result, ret);
}
@@ -877,7 +874,7 @@
/**
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void sendDtmf(char c, Message result) {
@@ -886,7 +883,7 @@
/**
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void startDtmf(char c, Message result) {
@@ -895,7 +892,7 @@
/**
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void stopDtmf(Message result) {
@@ -904,7 +901,7 @@
/**
* ar.exception carries exception on failure
- * ar.userObject contains the orignal value of result.obj
+ * ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
public void sendBurstDtmf(String dtmfString, int on, int off, Message result) {
@@ -939,6 +936,7 @@
unimplemented(response);
}
+ @Deprecated
public void setupDefaultPDP(String apn, String user, String password, Message result) {
unimplemented(result);
}
@@ -950,9 +948,7 @@
public void deactivateDataCall(int cid, Message result) {unimplemented(result);}
- /**
- * @deprecated
- */
+ @Deprecated
public void deactivateDefaultPDP(int cid, Message result) {unimplemented(result);}
public void setPreferredNetworkType(int networkType , Message result) {
@@ -1029,7 +1025,7 @@
}
/**
- * parameters equivilient to 27.007 AT+CRSM command
+ * parameters equivalent to 27.007 AT+CRSM command
* response.obj will be an AsyncResult
* response.obj.userObj will be a SimIoResult on success
*/
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
index 3103fc1..215c6ce 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
@@ -24,8 +24,6 @@
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
public class GsmSmsTest extends AndroidTestCase {
@SmallTest
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
index b96743a..485542b 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
@@ -18,27 +18,21 @@
import android.os.AsyncResult;
import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
import android.os.Message;
-import android.os.Process;
import android.telephony.ServiceState;
import android.test.AndroidTestCase;
import android.test.PerformanceTestCase;
-import android.util.Log;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.MmiCode;
import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.TestPhoneNotifier;
import com.android.internal.telephony.gsm.CallFailCause;
import com.android.internal.telephony.gsm.GSMPhone;
import com.android.internal.telephony.gsm.GSMTestHandler;
import com.android.internal.telephony.gsm.GsmMmiCode;
import com.android.internal.telephony.gsm.SuppServiceNotification;
-import com.android.internal.telephony.test.SimulatedCommands;
import com.android.internal.telephony.test.SimulatedRadioControl;
import java.util.List;
diff --git a/test-runner/src/android/test/suitebuilder/TestGrouping.java b/test-runner/src/android/test/suitebuilder/TestGrouping.java
index df6da70..a2b94ff 100644
--- a/test-runner/src/android/test/suitebuilder/TestGrouping.java
+++ b/test-runner/src/android/test/suitebuilder/TestGrouping.java
@@ -46,6 +46,8 @@
*/
public class TestGrouping {
+ private static final String LOG_TAG = "TestGrouping";
+
SortedSet<Class<? extends TestCase>> testCaseClasses;
public static final Comparator<Class<? extends TestCase>> SORT_BY_SIMPLE_NAME
@@ -114,7 +116,7 @@
for (String packageName : packageNames) {
List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
if (addedClasses.isEmpty()) {
- Log.w("TestGrouping", "Invalid Package: '" + packageName
+ Log.w(LOG_TAG, "Invalid Package: '" + packageName
+ "' could not be found or has no tests");
}
testCaseClasses.addAll(addedClasses);
@@ -234,6 +236,10 @@
}
}
}
+ Log.i(LOG_TAG, String.format(
+ "TestCase class %s is missing a public constructor with no parameters " +
+ "or a single String parameter - skipping",
+ aClass.getName()));
return false;
}
}
diff --git a/test-runner/tests/src/android/test/suitebuilder/TestGroupingTest.java b/test-runner/tests/src/android/test/suitebuilder/TestGroupingTest.java
new file mode 100644
index 0000000..f4477d1
--- /dev/null
+++ b/test-runner/tests/src/android/test/suitebuilder/TestGroupingTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.test.suitebuilder;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link TestGrouping}
+ */
+public class TestGroupingTest extends TestCase {
+
+ private TestGrouping mGrouping;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mGrouping = new TestGrouping(TestGrouping.SORT_BY_SIMPLE_NAME);
+ }
+
+ /**
+ * Verifies that TestCases with no public constructor are not loaded.
+ * Relies on fixture classes in android.test.suitebuilder.examples.constructor
+ */
+ public void testGetTests_noPublicConstructor() {
+ mGrouping.addPackagesRecursive("android.test.suitebuilder.examples.constructor");
+ List<TestMethod> tests = mGrouping.getTests();
+ // only the PublicConstructorTest's test method should be present
+ assertEquals(1, tests.size());
+ assertEquals("testPublicConstructor", tests.get(0).getName());
+ }
+}
diff --git a/test-runner/tests/src/android/test/suitebuilder/examples/constructor/NoPublicConstructorTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/NoPublicConstructorTest.java
new file mode 100644
index 0000000..d7909a1
--- /dev/null
+++ b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/NoPublicConstructorTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.test.suitebuilder.examples.constructor;
+
+import junit.framework.TestCase;
+
+/**
+ * A {@link TestCase} which should not be loaded since it has non-public constructors with no args.
+ */
+public class NoPublicConstructorTest extends TestCase {
+
+ NoPublicConstructorTest() {
+ }
+
+ public NoPublicConstructorTest(String foo, String foo2) {
+ }
+
+ public void testNotRun() {
+ fail("method in NoPublicConstructorTest run unexpectedly");
+ }
+}
diff --git a/test-runner/tests/src/android/test/suitebuilder/examples/constructor/ProtectedConstructorTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/ProtectedConstructorTest.java
new file mode 100644
index 0000000..d2862fd
--- /dev/null
+++ b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/ProtectedConstructorTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.test.suitebuilder.examples.constructor;
+
+import junit.framework.TestCase;
+
+/**
+ * A protected constructor test case that should not be loaded.
+ */
+public class ProtectedConstructorTest extends TestCase {
+
+ protected ProtectedConstructorTest() {
+ }
+
+ public void testNotRun() {
+ fail("method in ProtectedConstructorTest run unexpectedly");
+ }
+
+}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java
similarity index 65%
copy from graphics/java/android/renderscript/Vector2f.java
copy to test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java
index 567d57fa..a11e25d 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -14,24 +14,15 @@
* limitations under the License.
*/
-package android.renderscript;
+package android.test.suitebuilder.examples.constructor;
-import java.lang.Math;
-import android.util.Log;
-
+import junit.framework.TestCase;
/**
- * @hide
- *
- **/
-public class Vector2f {
- public Vector2f() {
+ * A public constructor test case that should be loaded.
+ */
+public class PublicConstructorTest extends TestCase {
+
+ public void testPublicConstructor() {
}
-
- public float x;
- public float y;
}
-
-
-
-
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
index 77fd3ed..221b218 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
@@ -79,19 +79,20 @@
};
static void fillIgnoreResultList() {
- // This first block of tests are for HTML5 features, for which Android
+ // This first block of tests are for features for which Android
// should pass all tests. They are skipped only temporarily.
// TODO: Fix these failing tests and remove them from this list.
+ ignoreResultList.add("fast/events/touch/basic-touch-events.html"); // Requires multi-touch
+ ignoreResultList.add("fast/events/touch/send-oncancel-event.html"); // Expected output is incorrect upstream. See WebKit bug 37830.
ignoreResultList.add("http/tests/appcache/empty-manifest.html"); // flaky
ignoreResultList.add("http/tests/appcache/foreign-iframe-main.html"); // flaky - skips states
ignoreResultList.add("http/tests/appcache/manifest-with-empty-file.html"); // flaky
ignoreResultList.add("storage/database-lock-after-reload.html"); // Succeeds but DumpRenderTree does not read result correctly
ignoreResultList.add("storage/hash-change-with-xhr.html"); // Succeeds but DumpRenderTree does not read result correctly
- // Will always fail
- ignoreResultList.add("dom/svg/level3/xpath"); // XPath not supported
+ // Expected failures due to unsupported features.
+ ignoreResultList.add("fast/events/touch/touch-coords-in-zoom-and-scroll.html"); // Requires eventSender.zoomPageIn(),zoomPageOut()
ignoreResultList.add("fast/workers"); // workers not supported
- ignoreResultList.add("fast/xpath"); // XPath not supported
ignoreResultList.add("http/tests/eventsource/workers"); // workers not supported
ignoreResultList.add("http/tests/workers"); // workers not supported
ignoreResultList.add("http/tests/xmlhttprequest/workers"); // workers not supported
@@ -104,12 +105,9 @@
ignoreResultList.add("fast/css/case-transform.html"); // will not fix #619707
ignoreResultList.add("fast/dom/Element/offsetLeft-offsetTop-body-quirk.html"); // different screen size result in extra spaces in Apple compared to us
ignoreResultList.add("fast/dom/Window/Plug-ins.html"); // need test plugin
- ignoreResultList.add("fast/dom/Window/window-properties.html"); // xslt and xpath elements missing from property list
ignoreResultList.add("fast/dom/Window/window-screen-properties.html"); // pixel depth
ignoreResultList.add("fast/dom/Window/window-xy-properties.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/dom/attribute-namespaces-get-set.html"); // http://b/733229
- ignoreResultList.add("fast/dom/gc-9.html"); // requires xpath support
- ignoreResultList.add("fast/dom/global-constructors.html"); // requires xslt and xpath support
ignoreResultList.add("fast/dom/object-embed-plugin-scripting.html"); // dynamic plugins not supported
ignoreResultList.add("fast/dom/tabindex-clamp.html"); // there is extra spacing in the file due to multiple input boxes fitting on one line on Apple, ours are wrapped. Space at line ends are stripped.
ignoreResultList.add("fast/events/anchor-image-scrolled-x-y.html"); // requires eventSender.mouseDown(),mouseUp()
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index 81d5b08..ec8a8df 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -742,6 +742,7 @@
mDumpWebKitData = false;
mGetDrawtime = false;
mSaveImagePath = null;
+ setDefaultWebSettings(mWebView);
}
private long[] getDrawWebViewTime(WebView view, int count) {
@@ -786,6 +787,19 @@
return;
}
+ setDefaultWebSettings(webview);
+
+ webview.setWebChromeClient(mChromeClient);
+ webview.setWebViewClient(mViewClient);
+ // Setting a touch interval of -1 effectively disables the optimisation in WebView
+ // that stops repeated touch events flooding WebCore. The Event Sender only sends a
+ // single event rather than a stream of events (like what would generally happen in
+ // a real use of touch events in a WebView) and so if the WebView drops the event,
+ // the test will fail as the test expects one callback for every touch it synthesizes.
+ webview.setTouchInterval(-1);
+ }
+
+ public void setDefaultWebSettings(WebView webview) {
WebSettings settings = webview.getSettings();
settings.setAppCacheEnabled(true);
settings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
@@ -798,15 +812,6 @@
settings.setDatabasePath(getDir("databases",0).getAbsolutePath());
settings.setDomStorageEnabled(true);
settings.setWorkersEnabled(false);
-
- webview.setWebChromeClient(mChromeClient);
- webview.setWebViewClient(mViewClient);
- // Setting a touch interval of -1 effectively disables the optimisation in WebView
- // that stops repeated touch events flooding WebCore. The Event Sender only sends a
- // single event rather than a stream of events (like what would generally happen in
- // a real use of touch events in a WebView) and so if the WebView drops the event,
- // the test will fail as the test expects one callback for every touch it synthesizes.
- webview.setTouchInterval(-1);
}
private WebView mWebView;
diff --git a/tools/layoutlib/README b/tools/layoutlib/README
new file mode 100644
index 0000000..0fea9bd
--- /dev/null
+++ b/tools/layoutlib/README
@@ -0,0 +1,4 @@
+Layoutlib is a custom version of the android View framework designed to run inside Eclipse.
+The goal of the library is to provide layout rendering in Eclipse that are very very close to their rendering on devices.
+
+None of the com.android.* or android.* classes in layoutlib run on devices.
\ No newline at end of file