diff options
234 files changed, 15530 insertions, 6679 deletions
@@ -197,6 +197,9 @@ LOCAL_JAVA_LIBRARIES := core ext 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 @@ -293,7 +296,7 @@ fwbase_dirs_to_document := \ # as "final" in the official SDK APIs. fwbase_dirs_to_document += core/config/sdk -# These are relative to dalvik/libcore +# These are relative to libcore # Intentionally not included from libcore: # icu openssl suncompat support libcore_to_document := \ @@ -333,7 +336,7 @@ non_base_dirs := \ dirs_to_document := \ $(fwbase_dirs_to_document) \ $(non_base_dirs) \ - $(addprefix ../../dalvik/libcore/, $(libcore_to_document)) + $(addprefix ../../libcore/, $(libcore_to_document)) html_dirs := \ $(FRAMEWORKS_BASE_SUBDIRS) \ @@ -583,6 +586,9 @@ LOCAL_JAVA_LIBRARIES := core 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 1a7fad8..9d354ee 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" @@ -8303,6 +8358,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" @@ -8490,6 +8556,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" @@ -9678,6 +9755,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" @@ -9700,6 +9788,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" @@ -9876,6 +9975,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" @@ -10426,6 +10536,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" @@ -55653,6 +56131,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" @@ -56537,6 +57036,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" @@ -57405,6 +57936,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" @@ -57526,6 +58068,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" @@ -57643,6 +58196,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" @@ -57673,6 +58245,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" @@ -57892,6 +58481,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" @@ -58094,6 +58696,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" @@ -72952,6 +73565,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" @@ -73846,6 +74470,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" @@ -124203,6 +124857,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" @@ -124996,6 +125884,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" @@ -133366,6 +134396,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" @@ -133377,6 +134418,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" @@ -158147,7 +159199,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> @@ -201262,6 +202314,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" @@ -203223,6 +204464,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" @@ -203370,6 +204625,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" @@ -203441,6 +204713,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" @@ -209448,6 +210742,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 index fa9dced..fa9dced 100644..100755 --- a/cmds/input/input +++ b/cmds/input/input 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 @@ public class Activity extends ContextThemeWrapper 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 @@ public class Activity extends ContextThemeWrapper } /** + * 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 @@ public class Activity extends ContextThemeWrapper 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 @@ public class Activity extends ContextThemeWrapper 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 @@ public class Activity extends ContextThemeWrapper "Activity " + mComponent.toShortString() + " did not call through to super.onStart()"); } + mFragments.dispatchStart(); } final void performRestart() { @@ -3830,6 +3905,9 @@ public class Activity extends ContextThemeWrapper // 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 @@ public class Activity extends ContextThemeWrapper } final void performPause() { + mFragments.dispatchPause(); onPause(); } @@ -3853,6 +3932,8 @@ public class Activity extends ContextThemeWrapper mWindow.closeAllPanels(); } + mFragments.dispatchStop(); + mCalled = false; mInstrumentation.callActivityOnStop(this); if (!mCalled) { @@ -3877,6 +3958,11 @@ public class Activity extends ContextThemeWrapper mResumed = false; } + final void performDestroy() { + mFragments.dispatchDestroy(); + onDestroy(); + } + final boolean isResumed() { return mResumed; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index fd84859..87b77d6 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1465,7 +1465,7 @@ public final class ActivityThread { 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 @@ public final class ActivityThread { 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 @@ public final class ActivityThread { 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 @@ public final class ActivityThread { } 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ public class Instrumentation { } } - 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 abstract class Context { 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 @@ public interface SharedPreferences { 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 @@ public interface SharedPreferences { 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 d0b67cc..7f749bb 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 android.util.Pair; 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 @@ public class SyncManager implements OnAccountsUpdateListener { 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 @@ public class SyncManager implements OnAccountsUpdateListener { 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 @@ public class SyncManager implements OnAccountsUpdateListener { } 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 class SyncManager implements OnAccountsUpdateListener { 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 @@ public class SyncManager implements OnAccountsUpdateListener { } } + 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 @@ public class SyncManager implements OnAccountsUpdateListener { } } finally { final boolean isSyncInProgress = mActiveSyncContext != null; - if (!isSyncInProgress) { + if (!isSyncInProgress && mSyncWakeLock != null) { mSyncWakeLock.release(); + mSyncWakeLock = null; } manageSyncNotification(); manageErrorNotification(); @@ -1704,7 +1722,26 @@ public class SyncManager implements OnAccountsUpdateListener { 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..541f91a 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -18,16 +18,11 @@ package android.database; import android.content.ContentResolver; import android.net.Uri; +import android.os.Bundle; 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 java.lang.ref.WeakReference; -import java.lang.UnsupportedOperationException; import java.util.HashMap; import java.util.Map; @@ -88,7 +83,7 @@ public abstract class AbstractCursor implements CrossProcessCursor { } mDataSetObservable.notifyInvalidated(); } - + public boolean requery() { if (mSelfObserver != null && mSelfObserverRegistered == false) { mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); 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 @@ public interface Cursor { * 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 class DataSetObservable extends Observable<DataSetObserver> { */ 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 class DataSetObservable extends Observable<DataSetObserver> { */ 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 @@ package android.database.sqlite; * 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 @@ import android.util.Log; 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/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index 6e5b3e1..fea3438 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -16,12 +16,14 @@ package android.database.sqlite; +import android.app.ActivityThread; import android.database.AbstractWindowedCursor; import android.database.CursorWindow; import android.database.DataSetObserver; +import android.database.RequeryOnUiThreadException; import android.database.SQLException; - import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.Process; import android.text.TextUtils; @@ -74,6 +76,11 @@ public class SQLiteCursor extends AbstractWindowedCursor { private int mCursorState = 0; private ReentrantLock mLock = null; private boolean mPendingData = false; + + /** + * Used by {@link #requery()} to remember for which database we've already shown the warning. + */ + private static final HashMap<String, Boolean> sAlreadyWarned = new HashMap<String, Boolean>(); /** * support for a cursor variant that doesn't always read all results @@ -503,11 +510,30 @@ public class SQLiteCursor extends AbstractWindowedCursor { mDriver.cursorClosed(); } + /** + * Show a warning against the use of requery() if called on the main thread. + * This warning is shown per database per process. + */ + private void warnIfUiThread() { + if (Looper.getMainLooper() == Looper.myLooper()) { + String databasePath = mDatabase.getPath(); + // We show the warning once per database in order not to spam logcat. + if (!sAlreadyWarned.containsKey(databasePath)) { + sAlreadyWarned.put(databasePath, true); + String packageName = ActivityThread.currentPackageName(); + Log.w(TAG, "should not attempt requery on main (UI) thread: app = " + + packageName == null ? "'unknown'" : packageName, + new RequeryOnUiThreadException(packageName)); + } + } + } + @Override public boolean requery() { if (isClosed()) { return false; } + warnIfUiThread(); long timeStart = 0; if (Config.LOGV) { timeStart = System.currentTimeMillis(); diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index fb5507d..3a83c0c 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 android.util.Pair; 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 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { * 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. - */ - /* package */ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap(); + * (@link setMaxSqlCacheSize(int)}). + */ + // 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 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { @Override protected void onAllReferencesReleased() { if (isOpen()) { - if (SQLiteDebug.DEBUG_SQL_CACHE) { - mTimeClosed = getTime(); - } dbclose(); } } @@ -347,19 +370,8 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { * @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 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { } /** + * 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 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { * 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 @@ public class SQLiteDatabase extends SQLiteClosable { 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"); - } - // 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); + getPath() + ". Consider increasing cachesize."); } + } + /* 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 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { 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 @@ public class SQLiteDatabase extends SQLiteClosable { } if (pageCount > 0) { dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), - lookasideUsed)); + lookasideUsed, db.mNumCacheHits, db.mNumCacheMisses, + db.mCompiledQueries.size())); } } } @@ -2197,24 +2164,74 @@ public class SQLiteDatabase extends SQLiteClosable { } /** - * 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 89c3f96..9496079 100644 --- a/core/java/android/database/sqlite/SQLiteDebug.java +++ b/core/java/android/database/sqlite/SQLiteDebug.java @@ -132,11 +132,16 @@ public final class SQLiteDebug { /** 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 @@ public abstract class SQLiteOpenHelper { } 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 89a5f0d..a9c1ac6 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -147,7 +147,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { * @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/AsyncTask.java b/core/java/android/os/AsyncTask.java index d28148c..e74697a 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -16,16 +16,16 @@ package android.os; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.Callable; -import java.util.concurrent.FutureTask; +import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.CancellationException; import java.util.concurrent.atomic.AtomicInteger; /** @@ -36,8 +36,8 @@ import java.util.concurrent.atomic.AtomicInteger; * <p>An asynchronous task is defined by a computation that runs on a background thread and * whose result is published on the UI thread. An asynchronous task is defined by 3 generic * types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>, - * and 4 steps, called <code>begin</code>, <code>doInBackground</code>, - * <code>processProgress</code> and <code>end</code>.</p> + * and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>, + * <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p> * * <h2>Usage</h2> * <p>AsyncTask must be subclassed to be used. The subclass will override at least 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 @@ package android.os.storage; /** * 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 @@ import java.util.List; * {@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 @@ package android.os.storage; /** * 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 @@ public class RecurrenceSet { 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 @@ public class RecurrenceSet { } 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 @@ import java.util.Map; 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 @@ import java.util.Map; } /** - * 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.Map; 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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); - - 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; - } - 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; + // 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 (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; + } 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(encodedPhoneticFamilyName); mBuilder.append(VCARD_END_OF_LINE); - } + } // if (!TextUtils.isEmpty(phoneticFamilyName)) } } @@ -642,22 +700,18 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { phoneSet.add(actualPhoneNumber); appendTelLine(type, label, formattedPhoneNumber, isPrimary); } - } + } // for (String actualPhoneNumber : phoneNumberList) { } } } @@ -682,15 +736,38 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { // 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 class VCardBuilder { } 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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 @@ public class VCardBuilder { 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.Relation; 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 @@ import java.util.Map; /** * <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 @@ import java.util.Map; * 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 class VCardComposer { 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 @@ public class VCardComposer { 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 @@ public class VCardComposer { /** * <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 @@ public class VCardComposer { "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 @@ public class VCardComposer { 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,52 +278,107 @@ public class VCardComposer { }; 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); } /** - * Construct for supporting call log entry vCard composing. + * 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, String charset, + final boolean careHandlerErrors) { mContext = context; mVCardType = vcardType; mCareHandlerErrors = careHandlerErrors; 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; - } - 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; + 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 { - 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 @@ public class VCardComposer { } 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 @@ public class VCardComposer { 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 @@ public class VCardComposer { 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 @@ public class VCardComposer { } 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 @@ public class VCardComposer { 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 @@ public class VCardComposer { } } - 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 @@ public class VCardComposer { @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 @@ public class VCardConfig { /* 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"; + + /** + * 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"; - // 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"; - public static final int FLAG_V21 = 0; public static final int FLAG_V30 = 1; @@ -58,311 +75,329 @@ public class VCardConfig { 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_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_REFRAIN_IMAGE_EXPORT = 0x02000000; + 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 @@ public class VCardConfig { 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 @@ public class VCardConfig { 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 class VCardConstants { 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 @@ public class VCardConstants { // 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 class VCardConstants { 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 @@ public class VCardEntry { 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 @@ public class VCardEntry { 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 @@ public class VCardEntry { } } - 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 @@ public class VCardEntry { } } - 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 @@ public class VCardEntry { } } - 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 @@ public class VCardEntry { } } - static public class ImData { + public static class ImData { public final int protocol; public final String customProtocol; public final int type; @@ -441,7 +437,7 @@ public class VCardEntry { 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 @@ public class VCardEntry { 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 @@ public class VCardEntry { 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 @@ public class VCardEntry { } } - // 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 @@ public class VCardEntry { 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 @@ public class VCardEntry { */ 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 @@ public class VCardEntry { 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 @@ public class VCardEntry { && 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 class VCardEntry { } 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 class VCardEntryCommitter implements VCardEntryHandler { } } - 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.ArrayList; 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 with which byte array is encoded to String. - */ - final private String mCharsetForDecodedBytes; - final private boolean mStrictLineBreakParsing; - final private int mVCardType; - final private Account mAccount; + // 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; + + 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 int vcardType, final Account account) { + this(vcardType, account, 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, + final String inputCharset) { + this(vcardType, account, inputCharset, false); } - public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes, - final boolean strictLineBreakParsing, final int vcardType, - final Account account) { + /** + * @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 @@ public class VCardEntryConstructor implements VCardInterpreter { } } - /** - * 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 class VCardEntryConstructor implements VCardInterpreter { } public void endProperty() { - mCurrentContactStruct.addProperty(mCurrentProperty); + mCurrentVCardEntry.addProperty(mCurrentProperty); } public void propertyName(String name) { @@ -166,113 +168,41 @@ public class VCardEntryConstructor implements VCardInterpreter { 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 @@ public class VCardEntryConstructor implements VCardInterpreter { } 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 @@ import java.util.List; /** * <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 @@ import java.util.List; * {@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 android.pim.vcard.exception.VCardException; 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) - 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 */ - public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder) + public void parse(InputStream is, VCardInterpreter interepreter) 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. + * <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 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")); - - /** - * Though vCard 2.1 specification does not allow "B" encoding, some data may have it. - * We allow it for safety... - */ - 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; - - /** - * 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; - - public VCardParser_V21() { - this(null); - } - - public VCardParser_V21(VCardSourceDetector detector) { - this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN); - } - - public VCardParser_V21(int parseType) { - super(parseType); - if (parseType == VCardConfig.PARSE_TYPE_FOMA) { - mNestCount = 1; - } - } - - /** - * 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; - } - +public final class VCardParser_V21 implements VCardParser { /** - * @return True when successful. False when reaching the end of line - * @throws IOException - * @throws VCardException + * A unmodifiable Set storing the property names available in the vCard 2.1 specification. */ - 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."); - } + /* 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"))); /** - * 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 + * A unmodifiable Set storing the types known in vCard 2.1. */ - 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; - } - } - } + /* 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"))); - 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 + * A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1. */ - 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; - } + /* package */ static final Set<String> sKnownValueSet = + Collections.unmodifiableSet(new HashSet<String>( + Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"))); - 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 + "\""); - } - - 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. + * <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. + * </p> */ - 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); - } + /* 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))); - if (mBuilder != null) { - mBuilder.propertyValues(VCardUtils.constructListFromValue( - propertyValue, (getVersion() == VCardConfig.FLAG_V30))); - } - } + private final VCardParserImpl_V21 mVCardParserImpl; - /** - * 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; + public VCardParser_V21() { + mVCardParserImpl = new VCardParserImpl_V21(); } - /** - * 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 VCardParser_V21(int vcardType) { + mVCardParserImpl = new VCardParserImpl_V21(vcardType); } - 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; - } + public VCardParser_V21(int parseType, String inputCharset) { + mVCardParserImpl = new VCardParserImpl_V21(parseType, 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) + public void parse(InputStream is, VCardInterpreter interepreter) 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"); + mVCardParserImpl.parse(is, interepreter); } - 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; - - /** - * True when the caller wants the parser to be strict about the input. - * Currently this is only for testing. - */ - private final boolean mStrictParsing; - - public VCardParser_V30() { - super(); - mStrictParsing = false; - } + "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0 /** - * @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! + * <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> */ - public VCardParser_V30(boolean strictParsing) { - super(); - mStrictParsing = strictParsing; - } - - public VCardParser_V30(int parseMode) { - super(parseMode); - mStrictParsing = false; - } + /* 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))); - @Override - protected int getVersion() { - return VCardConfig.FLAG_V30; - } - - @Override - protected String getVersionString() { - return VCardConstants.VERSION_V30; - } + private final VCardParserImpl_V30 mVCardParserImpl; - @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; + public VCardParser_V30() { + mVCardParserImpl = new VCardParserImpl_V30(); } - @Override - protected boolean isValidEncoding(String encoding) { - return sAcceptableEncodingV30.contains(encoding.toUpperCase()); + public VCardParser_V30(int vcardType) { + mVCardParserImpl = new VCardParserImpl_V30(vcardType); } - @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); + public VCardParser_V30(int vcardType, String importCharset) { + mVCardParserImpl = new VCardParserImpl_V30(vcardType, importCharset); } - @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); - } - - /** - * 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); + mVCardParserImpl.parse(is, interepreter); } - 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 @@ public class VCardSourceDetector implements VCardInterpreter { "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 class VCardSourceDetector implements VCardInterpreter { 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 @@ public class VCardSourceDetector implements VCardInterpreter { } } - /* package */ int getEstimatedType() { - return mType; + /** + * @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; + } } - + /** - * Return charset String guessed from the source's properties. + * <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.Phone; 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 @@ import java.util.Set; /** * 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 @@ public class VCardUtils { } /** + * <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 @@ public class VCardUtils { 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 @@ public class VCardUtils { } /** + * <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 @@ public class VCardUtils { 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 @@ public class VCardUtils { } /** - * <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 @@ public class VCardUtils { 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..42d555c --- /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.Parcel; 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.view.ViewGroup; 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 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis } /** + * 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.content.SharedPreferences; 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 @@ import android.view.View; * 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 @@ public abstract class PreferenceActivity extends ListActivity implements 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 @@ public abstract class PreferenceActivity extends ListActivity implements @Override protected void onStop() { super.onStop(); - + mPreferenceManager.dispatchActivityStop(); } @Override protected void onDestroy() { super.onDestroy(); - mPreferenceManager.dispatchActivityDestroy(); } @@ -156,7 +213,7 @@ public abstract class PreferenceActivity extends ListActivity implements @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - + mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data); } @@ -176,7 +233,7 @@ public abstract class PreferenceActivity extends ListActivity implements 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 @@ public abstract class PreferenceActivity extends ListActivity implements } } } - + /** * Creates the {@link PreferenceManager}. - * + * * @return The {@link PreferenceManager} used by this activity. */ private PreferenceManager onCreatePreferenceManager() { @@ -198,7 +255,7 @@ public abstract class PreferenceActivity extends ListActivity implements preferenceManager.setOnPreferenceTreeClickListener(this); return preferenceManager; } - + /** * Returns the {@link PreferenceManager} used by this activity. * @return The {@link PreferenceManager}. @@ -206,7 +263,7 @@ public abstract class PreferenceActivity extends ListActivity implements 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 @@ public abstract class PreferenceActivity extends ListActivity implements /** * 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 @@ public abstract class PreferenceActivity extends ListActivity implements } } } - + /** * 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 abstract class PreferenceActivity extends ListActivity implements 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 @@ public abstract class PreferenceActivity extends ListActivity implements 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 @@ public final class ContactsContract { * 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 @@ public final class ContactsContract { 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 @@ public final class ContactsContract { "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 @@ public final class ContactsContract { "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 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { 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 @@ package android.text; 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 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * 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 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback 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 class BoringLayout extends Layout implements TextUtils.EllipsizeCallback 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 @@ extends Layout 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 com.android.internal.util.ArrayUtils; + 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.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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { } 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { } } } - + int dir = getParagraphDirection(i); int left = 0; int right = mWidth; @@ -309,7 +321,7 @@ public abstract class Layout { 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 @@ public abstract class Layout { // 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 @@ public abstract class Layout { mWidth = wid; } - + /** * Return the total height of this layout. */ @@ -450,7 +462,7 @@ public abstract class Layout { * 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 class Layout { */ 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 @@ public abstract class Layout { * 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { private float getLineMax(int line, Object[] tabs, boolean full) { int start = getLineStart(line); - int end; - - 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); - } + int end = full ? getLineEnd(line) : getLineVisibleEnd(line); + boolean hasTabs = getLineContainsTab(line); + Directions directions = getLineDirections(line); - 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 @@ public abstract class Layout { } /** - * 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { if (dist < bestdist) { bestdist = dist; - best = low; + best = low; } } @@ -802,8 +882,6 @@ public abstract class Layout { bestdist = dist; best = here; } - - here = there; } float dist = Math.abs(getPrimaryHorizontal(max) - horiz); @@ -823,14 +901,14 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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); - - if (line != getLineCount() - 1) - end--; - - 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); + return getOffsetToLeftRightOf(offset, false); + } - if (h > horiz && h < besth) { - best = candidate; - besth = h; + private int getOffsetToLeftRightOf(int caret, boolean toLeft) { + int line = getLineForOffset(caret); + int lineStart = getLineStart(line); + int lineEnd = getLineEnd(line); + int lineDir = getParagraphDirection(line); + + 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 } - } - - candidate = TextUtils.getOffsetBefore(mText, there); - if (candidate >= start && candidate <= end) { - h = getPrimaryHorizontal(candidate); - - if (h > horiz && h < besth) { - best = candidate; - besth = h; + } else { + if (line < getLineCount() - 1) { + ++line; + } else { + return caret; // at very end, don't move } } - here = there; - } - - float h = getPrimaryHorizontal(end); - - if (h > horiz && h < besth) { - best = end; - 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; + } } - if (best != offset) - return best; - - int dir = getParagraphDirection(line); + Directions directions = getLineDirections(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 @@ public abstract class Layout { /** * 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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; - } - buf = null; - } else { - buf = TextUtils.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - } - - 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; + /* 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); } + tl.set(paint, text, start, end, 1, directions, hasTabs, tabs); + return tl.metrics(null); + } finally { + TextLine.recycle(tl); + MeasuredText.recycle(mt); } - - 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 @@ public abstract class Layout { /** * 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; - - // 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) { + // 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. + + /* package */ int[] mDirections; + /* package */ Directions(int[] dirs) { mDirections = dirs; } } @@ -1831,6 +1430,7 @@ public abstract class Layout { * 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 abstract class Layout { 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 abstract class Layout { 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 @@ public abstract class Layout { 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 @@ import android.text.style.ReplacementSpan; * <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 @@ extends Layout 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 @@ extends Layout * 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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; - - 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; + MeasuredText measured = mMeasured; - 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; - else - end = TextUtils.indexOf(source, '\n', start, bufend); - - if (end < 0) - end = bufend; + int paraEnd; + for (int paraStart = bufstart; paraStart <= bufend; paraStart = paraEnd) { + paraEnd = TextUtils.indexOf(source, '\n', paraStart, bufend); + if (paraEnd < 0) + paraEnd = bufend; else - end++; + paraEnd++; + int paraLen = paraEnd - paraStart; int firstWidthLineCount = 1; int firstwidth = outerwidth; @@ -168,19 +153,20 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout } } - 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; - } - - 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; - } - } + 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; - // 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 @@ extends Layout 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 @@ extends Layout whichPaint = mWorkPaint; } - float wid = (float) bm.getWidth() * + float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight(); @@ -384,13 +274,13 @@ extends Layout 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 @@ extends Layout /* * 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout } } - if (end != here) { + if (paraEnd != here) { if ((fittop | fitbottom | fitdescent | fitascent) == 0) { paint.getFontMetricsInt(fm); @@ -571,20 +462,20 @@ extends Layout // 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 @@ extends Layout 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 @@ extends Layout } */ - 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout } 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout 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 @@ extends Layout } 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 @@ extends Layout 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.URLSpan; 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 @@ public class TextUtils { /** * 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 @@ public class TextUtils { * 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 @@ public class TextUtils { int len = text.length(); - // Use Paint.breakText() for the non-Spanned case to avoid having - // to allocate memory and accumulate the character widths ourselves. - - if (!(text instanceof Spanned)) { - float wid = p.measureText(text, 0, len); + MeasuredText mt = MeasuredText.obtain(); + try { + float width = setPara(mt, paint, text, 0, text.length(), + Layout.DIR_REQUEST_DEFAULT_LTR); - if (wid <= avail) { + if (width <= avail) { if (callback != null) { callback.ellipsized(0, 0); } @@ -1017,252 +1013,71 @@ public class TextUtils { 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'; - } - 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); - } + // XXX assumes ellipsis string does not require shaping and + // is unaffected by style + float ellipsiswid = paint.measureText(sEllipsis); + avail -= ellipsiswid; + + 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); - } + left = mt.breakText(0, len, true, avail); + } else { + right = len - mt.breakText(0, len, false, avail / 2); + avail -= mt.measure(right, len); + left = mt.breakText(0, right, true, avail); } - } - - // 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); + callback.ellipsized(left, right); } - return text; - } - - float ellipsiswid = p.measureText(sEllipsis); - - if (ellipsiswid > avail) { - if (callback != null) { - callback.ellipsized(0, len); - } + char[] buf = mt.mChars; + Spanned sp = text instanceof Spanned ? (Spanned) text : null; + int remaining = len - (right - left); if (preserveLength) { - char[] buf = obtain(len); - for (int i = 0; i < len; i++) { + if (remaining > 0) { // else eliminate the ellipsis too + buf[left++] = '\u2026'; + } + for (int i = left; i < right; 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; - } 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; + String s = new String(buf, 0, len); + if (sp == null) { + return s; } - - 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)); + 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, 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; + if (remaining == 0) { + return ""; } - if (callback != null) { - callback.ellipsized(left, right); + 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(); } - if (preserveLength) { - SpannableString ss = new SpannableString(blank(text, left, right)); - 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; - } + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append(text, 0, left); + ssb.append(sEllipsis); + ssb.append(text, right, len); + return ssb; + } finally { + MeasuredText.recycle(mt); } } - 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'; - } - } - - String ret = new String(buf, 0, len); - recycle(buf); - - return ret; - } - /** * Converts a CharSequence of the comma-separated form "Andy, Bob, * Charles, David" that is too wide to fit into the specified width @@ -1278,80 +1093,121 @@ public class TextUtils { 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++; + 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; } - } - - float[] wid; - if (text instanceof Spanned) { - Spanned sp = (Spanned) text; - TextPaint temppaint = new TextPaint(); - wid = new float[len * 2]; + char[] buf = mt.mChars; - 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); + int commaCount = 0; + for (int i = 0; i < len; i++) { + if (buf[i] == ',') { + commaCount++; + } } - System.arraycopy(wid, len, wid, 0, len); - } else { - wid = new float[len]; - p.getTextWidths(text, 0, len, wid); - } + int remaining = commaCount + 1; - int ok = 0; - int okRemaining = commaCount + 1; - String okFormat = ""; + int ok = 0; + int okRemaining = remaining; + String okFormat = ""; - int w = 0; - int count = 0; + int w = 0; + int count = 0; + float[] widths = mt.mWidths; - for (int i = 0; i < len; i++) { - w += wid[i]; + int request = mt.mDir == 1 ? Layout.DIR_REQUEST_LTR : + Layout.DIR_REQUEST_RTL; - if (buf[i] == ',') { - count++; + MeasuredText tempMt = MeasuredText.obtain(); + for (int i = 0; i < len; i++) { + w += widths[i]; - int remaining = commaCount - count + 1; - float moreWid; - String format; + if (buf[i] == ',') { + count++; - if (remaining == 1) { - format = " " + oneMore; - } else { - format = " " + String.format(more, remaining); - } + 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); + } - moreWid = p.measureText(format); + // 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; + if (w + moreWid <= avail) { + ok = i + 1; + okRemaining = remaining; + okFormat = format; + } } } - } + MeasuredText.recycle(tempMt); - if (w <= avail) { - return text; - } else { 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 class TextUtils { */ 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 class TextUtils { */ 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 class TextUtils { */ 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 @@ public class TextUtils { return mode; } - + private static Object sLock = new Object(); private static char[] sTemp = null; } 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 @@ public final class AccessibilityEvent implements Parcelable { 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 final class AccessibilityManager { 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 @@ public final class AccessibilityManager { * 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 @@ public final class AccessibilityManager { } /** + * 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 @@ import android.content.pm.ServiceInfo; */ 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 @@ public class WebSettings { "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 @@ public class WebSettings { 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 @@ public class WebSettings { 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); - } - } - 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); - } + StringBuilder buffer = new StringBuilder(); + addLocaleToHttpAcceptLanguage(buffer, locale); + + if (!Locale.US.equals(locale)) { + if (buffer.length() > 0) { + buffer.append(", "); } + 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 @@ public class WebSettings { } 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 @@ import java.util.ArrayList; 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 @@ import java.util.ArrayList; @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 @@ import java.util.ArrayList; } 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 6b316ce..3b61d2f 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -312,6 +312,8 @@ public class WebView extends AbsoluteLayout // 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 @@ public class WebView extends AbsoluteLayout if (AUTO_REDRAW_HACK && mAutoRedraw) { invalidate(); } + if (inEditingMode()) mWebTextView.onDrawSubstitute(); mWebViewCore.signalRepaintDone(); } @@ -3978,6 +3981,14 @@ public class WebView extends AbsoluteLayout 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 @@ public class WebView extends AbsoluteLayout 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 @@ public class WebView extends AbsoluteLayout // 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 @@ public class WebView extends AbsoluteLayout 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 @@ -4892,6 +4899,7 @@ public class WebView extends AbsoluteLayout break; } case MotionEvent.ACTION_UP: { + if (!isFocused()) requestFocus(); // pass the touch events from UI thread to WebCore thread if (shouldForwardTouchEvent()) { TouchEventData ted = new TouchEventData(); @@ -4900,10 +4908,6 @@ public class WebView extends AbsoluteLayout 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; @@ -4918,10 +4922,6 @@ public class WebView extends AbsoluteLayout 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(); @@ -4943,9 +4943,17 @@ public class WebView extends AbsoluteLayout 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(); @@ -6205,10 +6213,6 @@ public class WebView extends AbsoluteLayout // 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; @@ -6389,14 +6393,8 @@ public class WebView extends AbsoluteLayout 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; @@ -6505,27 +6503,31 @@ public class WebView extends AbsoluteLayout 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: @@ -6539,8 +6541,8 @@ public class WebView extends AbsoluteLayout 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 @@ final class WebViewCore { 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 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te 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 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe 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 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable, 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 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable, * @param context The context */ public CursorAdapter(Context context, Cursor c) { - init(context, c, true); + init(context, c, FLAG_AUTO_REQUERY); } /** @@ -99,19 +111,43 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable, * 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 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable, 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 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList // 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 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList */ 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 @@ public class QuickContactBadge extends ImageView implements OnClickListener { private QueryHandler mQueryHandler; private Drawable mBadgeBackground; private Drawable mNoBadgeBackground; + private Drawable mDefaultAvatar; protected String[] mExcludeMimes = null; @@ -117,6 +118,16 @@ public class QuickContactBadge extends ImageView implements OnClickListener { 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 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { 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 @@ import android.view.WindowManager.LayoutParams; * {@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.InputStream; 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 @@ public class XmlUtils 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 @@ public class XmlUtils } 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 @@ public class XmlUtils * * @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 @@ public class XmlUtils 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 @@ public class XmlUtils 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 @@ public class XmlUtils 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 @@ import android.widget.TextView; /** * 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 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList 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 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList 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 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList } /** - * 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 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList } /** + * 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 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList } /** - * 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 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList } /** + * 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 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList } /** + * 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 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList 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); - - 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); - } + 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); + + 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.provider.Settings; 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 @@ import java.util.Calendar; /** * 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 Binary files differdeleted file mode 100644 index fe220e4..0000000 --- a/core/res/assets/images/combobox-disabled.png +++ /dev/null diff --git a/core/res/assets/images/combobox-noHighlight.png b/core/res/assets/images/combobox-noHighlight.png Binary files differdeleted file mode 100644 index abcdf72..0000000 --- a/core/res/assets/images/combobox-noHighlight.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/combobox_disabled.png b/core/res/res/drawable-hdpi/combobox_disabled.png Binary files differnew file mode 100644 index 0000000..50eb45e --- /dev/null +++ b/core/res/res/drawable-hdpi/combobox_disabled.png diff --git a/core/res/res/drawable-hdpi/combobox_nohighlight.png b/core/res/res/drawable-hdpi/combobox_nohighlight.png Binary files differnew file mode 100644 index 0000000..9d60301 --- /dev/null +++ b/core/res/res/drawable-hdpi/combobox_nohighlight.png diff --git a/core/res/res/drawable-hdpi/quickcontact_nobadge.9.png b/core/res/res/drawable-hdpi/quickcontact_nobadge.9.png Binary files differnew file mode 100644 index 0000000..68d43c4 --- /dev/null +++ b/core/res/res/drawable-hdpi/quickcontact_nobadge.9.png diff --git a/core/res/res/drawable-hdpi/sym_action_call.png b/core/res/res/drawable-hdpi/sym_action_call.png Binary files differindex 105f7d0..da8afee 100644 --- a/core/res/res/drawable-hdpi/sym_action_call.png +++ b/core/res/res/drawable-hdpi/sym_action_call.png diff --git a/core/res/res/drawable-mdpi/combobox_disabled.png b/core/res/res/drawable-mdpi/combobox_disabled.png Binary files differnew file mode 100644 index 0000000..c32db7e --- /dev/null +++ b/core/res/res/drawable-mdpi/combobox_disabled.png diff --git a/core/res/res/drawable-mdpi/combobox_nohighlight.png b/core/res/res/drawable-mdpi/combobox_nohighlight.png Binary files differnew file mode 100644 index 0000000..1963316 --- /dev/null +++ b/core/res/res/drawable-mdpi/combobox_nohighlight.png diff --git a/core/res/res/drawable-mdpi/quickcontact_nobadge.9.png b/core/res/res/drawable-mdpi/quickcontact_nobadge.9.png Binary files differnew file mode 100644 index 0000000..ebe747b --- /dev/null +++ b/core/res/res/drawable-mdpi/quickcontact_nobadge.9.png diff --git a/core/res/res/drawable-nodpi/loading_tile_android.png b/core/res/res/drawable-nodpi/loading_tile_android.png Binary files differnew file mode 100644 index 0000000..8fde46f --- /dev/null +++ b/core/res/res/drawable-nodpi/loading_tile_android.png diff --git a/core/res/res/drawable-nodpi/no_tile_256.png b/core/res/res/drawable-nodpi/no_tile_256.png Binary files differnew file mode 100644 index 0000000..388234e --- /dev/null +++ b/core/res/res/drawable-nodpi/no_tile_256.png diff --git a/core/res/res/drawable/ic_btn_back.png b/core/res/res/drawable/ic_btn_back.png Binary files differnew file mode 100644 index 0000000..c9bff4c --- /dev/null +++ b/core/res/res/drawable/ic_btn_back.png diff --git a/core/res/res/drawable/ic_btn_next.png b/core/res/res/drawable/ic_btn_next.png Binary files differnew file mode 100755 index 0000000..c6cf436 --- /dev/null +++ b/core/res/res/drawable/ic_btn_next.png 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 Binary files differdeleted file mode 100644 index f0f50b3..0000000 --- a/core/res/res/drawable/quickcontact_nobadge_highlight.9.png +++ /dev/null diff --git a/core/res/res/drawable/quickcontact_nobadge_normal.9.png b/core/res/res/drawable/quickcontact_nobadge_normal.9.png Binary files differdeleted file mode 100644 index 01cc9dc..0000000 --- a/core/res/res/drawable/quickcontact_nobadge_normal.9.png +++ /dev/null diff --git a/core/res/res/drawable/quickcontact_nobadge_pressed.9.png b/core/res/res/drawable/quickcontact_nobadge_pressed.9.png Binary files differdeleted file mode 100644 index 6e22c87..0000000 --- a/core/res/res/drawable/quickcontact_nobadge_pressed.9.png +++ /dev/null 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 5d18e9e..fb50735 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. =============================================================== --> @@ -1258,4 +1258,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..9accda8 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1615,7 +1615,7 @@ <!-- Do not translate. WebView User Agent string --> <string name="web_user_agent" translatable="false"><xliff:g id="x">Mozilla/5.0 (Linux; U; Android %s) - AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1</xliff:g></string> + AppleWebKit/533.6 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.6</xliff:g></string> <!-- Title for a JavaScript dialog. "The page at <url of current page> says:" --> <string name="js_dialog_title">The page at \'<xliff:g id="title">%s</xliff:g>\' says:</string> @@ -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/accessibilityservice/AccessibilityTestService.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityTestService.java deleted file mode 100644 index 2a51eea..0000000 --- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityTestService.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 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.accessibilityservice; - -import android.accessibilityservice.AccessibilityService; -import android.accessibilityservice.AccessibilityServiceInfo; -import android.app.Notification; -import android.util.Log; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * This class text the accessibility framework end to end. - * <p> - * Note: Since accessibility is provided by {@link AccessibilityService}s we create one, - * and it generates an event and an interruption dispatching them through the - * {@link AccessibilityManager}. We verify the received result. To trigger the test - * go to Settings->Accessibility and select the enable accessibility check and then - * select the check for this service (same name as the class). - */ -public class AccessibilityTestService extends AccessibilityService { - - private static final String LOG_TAG = "AccessibilityTestService"; - - private static final String CLASS_NAME = "foo.bar.baz.Test"; - private static final String PACKAGE_NAME = "foo.bar.baz"; - private static final String TEXT = "Some stuff"; - private static final String BEFORE_TEXT = "Some other stuff"; - - private static final String CONTENT_DESCRIPTION = "Content description"; - - private static final int ITEM_COUNT = 10; - private static final int CURRENT_ITEM_INDEX = 1; - private static final int INTERRUPT_INVOCATION_TYPE = 0x00000200; - - private static final int FROM_INDEX = 1; - private static final int ADDED_COUNT = 2; - private static final int REMOVED_COUNT = 1; - - private static final int NOTIFICATION_TIMEOUT_MILLIS = 80; - - private int mReceivedResult; - - private Timer mTimer = new Timer(); - - @Override - public void onServiceConnected() { - AccessibilityServiceInfo info = new AccessibilityServiceInfo(); - info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; - info.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE; - info.notificationTimeout = NOTIFICATION_TIMEOUT_MILLIS; - info.flags &= AccessibilityServiceInfo.DEFAULT; - setServiceInfo(info); - - // we need to wait until the system picks our configuration - // otherwise it will not notify us - mTimer.schedule(new TimerTask() { - @Override - public void run() { - try { - testAccessibilityEventDispatching(); - testInterrupt(); - } catch (Exception e) { - Log.e(LOG_TAG, "Error in testing Accessibility feature", e); - } - } - }, 1000); - } - - /** - * Check here if the event we received is actually the one we sent. - */ - @Override - public void onAccessibilityEvent(AccessibilityEvent event) { - assert(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED == event.getEventType()); - assert(event != null); - assert(event.getEventTime() > 0); - assert(CLASS_NAME.equals(event.getClassName())); - assert(PACKAGE_NAME.equals(event.getPackageName())); - assert(1 == event.getText().size()); - assert(TEXT.equals(event.getText().get(0))); - assert(BEFORE_TEXT.equals(event.getBeforeText())); - assert(event.isChecked()); - assert(CONTENT_DESCRIPTION.equals(event.getContentDescription())); - assert(ITEM_COUNT == event.getItemCount()); - assert(CURRENT_ITEM_INDEX == event.getCurrentItemIndex()); - assert(event.isEnabled()); - assert(event.isPassword()); - assert(FROM_INDEX == event.getFromIndex()); - assert(ADDED_COUNT == event.getAddedCount()); - assert(REMOVED_COUNT == event.getRemovedCount()); - assert(event.getParcelableData() != null); - assert(1 == ((Notification) event.getParcelableData()).icon); - - // set the type of the receved request - mReceivedResult = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED; - } - - /** - * Set a flag that we received the interrupt request. - */ - @Override - public void onInterrupt() { - - // set the type of the receved request - mReceivedResult = INTERRUPT_INVOCATION_TYPE; - } - - /** - * If an {@link AccessibilityEvent} is sent and received correctly. - */ - public void testAccessibilityEventDispatching() throws Exception { - AccessibilityEvent event = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); - - assert(event != null); - event.setClassName(CLASS_NAME); - event.setPackageName(PACKAGE_NAME); - event.getText().add(TEXT); - event.setBeforeText(BEFORE_TEXT); - event.setChecked(true); - event.setContentDescription(CONTENT_DESCRIPTION); - event.setItemCount(ITEM_COUNT); - event.setCurrentItemIndex(CURRENT_ITEM_INDEX); - event.setEnabled(true); - event.setPassword(true); - event.setFromIndex(FROM_INDEX); - event.setAddedCount(ADDED_COUNT); - event.setRemovedCount(REMOVED_COUNT); - event.setParcelableData(new Notification(1, "Foo", 1234)); - - AccessibilityManager.getInstance(this).sendAccessibilityEvent(event); - - assert(mReceivedResult == event.getEventType()); - - Log.i(LOG_TAG, "AccessibilityTestService#testAccessibilityEventDispatching: Success"); - } - - /** - * If accessibility feedback interruption is triggered and received correctly. - */ - public void testInterrupt() throws Exception { - AccessibilityManager.getInstance(this).interrupt(); - - assert(INTERRUPT_INVOCATION_TYPE == mReceivedResult); - - Log.i(LOG_TAG, "AccessibilityTestService#testInterrupt: Success"); - } -} - 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 android.util.Log; import junit.framework.Assert; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; @@ -1023,6 +1024,34 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT } } + @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 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT } } } - } + } + + @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.StructuredName; 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 @@ public class VCardExporterTests extends VCardTestsBase { 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.StructuredPostal; 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 class VCardImporterTests extends VCardTestsBase { } 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 class VCardImporterTests extends VCardTestsBase { } 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 class VCardImporterTests extends VCardTestsBase { } 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 @@ public class VCardImporterTests extends VCardTestsBase { // 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 @@ public class VCardImporterTests extends VCardTestsBase { /** * 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 class VCardImporterTests extends VCardTestsBase { } 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 class VCardImporterTests extends VCardTestsBase { } 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 class VCardJapanizationTests extends VCardTestsBase { } 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 @@ public class VCardJapanizationTests extends VCardTestsBase { * 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 @@ public class VCardJapanizationTests extends VCardTestsBase { .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 @@ public class VCardJapanizationTests extends VCardTestsBase { .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 class VCardJapanizationTests extends VCardTestsBase { } 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 class VCardJapanizationTests extends VCardTestsBase { } 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 @@ public class VCardJapanizationTests extends VCardTestsBase { .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 @@ public class VCardJapanizationTests extends VCardTestsBase { .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 @@ public class VCardJapanizationTests extends VCardTestsBase { .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 @@ public class VCardJapanizationTests extends VCardTestsBase { * 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 class VCardJapanizationTests extends VCardTestsBase { } 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 class VCardJapanizationTests extends VCardTestsBase { } 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 @@ public class VCardJapanizationTests extends VCardTestsBase { * 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 class VCardJapanizationTests extends VCardTestsBase { } 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 class VCardJapanizationTests extends VCardTestsBase { } 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 class VCardJapanizationTests extends VCardTestsBase { } 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 @@ class MockContentProvider extends ContentProvider { 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 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 @@ import android.content.ContentValues; * 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 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.io.InputStream; 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 @@ import java.util.List; 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 @@ import java.util.List; 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 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 android.test.AndroidTestCase; 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 @@ import java.io.InputStream; 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 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.Entity; 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,75 +34,43 @@ import java.util.ArrayList; 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 ExportTestProvider extends MockContentProvider { + final private TestCase mTestCase; + final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>(); -/* package */ class MockEntityIterator implements EntityIterator { - List<Entity> mEntityList; - Iterator<Entity> mIterator; + 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); + 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(); } - 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 boolean hasNext() { + return mIterator.hasNext(); + } - public void reset() { - mIterator = mEntityList.iterator(); - } + public Entity next() { + return mIterator.next(); + } - public void close() { - } -} + public void remove() { + throw new UnsupportedOperationException("remove not supported"); + } -/** - * 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 void reset() { + mIterator = mEntityList.iterator(); + } - public List<ContentValues> getList() { - return mContentValuesList; + public void close() { + } } -} - -/* package */ class ExportTestProvider extends MockContentProvider { - final private TestCase mTestCase; - final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>(); public ExportTestProvider(TestCase testCase) { mTestCase = testCase; @@ -121,16 +90,6 @@ import java.util.List; * 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 @@ import java.util.List; } }; } -} +}
\ 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 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.Relation; 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.SortedMap; 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 @@ import java.util.Map.Entry; } 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 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 junit.framework.TestCase; 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 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 junit.framework.TestCase; 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 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.List; 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 class PropertyNode { */ 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 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 @@ import java.util.List; * 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 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.VCardParser_V30; 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.io.InputStream; 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"; - @Override - public ContentResolver getContentResolver() { - return mResolver; + private static class CustomMockContext extends MockContext { + final ContentResolver mResolver; + public CustomMockContext(ContentResolver resolver) { + mResolver = resolver; + } + + @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 @@ import java.util.Arrays; 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,26 +106,37 @@ import java.util.Arrays; mVerified = false; } - public void initForExportTest(int vcardType) { + // Should be called at the beginning of each import test. + public void initForImportTest(int vcardType, int resId) { if (mInitialized) { mTestCase.fail("Already initialized"); } - mExportTestResolver = new ExportTestResolver(mTestCase); mVCardType = vcardType; mIsV30 = VCardConfig.isV30(vcardType); mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); + setInputResourceId(resId); mInitialized = true; } - public void initForImportTest(int vcardType, int resId) { + // 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); - setInputResourceId(resId); mInitialized = true; + if (TextUtils.isEmpty(charset)) { + mCharset = "UTF-8"; + } else { + mCharset = charset; + } } private void setInputResourceId(int resId) { @@ -188,7 +215,7 @@ import java.util.Arrays; } 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 @@ import java.util.Arrays; } } - 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 @@ import java.util.Arrays; try { is.close(); } catch (IOException e) { + mTestCase.fail("Unexpected IOException: " + e.getMessage()); } } } @@ -267,10 +296,13 @@ import java.util.Arrays; 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 @@ import java.util.Arrays; 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 @@ import java.util.Arrays; 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 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 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.ArrayList; 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 @@ public class VNodeBuilder implements VCardInterpreter { 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 @@ public class VNodeBuilder implements VCardInterpreter { 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 @@ public class VNodeBuilder implements VCardInterpreter { mCurrentPropNode.propName = name; } - // Used only in VCard. public void propertyGroup(String group) { mCurrentPropNode.propGroupSet.add(group); } @@ -196,67 +189,8 @@ public class VNodeBuilder implements VCardInterpreter { 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 class VNodeBuilder implements VCardInterpreter { } 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 @@ public class SettingsProviderTest extends AndroidTestCase { 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 android.util.Log; import junit.framework.TestCase; /** - * Tests StaticLayout bidi implementation. + * Quick check of native bidi implementation. */ public class StaticLayoutBidiTest extends TestCase { @@ -41,73 +41,47 @@ public class StaticLayoutBidiTest extends TestCase { //@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 @@ public class StaticLayoutTest extends TestCase { } } - 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/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/docs/html/resources/samples/images/SpinnerTest1.png b/docs/html/resources/samples/images/SpinnerTest1.png Binary files differnew file mode 100644 index 0000000..21442f2 --- /dev/null +++ b/docs/html/resources/samples/images/SpinnerTest1.png diff --git a/docs/html/resources/samples/images/SpinnerTest2.png b/docs/html/resources/samples/images/SpinnerTest2.png Binary files differnew file mode 100644 index 0000000..79ffeb6 --- /dev/null +++ b/docs/html/resources/samples/images/SpinnerTest2.png 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 @@ public class Paint { /** * 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 @@ public class PorterDuff { /** [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 @@ public abstract class Drawable { * 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 @@ public abstract class Drawable { 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 @@ public abstract class Drawable { } 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 @@ public abstract class Drawable { 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 @@ public abstract class Drawable { 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 @@ public abstract class Drawable { 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 class Drawable { 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 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mDrawableContainerState.getOpacity(); } - public boolean selectDrawable(int idx) - { + public boolean selectDrawable(int idx) { if (idx == mCurIndex) { return false; } @@ -255,6 +262,12 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { 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 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { 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 @@ public class Allocation extends BaseObj { 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/Byte2.java b/graphics/java/android/renderscript/Byte2.java new file mode 100644 index 0000000..95cf88c --- /dev/null +++ b/graphics/java/android/renderscript/Byte2.java @@ -0,0 +1,37 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Byte2 { + public Byte2() { + } + + public byte x; + public byte y; +} + + + + diff --git a/graphics/java/android/renderscript/Byte3.java b/graphics/java/android/renderscript/Byte3.java new file mode 100644 index 0000000..a6c0ca9 --- /dev/null +++ b/graphics/java/android/renderscript/Byte3.java @@ -0,0 +1,38 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Byte3 { + public Byte3() { + } + + public byte x; + public byte y; + public byte z; +} + + + + diff --git a/graphics/java/android/renderscript/Byte4.java b/graphics/java/android/renderscript/Byte4.java new file mode 100644 index 0000000..a5bfc61 --- /dev/null +++ b/graphics/java/android/renderscript/Byte4.java @@ -0,0 +1,38 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Byte4 { + public Byte4() { + } + + 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 @@ public class Element extends BaseObj { 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 @@ public class FieldPacker { } } - 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 @@ public class FieldPacker { 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 @@ public class FieldPacker { 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 @@ public class FieldPacker { 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 @@ public class FieldPacker { 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 @@ public class FieldPacker { 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 index 567d57f..8fea91f 100644 --- a/graphics/java/android/renderscript/Vector2f.java +++ b/graphics/java/android/renderscript/Float2.java @@ -24,8 +24,8 @@ import android.util.Log; * @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 index f2842f3..9d9e406 100644 --- a/graphics/java/android/renderscript/Vector3f.java +++ b/graphics/java/android/renderscript/Float3.java @@ -24,8 +24,8 @@ import android.util.Log; * @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 index fabd959..a703e80 100644 --- a/graphics/java/android/renderscript/Vector4f.java +++ b/graphics/java/android/renderscript/Float4.java @@ -24,8 +24,8 @@ import android.util.Log; * @hide * **/ -public class Vector4f { - public Vector4f() { +public class Float4 { + public Float4() { } public float x; diff --git a/graphics/java/android/renderscript/Int2.java b/graphics/java/android/renderscript/Int2.java new file mode 100644 index 0000000..56e2fe9 --- /dev/null +++ b/graphics/java/android/renderscript/Int2.java @@ -0,0 +1,37 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Int2 { + public Int2() { + } + + public int x; + public int y; +} + + + + diff --git a/graphics/java/android/renderscript/Int3.java b/graphics/java/android/renderscript/Int3.java new file mode 100644 index 0000000..1b27509 --- /dev/null +++ b/graphics/java/android/renderscript/Int3.java @@ -0,0 +1,38 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Int3 { + public Int3() { + } + + public int x; + public int y; + public int z; +} + + + + diff --git a/graphics/java/android/renderscript/Int4.java b/graphics/java/android/renderscript/Int4.java new file mode 100644 index 0000000..3d6f3f5 --- /dev/null +++ b/graphics/java/android/renderscript/Int4.java @@ -0,0 +1,38 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Int4 { + public Int4() { + } + + public int x; + public int y; + public int z; + public int w; +} + + + diff --git a/graphics/java/android/renderscript/Long2.java b/graphics/java/android/renderscript/Long2.java new file mode 100644 index 0000000..11ead2f --- /dev/null +++ b/graphics/java/android/renderscript/Long2.java @@ -0,0 +1,37 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Long2 { + public Long2() { + } + + public long x; + public long y; +} + + + + diff --git a/graphics/java/android/renderscript/Long3.java b/graphics/java/android/renderscript/Long3.java new file mode 100644 index 0000000..1604532 --- /dev/null +++ b/graphics/java/android/renderscript/Long3.java @@ -0,0 +1,38 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Long3 { + public Long3() { + } + + public long x; + public long y; + public long z; +} + + + + diff --git a/graphics/java/android/renderscript/Long4.java b/graphics/java/android/renderscript/Long4.java new file mode 100644 index 0000000..2fd2747 --- /dev/null +++ b/graphics/java/android/renderscript/Long4.java @@ -0,0 +1,38 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Long4 { + public Long4() { + } + + 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 @@ public class RenderScript { 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 @@ public class Script extends BaseObj { } } + protected void invoke(int slot) { + mRS.nScriptInvoke(mID, slot); + } + Script(int id, RenderScript rs) { super(rs); mID = id; @@ -145,5 +149,48 @@ public class Script extends BaseObj { } + + 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 @@ public class ScriptC extends Script { 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/Short2.java b/graphics/java/android/renderscript/Short2.java new file mode 100644 index 0000000..426801f --- /dev/null +++ b/graphics/java/android/renderscript/Short2.java @@ -0,0 +1,37 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Short2 { + public Short2() { + } + + public short x; + public short y; +} + + + + diff --git a/graphics/java/android/renderscript/Short3.java b/graphics/java/android/renderscript/Short3.java new file mode 100644 index 0000000..7b9c305 --- /dev/null +++ b/graphics/java/android/renderscript/Short3.java @@ -0,0 +1,38 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Short3 { + public Short3() { + } + + public short x; + public short y; + public short z; +} + + + + diff --git a/graphics/java/android/renderscript/Short4.java b/graphics/java/android/renderscript/Short4.java new file mode 100644 index 0000000..9a474e2 --- /dev/null +++ b/graphics/java/android/renderscript/Short4.java @@ -0,0 +1,38 @@ +/* + * 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.renderscript; + +import java.lang.Math; +import android.util.Log; + + +/** + * @hide + * + **/ +public class Short4 { + public Short4() { + } + + 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 @@ ifneq ($(TARGET_SIMULATOR),true) 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 @@ LOCAL_SRC_FILES:= \ 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 @@ extern "C" { 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 RsPrimitive { 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 @@ typedef struct Control_s { 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 @@ int main(int launchID) { 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 @@ int main(int launchID) { 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 @@ int main(int launchID) { 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 @@ SimpleMeshBindVertex { 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 @@ void Allocation::uploadToTexture(const Context *rsc) glGenerateMipmap(GL_TEXTURE_2D); } + rsc->checkError("Allocation::uploadToTexture"); } void Allocation::deferedUploadToBufferObject(const Context *rsc) @@ -201,6 +202,7 @@ void Allocation::uploadToBufferObject(const Context *rsc) 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 @@ void Context::appendNameDefines(String8 *str) const 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 @@ bool Context::objDestroyOOBInit() 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 @@ void Context::objDestroyOOBRun() } 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 @@ public: 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 @@ protected: 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 @@ void LocklessCommandFifo::flush() //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 @@ void LocklessCommandFifo::dumpState(const char *s) const 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 @@ public: 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 @@ protected: 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 @@ private: } +} #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/libs/rs/rsMutex.h b/libs/rs/rsMutex.h new file mode 100644 index 0000000..47725d7 --- /dev/null +++ b/libs/rs/rsMutex.h @@ -0,0 +1,43 @@ +/* + * 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_MUTEX_H +#define ANDROID_RS_MUTEX_H + + +#include "rsUtils.h" + +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/libs/rs/rsSignal.h b/libs/rs/rsSignal.h new file mode 100644 index 0000000..2e760f1 --- /dev/null +++ b/libs/rs/rsSignal.h @@ -0,0 +1,46 @@ +/* + * 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_SIGNAL_H +#define ANDROID_RS_SIGNAL_H + + +#include "rsUtils.h" + +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 ElementField { }; 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 @@ extern float16 __attribute__((overloadable)) trunc(float16); // 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 int8 __attribute__((ext_vector_type(8))); 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 @@ public class AudioManager { * {@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 @@ class LockScreen extends LinearLayout implements KeyguardScreen, KeyguardUpdateM } }); - setFocusable(true); setFocusableInTouchMode(true); setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); @@ -518,7 +517,7 @@ class LockScreen extends LinearLayout implements KeyguardScreen, KeyguardUpdateM mScreenLocked.setText(""); // layout - mScreenLocked.setVisibility(View.VISIBLE); + mScreenLocked.setVisibility(View.INVISIBLE); mSelector.setVisibility(View.VISIBLE); mEmergencyCallText.setVisibility(View.GONE); break; @@ -658,7 +657,6 @@ class LockScreen extends LinearLayout implements KeyguardScreen, KeyguardUpdateM /** {@inheritDoc} */ public void onResume() { resetStatusInfo(mUpdateMonitor); - mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); } /** {@inheritDoc} */ @@ -676,6 +674,5 @@ class LockScreen extends LinearLayout implements KeyguardScreen, KeyguardUpdateM } 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_MODULE:= services 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 class AccessibilityManagerService extends IAccessibilityManager.Stub }); } - 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 @@ public class LocationManagerService extends ILocationManager.Stub implements Run 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 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } 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 f1c67d2..3952a86 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -148,7 +148,6 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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; } } @@ -7933,7 +7931,7 @@ public class WindowManagerService extends IWindowManager.Stub final AppWindowToken atoken = mAppToken; return mSurface != null && !mAttachedHidden && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested) - && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending)) + && !mDrawPending && !mCommitDrawPending && !mExiting && !mDestroying; } @@ -8037,14 +8035,12 @@ public class WindowManagerService extends IWindowManager.Stub /** * 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, @@ -10302,12 +10298,6 @@ public class WindowManagerService extends IWindowManager.Stub 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); @@ -10703,28 +10693,23 @@ public class WindowManagerService extends IWindowManager.Stub } 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) { @@ -11001,8 +10986,6 @@ public class WindowManagerService extends IWindowManager.Stub mFreezeGcPending = now; } - if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException()); - mDisplayFrozen = true; if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; @@ -11026,8 +11009,6 @@ public class WindowManagerService extends IWindowManager.Stub 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/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 @@ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject o 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_set_agps_server(JNIEnv* env, jo 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/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index b07a10b..186b349 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -7,8 +7,10 @@ LOCAL_MODULE_TAGS := tests # Include all test java files. LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES := easymocklib LOCAL_JAVA_LIBRARIES := android.test.runner services + LOCAL_PACKAGE_NAME := FrameworksServicesTests LOCAL_CERTIFICATE := platform diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 5ce109f..f115f42 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -23,6 +23,19 @@ <application> <uses-library android:name="android.test.runner" /> + + <service android:name="com.android.server.AccessibilityManagerServiceTest$MyFirstMockAccessibilityService"> + <intent-filter> + <action android:name="android.accessibilityservice.AccessibilityService"/> + </intent-filter> + </service> + + <service android:name="com.android.server.AccessibilityManagerServiceTest$MySecondMockAccessibilityService"> + <intent-filter> + <action android:name="android.accessibilityservice.AccessibilityService"/> + </intent-filter> + </service> + </application> <instrumentation diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java new file mode 100644 index 0000000..0410635 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java @@ -0,0 +1,691 @@ +/* + * 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 com.android.server; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.os.Message; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.IAccessibilityManager; +import android.view.accessibility.IAccessibilityManagerClient; + +/** + * This test exercises the + * {@link com.android.server.AccessibilityManagerService} by mocking the + * {@link android.view.accessibility.AccessibilityManager} which talks to to the + * service. The service itself is interacting with the platform. Note: Testing + * the service in full isolation would require significant amount of work for + * mocking all system interactions. It would also require a lot of mocking code. + */ +public class AccessibilityManagerServiceTest extends AndroidTestCase { + + /** + * Timeout required for pending Binder calls or event processing to + * complete. + */ + private static final long TIMEOUT_BINDER_CALL = 100; + + /** + * Timeout used for testing that a service is notified only upon a + * notification timeout. + */ + private static final long TIMEOUT_TEST_NOTIFICATION_TIMEOUT = 300; + + /** + * The package name. + */ + private static String sPackageName; + + /** + * The interface used to talk to the tested service. + */ + private IAccessibilityManager mManagerService; + + @Override + public void setContext(Context context) { + super.setContext(context); + if (sPackageName == null) { + sPackageName = context.getPackageName(); + } + } + + /** + * Creates a new instance. + */ + public AccessibilityManagerServiceTest() { + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + mManagerService = IAccessibilityManager.Stub.asInterface(iBinder); + } + + @LargeTest + public void testAddClient_AccessibilityDisabledThenEnabled() throws Exception { + // make sure accessibility is disabled + ensureAccessibilityEnabled(mContext, false); + + // create a client mock instance + MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); + + // invoke the method under test + boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient); + + // check expected result + assertFalse("The client must be disabled since accessibility is disabled.", + enabledAccessibilityDisabled); + + // enable accessibility + ensureAccessibilityEnabled(mContext, true); + + // invoke the method under test + boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient); + + // check expected result + assertTrue("The client must be enabled since accessibility is enabled.", + enabledAccessibilityEnabled); + } + + @LargeTest + public void testAddClient_AccessibilityEnabledThenDisabled() throws Exception { + // enable accessibility before registering the client + ensureAccessibilityEnabled(mContext, true); + + // create a client mock instance + MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); + + // invoke the method under test + boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient); + + // check expected result + assertTrue("The client must be enabled since accessibility is enabled.", + enabledAccessibilityEnabled); + + // disable accessibility + ensureAccessibilityEnabled(mContext, false); + + // invoke the method under test + boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient); + + // check expected result + assertFalse("The client must be disabled since accessibility is disabled.", + enabledAccessibilityDisabled); + } + + @LargeTest + public void testGetAccessibilityServicesList() throws Exception { + boolean firstMockServiceInstalled = false; + boolean secondMockServiceInstalled = false; + + String packageName = getContext().getPackageName(); + String firstMockServiceClassName = MyFirstMockAccessibilityService.class.getName(); + String secondMockServiceClassName = MySecondMockAccessibilityService.class.getName(); + + // look for the two mock services + for (ServiceInfo serviceInfo : mManagerService.getAccessibilityServiceList()) { + if (packageName.equals(serviceInfo.packageName)) { + if (firstMockServiceClassName.equals(serviceInfo.name)) { + firstMockServiceInstalled = true; + } else if (secondMockServiceClassName.equals(serviceInfo.name)) { + secondMockServiceInstalled = true; + } + } + } + + // check expected result + assertTrue("First mock service must be installed", firstMockServiceInstalled); + assertTrue("Second mock service must be installed", secondMockServiceInstalled); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_MatchingPackageAndEventType() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations + service.expectEvent(sentEvent); + service.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_NotMatchingPackage() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + sentEvent.setPackageName("no.service.registered.for.this.package"); + + // set expectations + service.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_NotMatchingEventType() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + sentEvent.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + + // set expectations + service.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_NotifivationAfterTimeout() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo info = MockAccessibilityService.createDefaultInfo(); + info.notificationTimeout = TIMEOUT_TEST_NOTIFICATION_TIMEOUT; + service.setServiceInfo(info); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate the first event to be sent + AccessibilityEvent firstEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(firstEvent); + + // create and populate the second event to be sent + AccessibilityEvent secondEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(secondEvent); + + // set expectations + service.expectEvent(secondEvent); + service.replay(); + + // send the events + mManagerService.sendAccessibilityEvent(firstEvent); + mManagerService.sendAccessibilityEvent(secondEvent); + + // wait for #sendAccessibilityEvent to reach the backing service + Thread.sleep(TIMEOUT_BINDER_CALL); + + try { + service.verify(); + fail("No events must be dispatched before the expiration of the notification timeout."); + } catch (IllegalStateException ise) { + /* expected */ + } + + // wait for the configured notification timeout to expire + Thread.sleep(TIMEOUT_TEST_NOTIFICATION_TIMEOUT); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_DiffFeedback() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName, + MySecondMockAccessibilityService.sComponentName); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo firstInfo = MockAccessibilityService.createDefaultInfo(); + firstInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE; + firstService.setServiceInfo(firstInfo); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + AccessibilityServiceInfo secondInfo = MockAccessibilityService.createDefaultInfo(); + secondInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_HAPTIC; + secondService.setServiceInfo(secondInfo); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.expectEvent(sentEvent); + firstService.replay(); + + // set expectations for the second mock service + secondService.expectEvent(sentEvent); + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName, + MySecondMockAccessibilityService.sComponentName); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.expectEvent(sentEvent); + firstService.replay(); + + // set expectations for the second mock service + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_OneDefault() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName, + MySecondMockAccessibilityService.sComponentName); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo(); + firstInfo.flags = AccessibilityServiceInfo.DEFAULT; + firstService.setServiceInfo(firstInfo); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + secondService.setServiceInfo(MySecondMockAccessibilityService.createDefaultInfo()); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.replay(); + + // set expectations for the second mock service + secondService.expectEvent(sentEvent); + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_TwoDefault() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName, + MySecondMockAccessibilityService.sComponentName); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo(); + firstInfo.flags = AccessibilityServiceInfo.DEFAULT; + firstService.setServiceInfo(firstInfo); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + AccessibilityServiceInfo secondInfo = MyFirstMockAccessibilityService.createDefaultInfo(); + secondInfo.flags = AccessibilityServiceInfo.DEFAULT; + secondService.setServiceInfo(firstInfo); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.expectEvent(sentEvent); + firstService.replay(); + + // set expectations for the second mock service + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testInterrupt() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName, + MySecondMockAccessibilityService.sComponentName); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // set expectations for the first mock service + firstService.expectInterrupt(); + firstService.replay(); + + // set expectations for the second mock service + secondService.expectInterrupt(); + secondService.replay(); + + // call the method under test + mManagerService.interrupt(); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + /** + * Fully populates the {@link AccessibilityEvent} to marshal. + * + * @param sentEvent The event to populate. + */ + private void fullyPopulateDefaultAccessibilityEvent(AccessibilityEvent sentEvent) { + sentEvent.setAddedCount(1); + sentEvent.setBeforeText("BeforeText"); + sentEvent.setChecked(true); + sentEvent.setClassName("foo.bar.baz.Class"); + sentEvent.setContentDescription("ContentDescription"); + sentEvent.setCurrentItemIndex(1); + sentEvent.setEnabled(true); + sentEvent.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED); + sentEvent.setEventTime(1000); + sentEvent.setFromIndex(1); + sentEvent.setFullScreen(true); + sentEvent.setItemCount(1); + sentEvent.setPackageName("foo.bar.baz"); + sentEvent.setParcelableData(Message.obtain(null, 1, null)); + sentEvent.setPassword(true); + sentEvent.setRemovedCount(1); + } + + /** + * This class is a mock {@link IAccessibilityManagerClient}. + */ + public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub { + boolean mIsEnabled; + + public void setEnabled(boolean enabled) { + mIsEnabled = enabled; + } + } + + /** + * Ensures accessibility is in a given state by writing the state to the + * settings and waiting until the accessibility manager service pick it up. + * + * @param context A context handle to access the settings. + * @param enabled The accessibility state to write to the settings. + * @throws Exception If any error occurs. + */ + private void ensureAccessibilityEnabled(Context context, boolean enabled) throws Exception { + boolean isEnabled = (Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1 ? true : false); + + if (isEnabled == enabled) { + return; + } + + Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, + enabled ? 1 : 0); + + // wait the accessibility manager service to pick the change up + Thread.sleep(TIMEOUT_BINDER_CALL); + } + + /** + * Ensures the only {@link MockAccessibilityService}s with given component + * names are enabled by writing to the system settings and waiting until the + * accessibility manager service picks that up. + * + * @param context A context handle to access the settings. + * @param componentNames The string representation of the + * {@link ComponentName}s to enable. + * @throws Exception Exception If any error occurs. + */ + private void ensureOnlyMockServicesEnabled(Context context, String... componentNames) + throws Exception { + String enabledServices = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + + StringBuilder servicesToEnable = new StringBuilder(); + for (String componentName : componentNames) { + servicesToEnable.append(componentName).append(":"); + } + + if (servicesToEnable.equals(enabledServices)) { + return; + } + + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, servicesToEnable.toString()); + + // wait the system to perform asynchronous processing + Thread.sleep(TIMEOUT_BINDER_CALL); + } + + /** + * Asserts the the mock accessibility service has been successfully verified + * (which is it has received the expected method calls with expected + * arguments) within the {@link #TIMEOUT_BINDER_CALL}. The verified state is + * checked by polling upon small intervals. + * + * @param service The service to verify. + * @throws Exception If the verification has failed with exception after the + * {@link #TIMEOUT_BINDER_CALL}. + */ + private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service) + throws Exception { + Exception lastVerifyException = null; + long beginTime = SystemClock.uptimeMillis(); + long pollTmeout = TIMEOUT_BINDER_CALL / 5; + + // poll until the timeout has elapsed + while (SystemClock.uptimeMillis() - beginTime < TIMEOUT_BINDER_CALL) { + // sleep first since immediate call will always fail + try { + Thread.sleep(pollTmeout); + } catch (InterruptedException ie) { + /* ignore */ + } + // poll for verification and if this fails save the exception and + // keep polling + try { + service.verify(); + // reset so it does not accept more events + service.reset(); + return; + } catch (Exception e) { + lastVerifyException = e; + } + } + + // reset, we have already failed + service.reset(); + + // always not null + throw lastVerifyException; + } + + /** + * This class is the first mock {@link AccessibilityService}. + */ + public static class MyFirstMockAccessibilityService extends MockAccessibilityService { + + /** + * The service {@link ComponentName} flattened as a string. + */ + static final String sComponentName = new ComponentName( + sPackageName, + MyFirstMockAccessibilityService.class.getName() + ).flattenToShortString(); + + /** + * Handle to the service instance. + */ + static MyFirstMockAccessibilityService sInstance; + + /** + * Creates a new instance. + */ + public MyFirstMockAccessibilityService() { + sInstance = this; + } + } + + /** + * This class is the first mock {@link AccessibilityService}. + */ + public static class MySecondMockAccessibilityService extends MockAccessibilityService { + + /** + * The service {@link ComponentName} flattened as a string. + */ + static final String sComponentName = new ComponentName( + sPackageName, + MySecondMockAccessibilityService.class.getName() + ).flattenToShortString(); + + /** + * Handle to the service instance. + */ + static MySecondMockAccessibilityService sInstance; + + /** + * Creates a new instance. + */ + public MySecondMockAccessibilityService() { + sInstance = this; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java new file mode 100644 index 0000000..38fed22 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java @@ -0,0 +1,257 @@ +/* + * 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 com.android.server; + +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reportMatcher; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; + +import org.easymock.IArgumentMatcher; + +import android.content.pm.ServiceInfo; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; +import android.view.accessibility.IAccessibilityManagerClient; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for the AccessibilityManager which mocking the backing service. + */ +public class AccessibilityManagerTest extends AndroidTestCase { + + /** + * Timeout required for pending Binder calls or event processing to + * complete. + */ + public static final long TIMEOUT_BINDER_CALL = 50; + + /** + * The reusable mock {@link IAccessibilityManager}. + */ + private final IAccessibilityManager mMockServiceInterface = + createStrictMock(IAccessibilityManager.class); + + @Override + public void setUp() throws Exception { + reset(mMockServiceInterface); + } + + @MediumTest + public void testGetAccessibilityServiceList() throws Exception { + // create a list of installed accessibility services the mock service returns + List<ServiceInfo> expectedServices = new ArrayList<ServiceInfo>(); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.name = "TestServiceInfoName"; + expectedServices.add(serviceInfo); + + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.getAccessibilityServiceList()).andReturn(expectedServices); + replay(mockServiceInterface); + + // invoke the method under test + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + List<ServiceInfo> receivedServices = manager.getAccessibilityServiceList(); + + // check expected result (list equals() compares it contents as well) + assertEquals("All expected services must be returned", receivedServices, expectedServices); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @MediumTest + public void testInterrupt() throws Exception { + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + mockServiceInterface.interrupt(); + replay(mockServiceInterface); + + // invoke the method under test + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + manager.interrupt(); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @LargeTest + public void testIsEnabled() throws Exception { + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + replay(mockServiceInterface); + + // invoke the method under test + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + boolean isEnabledServiceEnabled = manager.isEnabled(); + + // check expected result + assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled); + + // disable accessibility + manager.getClient().setEnabled(false); + + // wait for the asynchronous IBinder call to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // invoke the method under test + boolean isEnabledServcieDisabled = manager.isEnabled(); + + // check expected result + assertFalse("Must be disabled since the mock service is disabled", + isEnabledServcieDisabled); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @MediumTest + public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception { + // create an event to be dispatched + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) + .andReturn(true); + expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) + .andReturn(false); + replay(mockServiceInterface); + + // invoke the method under test (manager and service in different processes) + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + manager.sendAccessibilityEvent(sentEvent); + + // check expected result + AccessibilityEvent nextEventDifferentProcesses = AccessibilityEvent.obtain(); + assertSame("The manager and the service are in different processes, so the event must be " + + "recycled", sentEvent, nextEventDifferentProcesses); + + // invoke the method under test (manager and service in the same process) + manager.sendAccessibilityEvent(sentEvent); + + // check expected result + AccessibilityEvent nextEventSameProcess = AccessibilityEvent.obtain(); + assertNotSame("The manager and the service are in the same process, so the event must not" + + "be recycled", sentEvent, nextEventSameProcess); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @MediumTest + public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception { + // create an event to be dispatched + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(false); + replay(mockServiceInterface); + + // invoke the method under test (accessibility disabled) + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + try { + manager.sendAccessibilityEvent(sentEvent); + fail("No accessibility events are sent if accessibility is disabled"); + } catch (IllegalStateException ise) { + // check expected result + assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage()); + } + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + /** + * Determines if an {@link AccessibilityEvent} passed as a method argument + * matches expectations. + * + * @param matched The event to check. + * @return True if expectations are matched. + */ + private static AccessibilityEvent eqAccessibilityEvent(AccessibilityEvent matched) { + reportMatcher(new AccessibilityEventMather(matched)); + return null; + } + + /** + * Determines if an {@link IAccessibilityManagerClient} passed as a method argument + * matches expectations which in this case are that any instance is accepted. + * + * @return <code>null</code>. + */ + private static IAccessibilityManagerClient anyIAccessibilityManagerClient() { + reportMatcher(new AnyIAccessibilityManagerClientMather()); + return null; + } + + /** + * Matcher for {@link AccessibilityEvent}s. + */ + private static class AccessibilityEventMather implements IArgumentMatcher { + private AccessibilityEvent mExpectedEvent; + + public AccessibilityEventMather(AccessibilityEvent expectedEvent) { + mExpectedEvent = expectedEvent; + } + + public boolean matches(Object matched) { + if (!(matched instanceof AccessibilityEvent)) { + return false; + } + AccessibilityEvent receivedEvent = (AccessibilityEvent) matched; + return mExpectedEvent.getEventType() == receivedEvent.getEventType(); + } + + public void appendTo(StringBuffer buffer) { + buffer.append("sendAccessibilityEvent()"); + buffer.append(" with event type \""); + buffer.append(mExpectedEvent.getEventType()); + buffer.append("\""); + } + } + + /** + * Matcher for {@link IAccessibilityManagerClient}s. + */ + private static class AnyIAccessibilityManagerClientMather implements IArgumentMatcher { + public boolean matches(Object matched) { + if (!(matched instanceof IAccessibilityManagerClient)) { + return false; + } + return true; + } + + public void appendTo(StringBuffer buffer) { + buffer.append("addClient() with any IAccessibilityManagerClient"); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java new file mode 100644 index 0000000..663c121 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java @@ -0,0 +1,230 @@ +/* + * 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 com.android.server; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.os.Message; +import android.view.accessibility.AccessibilityEvent; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import junit.framework.TestCase; + +/** + * This is the base class for mock {@link AccessibilityService}s. + */ +public abstract class MockAccessibilityService extends AccessibilityService { + + /** + * The event this service expects to receive. + */ + private final Queue<AccessibilityEvent> mExpectedEvents = new LinkedList<AccessibilityEvent>(); + + /** + * Interruption call this service expects to receive. + */ + private boolean mExpectedInterrupt; + + /** + * Flag if the mock is currently replaying. + */ + private boolean mReplaying; + + /** + * Creates an {@link AccessibilityServiceInfo} populated with default + * values. + * + * @return The default info. + */ + public static AccessibilityServiceInfo createDefaultInfo() { + AccessibilityServiceInfo defaultInfo = new AccessibilityServiceInfo(); + defaultInfo.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED; + defaultInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE; + defaultInfo.flags = 0; + defaultInfo.notificationTimeout = 0; + defaultInfo.packageNames = new String[] { + "foo.bar.baz" + }; + + return defaultInfo; + } + + /** + * Starts replaying the mock. + */ + public void replay() { + mReplaying = true; + } + + /** + * Verifies if all expected service methods have been called. + */ + public void verify() { + if (!mReplaying) { + throw new IllegalStateException("Did you forget to call replay()"); + } + + if (mExpectedInterrupt) { + throw new IllegalStateException("Expected call to #interrupt() not received"); + } + if (!mExpectedEvents.isEmpty()) { + throw new IllegalStateException("Expected a call to onAccessibilityEvent() for " + + "events \"" + mExpectedEvents + "\" not received"); + } + } + + /** + * Resets this instance so it can be reused. + */ + public void reset() { + mExpectedEvents.clear(); + mExpectedInterrupt = false; + mReplaying = false; + } + + /** + * Sets an expected call to + * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as + * argument. + * + * @param expectedEvent The expected event argument. + */ + public void expectEvent(AccessibilityEvent expectedEvent) { + mExpectedEvents.add(expectedEvent); + } + + /** + * Sets an expected call of {@link #onInterrupt()}. + */ + public void expectInterrupt() { + mExpectedInterrupt = true; + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent receivedEvent) { + if (!mReplaying) { + return; + } + + if (mExpectedEvents.isEmpty()) { + throw new IllegalStateException("Unexpected event: " + receivedEvent); + } + + AccessibilityEvent expectedEvent = mExpectedEvents.poll(); + assertEqualsAccessiblityEvent(expectedEvent, receivedEvent); + } + + @Override + public void onInterrupt() { + if (!mReplaying) { + return; + } + + if (!mExpectedInterrupt) { + throw new IllegalStateException("Unexpected call to onInterrupt()"); + } + + mExpectedInterrupt = false; + } + + /** + * Compares all properties of the <code>expectedEvent</code> and the + * <code>receviedEvent</code> to verify that the received event is the one + * that is expected. + */ + private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent, + AccessibilityEvent receivedEvent) { + TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(), + receivedEvent.getAddedCount()); + TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(), + receivedEvent.getBeforeText()); + TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(), + receivedEvent.isChecked()); + TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(), + receivedEvent.getClassName()); + TestCase.assertEquals("contentDescription has incorrect value", expectedEvent + .getContentDescription(), receivedEvent.getContentDescription()); + TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent + .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex()); + TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(), + receivedEvent.isEnabled()); + TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(), + receivedEvent.getEventType()); + TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(), + receivedEvent.getFromIndex()); + TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(), + receivedEvent.isFullScreen()); + TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(), + receivedEvent.getItemCount()); + assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent); + TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(), + receivedEvent.isPassword()); + TestCase.assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(), + receivedEvent.getRemovedCount()); + assertEqualsText(expectedEvent, receivedEvent); + } + + /** + * Compares the {@link android.os.Parcelable} data of the + * <code>expectedEvent</code> and <code>receivedEvent</code> to verify that + * the received event is the one that is expected. + */ + private void assertEqualsNotificationAsParcelableData(AccessibilityEvent expectedEvent, + AccessibilityEvent receivedEvent) { + String message = "parcelableData has incorrect value"; + Message expectedMessage = (Message) expectedEvent.getParcelableData(); + Message receivedMessage = (Message) receivedEvent.getParcelableData(); + + if (expectedMessage == null) { + if (receivedMessage == null) { + return; + } + } + + TestCase.assertNotNull(message, receivedMessage); + + // we do a very simple sanity check since we do not test Message + TestCase.assertEquals(message, expectedMessage.what, receivedMessage.what); + } + + /** + * Compares the text of the <code>expectedEvent</code> and + * <code>receivedEvent</code> by comparing the string representation of the + * corresponding {@link CharSequence}s. + */ + private void assertEqualsText(AccessibilityEvent expectedEvent, + AccessibilityEvent receivedEvent) { + String message = "text has incorrect value"; + List<CharSequence> expectedText = expectedEvent.getText(); + List<CharSequence> receivedText = receivedEvent.getText(); + + TestCase.assertEquals(message, expectedText.size(), receivedText.size()); + + Iterator<CharSequence> expectedTextIterator = expectedText.iterator(); + Iterator<CharSequence> receivedTextIterator = receivedText.iterator(); + + for (int i = 0; i < expectedText.size(); i++) { + // compare the string representation + TestCase.assertEquals(message, expectedTextIterator.next().toString(), + receivedTextIterator.next().toString()); + } + } +} 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 @@ public class PhoneNumberUtils 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 @@ public class PhoneNumberUtils // 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 @@ public class PhoneNumberUtils * 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 @@ public class PhoneNumberUtils * * @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 @@ public class PhoneNumberUtils * * @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 @@ public class PhoneNumberUtils * @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 @@ public class PhoneNumberUtils 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 @@ public class PhoneNumberUtils 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 @@ public class PhoneNumberUtils 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 @@ public class PhoneNumberUtils //===== 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 @@ public class PhoneNumberUtils 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 @@ public class PhoneNumberUtils /** * 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.RemoteException; 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 @@ import java.util.List; /* * 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 @@ import java.util.List; * 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 @@ public final class SmsManager { * 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 @@ public final class SmsManager { * @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 @@ public final class SmsManager { * 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 @@ public final class SmsManager { * @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 @@ public final class SmsManager { * 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 @@ public final class SmsManager { * @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.os.Parcel; 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 @@ import android.os.Handler; */ 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 @@ public interface CommandsInterface { // 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 @@ public interface CommandsInterface { 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 @@ public interface CommandsInterface { 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 @@ public interface CommandsInterface { /** * (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 @@ public interface CommandsInterface { /** * 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 @@ public interface CommandsInterface { /** * 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 @@ public class GsmAlphabet { * 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 @@ public class GsmAlphabet { * 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 @@ public class GsmAlphabet { * * @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 @@ public class GsmAlphabet { } /** - * 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 @@ public abstract class IccSmsInterfaceManager extends ISms.Stub { * @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 @@ public abstract class IccSmsInterfaceManager extends ISms.Stub { * 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 @@ public abstract class IccSmsInterfaceManager extends ISms.Stub { * 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 @@ public abstract class IccSmsInterfaceManager extends ISms.Stub { * <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 @@ public final class RIL extends BaseCommands implements CommandsInterface { } - //***** Handler implemementation - - public void + //***** Handler implementation + @Override public void handleMessage(Message msg) { RILRequest rr = (RILRequest)(msg.obj); RILRequest req = null; @@ -780,7 +779,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { 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 @@ public abstract class SMSDispatcher extends Handler { 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 @@ public abstract class SMSDispatcher extends Handler { mCm.unregisterForOn(this); } + @Override protected void finalize() { Log.d(TAG, "SMSDispatcher finalized"); } @@ -344,7 +345,7 @@ public abstract class SMSDispatcher extends Handler { 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 @@ public abstract class SMSDispatcher extends Handler { 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 @@ public abstract class SMSDispatcher extends Handler { 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 @@ public abstract class SMSDispatcher extends Handler { * @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 @@ public abstract class SMSDispatcher extends Handler { * 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 @@ public abstract class SMSDispatcher extends Handler { * 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 @@ public abstract class SMSDispatcher extends Handler { * <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 @@ public abstract class SMSDispatcher extends Handler { * 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 @@ public abstract class SMSDispatcher extends Handler { */ 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 @@ public abstract class SMSDispatcher extends Handler { } } - 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 @@ import java.util.ArrayList; */ 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 @@ public abstract class SmsMessageBase { /** * 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.io.ByteArrayOutputStream; 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 @@ final class CdmaSMSDispatcher extends SMSDispatcher { * @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 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } /** {@inheritDoc} */ + @Override protected int dispatchMessage(SmsMessageBase smsb) { // If sms is null, means there was a parsing error. @@ -176,7 +177,7 @@ final class CdmaSMSDispatcher extends SMSDispatcher { * 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 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } /** {@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 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } /** {@inheritDoc} */ + @Override protected void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) { SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( @@ -356,6 +359,7 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } /** {@inheritDoc} */ + @Override protected void sendMultipartText(String destAddr, String scAddr, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) { @@ -364,7 +368,7 @@ final class CdmaSMSDispatcher extends SMSDispatcher { * 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 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } /** {@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 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } /** {@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 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } /** {@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; 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.SmsHeader; 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 @@ public final class BearerData { 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 @@ public final class BearerData { * 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 @@ final class GsmSMSDispatcher extends SMSDispatcher { * @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 @@ final class GsmSMSDispatcher extends SMSDispatcher { /** {@inheritDoc} */ + @Override protected int dispatchMessage(SmsMessageBase smsb) { // If sms is null, means there was a parsing error. @@ -145,6 +147,7 @@ final class GsmSMSDispatcher extends SMSDispatcher { } /** {@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 @@ final class GsmSMSDispatcher extends SMSDispatcher { } /** {@inheritDoc} */ + @Override protected void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) { SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( @@ -161,6 +165,7 @@ final class GsmSMSDispatcher extends SMSDispatcher { } /** {@inheritDoc} */ + @Override protected void sendMultipartText(String destinationAddress, String scAddress, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) { @@ -300,8 +305,9 @@ final class GsmSMSDispatcher extends SMSDispatcher { } /** {@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 @@ final class GsmSMSDispatcher extends SMSDispatcher { * * @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 @@ final class GsmSMSDispatcher extends SMSDispatcher { } /** {@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 @@ final class GsmSMSDispatcher extends SMSDispatcher { } /** {@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 @@ final class GsmSMSDispatcher extends SMSDispatcher { } /** {@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 @@ final class GsmSMSDispatcher extends SMSDispatcher { } /** {@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 class SimSmsInterfaceManager extends IccSmsInterfaceManager { public void dispose() { } + @Override protected void finalize() { try { super.finalize(); @@ -191,6 +192,7 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { 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.EncodeException; 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 @@ import static android.telephony.SmsMessage.MessageClass; * 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 @@ public class SmsMessage extends SmsMessageBase{ // 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 @@ public class SmsMessage extends SmsMessageBase{ * @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 @@ public class SmsMessage extends SmsMessageBase{ return bo; } - static class PduParser { + private static class PduParser { byte pdu[]; int cur; SmsHeader userDataHeader; @@ -482,10 +481,6 @@ public class SmsMessage extends SmsMessageBase{ int mUserDataSeptetPadding; int mUserDataSize; - PduParser(String s) { - this(IccUtils.hexStringToBytes(s)); - } - PduParser(byte[] pdu) { this.pdu = pdu; cur = 0; @@ -537,7 +532,7 @@ public class SmsMessage extends SmsMessageBase{ 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 @@ public class SmsMessage extends SmsMessageBase{ 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 @@ public class SmsMessage extends SmsMessageBase{ /* * 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 @@ public class SmsMessage extends SmsMessageBase{ } /** - * 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 @@ public class SmsMessage extends SmsMessageBase{ 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 @@ public class SmsMessage extends SmsMessageBase{ } /** {@inheritDoc} */ + @Override public int getProtocolIdentifier() { return protocolIdentifier; } /** {@inheritDoc} */ + @Override public boolean isReplace() { return (protocolIdentifier & 0xc0) == 0x40 && (protocolIdentifier & 0x3f) > 0 @@ -829,12 +826,14 @@ public class SmsMessage extends SmsMessageBase{ } /** {@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 @@ public class SmsMessage extends SmsMessageBase{ } /** {@inheritDoc} */ + @Override public boolean isMWISetMessage() { if (isMwi && (mwiSense == true)) { return true; @@ -855,6 +855,7 @@ public class SmsMessage extends SmsMessageBase{ } /** {@inheritDoc} */ + @Override public boolean isMwiDontStore() { if (isMwi && mwiDontStore) { return true; @@ -874,31 +875,34 @@ public class SmsMessage extends SmsMessageBase{ } /** {@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 @@ public class SmsMessage extends SmsMessageBase{ /** * {@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.BaseCommands; 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 @@ public final class SimulatedCommands extends BaseCommands /** * (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 @@ public final class SimulatedCommands extends BaseCommands * 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 @@ public final class SimulatedCommands extends BaseCommands * 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 @@ public final class SimulatedCommands extends BaseCommands * 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 @@ public final class SimulatedCommands extends BaseCommands * 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 @@ public final class SimulatedCommands extends BaseCommands * 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 @@ public final class SimulatedCommands extends BaseCommands * 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 @@ public final class SimulatedCommands extends BaseCommands * 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 @@ public final class SimulatedCommands extends BaseCommands * "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 @@ public final class SimulatedCommands extends BaseCommands * 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 @@ public final class SimulatedCommands extends BaseCommands * 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 @@ public final class SimulatedCommands extends BaseCommands * "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 @@ public final class SimulatedCommands extends BaseCommands * "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 @@ public final class SimulatedCommands extends BaseCommands /** * * 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 @@ public final class SimulatedCommands extends BaseCommands /** * 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 @@ public final class SimulatedCommands extends BaseCommands * * @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 @@ public final class SimulatedCommands extends BaseCommands ret[11] = null; ret[12] = null; ret[13] = null; - ret[14] = null; resultSuccess(result, ret); } @@ -877,7 +874,7 @@ public final class SimulatedCommands extends BaseCommands /** * 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 @@ public final class SimulatedCommands extends BaseCommands /** * 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 @@ public final class SimulatedCommands extends BaseCommands /** * 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 @@ public final class SimulatedCommands extends BaseCommands /** * 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 @@ public final class SimulatedCommands extends BaseCommands unimplemented(response); } + @Deprecated public void setupDefaultPDP(String apn, String user, String password, Message result) { unimplemented(result); } @@ -950,9 +948,7 @@ public final class SimulatedCommands extends BaseCommands 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 @@ public final class SimulatedCommands extends BaseCommands } /** - * 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 com.android.internal.util.HexDump; 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 @@ package com.android.internal.telephony.gsm; 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 @@ import java.util.TreeSet; */ 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 @@ public class TestGrouping { 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 @@ public class TestGrouping { } } } + 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/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java new file mode 100644 index 0000000..a11e25d --- /dev/null +++ b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java @@ -0,0 +1,28 @@ +/* + * 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 public constructor test case that should be loaded. + */ +public class PublicConstructorTest extends TestCase { + + public void testPublicConstructor() { + } +} diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java index 77fd3ed..e28603e 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java @@ -79,19 +79,22 @@ public class FileFilter { }; 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-multi-touch-events.html"); // Requires multi-touch + ignoreResultList.add("fast/events/touch/basic-single-touch-events.html"); // Delta of touch move is not great enough for Android. Fixed upstream in WebKit change 58208. + ignoreResultList.add("fast/events/touch/send-oncancel-event.html"); // Expected output is incorrect. Fixed upstream in WebKit change 58058. + ignoreResultList.add("fast/events/touch/touch-target.html"); // Requires multi-touch 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 +107,9 @@ public class FileFilter { 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 @@ public class TestShellActivity extends Activity implements LayoutTestController mDumpWebKitData = false; mGetDrawtime = false; mSaveImagePath = null; + setDefaultWebSettings(mWebView); } private long[] getDrawWebViewTime(WebView view, int count) { @@ -786,6 +787,19 @@ public class TestShellActivity extends Activity implements LayoutTestController 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 @@ public class TestShellActivity extends Activity implements LayoutTestController 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 |