diff options
author | Neal Nguyen <tommyn@google.com> | 2010-01-13 10:42:43 -0800 |
---|---|---|
committer | Neal Nguyen <tommyn@google.com> | 2010-01-29 13:35:51 -0800 |
commit | 1a44d5dcabc18cd5ef111f732ccff91683a1a093 (patch) | |
tree | e370267a65ba54a43e3026ff9b282cc4e3dad869 /core | |
parent | 35ec7863e18ce3d242010b76a50df5a8c285325b (diff) | |
download | frameworks_base-1a44d5dcabc18cd5ef111f732ccff91683a1a093.zip frameworks_base-1a44d5dcabc18cd5ef111f732ccff91683a1a093.tar.gz frameworks_base-1a44d5dcabc18cd5ef111f732ccff91683a1a093.tar.bz2 |
Phase 2 of test cleanup: moving test files from AndroidTests closer to their sources.
Most of these are file moves; a couple notable exceptions are the changes due to the move, and fixing up test code:
- database/DatabaseCursorTest.java
- database/DatabaseStatementTest.java
- net/UriTest.java
Diffstat (limited to 'core')
143 files changed, 23751 insertions, 5 deletions
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 77c176f..1fb9852 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -5,10 +5,14 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests # Include all test java files. -LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) \ + src/android/os/IAidlTest.aidl LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_PACKAGE_NAME := FrameworksCoreTests +LOCAL_CERTIFICATE := platform + include $(BUILD_PACKAGE) diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 02961f4..8d7e187 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -16,6 +16,46 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworks.coretests"> + + <permission android:name="com.android.frameworks.coretests.permission.TEST_GRANTED" + android:protectionLevel="normal" + android:label="@string/permlab_testGranted" + android:description="@string/permdesc_testGranted"> + <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> + <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" /> + <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" /> + <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" /> + <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" /> + <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" /> + </permission> + <permission android:name="com.android.frameworks.coretests.permission.TEST_DENIED" + android:protectionLevel="normal" + android:label="@string/permlab_testDenied" + android:description="@string/permdesc_testDenied" /> + + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.BROADCAST_STICKY" /> + <uses-permission android:name="android.permission.CLEAR_APP_CACHE" /> + <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" /> + <uses-permission android:name="android.permission.DELETE_CACHE_FILES" /> + <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.READ_CONTACTS" /> + <uses-permission android:name="android.permission.READ_LOGS"/> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.READ_SMS"/> + <uses-permission android:name="android.permission.USE_CREDENTIALS" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.WRITE_CONTACTS" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SMS"/> + <uses-permission android:name="android.permission.TEST_GRANTED" /> + <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" /> + <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" /> + <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" /> + <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> @@ -28,12 +68,8 @@ <!-- location test permissions --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/> - <uses-permission android:name="android.permission.WRITE_SETTINGS"/> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.HARDWARE_TEST" /> - <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <uses-permission android:name="android.permission.ACCESSIBILITY_EVENT_VIEW_TYPES" /> <uses-permission android:name="android.permission.ACCESSIBILITY_EVENT_TRANSITION_TYPES" /> <uses-permission android:name="android.permission.ACCESSIBILITY_EVENT_NOTIFICATION_TYPES" /> @@ -975,6 +1011,187 @@ </intent-filter> </activity> + + + <!-- Activity-level metadata --> + <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" /> + <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> + <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" /> + <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" /> + <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" /> + <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" /> + <meta-data android:name="com.android.frameworks.coretests.reference" + android:resource="@xml/metadata_app" /> + + <activity android:name="AndroidPerformanceTests" android:label="Android Performance Tests"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.UNIT_TEST" /> + </intent-filter> + </activity> + + <!-- Application components used for activity tests --> + + <activity android:name="android.app.activity.TestedActivity" + android:process=":remoteActivity"> + </activity> + <activity android:name="android.app.activity.LocalActivity" android:multiprocess="true"> + <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> + <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" /> + <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" /> + <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" /> + <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" /> + <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" /> + </activity> + <activity android:name="android.app.activity.TestedScreen" + android:process=":remoteScreen"> + </activity> + <activity android:name="android.app.activity.LocalScreen" android:multiprocess="true"> + </activity> + <activity android:name="android.app.activity.ClearTop" android:multiprocess="true" + android:launchMode="singleTop"> + </activity> + <activity android:name="android.app.activity.LocalDialog" android:multiprocess="true" + android:theme="@android:style/Theme.Dialog"> + </activity> + <activity android:name="android.app.activity.SubActivityScreen"> + </activity> + <activity android:name="android.app.activity.RemoteSubActivityScreen" + android:process=":remoteActivity"> + </activity> + <activity android:name="android.app.activity.LaunchpadActivity" android:multiprocess="true"> + </activity> + <activity android:name="android.app.activity.LaunchpadTabActivity" android:multiprocess="true"> + </activity> + + <receiver android:name="android.app.activity.AbortReceiver"> + <intent-filter android:priority="1"> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ABORT" /> + </intent-filter> + </receiver> + <receiver android:name="android.app.activity.LocalReceiver"> + <intent-filter android:priority="-1"> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ABORT" /> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ALL" /> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REPEAT" /> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_LOCAL" /> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_FAIL_REGISTER" /> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_FAIL_BIND" /> + </intent-filter> + <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> + <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" /> + <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" /> + <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" /> + <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" /> + <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" /> + </receiver> + <receiver android:name="android.app.activity.ResultReceiver"> + <intent-filter> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_RESULT" /> + </intent-filter> + </receiver> + <receiver android:name="android.app.activity.LocalGrantedReceiver" + android:permission="com.android.frameworks.coretests.permission.TEST_GRANTED"> + <intent-filter android:priority="-1"> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_LOCAL_GRANTED" /> + </intent-filter> + </receiver> + <receiver android:name="android.app.activity.LocalDeniedReceiver" + android:permission="com.android.frameworks.coretests.permission.TEST_DENIED"> + <intent-filter android:priority="-1"> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_LOCAL_DENIED" /> + </intent-filter> + </receiver> + <receiver android:name="android.app.activity.RemoteReceiver" + android:process=":remoteReceiver"> + <intent-filter android:priority="2"> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ABORT" /> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ALL" /> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REPEAT" /> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REMOTE" /> + </intent-filter> + </receiver> + <receiver android:name="android.app.activity.RemoteGrantedReceiver" + android:permission="com.android.frameworks.coretests.permission.TEST_GRANTED"> + <intent-filter android:priority="2"> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REMOTE_GRANTED" /> + </intent-filter> + </receiver> + <receiver android:name="android.app.activity.RemoteDeniedReceiver" + android:permission="com.android.frameworks.coretests.permission.TEST_DENIED"> + <intent-filter android:priority="2"> + <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REMOTE_DENIED" /> + </intent-filter> + </receiver> + <service android:name="android.app.activity.LocalService"> + <intent-filter> + <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL" /> + </intent-filter> + <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> + <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" /> + <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" /> + <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" /> + <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" /> + <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" /> + </service> + <service android:name="android.app.activity.LocalDeniedService" + android:permission="com.android.frameworks.coretests.permission.TEST_DENIED"> + <intent-filter> + <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL_DENIED" /> + </intent-filter> + </service> + <service android:name="android.app.activity.LocalGrantedService" + android:permission="com.android.frameworks.coretests.permission.TEST_GRANTED"> + <intent-filter> + <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL_GRANTED" /> + </intent-filter> + </service> + + <provider android:name="android.app.activity.LocalProvider" + android:authorities="com.android.frameworks.coretests.LocalProvider"> + <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> + <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" /> + <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" /> + <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" /> + <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" /> + <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" /> + </provider> + + <!-- Application components used for content tests --> + <provider android:name="android.content.MemoryFileProvider" + android:authorities="android.content.MemoryFileProvider" + android:process=":MemoryFileProvider"> + </provider> + + <!-- Application components used for os tests --> + + <service android:name="android.os.MessengerService" + android:process=":messengerService"> + </service> + + <!-- Application components used for search manager tests --> + + <activity android:name="android.app.activity.SearchableActivity" + android:label="Searchable Activity"> + <intent-filter> + <action android:name="android.intent.action.SEARCH" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + <meta-data android:name="android.app.searchable" + android:resource="@xml/searchable" /> + </activity> + + <provider android:name="android.app.SuggestionProvider" + android:authorities="android.app.SuggestionProvider"> + </provider> + + <!-- Used to test IPC. --> + <service android:name="com.android.frameworks.coretests.binder.BinderTestService" + android:process="binder.BinderTestService" /> + <service android:name="com.android.frameworks.coretests.binder.BinderPerformanceService" + android:process="binder.BinderPerformanceService" /> + <service android:name="com.android.frameworks.coretests.binder.BinderVsMessagingService" + android:process="binder.BinderVsMessagingService" /> </application> <instrumentation diff --git a/core/tests/coretests/assets/text.txt b/core/tests/coretests/assets/text.txt new file mode 100644 index 0000000..3d8c519 --- /dev/null +++ b/core/tests/coretests/assets/text.txt @@ -0,0 +1 @@ +OneTwoThreeFourFiveSixSevenEightNineTen
\ No newline at end of file diff --git a/core/tests/coretests/res/layout/layout_five.xml b/core/tests/coretests/res/layout/layout_five.xml new file mode 100644 index 0000000..9923eaf --- /dev/null +++ b/core/tests/coretests/res/layout/layout_five.xml @@ -0,0 +1,22 @@ +<?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. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <TextView android:id="@+id/text" android:text="@string/layout_five_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" /> +</LinearLayout> diff --git a/core/tests/coretests/res/layout/layout_four.xml b/core/tests/coretests/res/layout/layout_four.xml new file mode 100644 index 0000000..e5a78bc --- /dev/null +++ b/core/tests/coretests/res/layout/layout_four.xml @@ -0,0 +1,20 @@ +<?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. +*/ +--> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text" android:text="@string/layout_four_text_text"/> diff --git a/core/tests/coretests/res/layout/layout_one.xml b/core/tests/coretests/res/layout/layout_one.xml new file mode 100644 index 0000000..6966246 --- /dev/null +++ b/core/tests/coretests/res/layout/layout_one.xml @@ -0,0 +1,20 @@ +<?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. +*/ +--> +<view xmlns:android="http://schemas.android.com/apk/res/android" class="android.view.InflateTest$ViewOne" android:id="@+id/viewOne" android:layout_width="match_parent" android:layout_height="match_parent"/> diff --git a/core/tests/coretests/res/layout/layout_six.xml b/core/tests/coretests/res/layout/layout_six.xml new file mode 100644 index 0000000..b78082d --- /dev/null +++ b/core/tests/coretests/res/layout/layout_six.xml @@ -0,0 +1,27 @@ +<?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. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" /> +</LinearLayout> diff --git a/core/tests/coretests/res/layout/layout_tag.xml b/core/tests/coretests/res/layout/layout_tag.xml new file mode 100644 index 0000000..7fb0489 --- /dev/null +++ b/core/tests/coretests/res/layout/layout_tag.xml @@ -0,0 +1,23 @@ +<?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. +*/ +--> +<view xmlns:android="http://schemas.android.com/apk/res/android" + class="android.view.InflateTest$ViewOne" + android:id="@+id/viewOne" android:tag="MyTag" + android:layout_width="match_parent" android:layout_height="match_parent"/> diff --git a/core/tests/coretests/res/layout/layout_three.xml b/core/tests/coretests/res/layout/layout_three.xml new file mode 100644 index 0000000..7242fc8 --- /dev/null +++ b/core/tests/coretests/res/layout/layout_three.xml @@ -0,0 +1,27 @@ +<?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. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <view class="android.view.InflateTest$ViewOne" android:id="@+id/view1" android:layout_width="match_parent" android:layout_height="match_parent"/> + <view class="android.view.InflateTest$ViewOne" android:id="@+id/view2" android:layout_width="match_parent" android:layout_height="match_parent"/> + <view class="android.view.InflateTest$ViewOne" android:id="@+id/view3" android:layout_width="match_parent" android:layout_height="match_parent"/> + <view class="android.view.InflateTest$ViewOne" android:id="@+id/view4" android:layout_width="match_parent" android:layout_height="match_parent"/> + <view class="android.view.InflateTest$ViewOne" android:id="@+id/view5" android:layout_width="match_parent" android:layout_height="match_parent"/> + <view class="android.view.InflateTest$ViewOne" android:id="@+id/view6" android:layout_width="match_parent" android:layout_height="match_parent"/> +</LinearLayout> diff --git a/core/tests/coretests/res/layout/layout_two.xml b/core/tests/coretests/res/layout/layout_two.xml new file mode 100644 index 0000000..9fb7d3b --- /dev/null +++ b/core/tests/coretests/res/layout/layout_two.xml @@ -0,0 +1,23 @@ +<?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. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <view class="android.view.InflateTest$ViewOne" android:id="@+id/viewOne" android:layout_width="match_parent" android:layout_height="match_parent"/> +</LinearLayout> + diff --git a/core/tests/coretests/res/raw/medium.xml b/core/tests/coretests/res/raw/medium.xml new file mode 100644 index 0000000..5757a24 --- /dev/null +++ b/core/tests/coretests/res/raw/medium.xml @@ -0,0 +1,27 @@ +<?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. +*/ +--> +<LinearLayout id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <TextView id="@+id/text" android:text="S" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView id="@+id/text" android:text="M" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView id="@+id/text" android:text="T" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView id="@+id/text" android:text="W" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView id="@+id/text" android:text="H" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <TextView id="@+id/text" android:text="F" android:layout_width="match_parent" android:layout_height="wrap_content" /> +</LinearLayout> diff --git a/core/tests/coretests/res/raw/small.xml b/core/tests/coretests/res/raw/small.xml new file mode 100644 index 0000000..ee859b9 --- /dev/null +++ b/core/tests/coretests/res/raw/small.xml @@ -0,0 +1,20 @@ +<?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. +*/ +--> +<view class="com.android.tests.InflateTest$ViewOne" id="@+id/viewOne" android:layout_width="match_parent" android:layout_height="match_parent"/> diff --git a/core/tests/coretests/res/raw/text.txt b/core/tests/coretests/res/raw/text.txt new file mode 100644 index 0000000..3d8c519 --- /dev/null +++ b/core/tests/coretests/res/raw/text.txt @@ -0,0 +1 @@ +OneTwoThreeFourFiveSixSevenEightNineTen
\ No newline at end of file diff --git a/core/tests/coretests/res/raw/v21_backslash.vcf b/core/tests/coretests/res/raw/v21_backslash.vcf new file mode 100644 index 0000000..bd3002b --- /dev/null +++ b/core/tests/coretests/res/raw/v21_backslash.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD
+VERSION:2.1
+N:;A\;B\\;C\\\;;D;\:E;\\\\;
+FN:A;B\C\;D:E\\
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_complicated.vcf b/core/tests/coretests/res/raw/v21_complicated.vcf new file mode 100644 index 0000000..de34e16 --- /dev/null +++ b/core/tests/coretests/res/raw/v21_complicated.vcf @@ -0,0 +1,106 @@ +BEGIN:VCARD
+VERSION:2.1
+N:Gump;Forrest;Hoge;Pos;Tao
+FN:Joe Due
+ORG:Gump Shrimp Co.;Sales Dept.\;Manager;Fish keeper
+ROLE:Fish Cake Keeper!
+X-CLASS:PUBLIC
+TITLE:Shrimp Man
+TEL;WORK;VOICE:(111) 555-1212
+TEL;HOME;VOICE:(404) 555-1212
+TEL;CELL:0311111111
+TEL;VIDEO:0322222222
+TEL;VOICE:0333333333
+ADR;WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
+LABEL;WORK;ENCODING=QUOTED-PRINTABLE:100 Waters Edge=0D=0ABaytown, LA 30314=0D=0AUnited States of America
+ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
+LABEL;HOME;ENCODING=QUOTED-PRINTABLE:42 Plantation St.=0D=0A=
+Baytown, LA 30314=0D=0A=
+United States of America
+EMAIL;PREF;INTERNET:forrestgump@walladalla.com
+EMAIL;CELL:cell@example.com
+NOTE:The following note is the example from RFC 2045.
+NOTE;ENCODING=QUOTED-PRINTABLE:Now's the time =
+for all folk to come=
+ to the aid of their country.
+
+PHOTO;ENCODING=BASE64;TYPE=JPEG:
+ /9j/4QoPRXhpZgAATU0AKgAAAAgADQEOAAIAAAAPAAAAqgEPAAIAAAAHAAAAugEQAAIAAAAG
+ AAAAwgESAAMAAAABAAEAAAEaAAUAAAABAAAAyAEbAAUAAAABAAAA0AEoAAMAAAABAAIAAAEx
+ AAIAAAAOAAAA2AEyAAIAAAAUAAAA5gITAAMAAAABAAEAAIKYAAIAAAAOAAAA+odpAAQAAAAB
+ AAABhMSlAAcAAAB8AAABCAAABB4yMDA4MTAyOTEzNTUzMQAARG9Db01vAABEOTA1aQAAAABI
+ AAAAAQAAAEgAAAABRDkwNWkgVmVyMS4wMAAyMDA4OjEwOjI5IDEzOjU1OjQ3ACAgICAgICAg
+ ICAgICAAUHJpbnRJTQAwMzAwAAAABgABABQAFAACAQAAAAADAAAANAEABQAAAAEBAQAAAAEQ
+ gAAAAAAAEQkAACcQAAAPCwAAJxAAAAWXAAAnEAAACLAAACcQAAAcAQAAJxAAAAJeAAAnEAAA
+ AIsAACcQAAADywAAJxAAABvlAAAnEAAogpoABQAAAAEAAANqgp0ABQAAAAEAAANyiCIAAwAA
+ AAEAAgAAkAAABwAAAAQwMjIwkAMAAgAAABQAAAN6kAQAAgAAABQAAAOOkQEABwAAAAQBAgMA
+ kQIABQAAAAEAAAOikgEACgAAAAEAAAOqkgIABQAAAAEAAAOykgQACgAAAAEAAAO6kgUABQAA
+ AAEAAAPCkgcAAwAAAAEAAgAAkggAAwAAAAEAAAAAkgkAAwAAAAEAAAAAkgoABQAAAAEAAAPK
+ knwABwAAAAEAAAAAkoYABwAAABYAAAPSoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIAAwAA
+ AAEAYAAAoAMAAwAAAAEASAAAoAUABAAAAAEAAAQAog4ABQAAAAEAAAPoog8ABQAAAAEAAAPw
+ ohAAAwAAAAEAAgAAohcAAwAAAAEAAgAAowAABwAAAAEDAAAAowEABwAAAAEBAAAApAEAAwAA
+ AAEAAAAApAIAAwAAAAEAAAAApAMAAwAAAAEAAAAApAQABQAAAAEAAAP4pAUAAwAAAAEAHQAA
+ pAYAAwAAAAEAAAAApAcAAwAAAAEAAAAApAgAAwAAAAEAAAAApAkAAwAAAAEAAAAApAoAAwAA
+ AAEAAAAApAwAAwAAAAEAAgAAAAAAAAAAAFMAACcQAAABXgAAAGQyMDA4OjEwOjI5IDEzOjU1
+ OjMxADIwMDg6MTA6MjkgMTM6NTU6NDcAAAApiAAAGwAAAAKyAAAAZAAAAV4AAABkAAAAAAAA
+ AGQAAAAlAAAACgAADpIAAAPoAAAAAAAAAAAyMDA4MTAyOTEzNTUzMQAAICoAAAAKAAAq4gAA
+ AAoAAAAAAAAAAQACAAEAAgAAAARSOTgAAAIABwAAAAQwMTAwAAAAAAAGAQMAAwAAAAEABgAA
+ ARoABQAAAAEAAARsARsABQAAAAEAAAR0ASgAAwAAAAEAAgAAAgEABAAAAAEAAAR8AgIABAAA
+ AAEAAAWLAAAAAAAAAEgAAAABAAAASAAAAAH/2P/bAIQAIBYYHBgUIBwaHCQiICYwUDQwLCww
+ YkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxgEiJCQwKjBeNDRe
+ xoRwhMbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbG
+ /8AAEQgAeACgAwEhAAIRAQMRAf/EAaIAAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKCxAA
+ AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK
+ FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG
+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl
+ 5ufo6erx8vP09fb3+Pn6AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgsRAAIBAgQEAwQH
+ BQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBka
+ JicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
+ lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz
+ 9PX29/j5+v/aAAwDAQACEQMRAD8AFFSqKkZIoqRVpgSKKeBTEOApwFADsUYpgIRSEUANIppF
+ ICNhUTCgCMio2FICJhULCgC0oqVaAJFFSqKBkgFOApiHCnCgB2KMUCENJQA0imEUDGMKiYUA
+ RtUbUgIWqJhQBZSpFoAlWpVoGPFPFMQ7tSK2ODQA4yKO9HmKe9FxAzDHFIOlAAaYaAGNUTUD
+ ImqNqQETVE1AE6VKKAJFNSqaAHg08GmANIFFQM5Y5qJMBuT60ZNQIcrkVYSQMKuLGKaaasQx
+ qiagZE1RtSAjaomoAkQ1KpoAlU1IpoAkU07OBTArO+5qkV12Y71lfUBmaKkCRSuznrTFba2a
+ oCwGyM0E1qIjY1GxoGRNUZNICNqiagByGplNAEimpFNMB4YDvSucpxSYEIU04KazsAu1qArU
+ WELtPpTSposBNETt5pxNaoCNjUbGgCNjUZoGRtUTUgFU1KpoAkBqQHigCFnO7rUqOdlZp6gA
+ c+tODn1pXAXzD60eYfWncQvmNSGQ07gOMhCVEJGz1ptgS5yKYxqwGE1GxoAiamGkMapqVTQB
+ Kpp+eKAICfmqWM/Kaz6gANOBqQFzRmmAuaTNACsfkqMHmm9wJs8U0mtRDGNRsaAI2phpDI1N
+ SqaAJFNSA8UCISfmqSM/Kaz6jAHmnA1ICg0uaAFzSZpgKx+SmDrTe4E2eKaTWoiMmmMaAIzT
+ DSGRKakU0ASKaeDTERseakjPyms+oxAacDUgOBpc0gFzSZpgOY/KKYv3qrqIlpprQBjGoyaA
+ GGmmkMgU1IppgPBqQGgQu0Gn4wvFKwEQpwNZDHZpc0ALmigRKBleaQKBWtgA001QDGqM0gGm
+ mGkMrqakBoAepp4NMRIDTwaAE2A008GokgHxjd1pzKFpW0uAg5NSBBTirgOpDWgDTTTQAw0w
+ 0gGGmmgZWBp4pASKaeDTEOBp4NADwajbrUyBEkXWnSUdAGr1qeiAMSkNWAhphoAaaYaQDDTT
+ SGVRTwaYDxTwaBDwaeDQA4GlK5oauIeo20pGaLaAKqgU6hKwBSGmAhphoAaaYaQxhpppDKgN
+ PFMB4p4oEPFOBpgPBp4NAhwpwoAWloAKSgBDTTQMYaYaQDTTTSGA/9n/2wCEAAoHBwgHBgoI
+ CAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9
+ PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7
+ Ozs7Ozs7Ozs7Ozs7O//AABEIAEgAYAMBIQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAA
+ AQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNC
+ scEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp
+ anN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS
+ 09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
+ CQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVi
+ ctEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4
+ eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY
+ 2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AJ7SLgcVr20I4rNFGvbQAAHFaEUX
+ SrQi5HCMdKk8oY6VSJYx4hjpVaWMelAFC4iGDxWPdR8mkxmRdxjBrEvI+tZjN20xtHNbNqAc
+ UIDXg6Cr0WKtCY8XKQOyzOB3FKNWsyceZ+lS6sY6NkjvPSdwImBHUmmy4q076oCjOODWPdgc
+ 0MpGPdAYNYl4o5rNjNKzkyorZtXxihAa1vIDip7m9Frb7/4jwKcnyxbEzN3ieJppZsyZ4U1H
+ urzZau4mWVlNrGk0UuWPVa1YroXEIkHfrXZh5W90RWncAHmsi6bJNdQ0ZNw3BrGuiMGs2Mks
+ puBzWzbzdOaEBeOpR2oUtkk9hTru7iuo4m8wgemKyqTi04sBsfkEf68j8KlUQZz9o/SuZRj3
+ JYriAji4/Sp7W6htbV2aXcu70ramoxle4gN7HcIXjbis+4k5NdaaauhmVcv1rHuW61DGiG1m
+ 6c1s20/TmgAv5vmj57VKk3+ixnPc1xVV70h9CVJuOtSrL71hFgxzScUkkn+iY/2q1i9xDrGT
+ 9y31pJ5Otd1L+GhMy7mTrWXO2SapjRn28vTmta3nxjmgGOvJd2w1Kkv+ipz/ABGuOoveYdCe
+ ObjrU6y5rlsA8ycUksn+ij/eNaw6iJLNsW59zTJn6816FP4EJmbO+Saz5m602UjIgk4HNadv
+ LwKaBl+MpIMOMipp490SCJeF7CoqQvF2JuRqWQ4YEGrSiQJuKnHrXByMpki73GFBNXIoh9n2
+ SrnnOK6MPTbd3sSwIVF2qMCqkzHmuy1lYRnTHrVGWpZaMKB+BWlbycYoQM0IZDxzV+GU8c1a
+ IYy5Y+dnHatAsfsAHfArmS1mPoh1gT8x9qtk1rQX7tCe5DIapzGtGBQm71SlqGWjnIH6Vowt
+ zmhAy/E3vV6F6tEMuxlWIyAfrVxCCAO1VZEEyYA4AApxNGwyJ+lVJRUsaKMw61SlFQzRAP/Z
+
+X-ATTRIBUTE:Some String
+BDAY:19800101
+GEO:35.6563854,139.6994233
+URL:http://www.example.com/
+REV:20080424T195243Z
+END:VCARD
\ No newline at end of file diff --git a/core/tests/coretests/res/raw/v21_invalid_comment_line.vcf b/core/tests/coretests/res/raw/v21_invalid_comment_line.vcf new file mode 100644 index 0000000..f910710 --- /dev/null +++ b/core/tests/coretests/res/raw/v21_invalid_comment_line.vcf @@ -0,0 +1,10 @@ +BEGIN:vCard
+VERSION:2.1
+UID:357
+N:;Conference Call
+FN:Conference Call
+# This line must be ignored.
+NOTE;ENCODING=QUOTED-PRINTABLE:This is an (sharp ->=
+#<- sharp) example. This message must NOT be ignored.
+# This line must be ignored too.
+END:vCard
diff --git a/core/tests/coretests/res/raw/v21_japanese_1.vcf b/core/tests/coretests/res/raw/v21_japanese_1.vcf new file mode 100644 index 0000000..d05e2ff --- /dev/null +++ b/core/tests/coretests/res/raw/v21_japanese_1.vcf @@ -0,0 +1,6 @@ +BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ;;;;
+TEL;PREF;VOICE:0300000000
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_japanese_2.vcf b/core/tests/coretests/res/raw/v21_japanese_2.vcf new file mode 100644 index 0000000..fa54acb --- /dev/null +++ b/core/tests/coretests/res/raw/v21_japanese_2.vcf @@ -0,0 +1,10 @@ +BEGIN:VCARD
+VERSION:2.1
+FN;CHARSET=SHIFT_JIS:ˆÀ“¡ ƒƒCƒh 1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡;ƒƒCƒh1;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³;Û²ÄÞ1;;;
+ADR;HOME;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:;=93=8C=8B=9E=93=73=
+=8F=61=92=4A=8B=E6=8D=F7=8B=75=92=AC26-1=83=5A=83=8B=83=8A=83=41=83=93=
+=83=5E=83=8F=81=5B6=8A=4B;;;;150-8512;
+NOTE;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:=83=81=83=82
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_multiple_entry.vcf b/core/tests/coretests/res/raw/v21_multiple_entry.vcf new file mode 100644 index 0000000..ebbb19a --- /dev/null +++ b/core/tests/coretests/res/raw/v21_multiple_entry.vcf @@ -0,0 +1,33 @@ +BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh3;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ3;;;;
+TEL;X-NEC-SECRET:9
+TEL;X-NEC-HOTEL:10
+TEL;X-NEC-SCHOOL:11
+TEL;HOME;FAX:12
+END:VCARD
+
+
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh4;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ4;;;;
+TEL;MODEM:13
+TEL;PAGER:14
+TEL;X-NEC-FAMILY:15
+TEL;X-NEC-GIRL:16
+END:VCARD
+
+
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh5;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ5;;;;
+TEL;X-NEC-BOY:17
+TEL;X-NEC-FRIEND:18
+TEL;X-NEC-PHS:19
+TEL;X-NEC-RESTAURANT:20
+END:VCARD
+
+
diff --git a/core/tests/coretests/res/raw/v21_org_before_title.vcf b/core/tests/coretests/res/raw/v21_org_before_title.vcf new file mode 100644 index 0000000..8ff1190 --- /dev/null +++ b/core/tests/coretests/res/raw/v21_org_before_title.vcf @@ -0,0 +1,6 @@ +BEGIN:VCARD
+VERSION:2.1
+FN:Normal Guy
+ORG:Company;Organization;Devision;Room;Sheet No.
+TITLE:Excellent Janitor
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_pref_handling.vcf b/core/tests/coretests/res/raw/v21_pref_handling.vcf new file mode 100644 index 0000000..5105310 --- /dev/null +++ b/core/tests/coretests/res/raw/v21_pref_handling.vcf @@ -0,0 +1,15 @@ +BEGIN:VCARD +VERSION:2.1 +FN:Smith +TEL;HOME:1 +TEL;WORK;PREF:2 +TEL;ISDN:3 +EMAIL;PREF;HOME:test@example.com +EMAIL;CELL;PREF:test2@examination.com +ORG:Company +TITLE:Engineer +ORG:Mystery +TITLE:Blogger +ORG:Poetry +TITLE:Poet +END:VCARD diff --git a/core/tests/coretests/res/raw/v21_simple_1.vcf b/core/tests/coretests/res/raw/v21_simple_1.vcf new file mode 100644 index 0000000..6aabb4c --- /dev/null +++ b/core/tests/coretests/res/raw/v21_simple_1.vcf @@ -0,0 +1,3 @@ +BEGIN:VCARD
+N:Ando;Roid;
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_simple_2.vcf b/core/tests/coretests/res/raw/v21_simple_2.vcf new file mode 100644 index 0000000..f0d5ab5 --- /dev/null +++ b/core/tests/coretests/res/raw/v21_simple_2.vcf @@ -0,0 +1,3 @@ +BEGIN:VCARD
+FN:Ando Roid
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_simple_3.vcf b/core/tests/coretests/res/raw/v21_simple_3.vcf new file mode 100644 index 0000000..beddabb --- /dev/null +++ b/core/tests/coretests/res/raw/v21_simple_3.vcf @@ -0,0 +1,4 @@ +BEGIN:VCARD
+N:Ando;Roid;
+FN:Ando Roid
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_title_before_org.vcf b/core/tests/coretests/res/raw/v21_title_before_org.vcf new file mode 100644 index 0000000..9fdc738 --- /dev/null +++ b/core/tests/coretests/res/raw/v21_title_before_org.vcf @@ -0,0 +1,6 @@ +BEGIN:VCARD
+VERSION:2.1
+FN:Nice Guy
+TITLE:Cool Title
+ORG:Marverous;Perfect;Great;Good;Bad;Poor
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_winmo_65.vcf b/core/tests/coretests/res/raw/v21_winmo_65.vcf new file mode 100644 index 0000000..f380d0d --- /dev/null +++ b/core/tests/coretests/res/raw/v21_winmo_65.vcf @@ -0,0 +1,10 @@ +BEGIN:VCARD
+VERSION:2.1
+N:Example;;;;
+FN:Example
+ANNIVERSARY;VALUE=DATE:20091010
+AGENT:Invalid line which must be handled correctly.
+X-CLASS:PUBLIC
+X-REDUCTION:
+X-NO:
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v30_comma_separated.vcf b/core/tests/coretests/res/raw/v30_comma_separated.vcf new file mode 100644 index 0000000..98a7f20 --- /dev/null +++ b/core/tests/coretests/res/raw/v30_comma_separated.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD
+VERSION:3.0
+N:F;G;M;;
+TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v30_simple.vcf b/core/tests/coretests/res/raw/v30_simple.vcf new file mode 100644 index 0000000..418661f --- /dev/null +++ b/core/tests/coretests/res/raw/v30_simple.vcf @@ -0,0 +1,13 @@ +BEGIN:VCARD
+VERSION:3.0
+FN:And Roid
+N:And;Roid;;;
+ORG:Open;Handset; Alliance
+SORT-STRING:android
+TEL;TYPE=PREF;TYPE=VOICE:0300000000
+CLASS:PUBLIC
+X-GNO:0
+X-GN:group0
+X-REDUCTION:0
+REV:20081031T065854Z
+END:VCARD
diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml index 05b57e0..807386a 100644 --- a/core/tests/coretests/res/values/strings.xml +++ b/core/tests/coretests/res/values/strings.xml @@ -100,4 +100,21 @@ <string name="include_button">I was included!</string> <string name="view">View</string> + + <string name="layout_five_text_text">S</string> + <string name="layout_four_text_text">S</string> + <string name="layout_six_text_text">S</string> + + <string name="menu_test">test</string> + <string name="metadata_text">text</string> + + <string name="permlab_testGranted">Test Granted</string> + <string name="permdesc_testGranted">Used for running unit tests, for + testing operations where we have the permission.</string> + <string name="permlab_testDenied">Test Denied</string> + <string name="permdesc_testDenied">Used for running unit tests, for + testing operations where we do not have the permission.</string> + + <string name="searchable_label">SearchManager Test</string> + <string name="searchable_hint">A search hint</string> </resources> diff --git a/core/tests/coretests/res/xml/metadata.xml b/core/tests/coretests/res/xml/metadata.xml new file mode 100644 index 0000000..e352f27 --- /dev/null +++ b/core/tests/coretests/res/xml/metadata.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<thedata xmlns:android="http://schemas.android.com/apk/res/android" + rawText="some raw text" + rawColor="#ffffff00" + android:color="#f00" + android:text="@string/metadata_text" + +/> diff --git a/core/tests/coretests/res/xml/metadata_app.xml b/core/tests/coretests/res/xml/metadata_app.xml new file mode 100644 index 0000000..c37e6ba --- /dev/null +++ b/core/tests/coretests/res/xml/metadata_app.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<thedata xmlns:android="http://schemas.android.com/apk/res/android" + rawText="some raw text" + rawColor="#ffffff00" + android:color="#f00" + android:text="@string/metadata_text" + appInfo="true" + +/> diff --git a/core/tests/coretests/res/xml/searchable.xml b/core/tests/coretests/res/xml/searchable.xml new file mode 100644 index 0000000..9d293b5 --- /dev/null +++ b/core/tests/coretests/res/xml/searchable.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<searchable xmlns:android="http://schemas.android.com/apk/res/android" + android:label="@string/searchable_label" + android:hint="@string/searchable_hint" + android:searchSuggestAuthority="com.android.unit_tests.SuggestionProvider" + > + + <actionkey android:keycode="KEYCODE_CALL" + android:suggestActionMsgColumn="suggest_action_msg_call" /> + +</searchable>
\ No newline at end of file diff --git a/core/tests/coretests/src/android/accounts/AccountManagerServiceTest.java b/core/tests/coretests/src/android/accounts/AccountManagerServiceTest.java new file mode 100644 index 0000000..394b9f2 --- /dev/null +++ b/core/tests/coretests/src/android/accounts/AccountManagerServiceTest.java @@ -0,0 +1,178 @@ +/* + * 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.accounts; + +import android.test.AndroidTestCase; +import android.test.RenamingDelegatingContext; +import android.test.IsolatedContext; +import android.test.mock.MockContext; +import android.test.mock.MockContentResolver; +import android.content.*; +import android.accounts.Account; +import android.accounts.AccountManagerService; +import android.os.Bundle; + +import java.util.Arrays; +import java.util.Comparator; + +public class AccountManagerServiceTest extends AndroidTestCase { + @Override + protected void setUp() throws Exception { + final String filenamePrefix = "test."; + MockContentResolver resolver = new MockContentResolver(); + RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( + new MockContext(), // The context that most methods are delegated to + getContext(), // The context that file methods are delegated to + filenamePrefix); + Context context = new IsolatedContext(resolver, targetContextWrapper); + setContext(context); + } + + public class AccountSorter implements Comparator<Account> { + public int compare(Account object1, Account object2) { + if (object1 == object2) return 0; + if (object1 == null) return 1; + if (object2 == null) return -1; + int result = object1.type.compareTo(object2.type); + if (result != 0) return result; + return object1.name.compareTo(object2.name); + } + } + + public void testCheckAddAccount() throws Exception { + AccountManagerService ams = new AccountManagerService(getContext()); + Account a11 = new Account("account1", "type1"); + Account a21 = new Account("account2", "type1"); + Account a31 = new Account("account3", "type1"); + Account a12 = new Account("account1", "type2"); + Account a22 = new Account("account2", "type2"); + Account a32 = new Account("account3", "type2"); + ams.addAccount(a11, "p11", null); + ams.addAccount(a12, "p12", null); + ams.addAccount(a21, "p21", null); + ams.addAccount(a22, "p22", null); + ams.addAccount(a31, "p31", null); + ams.addAccount(a32, "p32", null); + + Account[] accounts = ams.getAccounts(null); + Arrays.sort(accounts, new AccountSorter()); + assertEquals(6, accounts.length); + assertEquals(a11, accounts[0]); + assertEquals(a21, accounts[1]); + assertEquals(a31, accounts[2]); + assertEquals(a12, accounts[3]); + assertEquals(a22, accounts[4]); + assertEquals(a32, accounts[5]); + + accounts = ams.getAccountsByType("type1" ); + Arrays.sort(accounts, new AccountSorter()); + assertEquals(3, accounts.length); + assertEquals(a11, accounts[0]); + assertEquals(a21, accounts[1]); + assertEquals(a31, accounts[2]); + + ams.removeAccount(null, a21); + + accounts = ams.getAccountsByType("type1" ); + Arrays.sort(accounts, new AccountSorter()); + assertEquals(2, accounts.length); + assertEquals(a11, accounts[0]); + assertEquals(a31, accounts[1]); + } + + public void testPasswords() throws Exception { + AccountManagerService ams = new AccountManagerService(getContext()); + Account a11 = new Account("account1", "type1"); + Account a12 = new Account("account1", "type2"); + ams.addAccount(a11, "p11", null); + ams.addAccount(a12, "p12", null); + + assertEquals("p11", ams.getPassword(a11)); + assertEquals("p12", ams.getPassword(a12)); + + ams.setPassword(a11, "p11b"); + + assertEquals("p11b", ams.getPassword(a11)); + assertEquals("p12", ams.getPassword(a12)); + } + + public void testUserdata() throws Exception { + AccountManagerService ams = new AccountManagerService(getContext()); + Account a11 = new Account("account1", "type1"); + Bundle u11 = new Bundle(); + u11.putString("a", "a_a11"); + u11.putString("b", "b_a11"); + u11.putString("c", "c_a11"); + Account a12 = new Account("account1", "type2"); + Bundle u12 = new Bundle(); + u12.putString("a", "a_a12"); + u12.putString("b", "b_a12"); + u12.putString("c", "c_a12"); + ams.addAccount(a11, "p11", u11); + ams.addAccount(a12, "p12", u12); + + assertEquals("a_a11", ams.getUserData(a11, "a")); + assertEquals("b_a11", ams.getUserData(a11, "b")); + assertEquals("c_a11", ams.getUserData(a11, "c")); + assertEquals("a_a12", ams.getUserData(a12, "a")); + assertEquals("b_a12", ams.getUserData(a12, "b")); + assertEquals("c_a12", ams.getUserData(a12, "c")); + + ams.setUserData(a11, "b", "b_a11b"); + + assertEquals("a_a11", ams.getUserData(a11, "a")); + assertEquals("b_a11b", ams.getUserData(a11, "b")); + assertEquals("c_a11", ams.getUserData(a11, "c")); + assertEquals("a_a12", ams.getUserData(a12, "a")); + assertEquals("b_a12", ams.getUserData(a12, "b")); + assertEquals("c_a12", ams.getUserData(a12, "c")); + } + + public void testAuthtokens() throws Exception { + AccountManagerService ams = new AccountManagerService(getContext()); + Account a11 = new Account("account1", "type1"); + Account a12 = new Account("account1", "type2"); + ams.addAccount(a11, "p11", null); + ams.addAccount(a12, "p12", null); + + ams.setAuthToken(a11, "att1", "a11_att1"); + ams.setAuthToken(a11, "att2", "a11_att2"); + ams.setAuthToken(a11, "att3", "a11_att3"); + ams.setAuthToken(a12, "att1", "a12_att1"); + ams.setAuthToken(a12, "att2", "a12_att2"); + ams.setAuthToken(a12, "att3", "a12_att3"); + + assertEquals("a11_att1", ams.peekAuthToken(a11, "att1")); + assertEquals("a11_att2", ams.peekAuthToken(a11, "att2")); + assertEquals("a11_att3", ams.peekAuthToken(a11, "att3")); + assertEquals("a12_att1", ams.peekAuthToken(a12, "att1")); + assertEquals("a12_att2", ams.peekAuthToken(a12, "att2")); + assertEquals("a12_att3", ams.peekAuthToken(a12, "att3")); + + ams.setAuthToken(a11, "att3", "a11_att3b"); + ams.invalidateAuthToken(a12.type, "a12_att2"); + + assertEquals("a11_att1", ams.peekAuthToken(a11, "att1")); + assertEquals("a11_att2", ams.peekAuthToken(a11, "att2")); + assertEquals("a11_att3b", ams.peekAuthToken(a11, "att3")); + assertEquals("a12_att1", ams.peekAuthToken(a12, "att1")); + assertNull(ams.peekAuthToken(a12, "att2")); + assertEquals("a12_att3", ams.peekAuthToken(a12, "att3")); + + assertNull(ams.readAuthTokenFromDatabase(a12, "att2")); + } +} diff --git a/core/tests/coretests/src/android/app/SearchManagerTest.java b/core/tests/coretests/src/android/app/SearchManagerTest.java new file mode 100644 index 0000000..21ed4c5 --- /dev/null +++ b/core/tests/coretests/src/android/app/SearchManagerTest.java @@ -0,0 +1,195 @@ +/* + * 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.app; + +import android.app.activity.LocalActivity; + +import android.app.Activity; +import android.app.ISearchManager; +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.content.ComponentName; +import android.content.Context; +import android.os.ServiceManager; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; + +/** + * To launch this test from the command line: + * + * adb shell am instrument -w \ + * -e class com.android.unit_tests.SearchManagerTest \ + * com.android.unit_tests/android.test.InstrumentationTestRunner + */ +public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalActivity> { + + private ComponentName SEARCHABLE_ACTIVITY = + new ComponentName("com.android.frameworks.coretests", + "android.app.activity.SearchableActivity"); + + /* + * Bug list of test ideas. + * + * testSearchManagerInterfaceAvailable() + * Exercise the interface obtained + * + * testSearchManagerAvailable() + * Exercise the interface obtained + * + * testSearchManagerInvocations() + * FIX - make it work again + * stress test with a very long string + * + * SearchManager tests + * confirm proper identification of "default" activity based on policy, not hardcoded contacts + * + * SearchBar tests + * Maybe have to do with framework / unittest runner - need instrumented activity? + * How can we unit test the suggestions content providers? + * Should I write unit tests for any of them? + * Test scenarios: + * type-BACK (cancel) + * type-GO (send) + * type-navigate-click (suggestion) + * type-action + * type-navigate-action (suggestion) + */ + + /** + * Local copy of activity context + */ + Context mContext; + + public SearchManagerTest() { + super("com.android.frameworks.coretests", LocalActivity.class); + } + + /** + * Setup any common data for the upcoming tests. + */ + @Override + public void setUp() throws Exception { + super.setUp(); + + Activity testActivity = getActivity(); + mContext = testActivity; + } + + private ISearchManager getSearchManagerService() { + return ISearchManager.Stub.asInterface( + ServiceManager.getService(Context.SEARCH_SERVICE)); + } + + /** + * The goal of this test is to confirm that we can obtain + * a search manager interface. + */ + @MediumTest + public void testSearchManagerInterfaceAvailable() { + assertNotNull(getSearchManagerService()); + } + + /** + * The goal of this test is to confirm that we can obtain + * a search manager at any time, and that for any given context, + * it is a singleton. + */ + @LargeTest + public void testSearchManagerAvailable() { + SearchManager searchManager1 = (SearchManager) + mContext.getSystemService(Context.SEARCH_SERVICE); + assertNotNull(searchManager1); + SearchManager searchManager2 = (SearchManager) + mContext.getSystemService(Context.SEARCH_SERVICE); + assertNotNull(searchManager2); + assertSame(searchManager1, searchManager2 ); + } + + @MediumTest + public void testSearchables() { + SearchManager searchManager = (SearchManager) + mContext.getSystemService(Context.SEARCH_SERVICE); + SearchableInfo si; + + si = searchManager.getSearchableInfo(SEARCHABLE_ACTIVITY, false); + assertNotNull(si); + assertFalse(searchManager.isDefaultSearchable(si)); + si = searchManager.getSearchableInfo(SEARCHABLE_ACTIVITY, true); + assertNotNull(si); + assertTrue(searchManager.isDefaultSearchable(si)); + si = searchManager.getSearchableInfo(null, true); + assertNotNull(si); + assertTrue(searchManager.isDefaultSearchable(si)); + } + + /** + * Tests that startSearch() can be called multiple times without stopSearch() + * in between. + */ + @MediumTest + public void testStartSearchIdempotent() throws Exception { + SearchManager searchManager = (SearchManager) + mContext.getSystemService(Context.SEARCH_SERVICE); + assertNotNull(searchManager); + + searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false); + searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false); + searchManager.stopSearch(); + } + + /** + * Tests that stopSearch() can be called when the search UI is not visible and can be + * called multiple times without startSearch() in between. + */ + @MediumTest + public void testStopSearchIdempotent() throws Exception { + SearchManager searchManager = (SearchManager) + mContext.getSystemService(Context.SEARCH_SERVICE); + assertNotNull(searchManager); + searchManager.stopSearch(); + + searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false); + searchManager.stopSearch(); + searchManager.stopSearch(); + } + + /** + * The goal of this test is to confirm that we can start and then + * stop a simple search. + */ + @MediumTest + public void testSearchManagerInvocations() throws Exception { + SearchManager searchManager = (SearchManager) + mContext.getSystemService(Context.SEARCH_SERVICE); + assertNotNull(searchManager); + + // These tests should simply run to completion w/o exceptions + searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false); + searchManager.stopSearch(); + + searchManager.startSearch("", false, SEARCHABLE_ACTIVITY, null, false); + searchManager.stopSearch(); + + searchManager.startSearch("test search string", false, SEARCHABLE_ACTIVITY, null, false); + searchManager.stopSearch(); + + searchManager.startSearch("test search string", true, SEARCHABLE_ACTIVITY, null, false); + searchManager.stopSearch(); + } + +} diff --git a/core/tests/coretests/src/android/app/SearchablesTest.java b/core/tests/coretests/src/android/app/SearchablesTest.java new file mode 100644 index 0000000..9b4520e --- /dev/null +++ b/core/tests/coretests/src/android/app/SearchablesTest.java @@ -0,0 +1,477 @@ +/* + * 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.app; + +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.app.SearchableInfo.ActionKeyInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.os.RemoteException; +import android.server.search.Searchables; +import android.test.AndroidTestCase; +import android.test.MoreAsserts; +import android.test.mock.MockContext; +import android.test.mock.MockPackageManager; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.KeyEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * To launch this test from the command line: + * + * adb shell am instrument -w \ + * -e class com.android.unit_tests.SearchablesTest \ + * com.android.unit_tests/android.test.InstrumentationTestRunner + */ +@SmallTest +public class SearchablesTest extends AndroidTestCase { + + /* + * SearchableInfo tests + * Mock the context so I can provide very specific input data + * Confirm OK with "zero" searchables + * Confirm "good" metadata read properly + * Confirm "bad" metadata skipped properly + * Confirm ordering of searchables + * Confirm "good" actionkeys + * confirm "bad" actionkeys are rejected + * confirm XML ordering enforced (will fail today - bug in SearchableInfo) + * findActionKey works + * getIcon works + */ + + /** + * The goal of this test is to confirm proper operation of the + * SearchableInfo helper class. + * + * TODO: The metadata source needs to be mocked out because adding + * searchability metadata via this test is causing it to leak into the + * real system. So for now I'm just going to test for existence of the + * GlobalSearch app (which is searchable). + */ + public void testSearchableGlobalSearch() { + // test basic array & hashmap + Searchables searchables = new Searchables(mContext); + searchables.buildSearchableList(); + + // test linkage from another activity + // TODO inject this via mocking into the package manager. + // TODO for now, just check for searchable GlobalSearch app (this isn't really a unit test) + ComponentName thisActivity = new ComponentName( + "com.android.globalsearch", + "com.android.globalsearch.GlobalSearch"); + + SearchableInfo si = searchables.getSearchableInfo(thisActivity); + assertNotNull(si); + assertEquals(thisActivity, si.getSearchActivity()); + + Context appContext = si.getActivityContext(mContext); + assertNotNull(appContext); + MoreAsserts.assertNotEqual(appContext, mContext); + assertEquals("Quick Search Box", appContext.getString(si.getHintId())); + assertEquals("Quick Search Box", appContext.getString(si.getLabelId())); + } + + /** + * Test that non-searchable activities return no searchable info (this would typically + * trigger the use of the default searchable e.g. contacts) + */ + public void testNonSearchable() { + // test basic array & hashmap + Searchables searchables = new Searchables(mContext); + searchables.buildSearchableList(); + + // confirm that we return null for non-searchy activities + ComponentName nonActivity = new ComponentName( + "com.android.frameworks.coretests", + "com.android.frameworks.coretests.activity.NO_SEARCH_ACTIVITY"); + SearchableInfo si = searchables.getSearchableInfo(nonActivity); + assertNull(si); + } + + /** + * Test that there is a default searchable (aka global search provider). + */ + public void testDefaultSearchable() { + Searchables searchables = new Searchables(mContext); + searchables.buildSearchableList(); + SearchableInfo si = searchables.getDefaultSearchable(); + checkSearchable(si); + assertTrue(searchables.isDefaultSearchable(si)); + } + + /** + * This is an attempt to run the searchable info list with a mocked context. Here are some + * things I'd like to test. + * + * Confirm OK with "zero" searchables + * Confirm "good" metadata read properly + * Confirm "bad" metadata skipped properly + * Confirm ordering of searchables + * Confirm "good" actionkeys + * confirm "bad" actionkeys are rejected + * confirm XML ordering enforced (will fail today - bug in SearchableInfo) + * findActionKey works + * getIcon works + + */ + public void testSearchablesListReal() { + MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager()); + MyMockContext mockContext = new MyMockContext(mContext, mockPM); + + // build item list with real-world source data + mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH); + Searchables searchables = new Searchables(mockContext); + searchables.buildSearchableList(); + // tests with "real" searchables (deprecate, this should be a unit test) + ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList(); + int count = searchablesList.size(); + assertTrue(count >= 1); // this isn't really a unit test + checkSearchables(searchablesList); + ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList(); + checkSearchables(global); + } + + /** + * This round of tests confirms good operations with "zero" searchables found + */ + public void testSearchablesListEmpty() { + MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager()); + MyMockContext mockContext = new MyMockContext(mContext, mockPM); + + mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO); + Searchables searchables = new Searchables(mockContext); + searchables.buildSearchableList(); + ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList(); + assertNotNull(searchablesList); + MoreAsserts.assertEmpty(searchablesList); + ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList(); + MoreAsserts.assertEmpty(global); + } + + /** + * Generic health checker for an array of searchables. + * + * This is designed to pass for any semi-legal searchable, without knowing much about + * the format of the underlying data. It's fairly easy for a non-compliant application + * to provide meta-data that will pass here (e.g. a non-existent suggestions authority). + * + * @param searchables The list of searchables to examine. + */ + private void checkSearchables(ArrayList<SearchableInfo> searchablesList) { + assertNotNull(searchablesList); + int count = searchablesList.size(); + for (int ii = 0; ii < count; ii++) { + SearchableInfo si = searchablesList.get(ii); + checkSearchable(si); + } + } + + private void checkSearchable(SearchableInfo si) { + assertNotNull(si); + assertTrue(si.getLabelId() != 0); // This must be a useable string + assertNotEmpty(si.getSearchActivity().getClassName()); + assertNotEmpty(si.getSearchActivity().getPackageName()); + if (si.getSuggestAuthority() != null) { + // The suggestion fields are largely optional, so we'll just confirm basic health + assertNotEmpty(si.getSuggestAuthority()); + assertNullOrNotEmpty(si.getSuggestPath()); + assertNullOrNotEmpty(si.getSuggestSelection()); + assertNullOrNotEmpty(si.getSuggestIntentAction()); + assertNullOrNotEmpty(si.getSuggestIntentData()); + } + /* Add a way to get the entire action key list, then explicitly test its elements */ + /* For now, test the most common action key (CALL) */ + ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL); + if (ai != null) { + assertEquals(ai.getKeyCode(), KeyEvent.KEYCODE_CALL); + // one of these three fields must be non-null & non-empty + boolean m1 = (ai.getQueryActionMsg() != null) && (ai.getQueryActionMsg().length() > 0); + boolean m2 = (ai.getSuggestActionMsg() != null) && (ai.getSuggestActionMsg().length() > 0); + boolean m3 = (ai.getSuggestActionMsgColumn() != null) && + (ai.getSuggestActionMsgColumn().length() > 0); + assertTrue(m1 || m2 || m3); + } + + /* + * Find ways to test these: + * + * private int mSearchMode + * private Drawable mIcon + */ + + /* + * Explicitly not tested here: + * + * Can be null, so not much to see: + * public String mSearchHint + * private String mZeroQueryBanner + * + * To be deprecated/removed, so don't bother: + * public boolean mFilterMode + * public boolean mQuickStart + * private boolean mIconResized + * private int mIconResizeWidth + * private int mIconResizeHeight + * + * All of these are "internal" working variables, not part of any contract + * private ActivityInfo mActivityInfo + * private Rect mTempRect + * private String mSuggestProviderPackage + * private String mCacheActivityContext + */ + } + + /** + * Combo assert for "string not null and not empty" + */ + private void assertNotEmpty(final String s) { + assertNotNull(s); + MoreAsserts.assertNotEqual(s, ""); + } + + /** + * Combo assert for "string null or (not null and not empty)" + */ + private void assertNullOrNotEmpty(final String s) { + if (s != null) { + MoreAsserts.assertNotEqual(s, ""); + } + } + + /** + * This is a mock for context. Used to perform a true unit test on SearchableInfo. + * + */ + private class MyMockContext extends MockContext { + + protected Context mRealContext; + protected PackageManager mPackageManager; + + /** + * Constructor. + * + * @param realContext Please pass in a real context for some pass-throughs to function. + */ + MyMockContext(Context realContext, PackageManager packageManager) { + mRealContext = realContext; + mPackageManager = packageManager; + } + + /** + * Resources. Pass through for now. + */ + @Override + public Resources getResources() { + return mRealContext.getResources(); + } + + /** + * Package manager. Pass through for now. + */ + @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + /** + * Package manager. Pass through for now. + */ + @Override + public Context createPackageContext(String packageName, int flags) + throws PackageManager.NameNotFoundException { + return mRealContext.createPackageContext(packageName, flags); + } + + /** + * Message broadcast. Pass through for now. + */ + @Override + public void sendBroadcast(Intent intent) { + mRealContext.sendBroadcast(intent); + } + } + +/** + * This is a mock for package manager. Used to perform a true unit test on SearchableInfo. + * + */ + private class MyMockPackageManager extends MockPackageManager { + + public final static int SEARCHABLES_PASSTHROUGH = 0; + public final static int SEARCHABLES_MOCK_ZERO = 1; + public final static int SEARCHABLES_MOCK_ONEGOOD = 2; + public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3; + + protected PackageManager mRealPackageManager; + protected int mSearchablesMode; + + public MyMockPackageManager(PackageManager realPM) { + mRealPackageManager = realPM; + mSearchablesMode = SEARCHABLES_PASSTHROUGH; + } + + /** + * Set the mode for various tests. + */ + public void setSearchablesMode(int newMode) { + switch (newMode) { + case SEARCHABLES_PASSTHROUGH: + case SEARCHABLES_MOCK_ZERO: + mSearchablesMode = newMode; + break; + + default: + throw new UnsupportedOperationException(); + } + } + + /** + * Find activities that support a given intent. + * + * Retrieve all activities that can be performed for the given intent. + * + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags. The most important is + * MATCH_DEFAULT_ONLY, to limit the resolution to only + * those activities that support the CATEGORY_DEFAULT. + * + * @return A List<ResolveInfo> containing one entry for each matching + * Activity. These are ordered from best to worst match -- that + * is, the first item in the list is what is returned by + * resolveActivity(). If there are no matching activities, an empty + * list is returned. + */ + @Override + public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { + assertNotNull(intent); + assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH) + || intent.getAction().equals(Intent.ACTION_WEB_SEARCH)); + switch (mSearchablesMode) { + case SEARCHABLES_PASSTHROUGH: + return mRealPackageManager.queryIntentActivities(intent, flags); + case SEARCHABLES_MOCK_ZERO: + return null; + default: + throw new UnsupportedOperationException(); + } + } + + @Override + public ResolveInfo resolveActivity(Intent intent, int flags) { + assertNotNull(intent); + assertTrue(intent.getAction().equals(Intent.ACTION_WEB_SEARCH) + || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH)); + switch (mSearchablesMode) { + case SEARCHABLES_PASSTHROUGH: + return mRealPackageManager.resolveActivity(intent, flags); + case SEARCHABLES_MOCK_ZERO: + return null; + default: + throw new UnsupportedOperationException(); + } + } + + /** + * Retrieve an XML file from a package. This is a low-level API used to + * retrieve XML meta data. + * + * @param packageName The name of the package that this xml is coming from. + * Can not be null. + * @param resid The resource identifier of the desired xml. Can not be 0. + * @param appInfo Overall information about <var>packageName</var>. This + * may be null, in which case the application information will be retrieved + * for you if needed; if you already have this information around, it can + * be much more efficient to supply it here. + * + * @return Returns an XmlPullParser allowing you to parse out the XML + * data. Returns null if the xml resource could not be found for any + * reason. + */ + @Override + public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) { + assertNotNull(packageName); + MoreAsserts.assertNotEqual(packageName, ""); + MoreAsserts.assertNotEqual(resid, 0); + switch (mSearchablesMode) { + case SEARCHABLES_PASSTHROUGH: + return mRealPackageManager.getXml(packageName, resid, appInfo); + case SEARCHABLES_MOCK_ZERO: + default: + throw new UnsupportedOperationException(); + } + } + + /** + * Find a single content provider by its base path name. + * + * @param name The name of the provider to find. + * @param flags Additional option flags. Currently should always be 0. + * + * @return ContentProviderInfo Information about the provider, if found, + * else null. + */ + @Override + public ProviderInfo resolveContentProvider(String name, int flags) { + assertNotNull(name); + MoreAsserts.assertNotEqual(name, ""); + assertEquals(flags, 0); + switch (mSearchablesMode) { + case SEARCHABLES_PASSTHROUGH: + return mRealPackageManager.resolveContentProvider(name, flags); + case SEARCHABLES_MOCK_ZERO: + default: + throw new UnsupportedOperationException(); + } + } + + /** + * Get the activity information for a particular activity. + * + * @param name The name of the activity to find. + * @param flags Additional option flags. + * + * @return ActivityInfo Information about the activity, if found, else null. + */ + @Override + public ActivityInfo getActivityInfo(ComponentName name, int flags) + throws NameNotFoundException { + assertNotNull(name); + MoreAsserts.assertNotEqual(name, ""); + switch (mSearchablesMode) { + case SEARCHABLES_PASSTHROUGH: + return mRealPackageManager.getActivityInfo(name, flags); + case SEARCHABLES_MOCK_ZERO: + throw new NameNotFoundException(); + default: + throw new UnsupportedOperationException(); + } + } + } +} + diff --git a/core/tests/coretests/src/android/app/SuggestionProvider.java b/core/tests/coretests/src/android/app/SuggestionProvider.java new file mode 100644 index 0000000..9fb7dcf --- /dev/null +++ b/core/tests/coretests/src/android/app/SuggestionProvider.java @@ -0,0 +1,110 @@ +/* + * 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.app; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Intent; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; + +/** Simple test provider that runs in the local process. + * + * Used by {@link SearchManagerTest}. + */ +public class SuggestionProvider extends ContentProvider { + private static final String TAG = "SuggestionProvider"; + + private static final int SEARCH_SUGGESTIONS = 1; + + private static final UriMatcher sURLMatcher = new UriMatcher( + UriMatcher.NO_MATCH); + + static { + sURLMatcher.addURI("*", SearchManager.SUGGEST_URI_PATH_QUERY, + SEARCH_SUGGESTIONS); + sURLMatcher.addURI("*", SearchManager.SUGGEST_URI_PATH_QUERY + "/*", + SEARCH_SUGGESTIONS); + } + + private static final String[] COLUMNS = new String[] { + "_id", + SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_INTENT_ACTION, + SearchManager.SUGGEST_COLUMN_QUERY + }; + + public SuggestionProvider() { + } + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri url, String[] projectionIn, String selection, + String[] selectionArgs, String sort) { + int match = sURLMatcher.match(url); + switch (match) { + case SEARCH_SUGGESTIONS: + String query = url.getLastPathSegment(); + MatrixCursor cursor = new MatrixCursor(COLUMNS); + String[] suffixes = { "", "a", " foo", "XXXXXXXXXXXXXXXXX" }; + for (String suffix : suffixes) { + addRow(cursor, query + suffix); + } + return cursor; + default: + throw new IllegalArgumentException("Unknown URL: " + url); + } + } + + private void addRow(MatrixCursor cursor, String string) { + long id = cursor.getCount(); + cursor.newRow().add(id).add(string).add(Intent.ACTION_SEARCH).add(string); + } + + @Override + public String getType(Uri url) { + int match = sURLMatcher.match(url); + switch (match) { + case SEARCH_SUGGESTIONS: + return SearchManager.SUGGEST_MIME_TYPE; + default: + throw new IllegalArgumentException("Unknown URL: " + url); + } + } + + @Override + public int update(Uri url, ContentValues values, String where, String[] whereArgs) { + throw new UnsupportedOperationException("update not supported"); + } + + @Override + public Uri insert(Uri url, ContentValues initialValues) { + throw new UnsupportedOperationException("insert not supported"); + } + + @Override + public int delete(Uri url, String where, String[] whereArgs) { + throw new UnsupportedOperationException("delete not supported"); + } +} diff --git a/core/tests/coretests/src/android/app/activity/AbortReceiver.java b/core/tests/coretests/src/android/app/activity/AbortReceiver.java new file mode 100644 index 0000000..fef1775 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/AbortReceiver.java @@ -0,0 +1,49 @@ +/* + * 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.app.activity; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; +import android.util.Log; + +public class AbortReceiver extends BroadcastReceiver +{ + public AbortReceiver() + { + } + + public void onReceive(Context context, Intent intent) + { + //Log.i("AbortReceiver", "onReceiveIntent!"); + try { + IBinder caller = intent.getIBinderExtra("caller"); + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(LaunchpadActivity.LAUNCH); + data.writeString(LaunchpadActivity.RECEIVER_ABORT); + caller.transact(LaunchpadActivity.GOT_RECEIVE_TRANSACTION, data, null, 0); + data.recycle(); + } catch (RemoteException ex) { + } + + // abort the broadcast!!! + abortBroadcast(); + } +} diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java new file mode 100644 index 0000000..61d73bc --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java @@ -0,0 +1,135 @@ +/* + * 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. + */ + +package android.app.activity; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ConfigurationInfo; +import android.content.res.Configuration; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; + +import java.util.Iterator; +import java.util.List; + +public class ActivityManagerTest extends AndroidTestCase { + + protected Context mContext; + protected ActivityManager mActivityManager; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mContext = getContext(); + mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + } + + // TODO should write a test for getRecentTasks() + // TODO should write a test for getRunningTasks() + // TODO should write a test for getMemoryInfo() + + // TODO: Find a way to re-enable this. It fails if any other app has failed during startup. + // This is probably an OK assumption given the desired system status when we run unit tests, + // but it's not necessarily the right assumption for a unit test. + @Suppress + public void disabledTestErrorTasksEmpty() throws Exception { + + List<ActivityManager.ProcessErrorStateInfo> errList; + + errList = mActivityManager.getProcessesInErrorState(); + + // test: confirm list is empty + assertNull(errList); + } + + // TODO: Force an activity into an error state - then see if we can catch it here? + @SmallTest + public void testErrorTasksWithError() throws Exception { + + List<ActivityManager.ProcessErrorStateInfo> errList; + + // TODO force another process into an error condition. How? + + // test: confirm error list length is at least 1 under varying query lengths +// checkErrorListMax(1,-1); + + errList = mActivityManager.getProcessesInErrorState(); + + // test: the list itself is healthy + checkErrorListSanity(errList); + + // test: confirm our application shows up in the list + } + + // TODO: Force an activity into an ANR state - then see if we can catch it here? + @SmallTest + public void testErrorTasksWithANR() throws Exception { + + List<ActivityManager.ProcessErrorStateInfo> errList; + + // TODO: force an application into an ANR state + + errList = mActivityManager.getProcessesInErrorState(); + + // test: the list itself is healthy + checkErrorListSanity(errList); + + // test: confirm our ANR'ing application shows up in the list + } + + @SmallTest + public void testGetDeviceConfigurationInfo() throws Exception { + ConfigurationInfo config = mActivityManager.getDeviceConfigurationInfo(); + assertNotNull(config); + // Validate values against configuration retrieved from resources + Configuration vconfig = mContext.getResources().getConfiguration(); + assertNotNull(vconfig); + assertEquals(config.reqKeyboardType, vconfig.keyboard); + assertEquals(config.reqTouchScreen, vconfig.touchscreen); + assertEquals(config.reqNavigation, vconfig.navigation); + if (vconfig.navigation == Configuration.NAVIGATION_NONAV) { + assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV); + } + if (vconfig.keyboard != Configuration.KEYBOARD_UNDEFINED) { + assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD); + } + } + + // If any entries in appear in the list, sanity check them against all running applications + private void checkErrorListSanity(List<ActivityManager.ProcessErrorStateInfo> errList) { + if (errList == null) return; + + Iterator<ActivityManager.ProcessErrorStateInfo> iter = errList.iterator(); + while (iter.hasNext()) { + ActivityManager.ProcessErrorStateInfo info = iter.next(); + assertNotNull(info); + // sanity checks + assertTrue((info.condition == ActivityManager.ProcessErrorStateInfo.CRASHED) || + (info.condition == ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING)); + // TODO look at each of these and consider a stronger test + // TODO can we cross-check at the process name via some other API? + // TODO is there a better test for strings, e.g. "assertIsLegalString") + assertNotNull(info.processName); + // reasonableness test for info.pid ? + assertNotNull(info.longMsg); + assertNotNull(info.shortMsg); + // is there any reasonable test for the crashData? Probably not. + } + } +} + diff --git a/core/tests/coretests/src/android/app/activity/ActivityTests.java b/core/tests/coretests/src/android/app/activity/ActivityTests.java new file mode 100644 index 0000000..c57fe98 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/ActivityTests.java @@ -0,0 +1,40 @@ +/* + * 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.app.activity; + +import junit.framework.TestSuite; + +public class ActivityTests { + public static final boolean DEBUG_LIFECYCLE = false; + + public static TestSuite suite() { + TestSuite suite = new TestSuite(ActivityTests.class.getName()); + + suite.addTestSuite(BroadcastTest.class); + suite.addTestSuite(IntentSenderTest.class); + suite.addTestSuite(ActivityManagerTest.class); + suite.addTestSuite(LaunchTest.class); + suite.addTestSuite(LifecycleTest.class); + suite.addTestSuite(ServiceTest.class); + suite.addTestSuite(MetaDataTest.class); + // Remove temporarily until bug 1171309 is fixed. + //suite.addTestSuite(SubActivityTest.class); + suite.addTestSuite(SetTimeZonePermissionsTest.class); + + return suite; + } +} diff --git a/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java new file mode 100644 index 0000000..232abe2 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java @@ -0,0 +1,212 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.test.AndroidTestCase; +import android.test.PerformanceTestCase; + +public class ActivityTestsBase extends AndroidTestCase + implements PerformanceTestCase, LaunchpadActivity.CallingTest { + public static final String PERMISSION_GRANTED = + "com.android.frameworks.coretests.permission.TEST_GRANTED"; + public static final String PERMISSION_DENIED = + "com.android.frameworks.coretests.permission.TEST_DENIED"; + + protected Intent mIntent; + + private PerformanceTestCase.Intermediates mIntermediates; + private String mExpecting; + + // Synchronization of activity result. + private boolean mFinished; + private int mResultCode = 0; + private Intent mData; + private RuntimeException mResultStack = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mIntent = new Intent(mContext, LaunchpadActivity.class); + mIntermediates = null; + } + + @Override + protected void tearDown() throws Exception { + mIntermediates = null; + super.tearDown(); + } + + public boolean isPerformanceOnly() { + return false; + } + + public void setInternalIterations(int count) { + } + + public void startTiming(boolean realTime) { + if (mIntermediates != null) { + mIntermediates.startTiming(realTime); + } + } + + public void addIntermediate(String name) { + if (mIntermediates != null) { + mIntermediates.addIntermediate(name); + } + } + + public void addIntermediate(String name, long timeInNS) { + if (mIntermediates != null) { + mIntermediates.addIntermediate(name, timeInNS); + } + } + + public void finishTiming(boolean realTime) { + if (mIntermediates != null) { + mIntermediates.finishTiming(realTime); + } + } + + public void activityFinished(int resultCode, Intent data, RuntimeException where) { + finishWithResult(resultCode, data, where); + } + + public Intent editIntent() { + return mIntent; + } + + public Context getContext() { + return mContext; + } + + public int startPerformance(Intermediates intermediates) { + mIntermediates = intermediates; + return 1; + } + + public void finishGood() { + finishWithResult(Activity.RESULT_OK, null); + } + + public void finishBad(String error) { + finishWithResult(Activity.RESULT_CANCELED, (new Intent()).setAction(error)); + } + + public void finishWithResult(int resultCode, Intent data) { + RuntimeException where = new RuntimeException("Original error was here"); + where.fillInStackTrace(); + finishWithResult(resultCode, data, where); + } + + public void finishWithResult(int resultCode, Intent data, RuntimeException where) { + synchronized (this) { + //System.out.println("*** Activity finished!!"); + mResultCode = resultCode; + mData = data; + mResultStack = where; + mFinished = true; + notifyAll(); + } + } + + public int runLaunchpad(String action) { + LaunchpadActivity.setCallingTest(this); + + synchronized (this) { + mIntent.setAction(action); + mFinished = false; + //System.out.println("*** Starting: " + mIntent); + mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(mIntent); + } + + return waitForResultOrThrow(60 * 1000); + } + + public int waitForResultOrThrow(int timeoutMs) { + return waitForResultOrThrow(timeoutMs, null); + } + + public int waitForResultOrThrow(int timeoutMs, String expected) { + int res = waitForResult(timeoutMs, expected); + + if (res == Activity.RESULT_CANCELED) { + if (mResultStack != null) { + throw new RuntimeException( + mData != null ? mData.toString() : "Unable to launch", + mResultStack); + } else { + throw new RuntimeException( + mData != null ? mData.toString() : "Unable to launch"); + } + } + return res; + } + + public int waitForResult(int timeoutMs, String expected) { + mExpecting = expected; + + long endTime = System.currentTimeMillis() + timeoutMs; + + boolean timeout = false; + synchronized (this) { + while (!mFinished) { + long delay = endTime - System.currentTimeMillis(); + if (delay < 0) { + timeout = true; + break; + } + + try { + wait(delay); + } catch (java.lang.InterruptedException e) { + // do nothing + } + } + } + + mFinished = false; + + if (timeout) { + mResultCode = Activity.RESULT_CANCELED; + onTimeout(); + } + return mResultCode; + } + + public int getResultCode() { + return mResultCode; + } + + public Intent getResultData() { + return mData; + } + + public RuntimeException getResultStack() { + return mResultStack; + } + + public void onTimeout() { + String msg = mExpecting == null + ? "Timeout" : ("Timeout while expecting " + mExpecting); + finishWithResult(Activity.RESULT_CANCELED, (new Intent()).setAction(msg)); + } +} + diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java new file mode 100644 index 0000000..4b1f9fd --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java @@ -0,0 +1,536 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.test.FlakyTest; +import android.test.suitebuilder.annotation.Suppress; +import android.util.Log; + +import java.util.Arrays; + +public class BroadcastTest extends ActivityTestsBase { + public static final int BROADCAST_TIMEOUT = 5 * 1000; + + public static final String BROADCAST_REGISTERED = + "com.android.frameworks.coretests.activity.BROADCAST_REGISTERED"; + public static final String BROADCAST_LOCAL = + "com.android.frameworks.coretests.activity.BROADCAST_LOCAL"; + public static final String BROADCAST_LOCAL_GRANTED = + "com.android.frameworks.coretests.activity.BROADCAST_LOCAL_GRANTED"; + public static final String BROADCAST_LOCAL_DENIED = + "com.android.frameworks.coretests.activity.BROADCAST_LOCAL_DENIED"; + public static final String BROADCAST_REMOTE = + "com.android.frameworks.coretests.activity.BROADCAST_REMOTE"; + public static final String BROADCAST_REMOTE_GRANTED = + "com.android.frameworks.coretests.activity.BROADCAST_REMOTE_GRANTED"; + public static final String BROADCAST_REMOTE_DENIED = + "com.android.frameworks.coretests.activity.BROADCAST_REMOTE_DENIED"; + public static final String BROADCAST_ALL = + "com.android.frameworks.coretests.activity.BROADCAST_ALL"; + public static final String BROADCAST_MULTI = + "com.android.frameworks.coretests.activity.BROADCAST_MULTI"; + public static final String BROADCAST_ABORT = + "com.android.frameworks.coretests.activity.BROADCAST_ABORT"; + + public static final String BROADCAST_STICKY1 = + "com.android.frameworks.coretests.activity.BROADCAST_STICKY1"; + public static final String BROADCAST_STICKY2 = + "com.android.frameworks.coretests.activity.BROADCAST_STICKY2"; + + public static final String BROADCAST_FAIL_REGISTER = + "com.android.frameworks.coretests.activity.BROADCAST_FAIL_REGISTER"; + public static final String BROADCAST_FAIL_BIND = + "com.android.frameworks.coretests.activity.BROADCAST_FAIL_BIND"; + + public static final String RECEIVER_REG = "receiver-reg"; + public static final String RECEIVER_LOCAL = "receiver-local"; + public static final String RECEIVER_REMOTE = "receiver-remote"; + public static final String RECEIVER_ABORT = "receiver-abort"; + public static final String RECEIVER_RESULTS = "receiver-results"; + + public static final String DATA_1 = "one"; + public static final String DATA_2 = "two"; + + public static final int GOT_RECEIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION; + public static final int ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1; + + private String[] mExpectedReceivers = null; + private int mNextReceiver; + + private String[] mExpectedData = null; + private boolean[] mReceivedData = null; + + boolean mReceiverRegistered = false; + + public void setExpectedReceivers(String[] receivers) { + mExpectedReceivers = receivers; + mNextReceiver = 0; + } + + public void setExpectedData(String[] data) { + mExpectedData = data; + mReceivedData = new boolean[data.length]; + } + + public void onTimeout() { + String msg = "Timeout"; + if (mExpectedReceivers != null && mNextReceiver < mExpectedReceivers.length) { + msg = msg + " waiting for " + mExpectedReceivers[mNextReceiver]; + } + finishBad(msg); + } + + public Intent makeBroadcastIntent(String action) { + Intent intent = new Intent(action, null); + intent.putExtra("caller", mCallTarget); + return intent; + } + + public void finishWithResult(int resultCode, Intent data) { + unregisterMyReceiver(); + super.finishWithResult(resultCode, data); + } + + public final void gotReceive(String name, Intent intent) { + synchronized (this) { + + //System.out.println("Got receive: " + name); + //System.out.println(mNextReceiver + " in " + mExpectedReceivers); + //new RuntimeException("stack").printStackTrace(); + + addIntermediate(name); + + if (mExpectedData != null) { + int n = mExpectedData.length; + int i; + boolean prev = false; + for (i = 0; i < n; i++) { + if (mExpectedData[i].equals(intent.getStringExtra("test"))) { + if (mReceivedData[i]) { + prev = true; + continue; + } + mReceivedData[i] = true; + break; + } + } + if (i >= n) { + if (prev) { + finishBad("Receive got data too many times: " + + intent.getStringExtra("test")); + } else { + finishBad("Receive got unexpected data: " + + intent.getStringExtra("test")); + } + new RuntimeException("stack").printStackTrace(); + return; + } + } + + if (mNextReceiver >= mExpectedReceivers.length) { + finishBad("Got too many onReceiveIntent() calls!"); +// System.out.println("Too many intents received: now at " +// + mNextReceiver + ", expect list: " +// + Arrays.toString(mExpectedReceivers)); + fail("Got too many onReceiveIntent() calls!"); + } else if (!mExpectedReceivers[mNextReceiver].equals(name)) { + finishBad("Receive out of order: got " + name + + " but expected " + + mExpectedReceivers[mNextReceiver]); + fail("Receive out of order: got " + name + + " but expected " + + mExpectedReceivers[mNextReceiver]); + } else { + mNextReceiver++; + if (mNextReceiver == mExpectedReceivers.length) { + finishTest(); + } + } + } + } + + public void registerMyReceiver(IntentFilter filter, String permission) { + mReceiverRegistered = true; + //System.out.println("Registering: " + mReceiver); + getContext().registerReceiver(mReceiver, filter, permission, null); + } + + public void unregisterMyReceiver() { + if (mReceiverRegistered) { + unregisterMyReceiverNoCheck(); + } + } + + public void unregisterMyReceiverNoCheck() { + mReceiverRegistered = false; + //System.out.println("Unregistering: " + mReceiver); + getContext().unregisterReceiver(mReceiver); + } + + public void onRegisteredReceiver(Intent intent) { + gotReceive(RECEIVER_REG, intent); + } + + private Binder mCallTarget = new Binder() { + public boolean onTransact(int code, Parcel data, Parcel reply, + int flags) { + data.setDataPosition(0); + data.enforceInterface(LaunchpadActivity.LAUNCH); + if (code == GOT_RECEIVE_TRANSACTION) { + String name = data.readString(); + gotReceive(name, null); + return true; + } else if (code == ERROR_TRANSACTION) { + finishBad(data.readString()); + return true; + } + return false; + } + }; + + private void finishTest() { + if (mReceiverRegistered) { + addIntermediate("before-unregister"); + unregisterMyReceiver(); + } + finishTiming(true); + finishGood(); + } + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + //System.out.println("Receive in: " + this + ": " + intent); + onRegisteredReceiver(intent); + } + }; + + // Mark flaky until http://b/issue?id=1191607 is resolved. + @FlakyTest(tolerance=2) + public void testRegistered() throws Exception { + runLaunchpad(LaunchpadActivity.BROADCAST_REGISTERED); + } + + public void testLocal() throws Exception { + runLaunchpad(LaunchpadActivity.BROADCAST_LOCAL); + } + + public void testRemote() throws Exception { + runLaunchpad(LaunchpadActivity.BROADCAST_REMOTE); + } + + public void testAbort() throws Exception { + runLaunchpad(LaunchpadActivity.BROADCAST_ABORT); + } + + @FlakyTest(tolerance=2) + public void testAll() throws Exception { + runLaunchpad(LaunchpadActivity.BROADCAST_ALL); + } + + @FlakyTest(tolerance=2) + public void testMulti() throws Exception { + runLaunchpad(LaunchpadActivity.BROADCAST_MULTI); + } + + private class TestBroadcastReceiver extends BroadcastReceiver { + public boolean mHaveResult = false; + + @Override + public void onReceive(Context context, Intent intent) { + synchronized (BroadcastTest.this) { + mHaveResult = true; + BroadcastTest.this.notifyAll(); + } + } + } + + public void testResult() throws Exception { + TestBroadcastReceiver broadcastReceiver = new TestBroadcastReceiver(); + + synchronized (this) { + Bundle map = new Bundle(); + map.putString("foo", "you"); + map.putString("remove", "me"); + getContext().sendOrderedBroadcast( + new Intent("com.android.frameworks.coretests.activity.BROADCAST_RESULT"), + null, broadcastReceiver, null, 1, "foo", map); + while (!broadcastReceiver.mHaveResult) { + try { + wait(); + } catch (InterruptedException e) { + } + } + + //System.out.println("Code: " + mResultCode + ", data: " + mResultData); + //System.out.println("Extras: " + mResultExtras); + + assertEquals("Incorrect code: " + broadcastReceiver.getResultCode(), + 3, broadcastReceiver.getResultCode()); + + assertEquals("bar", broadcastReceiver.getResultData()); + + Bundle resultExtras = broadcastReceiver.getResultExtras(false); + assertEquals("them", resultExtras.getString("bar")); + assertEquals("you", resultExtras.getString("foo")); + assertNull(resultExtras.getString("remove")); + } + } + + public void testSetSticky() throws Exception { + Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null); + intent.putExtra("test", LaunchpadActivity.DATA_1); + ActivityManagerNative.getDefault().unbroadcastIntent(null, intent); + + ActivityManagerNative.broadcastStickyIntent(intent, null); + addIntermediate("finished-broadcast"); + + IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1); + Intent sticky = getContext().registerReceiver(null, filter); + assertNotNull("Sticky not found", sticky); + assertEquals(LaunchpadActivity.DATA_1, sticky.getStringExtra("test")); + } + + public void testClearSticky() throws Exception { + Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null); + intent.putExtra("test", LaunchpadActivity.DATA_1); + ActivityManagerNative.broadcastStickyIntent(intent, null); + + ActivityManagerNative.getDefault().unbroadcastIntent( + null, new Intent(LaunchpadActivity.BROADCAST_STICKY1, null)); + addIntermediate("finished-unbroadcast"); + + IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1); + Intent sticky = getContext().registerReceiver(null, filter); + assertNull("Sticky not found", sticky); + } + + public void testReplaceSticky() throws Exception { + Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null); + intent.putExtra("test", LaunchpadActivity.DATA_1); + ActivityManagerNative.broadcastStickyIntent(intent, null); + intent.putExtra("test", LaunchpadActivity.DATA_2); + + ActivityManagerNative.broadcastStickyIntent(intent, null); + addIntermediate("finished-broadcast"); + + IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1); + Intent sticky = getContext().registerReceiver(null, filter); + assertNotNull("Sticky not found", sticky); + assertEquals(LaunchpadActivity.DATA_2, sticky.getStringExtra("test")); + } + + // Marking flaky until http://b/issue?id=1191337 is resolved + @FlakyTest(tolerance=2) + public void testReceiveSticky() throws Exception { + Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null); + intent.putExtra("test", LaunchpadActivity.DATA_1); + ActivityManagerNative.broadcastStickyIntent(intent, null); + + runLaunchpad(LaunchpadActivity.BROADCAST_STICKY1); + } + + // Marking flaky until http://b/issue?id=1191337 is resolved + @FlakyTest(tolerance=2) + public void testReceive2Sticky() throws Exception { + Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null); + intent.putExtra("test", LaunchpadActivity.DATA_1); + ActivityManagerNative.broadcastStickyIntent(intent, null); + intent = new Intent(LaunchpadActivity.BROADCAST_STICKY2, null); + intent.putExtra("test", LaunchpadActivity.DATA_2); + ActivityManagerNative.broadcastStickyIntent(intent, null); + + runLaunchpad(LaunchpadActivity.BROADCAST_STICKY2); + } + + public void testRegisteredReceivePermissionGranted() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_REG}); + registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_GRANTED); + addIntermediate("after-register"); + getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_REGISTERED)); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testRegisteredReceivePermissionDenied() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_RESULTS}); + registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_DENIED); + addIntermediate("after-register"); + + BroadcastReceiver finish = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + gotReceive(RECEIVER_RESULTS, intent); + } + }; + + getContext().sendOrderedBroadcast( + makeBroadcastIntent(BROADCAST_REGISTERED), + null, finish, null, Activity.RESULT_CANCELED, null, null); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testRegisteredBroadcastPermissionGranted() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_REG}); + registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), null); + addIntermediate("after-register"); + getContext().sendBroadcast( + makeBroadcastIntent(BROADCAST_REGISTERED), + PERMISSION_GRANTED); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testRegisteredBroadcastPermissionDenied() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_RESULTS}); + registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), null); + addIntermediate("after-register"); + + BroadcastReceiver finish = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + gotReceive(RECEIVER_RESULTS, intent); + } + }; + + getContext().sendOrderedBroadcast( + makeBroadcastIntent(BROADCAST_REGISTERED), + PERMISSION_DENIED, finish, null, Activity.RESULT_CANCELED, + null, null); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testLocalReceivePermissionGranted() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_LOCAL}); + getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_LOCAL_GRANTED)); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testLocalReceivePermissionDenied() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_RESULTS}); + + BroadcastReceiver finish = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + gotReceive(RECEIVER_RESULTS, intent); + } + }; + + getContext().sendOrderedBroadcast( + makeBroadcastIntent(BROADCAST_LOCAL_DENIED), + null, finish, null, Activity.RESULT_CANCELED, + null, null); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testLocalBroadcastPermissionGranted() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_LOCAL}); + getContext().sendBroadcast( + makeBroadcastIntent(BROADCAST_LOCAL), + PERMISSION_GRANTED); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testLocalBroadcastPermissionDenied() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_RESULTS}); + + BroadcastReceiver finish = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + gotReceive(RECEIVER_RESULTS, intent); + } + }; + + getContext().sendOrderedBroadcast( + makeBroadcastIntent(BROADCAST_LOCAL), + PERMISSION_DENIED, finish, null, Activity.RESULT_CANCELED, + null, null); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testRemoteReceivePermissionGranted() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_REMOTE}); + getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_REMOTE_GRANTED)); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testRemoteReceivePermissionDenied() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_RESULTS}); + + BroadcastReceiver finish = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + gotReceive(RECEIVER_RESULTS, intent); + } + }; + + getContext().sendOrderedBroadcast( + makeBroadcastIntent(BROADCAST_REMOTE_DENIED), + null, finish, null, Activity.RESULT_CANCELED, + null, null); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testRemoteBroadcastPermissionGranted() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_REMOTE}); + getContext().sendBroadcast( + makeBroadcastIntent(BROADCAST_REMOTE), + PERMISSION_GRANTED); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testRemoteBroadcastPermissionDenied() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_RESULTS}); + + BroadcastReceiver finish = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + gotReceive(RECEIVER_RESULTS, intent); + } + }; + + getContext().sendOrderedBroadcast( + makeBroadcastIntent(BROADCAST_REMOTE), + PERMISSION_DENIED, finish, null, Activity.RESULT_CANCELED, + null, null); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testReceiverCanNotRegister() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_LOCAL}); + getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_FAIL_REGISTER)); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testReceiverCanNotBind() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_LOCAL}); + getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_FAIL_BIND)); + waitForResultOrThrow(BROADCAST_TIMEOUT); + } + + public void testLocalUnregisterTwice() throws Exception { + registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), null); + unregisterMyReceiverNoCheck(); + try { + unregisterMyReceiverNoCheck(); + fail("No exception thrown on second unregister"); + } catch (IllegalArgumentException e) { + Log.i("foo", "Unregister exception", e); + } + } +} diff --git a/core/tests/coretests/src/android/app/activity/ClearTop.java b/core/tests/coretests/src/android/app/activity/ClearTop.java new file mode 100644 index 0000000..a5ee2ce --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/ClearTop.java @@ -0,0 +1,51 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +public class ClearTop extends Activity { + public static final String WAIT_CLEAR_TASK = "waitClearTask"; + + public ClearTop() { + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + //Log.i("foo", "Creating: " + this); + Intent intent = new Intent(getIntent()).setAction(LocalScreen.CLEAR_TASK) + .setClass(this, LocalScreen.class); + startActivity(intent); + } + + @Override + public void onNewIntent(Intent intent) { + //Log.i("foo", "New intent in " + this + ": " + intent); + if (LocalScreen.CLEAR_TASK.equals(intent.getAction())) { + setResult(RESULT_OK); + } else { + setResult(RESULT_CANCELED, new Intent().setAction( + "New intent received " + intent + ", expecting action " + + TestedScreen.CLEAR_TASK)); + } + finish(); + } +} diff --git a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java new file mode 100644 index 0000000..3c30915 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java @@ -0,0 +1,86 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.test.suitebuilder.annotation.Suppress; +import android.os.Bundle; +import android.test.suitebuilder.annotation.Suppress; + +public class IntentSenderTest extends BroadcastTest { + + public void testRegisteredReceivePermissionGranted() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_REG}); + registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_GRANTED); + addIntermediate("after-register"); + PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, + makeBroadcastIntent(BROADCAST_REGISTERED), 0); + is.send(); + waitForResultOrThrow(BROADCAST_TIMEOUT); + is.cancel(); + } + + public void testRegisteredReceivePermissionDenied() throws Exception { + final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED); + + setExpectedReceivers(new String[]{RECEIVER_RESULTS}); + registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_DENIED); + addIntermediate("after-register"); + + PendingIntent.OnFinished finish = new PendingIntent.OnFinished() { + public void onSendFinished(PendingIntent pi, Intent intent, + int resultCode, String resultData, Bundle resultExtras) { + gotReceive(RECEIVER_RESULTS, intent); + } + }; + + PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, 0); + is.send(Activity.RESULT_CANCELED, finish, null); + waitForResultOrThrow(BROADCAST_TIMEOUT); + is.cancel(); + } + + public void testLocalReceivePermissionGranted() throws Exception { + setExpectedReceivers(new String[]{RECEIVER_LOCAL}); + PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, + makeBroadcastIntent(BROADCAST_LOCAL_GRANTED), 0); + is.send(); + waitForResultOrThrow(BROADCAST_TIMEOUT); + is.cancel(); + } + + public void testLocalReceivePermissionDenied() throws Exception { + final Intent intent = makeBroadcastIntent(BROADCAST_LOCAL_DENIED); + + setExpectedReceivers(new String[]{RECEIVER_RESULTS}); + + PendingIntent.OnFinished finish = new PendingIntent.OnFinished() { + public void onSendFinished(PendingIntent pi, Intent intent, + int resultCode, String resultData, Bundle resultExtras) { + gotReceive(RECEIVER_RESULTS, intent); + } + }; + + PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, 0); + is.send(Activity.RESULT_CANCELED, finish, null); + waitForResultOrThrow(BROADCAST_TIMEOUT); + is.cancel(); + } +} diff --git a/core/tests/coretests/src/android/app/activity/LaunchTest.java b/core/tests/coretests/src/android/app/activity/LaunchTest.java new file mode 100644 index 0000000..5893fd0 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LaunchTest.java @@ -0,0 +1,75 @@ +/* + * 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.app.activity; + +import android.content.ComponentName; +import android.test.suitebuilder.annotation.LargeTest; + +public class LaunchTest extends ActivityTestsBase { + + @LargeTest + public void testColdActivity() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), TestedActivity.class)); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + @LargeTest + public void testLocalActivity() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), LocalActivity.class)); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + @LargeTest + public void testColdScreen() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), TestedScreen.class)); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + @LargeTest + public void testLocalScreen() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), LocalScreen.class)); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + @LargeTest + public void testForwardResult() throws Exception { + runLaunchpad(LaunchpadActivity.FORWARD_RESULT); + } + + // The following is disabled until we can catch and recover from + // application errors. + public void xxtestBadParcelable() throws Exception { + // All we really care about for this test is that the system + // doesn't crash. + runLaunchpad(LaunchpadActivity.BAD_PARCELABLE); + } + + @LargeTest + public void testClearTopInCreate() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), ClearTop.class)); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + @LargeTest + public void testClearTopWhileResumed() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), ClearTop.class)); + mIntent.putExtra(ClearTop.WAIT_CLEAR_TASK, true); + runLaunchpad(LaunchpadActivity.LAUNCH); + } +} + + diff --git a/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java new file mode 100644 index 0000000..7662456 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java @@ -0,0 +1,588 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; +import android.test.PerformanceTestCase; +import android.util.Log; + +class MyBadParcelable implements Parcelable { + public MyBadParcelable() { + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString("I am bad"); + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<MyBadParcelable> CREATOR + = new Parcelable.Creator<MyBadParcelable>() { + public MyBadParcelable createFromParcel(Parcel in) { + return new MyBadParcelable(in); + } + + public MyBadParcelable[] newArray(int size) { + return new MyBadParcelable[size]; + } + }; + + public MyBadParcelable(Parcel in) { + String nm = in.readString(); + } +} + +public class LaunchpadActivity extends Activity { + public interface CallingTest extends PerformanceTestCase.Intermediates { + public void startTiming(boolean realTime); + public void addIntermediate(String name); + public void addIntermediate(String name, long timeInNS); + public void finishTiming(boolean realTime); + public void activityFinished(int resultCode, Intent data, + RuntimeException where); + } + + // Also used as the Binder interface descriptor string in these tests + public static final String LAUNCH = "com.android.frameworks.coretests.activity.LAUNCH"; + + public static final String FORWARD_RESULT = + "com.android.frameworks.coretests.activity.FORWARD_RESULT"; + public static final String RETURNED_RESULT = + "com.android.frameworks.coretests.activity.RETURNED_RESULT"; + + public static final String BAD_PARCELABLE = + "comcom.android.frameworks.coretests.activity.BAD_PARCELABLE"; + + public static final int LAUNCHED_RESULT = 1; + public static final int FORWARDED_RESULT = 2; + + public static final String LIFECYCLE_BASIC = + "com.android.frameworks.coretests.activity.LIFECYCLE_BASIC"; + public static final String LIFECYCLE_SCREEN = + "com.android.frameworks.coretests.activity.LIFECYCLE_SCREEN"; + public static final String LIFECYCLE_DIALOG = + "com.android.frameworks.coretests.activity.LIFECYCLE_DIALOG"; + public static final String LIFECYCLE_FINISH_CREATE = + "com.android.frameworks.coretests.activity.LIFECYCLE_FINISH_CREATE"; + public static final String LIFECYCLE_FINISH_START = + "com.android.frameworks.coretests.activity.LIFECYCLE_FINISH_START"; + + public static final String BROADCAST_REGISTERED = + "com.android.frameworks.coretests.activity.BROADCAST_REGISTERED"; + public static final String BROADCAST_LOCAL = + "com.android.frameworks.coretests.activity.BROADCAST_LOCAL"; + public static final String BROADCAST_REMOTE = + "com.android.frameworks.coretests.activity.BROADCAST_REMOTE"; + public static final String BROADCAST_ALL = + "com.android.frameworks.coretests.activity.BROADCAST_ALL"; + public static final String BROADCAST_REPEAT = + "com.android.frameworks.coretests.activity.BROADCAST_REPEAT"; + public static final String BROADCAST_MULTI = + "com.android.frameworks.coretests.activity.BROADCAST_MULTI"; + public static final String BROADCAST_ABORT = + "com.android.frameworks.coretests.activity.BROADCAST_ABORT"; + + public static final String BROADCAST_STICKY1 = + "com.android.frameworks.coretests.activity.BROADCAST_STICKY1"; + public static final String BROADCAST_STICKY2 = + "com.android.frameworks.coretests.activity.BROADCAST_STICKY2"; + + public static final String RECEIVER_REG = "receiver-reg"; + public static final String RECEIVER_LOCAL = "receiver-local"; + public static final String RECEIVER_REMOTE = "receiver-remote"; + public static final String RECEIVER_ABORT = "receiver-abort"; + + public static final String DATA_1 = "one"; + public static final String DATA_2 = "two"; + + public static final String ON_START = "onStart"; + public static final String ON_RESTART = "onRestart"; + public static final String ON_RESUME = "onResume"; + public static final String ON_FREEZE = "onSaveInstanceState"; + public static final String ON_PAUSE = "onPause"; + public static final String ON_STOP = "onStop"; + public static final String ON_DESTROY = "onDestroy"; + + public static final String DO_FINISH = "finish"; + public static final String DO_LOCAL_SCREEN = "local-screen"; + public static final String DO_LOCAL_DIALOG = "local-dialog"; + + private boolean mBadParcelable = false; + + private boolean mStarted = false; + private long mStartTime; + + private int mResultCode = RESULT_CANCELED; + private Intent mData = (new Intent()).setAction("No result received"); + private RuntimeException mResultStack = null; + + private String[] mExpectedLifecycle = null; + private int mNextLifecycle; + + private String[] mExpectedReceivers = null; + private int mNextReceiver; + + private String[] mExpectedData = null; + private boolean[] mReceivedData = null; + + boolean mReceiverRegistered = false; + + private static CallingTest sCallingTest = null; + + public static void setCallingTest(CallingTest ct) { + sCallingTest = ct; + } + + public LaunchpadActivity() { + mStartTime = System.currentTimeMillis(); + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + String action = getIntent().getAction(); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "CREATE lauchpad " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + if (LIFECYCLE_BASIC.equals(action)) { + setExpectedLifecycle(new String[]{ON_START, ON_RESUME, + DO_FINISH, ON_PAUSE, ON_STOP, ON_DESTROY}); + } else if (LIFECYCLE_SCREEN.equals(action)) { + setExpectedLifecycle(new String[]{ON_START, ON_RESUME, + DO_LOCAL_SCREEN, ON_FREEZE, ON_PAUSE, ON_STOP, + ON_RESTART, ON_START, ON_RESUME, + DO_FINISH, ON_PAUSE, ON_STOP, ON_DESTROY}); + } else if (LIFECYCLE_DIALOG.equals(action)) { + setExpectedLifecycle(new String[]{ON_START, ON_RESUME, + DO_LOCAL_DIALOG, ON_FREEZE, ON_PAUSE, ON_RESUME, + DO_FINISH, ON_PAUSE, ON_STOP, ON_DESTROY}); + } else if (LIFECYCLE_FINISH_CREATE.equals(action)) { + // This one behaves a little differently when running in a group. + if (getParent() == null) { + setExpectedLifecycle(new String[]{ON_DESTROY}); + } else { + setExpectedLifecycle(new String[]{ON_START, ON_STOP, ON_DESTROY}); + } + finish(); + } else if (LIFECYCLE_FINISH_START.equals(action)) { + setExpectedLifecycle(new String[]{ON_START, DO_FINISH, + ON_STOP, ON_DESTROY}); + } + } + + @Override + protected void onStart() { + super.onStart(); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "START lauchpad " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + checkLifecycle(ON_START); + } + + @Override + protected void onRestart() { + super.onStart(); + checkLifecycle(ON_RESTART); + } + + @Override + protected void onResume() { + super.onResume(); + + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "RESUME lauchpad " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + checkLifecycle(ON_RESUME); + + if (!mStarted) { + mStarted = true; + + mHandler.postDelayed(mTimeout, 5 * 1000); + + String action = getIntent().getAction(); + + sCallingTest.startTiming(true); + + if (LAUNCH.equals(action)) { + Intent intent = getIntent(); + intent.setFlags(0); + intent.setComponent((ComponentName) + intent.getParcelableExtra("component")); + //System.out.println("*** Launchpad is starting: comp=" + intent.component); + startActivityForResult(intent, LAUNCHED_RESULT); + } else if (FORWARD_RESULT.equals(action)) { + Intent intent = getIntent(); + intent.setFlags(0); + intent.setClass(this, LocalScreen.class); + startActivityForResult(intent, FORWARDED_RESULT); + } else if (BAD_PARCELABLE.equals(action)) { + mBadParcelable = true; + Intent intent = getIntent(); + intent.setFlags(0); + intent.setClass(this, LocalScreen.class); + startActivityForResult(intent, LAUNCHED_RESULT); + } else if (BROADCAST_REGISTERED.equals(action)) { + setExpectedReceivers(new String[]{RECEIVER_REG}); + registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED)); + sCallingTest.addIntermediate("after-register"); + sendBroadcast(makeBroadcastIntent(BROADCAST_REGISTERED)); + } else if (BROADCAST_LOCAL.equals(action)) { + setExpectedReceivers(new String[]{RECEIVER_LOCAL}); + sendBroadcast(makeBroadcastIntent(BROADCAST_LOCAL)); + } else if (BROADCAST_REMOTE.equals(action)) { + setExpectedReceivers(new String[]{RECEIVER_REMOTE}); + sendBroadcast(makeBroadcastIntent(BROADCAST_REMOTE)); + } else if (BROADCAST_ALL.equals(action)) { + setExpectedReceivers(new String[]{ + RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL}); + registerMyReceiver(new IntentFilter(BROADCAST_ALL)); + sCallingTest.addIntermediate("after-register"); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null); + } else if (BROADCAST_MULTI.equals(action)) { + setExpectedReceivers(new String[]{ + RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL, + RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL, + RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL, + RECEIVER_LOCAL, RECEIVER_REMOTE, + RECEIVER_LOCAL, RECEIVER_REMOTE, + RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL, + RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL, + RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL, + RECEIVER_REMOTE, RECEIVER_LOCAL, + RECEIVER_REMOTE, RECEIVER_LOCAL}); + registerMyReceiver(new IntentFilter(BROADCAST_ALL)); + sCallingTest.addIntermediate("after-register"); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_LOCAL), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_REMOTE), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_LOCAL), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_REMOTE), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_REPEAT), null); + } else if (BROADCAST_ABORT.equals(action)) { + setExpectedReceivers(new String[]{ + RECEIVER_REMOTE, RECEIVER_ABORT}); + registerMyReceiver(new IntentFilter(BROADCAST_ABORT)); + sCallingTest.addIntermediate("after-register"); + sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ABORT), null); + } else if (BROADCAST_STICKY1.equals(action)) { + setExpectedReceivers(new String[]{RECEIVER_REG}); + setExpectedData(new String[]{DATA_1}); + registerMyReceiver(new IntentFilter(BROADCAST_STICKY1)); + sCallingTest.addIntermediate("after-register"); + } else if (BROADCAST_STICKY2.equals(action)) { + setExpectedReceivers(new String[]{RECEIVER_REG, RECEIVER_REG}); + setExpectedData(new String[]{DATA_1, DATA_2}); + IntentFilter filter = new IntentFilter(BROADCAST_STICKY1); + filter.addAction(BROADCAST_STICKY2); + registerMyReceiver(filter); + sCallingTest.addIntermediate("after-register"); + } + } + } + + @Override + protected void onSaveInstanceState(Bundle icicle) { + super.onSaveInstanceState(icicle); + checkLifecycle(ON_FREEZE); + if (mBadParcelable) { + icicle.putParcelable("baddy", new MyBadParcelable()); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "PAUSE lauchpad " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + checkLifecycle(ON_PAUSE); + } + + @Override + protected void onStop() { + super.onStop(); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "STOP lauchpad " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + checkLifecycle(ON_STOP); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + switch (requestCode) { + case LAUNCHED_RESULT: + sCallingTest.finishTiming(true); + finishWithResult(resultCode, data); + break; + case FORWARDED_RESULT: + sCallingTest.finishTiming(true); + if (RETURNED_RESULT.equals(data.getAction())) { + finishWithResult(resultCode, data); + } else { + finishWithResult(RESULT_CANCELED, (new Intent()).setAction( + "Bad data returned: " + data)); + } + break; + default: + sCallingTest.finishTiming(true); + finishWithResult(RESULT_CANCELED, (new Intent()).setAction( + "Unexpected request code: " + requestCode)); + break; + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "DESTROY lauchpad " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + checkLifecycle(ON_DESTROY); + sCallingTest.activityFinished(mResultCode, mData, mResultStack); + } + + private void setExpectedLifecycle(String[] lifecycle) { + mExpectedLifecycle = lifecycle; + mNextLifecycle = 0; + } + + private void checkLifecycle(String where) { + if (mExpectedLifecycle == null) return; + + if (mNextLifecycle >= mExpectedLifecycle.length) { + finishBad("Activity lifecycle incorrect: received " + where + + " but don't expect any more calls"); + mExpectedLifecycle = null; + return; + } + if (!mExpectedLifecycle[mNextLifecycle].equals(where)) { + finishBad("Activity lifecycle incorrect: received " + where + + " but expected " + mExpectedLifecycle[mNextLifecycle] + + " at " + mNextLifecycle); + mExpectedLifecycle = null; + return; + } + + mNextLifecycle++; + + if (mNextLifecycle >= mExpectedLifecycle.length) { + setTestResult(RESULT_OK, null); + return; + } + + String next = mExpectedLifecycle[mNextLifecycle]; + if (where.equals(ON_DESTROY)) { + finishBad("Activity lifecycle incorrect: received " + where + + " but expected more actions (next is " + next + ")"); + mExpectedLifecycle = null; + return; + } else if (next.equals(DO_FINISH)) { + mNextLifecycle++; + if (mNextLifecycle >= mExpectedLifecycle.length) { + setTestResult(RESULT_OK, null); + } + if (!isFinishing()) { + finish(); + } + } else if (next.equals(DO_LOCAL_SCREEN)) { + mNextLifecycle++; + Intent intent = new Intent(TestedScreen.WAIT_BEFORE_FINISH); + intent.setClass(this, LocalScreen.class); + startActivity(intent); + } else if (next.equals(DO_LOCAL_DIALOG)) { + mNextLifecycle++; + Intent intent = new Intent(TestedScreen.WAIT_BEFORE_FINISH); + intent.setClass(this, LocalDialog.class); + startActivity(intent); + } + } + + private void setExpectedReceivers(String[] receivers) { + mExpectedReceivers = receivers; + mNextReceiver = 0; + } + + private void setExpectedData(String[] data) { + mExpectedData = data; + mReceivedData = new boolean[data.length]; + } + + private Intent makeBroadcastIntent(String action) { + Intent intent = new Intent(action, null); + intent.putExtra("caller", mCallTarget); + return intent; + } + + private void finishGood() { + finishWithResult(RESULT_OK, null); + } + + private void finishBad(String error) { + finishWithResult(RESULT_CANCELED, (new Intent()).setAction(error)); + } + + private void finishWithResult(int resultCode, Intent data) { + setTestResult(resultCode, data); + finish(); + } + + private void setTestResult(int resultCode, Intent data) { + mHandler.removeCallbacks(mTimeout); + unregisterMyReceiver(); + mResultCode = resultCode; + mData = data; + mResultStack = new RuntimeException("Original error was here"); + mResultStack.fillInStackTrace(); + } + + private void registerMyReceiver(IntentFilter filter) { + mReceiverRegistered = true; + //System.out.println("Registering: " + mReceiver); + registerReceiver(mReceiver, filter); + } + + private void unregisterMyReceiver() { + if (mReceiverRegistered) { + mReceiverRegistered = false; + //System.out.println("Unregistering: " + mReceiver); + unregisterReceiver(mReceiver); + } + } + + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + } + }; + + static final int GOT_RECEIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION; + static final int ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1; + + private Binder mCallTarget = new Binder() { + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) { + data.setDataPosition(0); + data.enforceInterface(LaunchpadActivity.LAUNCH); + if (code == GOT_RECEIVE_TRANSACTION) { + String name = data.readString(); + gotReceive(name, null); + return true; + } else if (code == ERROR_TRANSACTION) { + finishBad(data.readString()); + return true; + } + return false; + } + }; + + private final void gotReceive(String name, Intent intent) { + synchronized (this) { + + //System.out.println("Got receive: " + name); + //System.out.println(mNextReceiver + " in " + mExpectedReceivers); + //new RuntimeException("stack").printStackTrace(); + + sCallingTest.addIntermediate(mNextReceiver + "-" + name); + + if (mExpectedData != null) { + int n = mExpectedData.length; + int i; + boolean prev = false; + for (i = 0; i < n; i++) { + if (mExpectedData[i].equals(intent.getStringExtra("test"))) { + if (mReceivedData[i]) { + prev = true; + continue; + } + mReceivedData[i] = true; + break; + } + } + if (i >= n) { + if (prev) { + finishBad("Receive got data too many times: " + + intent.getStringExtra("test")); + } else { + finishBad("Receive got unexpected data: " + + intent.getStringExtra("test")); + } + return; + } + } + + if (mNextReceiver >= mExpectedReceivers.length) { + finishBad("Got too many onReceiveIntent() calls!"); +// System.out.println("Too many intents received: now at " +// + mNextReceiver + ", expect list: " +// + Arrays.toString(mExpectedReceivers)); + } else if (!mExpectedReceivers[mNextReceiver].equals(name)) { + finishBad("Receive out of order: got " + name + " but expected " + + mExpectedReceivers[mNextReceiver] + " at " + + mNextReceiver); + } else { + mNextReceiver++; + if (mNextReceiver == mExpectedReceivers.length) { + mHandler.post(mUnregister); + } + } + + } + } + + private Runnable mUnregister = new Runnable() { + public void run() { + if (mReceiverRegistered) { + sCallingTest.addIntermediate("before-unregister"); + unregisterMyReceiver(); + } + sCallingTest.finishTiming(true); + finishGood(); + } + }; + + private Runnable mTimeout = new Runnable() { + public void run() { + Log.i("foo", "**** TIMEOUT"); + String msg = "Timeout"; + if (mExpectedReceivers != null + && mNextReceiver < mExpectedReceivers.length) { + msg = msg + " waiting for " + mExpectedReceivers[mNextReceiver]; + } + finishBad(msg); + } + }; + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + //System.out.println("Receive in: " + this + ": " + intent); + gotReceive(RECEIVER_REG, intent); + } + }; +} + diff --git a/core/tests/coretests/src/android/app/activity/LaunchpadTabActivity.java b/core/tests/coretests/src/android/app/activity/LaunchpadTabActivity.java new file mode 100644 index 0000000..79b860f --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LaunchpadTabActivity.java @@ -0,0 +1,43 @@ +/* + * 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.app.activity; + +import android.app.TabActivity; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Bundle; +import android.widget.TabHost; + +public class LaunchpadTabActivity extends TabActivity { + public LaunchpadTabActivity() { + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + Intent tabIntent = new Intent(getIntent()); + tabIntent.setComponent((ComponentName)tabIntent.getParcelableExtra("tab")); + + TabHost th = getTabHost(); + TabHost.TabSpec ts = th.newTabSpec("1"); + ts.setIndicator("One"); + ts.setContent(tabIntent); + th.addTab(ts); + } +} + diff --git a/core/tests/coretests/src/android/app/activity/LifecycleTest.java b/core/tests/coretests/src/android/app/activity/LifecycleTest.java new file mode 100644 index 0000000..768a9a4 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LifecycleTest.java @@ -0,0 +1,109 @@ +/* + * 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.app.activity; + +import android.content.ComponentName; +import android.content.Intent; +import android.test.FlakyTest; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; + +public class LifecycleTest extends ActivityTestsBase { + private Intent mTopIntent; + private Intent mTabIntent; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mTopIntent = mIntent; + mTabIntent = new Intent(mContext, LaunchpadTabActivity.class); + mTabIntent.putExtra("tab", new ComponentName(mContext, + LaunchpadActivity.class)); + } + + @LargeTest + public void testBasic() throws Exception { + mIntent = mTopIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_BASIC); + } + + //Suppressing until 1285425 is fixed. + @Suppress + public void testTabBasic() throws Exception { + mIntent = mTabIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_BASIC); + } + + //Marking flaky until bug 1164344 is fixed. + // @FlakyTest(tolerance=2) + // @LargeTest + public void testScreen() throws Exception { + mIntent = mTopIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_SCREEN); + } + + //Marking flaky until bug 1164344 is fixed. + //@FlakyTest(tolerance=2) + //Suppressing until 1285425 is fixed. + @Suppress + public void testTabScreen() throws Exception { + mIntent = mTabIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_SCREEN); + } + + //flaky test, removing from large suite until 1866891 is fixed + //@LargeTest + public void testDialog() throws Exception { + mIntent = mTopIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_DIALOG); + } + + //Suppressing until 1285425 is fixed. + @Suppress + public void testTabDialog() throws Exception { + mIntent = mTabIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_DIALOG); + } + + @MediumTest + public void testFinishCreate() throws Exception { + mIntent = mTopIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_FINISH_CREATE); + } + + //Suppressing until 1285425 is fixed. + @Suppress + public void testTabFinishCreate() throws Exception { + mIntent = mTabIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_FINISH_CREATE); + } + + @MediumTest + public void testFinishStart() throws Exception { + mIntent = mTopIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_FINISH_START); + } + + //Suppressing until 1285425 is fixed. + @Suppress + public void testTabFinishStart() throws Exception { + mIntent = mTabIntent; + runLaunchpad(LaunchpadActivity.LIFECYCLE_FINISH_START); + } +} diff --git a/core/tests/coretests/src/android/app/activity/LocalActivity.java b/core/tests/coretests/src/android/app/activity/LocalActivity.java new file mode 100644 index 0000000..01f1fb6 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalActivity.java @@ -0,0 +1,33 @@ +/* + * 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.app.activity; + +import java.util.Map; + +import android.app.Activity; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; + +public class LocalActivity extends TestedActivity +{ + public LocalActivity() + { + } +} + diff --git a/core/tests/coretests/src/android/app/activity/LocalDeniedReceiver.java b/core/tests/coretests/src/android/app/activity/LocalDeniedReceiver.java new file mode 100644 index 0000000..2120a1d --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalDeniedReceiver.java @@ -0,0 +1,42 @@ +/* + * 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.app.activity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; + +class LocalDeniedReceiver extends BroadcastReceiver { + public LocalDeniedReceiver() { + } + + public void onReceive(Context context, Intent intent) { + try { + IBinder caller = intent.getIBinderExtra("caller"); + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(LaunchpadActivity.LAUNCH); + data.writeString(BroadcastTest.RECEIVER_LOCAL); + caller.transact(BroadcastTest.GOT_RECEIVE_TRANSACTION, data, null, 0); + data.recycle(); + } catch (RemoteException ex) { + } + } +} + diff --git a/core/tests/coretests/src/android/app/activity/LocalDeniedService.java b/core/tests/coretests/src/android/app/activity/LocalDeniedService.java new file mode 100644 index 0000000..3bdac22 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalDeniedService.java @@ -0,0 +1,22 @@ +/* + * 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.app.activity; + +public class LocalDeniedService extends LocalService +{ +} + diff --git a/core/tests/coretests/src/android/app/activity/LocalDialog.java b/core/tests/coretests/src/android/app/activity/LocalDialog.java new file mode 100644 index 0000000..c92fa43 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalDialog.java @@ -0,0 +1,33 @@ +/* + * 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.app.activity; + +import java.util.Map; + +import android.app.Activity; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; + +public class LocalDialog extends TestedScreen +{ + public LocalDialog() + { + } +} + diff --git a/core/tests/coretests/src/android/app/activity/LocalGrantedReceiver.java b/core/tests/coretests/src/android/app/activity/LocalGrantedReceiver.java new file mode 100644 index 0000000..c9e6ab4 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalGrantedReceiver.java @@ -0,0 +1,42 @@ +/* + * 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.app.activity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; + +public class LocalGrantedReceiver extends BroadcastReceiver { + public LocalGrantedReceiver() { + } + + public void onReceive(Context context, Intent intent) { + try { + IBinder caller = intent.getIBinderExtra("caller"); + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(LaunchpadActivity.LAUNCH); + data.writeString(BroadcastTest.RECEIVER_LOCAL); + caller.transact(BroadcastTest.GOT_RECEIVE_TRANSACTION, data, null, 0); + data.recycle(); + } catch (RemoteException ex) { + } + } +} + diff --git a/core/tests/coretests/src/android/app/activity/LocalGrantedService.java b/core/tests/coretests/src/android/app/activity/LocalGrantedService.java new file mode 100644 index 0000000..7ab0fb4 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalGrantedService.java @@ -0,0 +1,22 @@ +/* + * 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.app.activity; + +public class LocalGrantedService extends LocalService +{ +} + diff --git a/core/tests/coretests/src/android/app/activity/LocalProvider.java b/core/tests/coretests/src/android/app/activity/LocalProvider.java new file mode 100644 index 0000000..085e622 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalProvider.java @@ -0,0 +1,163 @@ +/* + * 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.app.activity; + +import android.content.UriMatcher; +import android.content.*; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.util.Config; +import android.util.Log; + +/** Simple test provider that runs in the local process. */ +public class LocalProvider extends ContentProvider { + private static final String TAG = "LocalProvider"; + + private SQLiteOpenHelper mOpenHelper; + + private static final int DATA = 1; + private static final int DATA_ID = 2; + private static final UriMatcher sURLMatcher = new UriMatcher( + UriMatcher.NO_MATCH); + + static { + sURLMatcher.addURI("*", "data", DATA); + sURLMatcher.addURI("*", "data/#", DATA_ID); + } + + private static class DatabaseHelper extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "local.db"; + private static final int DATABASE_VERSION = 1; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE data (" + + "_id INTEGER PRIMARY KEY," + + "text TEXT, " + + "integer INTEGER);"); + + // insert alarms + db.execSQL("INSERT INTO data (text, integer) VALUES ('first data', 100);"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { + Log.w(TAG, "Upgrading test database from version " + + oldVersion + " to " + currentVersion + + ", which will destroy all old data"); + db.execSQL("DROP TABLE IF EXISTS data"); + onCreate(db); + } + } + + + public LocalProvider() { + } + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + return true; + } + + @Override + public Cursor query(Uri url, String[] projectionIn, String selection, + String[] selectionArgs, String sort) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + // Generate the body of the query + int match = sURLMatcher.match(url); + switch (match) { + case DATA: + qb.setTables("data"); + break; + case DATA_ID: + qb.setTables("data"); + qb.appendWhere("_id="); + qb.appendWhere(url.getPathSegments().get(1)); + break; + default: + throw new IllegalArgumentException("Unknown URL " + url); + } + + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, + null, null, sort); + + if (ret == null) { + if (Config.LOGD) Log.d(TAG, "Alarms.query: failed"); + } else { + ret.setNotificationUri(getContext().getContentResolver(), url); + } + + return ret; + } + + @Override + public String getType(Uri url) { + int match = sURLMatcher.match(url); + switch (match) { + case DATA: + return "vnd.android.cursor.dir/vnd.google.unit_tests.local"; + case DATA_ID: + return "vnd.android.cursor.item/vnd.google.unit_tests.local"; + default: + throw new IllegalArgumentException("Unknown URL"); + } + } + + @Override + public int update(Uri url, ContentValues values, String where, String[] whereArgs) { + int count; + long rowId = 0; + int match = sURLMatcher.match(url); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + switch (match) { + case DATA_ID: { + String segment = url.getPathSegments().get(1); + rowId = Long.parseLong(segment); + count = db.update("data", values, "_id=" + rowId, null); + break; + } + default: { + throw new UnsupportedOperationException( + "Cannot update URL: " + url); + } + } + if (Config.LOGD) Log.d(TAG, "*** notifyChange() rowId: " + rowId); + getContext().getContentResolver().notifyChange(url, null); + return count; + } + + + @Override + public Uri insert(Uri url, ContentValues initialValues) { + return null; + } + + @Override + public int delete(Uri url, String where, String[] whereArgs) { + throw new UnsupportedOperationException("delete not supported"); + } +} diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java new file mode 100644 index 0000000..bfd543f --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java @@ -0,0 +1,77 @@ +/* + * 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.app.activity; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ReceiverCallNotAllowedException; +import android.content.ServiceConnection; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; + +public class LocalReceiver extends BroadcastReceiver { + public LocalReceiver() { + } + + public void onReceive(Context context, Intent intent) { + String resultString = LaunchpadActivity.RECEIVER_LOCAL; + if (BroadcastTest.BROADCAST_FAIL_REGISTER.equals(intent.getAction())) { + resultString = "Successfully registered, but expected it to fail"; + try { + context.registerReceiver(this, new IntentFilter("foo.bar")); + context.unregisterReceiver(this); + } catch (ReceiverCallNotAllowedException e) { + //resultString = "This is the correct behavior but not yet implemented"; + resultString = LaunchpadActivity.RECEIVER_LOCAL; + } + } else if (BroadcastTest.BROADCAST_FAIL_BIND.equals(intent.getAction())) { + resultString = "Successfully bound to service, but expected it to fail"; + try { + ServiceConnection sc = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder service) { + } + + public void onServiceDisconnected(ComponentName name) { + } + }; + context.bindService(new Intent(context, LocalService.class), sc, 0); + context.unbindService(sc); + } catch (ReceiverCallNotAllowedException e) { + //resultString = "This is the correct behavior but not yet implemented"; + resultString = LaunchpadActivity.RECEIVER_LOCAL; + } + } else if (LaunchpadActivity.BROADCAST_REPEAT.equals(intent.getAction())) { + Intent newIntent = new Intent(intent); + newIntent.setAction(LaunchpadActivity.BROADCAST_LOCAL); + context.sendOrderedBroadcast(newIntent, null); + } + try { + IBinder caller = intent.getIBinderExtra("caller"); + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(LaunchpadActivity.LAUNCH); + data.writeString(resultString); + caller.transact(LaunchpadActivity.GOT_RECEIVE_TRANSACTION, data, null, 0); + data.recycle(); + } catch (RemoteException ex) { + } + } +} + diff --git a/core/tests/coretests/src/android/app/activity/LocalScreen.java b/core/tests/coretests/src/android/app/activity/LocalScreen.java new file mode 100644 index 0000000..f7c8c33 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalScreen.java @@ -0,0 +1,33 @@ +/* + * 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.app.activity; + +import java.util.Map; + +import android.app.Activity; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; + +public class LocalScreen extends TestedScreen +{ + public LocalScreen() + { + } +} + diff --git a/core/tests/coretests/src/android/app/activity/LocalService.java b/core/tests/coretests/src/android/app/activity/LocalService.java new file mode 100644 index 0000000..c31ca4b --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/LocalService.java @@ -0,0 +1,122 @@ +/* + * 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.app.activity; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; +import android.util.Log; + +public class LocalService extends Service { + private final IBinder mBinder = new Binder() { + + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, + int flags) throws RemoteException { + if (code == ServiceTest.SET_REPORTER_CODE) { + data.enforceInterface(ServiceTest.SERVICE_LOCAL); + mReportObject = data.readStrongBinder(); + return true; + } else { + return super.onTransact(code, data, reply, flags); + } + } + + }; + + private IBinder mReportObject; + private int mStartCount = 1; + + public LocalService() { + } + + @Override + public void onStart(Intent intent, int startId) { + //Log.i("LocalService", "onStart: " + intent); + if (intent.getExtras() != null) { + mReportObject = intent.getExtras().getIBinder(ServiceTest.REPORT_OBJ_NAME); + if (mReportObject != null) { + try { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL); + data.writeInt(mStartCount); + mStartCount++; + mReportObject.transact( + ServiceTest.STARTED_CODE, data, null, 0); + data.recycle(); + } catch (RemoteException e) { + } + } + } + } + + @Override + public void onDestroy() { + Log.i("LocalService", "onDestroy: mReportObject=" + mReportObject); + if (mReportObject != null) { + try { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL); + mReportObject.transact( + ServiceTest.DESTROYED_CODE, data, null, 0); + data.recycle(); + } catch (RemoteException e) { + } + } + } + + @Override + public IBinder onBind(Intent intent) { + Log.i("LocalService", "onBind: " + intent); + return mBinder; + } + + @Override + public boolean onUnbind(Intent intent) { + Log.i("LocalService", "onUnbind: " + intent); + if (mReportObject != null) { + try { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL); + mReportObject.transact( + ServiceTest.UNBIND_CODE, data, null, 0); + data.recycle(); + } catch (RemoteException e) { + } + } + return true; + } + + @Override + public void onRebind(Intent intent) { + Log.i("LocalService", "onUnbind: " + intent); + if (mReportObject != null) { + try { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL); + mReportObject.transact( + ServiceTest.REBIND_CODE, data, null, 0); + data.recycle(); + } catch (RemoteException e) { + } + } + } +} diff --git a/core/tests/coretests/src/android/app/activity/MetaDataTest.java b/core/tests/coretests/src/android/app/activity/MetaDataTest.java new file mode 100644 index 0000000..214bc91 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/MetaDataTest.java @@ -0,0 +1,166 @@ +/* + * 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.app.activity; + +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.ServiceInfo; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import com.android.frameworks.coretests.R; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * Tests for meta-data associated with application components. + */ +public class MetaDataTest extends AndroidTestCase { + + private void checkMetaData(ComponentName cn, PackageItemInfo ci) + throws IOException, XmlPullParserException { + assertNotNull("Unable to find component " + cn, ci); + + Bundle md = ci.metaData; + assertNotNull("No meta data found", md); + + assertEquals("foo", md.getString("com.android.frameworks.coretests.string")); + assertTrue(md.getBoolean("com.android.frameworks.coretests.boolean")); + assertEquals(100, md.getInt("com.android.frameworks.coretests.integer")); + assertEquals(0xff000000, md.getInt("com.android.frameworks.coretests.color")); + + assertEquals((double) 1001, + Math.floor(md.getFloat("com.android.frameworks.coretests.float") * 10 + .5)); + + assertEquals(R.xml.metadata, md.getInt("com.android.frameworks.coretests.reference")); + + XmlResourceParser xml = ci.loadXmlMetaData(mContext.getPackageManager(), + "com.android.frameworks.coretests.reference"); + assertNotNull(xml); + + int type; + while ((type = xml.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + } + assertEquals(XmlPullParser.START_TAG, type); + assertEquals("thedata", xml.getName()); + + // method 1: direct access + final String rawAttr = xml.getAttributeValue(null, "rawText"); + assertEquals("some raw text", rawAttr); + + // method 2: direct access of typed value + final int rawColorIntAttr = xml.getAttributeIntValue(null, "rawColor", 0); + assertEquals(0xffffff00, rawColorIntAttr); + final String rawColorStrAttr = xml.getAttributeValue(null, "rawColor"); + assertEquals("#ffffff00", rawColorStrAttr); + + // method 2: direct access of resource attribute + final String nameSpace = "http://schemas.android.com/apk/res/android"; + final int colorIntAttr = xml.getAttributeIntValue(nameSpace, "color", 0); + assertEquals(0xffff0000, colorIntAttr); + final String colorStrAttr = xml.getAttributeValue(nameSpace, "color"); + assertEquals("#ffff0000", colorStrAttr); + + // method 3: styled access (borrowing an attr from view system here) + TypedArray a = mContext.obtainStyledAttributes(xml, + android.R.styleable.TextView); + String styledAttr = a.getString(android.R.styleable.TextView_text); + assertEquals("text", styledAttr); + a.recycle(); + + xml.close(); + } + + @SmallTest + public void testActivityWithData() throws Exception { + ComponentName cn = new ComponentName(mContext, LocalActivity.class); + ActivityInfo ai = mContext.getPackageManager().getActivityInfo( + cn, PackageManager.GET_META_DATA); + + checkMetaData(cn, ai); + + ai = mContext.getPackageManager().getActivityInfo(cn, 0); + + assertNull("Meta data returned when not requested", ai.metaData); + } + + @SmallTest + public void testReceiverWithData() throws Exception { + ComponentName cn = new ComponentName(mContext, LocalReceiver.class); + ActivityInfo ai = mContext.getPackageManager().getReceiverInfo( + cn, PackageManager.GET_META_DATA); + + checkMetaData(cn, ai); + + ai = mContext.getPackageManager().getReceiverInfo(cn, 0); + + assertNull("Meta data returned when not requested", ai.metaData); + } + + @SmallTest + public void testServiceWithData() throws Exception { + ComponentName cn = new ComponentName(mContext, LocalService.class); + ServiceInfo si = mContext.getPackageManager().getServiceInfo( + cn, PackageManager.GET_META_DATA); + + checkMetaData(cn, si); + + si = mContext.getPackageManager().getServiceInfo(cn, 0); + + assertNull("Meta data returned when not requested", si.metaData); + } + + @MediumTest + public void testProviderWithData() throws Exception { + ComponentName cn = new ComponentName(mContext, LocalProvider.class); + ProviderInfo pi = mContext.getPackageManager().resolveContentProvider( + "com.android.frameworks.coretests.LocalProvider", + PackageManager.GET_META_DATA); + checkMetaData(cn, pi); + + pi = mContext.getPackageManager().resolveContentProvider( + "com.android.frameworks.coretests.LocalProvider", 0); + + assertNull("Meta data returned when not requested", pi.metaData); + } + + @SmallTest + public void testPermissionWithData() throws Exception { + ComponentName cn = new ComponentName("foo", + "com.android.frameworks.coretests.permission.TEST_GRANTED"); + PermissionInfo pi = mContext.getPackageManager().getPermissionInfo( + cn.getClassName(), PackageManager.GET_META_DATA); + checkMetaData(cn, pi); + + pi = mContext.getPackageManager().getPermissionInfo( + cn.getClassName(), 0); + + assertNull("Meta data returned when not requested", pi.metaData); + } +} + + diff --git a/core/tests/coretests/src/android/app/activity/RemoteDeniedReceiver.java b/core/tests/coretests/src/android/app/activity/RemoteDeniedReceiver.java new file mode 100644 index 0000000..7c89346 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/RemoteDeniedReceiver.java @@ -0,0 +1,42 @@ +/* + * 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.app.activity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; + +class RemoteDeniedReceiver extends BroadcastReceiver { + public RemoteDeniedReceiver() { + } + + public void onReceive(Context context, Intent intent) { + try { + IBinder caller = intent.getIBinderExtra("caller"); + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(LaunchpadActivity.LAUNCH); + data.writeString(BroadcastTest.RECEIVER_REMOTE); + caller.transact(BroadcastTest.GOT_RECEIVE_TRANSACTION, data, null, 0); + data.recycle(); + } catch (RemoteException ex) { + } + } +} + diff --git a/core/tests/coretests/src/android/app/activity/RemoteGrantedReceiver.java b/core/tests/coretests/src/android/app/activity/RemoteGrantedReceiver.java new file mode 100644 index 0000000..0eca8f7 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/RemoteGrantedReceiver.java @@ -0,0 +1,42 @@ +/* + * 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.app.activity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; + +public class RemoteGrantedReceiver extends BroadcastReceiver { + public RemoteGrantedReceiver() { + } + + public void onReceive(Context context, Intent intent) { + try { + IBinder caller = intent.getIBinderExtra("caller"); + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(LaunchpadActivity.LAUNCH); + data.writeString(BroadcastTest.RECEIVER_REMOTE); + caller.transact(BroadcastTest.GOT_RECEIVE_TRANSACTION, data, null, 0); + data.recycle(); + } catch (RemoteException ex) { + } + } +} + diff --git a/core/tests/coretests/src/android/app/activity/RemoteReceiver.java b/core/tests/coretests/src/android/app/activity/RemoteReceiver.java new file mode 100644 index 0000000..9608fc4 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/RemoteReceiver.java @@ -0,0 +1,50 @@ +/* + * 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.app.activity; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; + +public class RemoteReceiver extends BroadcastReceiver +{ + public RemoteReceiver() + { + } + + public void onReceive(Context context, Intent intent) + { + if (LaunchpadActivity.BROADCAST_REPEAT.equals(intent.getAction())) { + Intent newIntent = new Intent(intent); + newIntent.setAction(LaunchpadActivity.BROADCAST_REMOTE); + context.sendOrderedBroadcast(newIntent, null); + } + try { + IBinder caller = intent.getIBinderExtra("caller"); + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(LaunchpadActivity.LAUNCH); + data.writeString(LaunchpadActivity.RECEIVER_REMOTE); + caller.transact(LaunchpadActivity.GOT_RECEIVE_TRANSACTION, data, null, 0); + data.recycle(); + } catch (RemoteException ex) { + } + } +} + diff --git a/core/tests/coretests/src/android/app/activity/RemoteSubActivityScreen.java b/core/tests/coretests/src/android/app/activity/RemoteSubActivityScreen.java new file mode 100644 index 0000000..e969d10 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/RemoteSubActivityScreen.java @@ -0,0 +1,59 @@ +/* //device/apps/AndroidTests/src/com.android.unit_tests/activity/TestedScreen.java +** +** 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. +*/ + +package android.app.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Process; +import android.util.Log; + +public class RemoteSubActivityScreen extends SubActivityScreen { + Handler mHandler = new Handler(); + boolean mFirst = false; + + public RemoteSubActivityScreen() { + } + + @Override + public void onCreate(Bundle icicle) { + // We are running in a remote process, so want to have the sub-activity + // sending the result back in the original process. + Intent intent = getIntent(); + intent.setClass(this, SubActivityScreen.class); + + super.onCreate(icicle); + + boolean kill = intent.getBooleanExtra("kill", false); + //Log.i("foo", "RemoteSubActivityScreen pid=" + Process.myPid() + // + " kill=" + kill); + + if (kill) { + // After finishing initialization, kill the process! But only if + // this is the first time... + if (icicle == null) { + mHandler.post(new Runnable() { + public void run() { + handleBeforeStopping(); + Process.killProcess(Process.myPid()); + } + }); + } + } + } +} diff --git a/core/tests/coretests/src/android/app/activity/ResultReceiver.java b/core/tests/coretests/src/android/app/activity/ResultReceiver.java new file mode 100644 index 0000000..f7daf2c --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/ResultReceiver.java @@ -0,0 +1,43 @@ +/* + * 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.app.activity; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Bundle; + +import java.util.Map; + +public class ResultReceiver extends BroadcastReceiver +{ + public ResultReceiver() + { + } + + public void onReceive(Context context, Intent intent) + { + setResultCode(3); + setResultData("bar"); + Bundle map = getResultExtras(false); + map.remove("remove"); + map.putString("bar", "them"); + } +} + diff --git a/core/tests/coretests/src/android/app/activity/SearchableActivity.java b/core/tests/coretests/src/android/app/activity/SearchableActivity.java new file mode 100644 index 0000000..e238572 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/SearchableActivity.java @@ -0,0 +1,30 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.os.Bundle; + +public class SearchableActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + finish(); + } + +} diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java new file mode 100644 index 0000000..d3ae415 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java @@ -0,0 +1,469 @@ +/* + * 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.app.activity; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; +import android.util.Log; + +// These test binders purport to support an interface whose canonical +// interface name is ServiceTest.SERVICE_LOCAL +// Temporarily suppress, this test is causing unit test suite run to fail +// TODO: remove this suppress +@Suppress +public class ServiceTest extends ActivityTestsBase { + + public static final String SERVICE_LOCAL = + "com.android.frameworks.coretests.activity.SERVICE_LOCAL"; + public static final String SERVICE_LOCAL_GRANTED = + "com.android.frameworks.coretests.activity.SERVICE_LOCAL_GRANTED"; + public static final String SERVICE_LOCAL_DENIED = + "com.android.frameworks.coretests.activity.SERVICE_LOCAL_DENIED"; + + public static final String REPORT_OBJ_NAME = "report"; + + public static final int STARTED_CODE = 1; + public static final int DESTROYED_CODE = 2; + public static final int SET_REPORTER_CODE = 3; + public static final int UNBIND_CODE = 4; + public static final int REBIND_CODE = 5; + + public static final int STATE_START_1 = 0; + public static final int STATE_START_2 = 1; + public static final int STATE_UNBIND = 2; + public static final int STATE_DESTROY = 3; + public static final int STATE_REBIND = 4; + public static final int STATE_UNBIND_ONLY = 5; + public int mStartState; + + public IBinder mStartReceiver = new Binder() { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, + int flags) throws RemoteException { + //Log.i("ServiceTest", "Received code " + code + " in state " + mStartState); + if (code == STARTED_CODE) { + data.enforceInterface(SERVICE_LOCAL); + int count = data.readInt(); + if (mStartState == STATE_START_1) { + if (count == 1) { + finishGood(); + } else { + finishBad("onStart() again on an object when it should have been the first time"); + } + } else if (mStartState == STATE_START_2) { + if (count == 2) { + finishGood(); + } else { + finishBad("onStart() the first time on an object when it should have been the second time"); + } + } else { + finishBad("onStart() was called when not expected (state="+mStartState+")"); + } + return true; + } else if (code == DESTROYED_CODE) { + data.enforceInterface(SERVICE_LOCAL); + if (mStartState == STATE_DESTROY) { + finishGood(); + } else { + finishBad("onDestroy() was called when not expected (state="+mStartState+")"); + } + return true; + } else if (code == UNBIND_CODE) { + data.enforceInterface(SERVICE_LOCAL); + if (mStartState == STATE_UNBIND) { + mStartState = STATE_DESTROY; + } else if (mStartState == STATE_UNBIND_ONLY) { + finishGood(); + } else { + finishBad("onUnbind() was called when not expected (state="+mStartState+")"); + } + return true; + } else if (code == REBIND_CODE) { + data.enforceInterface(SERVICE_LOCAL); + if (mStartState == STATE_REBIND) { + finishGood(); + } else { + finishBad("onRebind() was called when not expected (state="+mStartState+")"); + } + return true; + } else { + return super.onTransact(code, data, reply, flags); + } + } + }; + + public class EmptyConnection implements ServiceConnection { + public void onServiceConnected(ComponentName name, IBinder service) { + } + + public void onServiceDisconnected(ComponentName name) { + } + } + + public class TestConnection implements ServiceConnection { + private final boolean mExpectDisconnect; + private final boolean mSetReporter; + private boolean mMonitor; + private int mCount; + + public TestConnection(boolean expectDisconnect, boolean setReporter) { + mExpectDisconnect = expectDisconnect; + mSetReporter = setReporter; + mMonitor = !setReporter; + } + + void setMonitor(boolean v) { + mMonitor = v; + } + + public void onServiceConnected(ComponentName name, IBinder service) { + if (mSetReporter) { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(SERVICE_LOCAL); + data.writeStrongBinder(mStartReceiver); + try { + service.transact(SET_REPORTER_CODE, data, null, 0); + } catch (RemoteException e) { + finishBad("DeadObjectException when sending reporting object"); + } + data.recycle(); + } + + if (mMonitor) { + mCount++; + if (mStartState == STATE_START_1) { + if (mCount == 1) { + finishGood(); + } else { + finishBad("onServiceConnected() again on an object when it should have been the first time"); + } + } else if (mStartState == STATE_START_2) { + if (mCount == 2) { + finishGood(); + } else { + finishBad("onServiceConnected() the first time on an object when it should have been the second time"); + } + } else { + finishBad("onServiceConnected() called unexpectedly"); + } + } + } + + public void onServiceDisconnected(ComponentName name) { + if (mMonitor) { + if (mStartState == STATE_DESTROY) { + if (mExpectDisconnect) { + finishGood(); + } else { + finishBad("onServiceDisconnected() when it shouldn't have been"); + } + } else { + finishBad("onServiceDisconnected() called unexpectedly"); + } + } + } + } + + void startExpectResult(Intent service) { + startExpectResult(service, new Bundle()); + } + + void startExpectResult(Intent service, Bundle bundle) { + bundle.putIBinder(REPORT_OBJ_NAME, mStartReceiver); + boolean success = false; + try { + //Log.i("foo", "STATE_START_1"); + mStartState = STATE_START_1; + getContext().startService(new Intent(service).putExtras(bundle)); + waitForResultOrThrow(5 * 1000, "service to start first time"); + //Log.i("foo", "STATE_START_2"); + mStartState = STATE_START_2; + getContext().startService(new Intent(service).putExtras(bundle)); + waitForResultOrThrow(5 * 1000, "service to start second time"); + success = true; + } finally { + if (!success) { + try { + getContext().stopService(service); + } catch (Exception e) { + // eat + } + } + } + //Log.i("foo", "STATE_DESTROY"); + mStartState = STATE_DESTROY; + getContext().stopService(service); + waitForResultOrThrow(5 * 1000, "service to be destroyed"); + } + + void startExpectNoPermission(Intent service) { + try { + getContext().startService(service); + fail("Expected security exception when starting " + service); + } catch (SecurityException e) { + // expected + } + } + + void bindExpectResult(Intent service) { + TestConnection conn = new TestConnection(true, false); + TestConnection conn2 = new TestConnection(false, false); + boolean success = false; + try { + // Expect to see the TestConnection connected. + mStartState = STATE_START_1; + getContext().bindService(service, conn, 0); + getContext().startService(service); + waitForResultOrThrow(5 * 1000, "existing connection to receive service"); + + // Expect to see the second TestConnection connected. + getContext().bindService(service, conn2, 0); + waitForResultOrThrow(5 * 1000, "new connection to receive service"); + + getContext().unbindService(conn2); + success = true; + } finally { + if (!success) { + try { + getContext().stopService(service); + getContext().unbindService(conn); + getContext().unbindService(conn2); + } catch (Exception e) { + // eat + } + } + } + + // Expect to see the TestConnection disconnected. + mStartState = STATE_DESTROY; + getContext().stopService(service); + waitForResultOrThrow(5 * 1000, "existing connection to lose service"); + + getContext().unbindService(conn); + + conn = new TestConnection(true, true); + success = false; + try { + // Expect to see the TestConnection connected. + conn.setMonitor(true); + mStartState = STATE_START_1; + getContext().bindService(service, conn, 0); + getContext().startService(service); + waitForResultOrThrow(5 * 1000, "existing connection to receive service"); + + success = true; + } finally { + if (!success) { + try { + getContext().stopService(service); + getContext().unbindService(conn); + } catch (Exception e) { + // eat + } + } + } + + // Expect to see the service unbind and then destroyed. + conn.setMonitor(false); + mStartState = STATE_UNBIND; + getContext().stopService(service); + waitForResultOrThrow(5 * 1000, "existing connection to lose service"); + + getContext().unbindService(conn); + + conn = new TestConnection(true, true); + success = false; + try { + // Expect to see the TestConnection connected. + conn.setMonitor(true); + mStartState = STATE_START_1; + getContext().bindService(service, conn, 0); + getContext().startService(service); + waitForResultOrThrow(5 * 1000, "existing connection to receive service"); + + success = true; + } finally { + if (!success) { + try { + getContext().stopService(service); + getContext().unbindService(conn); + } catch (Exception e) { + // eat + } + } + } + + // Expect to see the service unbind but not destroyed. + conn.setMonitor(false); + mStartState = STATE_UNBIND_ONLY; + getContext().unbindService(conn); + waitForResultOrThrow(5 * 1000, "existing connection to unbind service"); + + // Expect to see the service rebound. + mStartState = STATE_REBIND; + getContext().bindService(service, conn, 0); + waitForResultOrThrow(5 * 1000, "existing connection to rebind service"); + + // Expect to see the service unbind and then destroyed. + mStartState = STATE_UNBIND; + getContext().stopService(service); + waitForResultOrThrow(5 * 1000, "existing connection to lose service"); + + getContext().unbindService(conn); + } + + void bindAutoExpectResult(Intent service) { + TestConnection conn = new TestConnection(false, true); + boolean success = false; + try { + conn.setMonitor(true); + mStartState = STATE_START_1; + getContext().bindService( + service, conn, Context.BIND_AUTO_CREATE); + waitForResultOrThrow(5 * 1000, "connection to start and receive service"); + success = true; + } finally { + if (!success) { + try { + getContext().unbindService(conn); + } catch (Exception e) { + // eat + } + } + } + mStartState = STATE_UNBIND; + getContext().unbindService(conn); + waitForResultOrThrow(5 * 1000, "disconnecting from service"); + } + + void bindExpectNoPermission(Intent service) { + TestConnection conn = new TestConnection(false, false); + try { + getContext().bindService(service, conn, Context.BIND_AUTO_CREATE); + fail("Expected security exception when binding " + service); + } catch (SecurityException e) { + // expected + } finally { + getContext().unbindService(conn); + } + } + + + @MediumTest + public void testLocalStartClass() throws Exception { + startExpectResult(new Intent(getContext(), LocalService.class)); + } + + @MediumTest + public void testLocalStartAction() throws Exception { + startExpectResult(new Intent(SERVICE_LOCAL)); + } + + @MediumTest + public void testLocalBindClass() throws Exception { + bindExpectResult(new Intent(getContext(), LocalService.class)); + } + + @MediumTest + public void testLocalBindAction() throws Exception { + bindExpectResult(new Intent(SERVICE_LOCAL)); + } + + @MediumTest + public void testLocalBindAutoClass() throws Exception { + bindAutoExpectResult(new Intent(getContext(), LocalService.class)); + } + + @MediumTest + public void testLocalBindAutoAction() throws Exception { + bindAutoExpectResult(new Intent(SERVICE_LOCAL)); + } + + @MediumTest + public void testLocalStartClassPermissionGranted() throws Exception { + startExpectResult(new Intent(getContext(), LocalGrantedService.class)); + } + + @MediumTest + public void testLocalStartActionPermissionGranted() throws Exception { + startExpectResult(new Intent(SERVICE_LOCAL_GRANTED)); + } + + @MediumTest + public void testLocalBindClassPermissionGranted() throws Exception { + bindExpectResult(new Intent(getContext(), LocalGrantedService.class)); + } + + @MediumTest + public void testLocalBindActionPermissionGranted() throws Exception { + bindExpectResult(new Intent(SERVICE_LOCAL_GRANTED)); + } + + @MediumTest + public void testLocalBindAutoClassPermissionGranted() throws Exception { + bindAutoExpectResult(new Intent(getContext(), LocalGrantedService.class)); + } + + @MediumTest + public void testLocalBindAutoActionPermissionGranted() throws Exception { + bindAutoExpectResult(new Intent(SERVICE_LOCAL_GRANTED)); + } + + @MediumTest + public void testLocalStartClassPermissionDenied() throws Exception { + startExpectNoPermission(new Intent(getContext(), LocalDeniedService.class)); + } + + @MediumTest + public void testLocalStartActionPermissionDenied() throws Exception { + startExpectNoPermission(new Intent(SERVICE_LOCAL_DENIED)); + } + + @MediumTest + public void testLocalBindClassPermissionDenied() throws Exception { + bindExpectNoPermission(new Intent(getContext(), LocalDeniedService.class)); + } + + @MediumTest + public void testLocalBindActionPermissionDenied() throws Exception { + bindExpectNoPermission(new Intent(SERVICE_LOCAL_DENIED)); + } + + @MediumTest + public void testLocalUnbindTwice() throws Exception { + EmptyConnection conn = new EmptyConnection(); + getContext().bindService( + new Intent(SERVICE_LOCAL_GRANTED), conn, 0); + getContext().unbindService(conn); + try { + getContext().unbindService(conn); + fail("No exception thrown on second unbind"); + } catch (IllegalArgumentException e) { + //Log.i("foo", "Unbind exception", e); + } + } +} diff --git a/core/tests/coretests/src/android/app/activity/SetTimeZonePermissionsTest.java b/core/tests/coretests/src/android/app/activity/SetTimeZonePermissionsTest.java new file mode 100644 index 0000000..41b9547 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/SetTimeZonePermissionsTest.java @@ -0,0 +1,70 @@ +/* + * 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. + */ + +package android.app.activity; + +import android.app.AlarmManager; +import android.content.Context; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +import java.util.TimeZone; + +public class SetTimeZonePermissionsTest extends AndroidTestCase { + + private String[] mZones; + private String mCurrentZone; + private AlarmManager mAlarm; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mZones = TimeZone.getAvailableIDs(); + mCurrentZone = TimeZone.getDefault().getID(); + mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + } + + /** + * Verify that non-system processes cannot set the time zone. + */ + @LargeTest + public void testSetTimeZonePermissions() { + /** + * Attempt to set several predefined time zones, verifying that the system + * system default time zone has not actually changed from its prior state + * after each attempt. + */ + int max = (mZones.length > 10) ? mZones.length : 10; + assertTrue("No system-defined time zones - test invalid", max > 0); + + for (int i = 0; i < max; i++) { + String tz = mZones[i]; + try { + mAlarm.setTimeZone(tz); + } catch (SecurityException se) { + // Expected failure; no need to handle specially since we're + // about to assert that the test invariant holds: no change + // to the system time zone. + } + + String newZone = TimeZone.getDefault().getID(); + assertEquals("AlarmManager.setTimeZone() succeeded despite lack of permission", + mCurrentZone, + newZone); + } + } +} diff --git a/core/tests/coretests/src/android/app/activity/SubActivityScreen.java b/core/tests/coretests/src/android/app/activity/SubActivityScreen.java new file mode 100644 index 0000000..919c591 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/SubActivityScreen.java @@ -0,0 +1,168 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; + +public class SubActivityScreen extends Activity { + static final int NO_RESULT_MODE = 0; + static final int RESULT_MODE = 1; + static final int PENDING_RESULT_MODE = 2; + static final int FINISH_SUB_MODE = 3; + + static final int CHILD_OFFSET = 1000; + + int mMode; + + public SubActivityScreen() { + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + mMode = getIntent().getIntExtra("mode", mMode); + //Log.i("foo", "SubActivityScreen pid=" + Process.myPid() + // + " mode=" + mMode); + + // Move on to the next thing that will generate a result... but only + // if we are being launched for the first time. + if (icicle == null) { + if (mMode == PENDING_RESULT_MODE) { + PendingIntent apr = createPendingResult(1, null, + Intent.FILL_IN_ACTION); + Intent res = new Intent(); + res.putExtra("tkey", "tval"); + res.setAction("test"); + try { + apr.send(this, RESULT_OK, res); + } catch (PendingIntent.CanceledException e) { + } + } else if (mMode < CHILD_OFFSET) { + Intent intent = new Intent(); + intent.setClass(this, SubActivityScreen.class); + intent.putExtra("mode", CHILD_OFFSET+mMode); + //System.out.println("*** Starting from onStart: " + intent); + startActivityForResult(intent, 1); + return; + } + } + } + + @Override + protected void onRestoreInstanceState(Bundle state) { + super.onRestoreInstanceState(state); + } + + @Override + protected void onResume() { + super.onResume(); + + //Log.i("foo", "SubActivityScreen pid=" + Process.myPid() + " onResume"); + + if (mMode >= CHILD_OFFSET) { + // Wait a little bit, to give our parent time to kill itself + // if that is something it is into. + try { + Thread.sleep(500); + } catch (InterruptedException e) { + setResult(RESULT_CANCELED, (new Intent()).setAction("Interrupted!")); + finish(); + return; + } + //System.out.println("Resuming sub-activity: mode=" + mMode); + switch (mMode-CHILD_OFFSET) { + case NO_RESULT_MODE: + finish(); + break; + case RESULT_MODE: + Intent res = new Intent(); + res.putExtra("tkey", "tval"); + res.setAction("test"); + setResult(RESULT_OK, res); + finish(); + break; + case FINISH_SUB_MODE: + break; + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + //Log.i("foo", "SubActivityScreen pid=" + Process.myPid() + // + " onActivityResult: req=" + requestCode + // + " res=" + resultCode); + + // Assume success. + setResult(RESULT_OK); + + if (requestCode == 1) { + switch (mMode) { + case NO_RESULT_MODE: + case FINISH_SUB_MODE: + if (resultCode != RESULT_CANCELED) { + setResult(RESULT_CANCELED, (new Intent()).setAction( + "Incorrect result code returned: " + resultCode)); + } + break; + case RESULT_MODE: + case PENDING_RESULT_MODE: + if (resultCode != RESULT_OK) { + setResult(RESULT_CANCELED, (new Intent()).setAction( + "Incorrect result code returned: " + resultCode)); + } else if (data == null) { + setResult(RESULT_CANCELED, (new Intent()).setAction( + "null data returned")); + } else if (!("test".equals(data.getAction()))) { + setResult(RESULT_CANCELED, (new Intent()).setAction( + "Incorrect action returned: " + data)); + } else if (!("tval".equals(data.getStringExtra("tkey")))) { + setResult(RESULT_CANCELED, (new Intent()).setAction( + "Incorrect extras returned: " + data.getExtras())); + } + break; + } + } else { + setResult(RESULT_CANCELED, (new Intent()).setAction( + "Incorrect request code returned: " + requestCode)); + } + + finish(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + } + + @Override + protected void onStop() { + super.onStop(); + handleBeforeStopping(); + } + + public void handleBeforeStopping() { + if (mMode == FINISH_SUB_MODE) { + finishActivity(1); + } + } +} + diff --git a/core/tests/coretests/src/android/app/activity/SubActivityTest.java b/core/tests/coretests/src/android/app/activity/SubActivityTest.java new file mode 100644 index 0000000..35dde8a --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/SubActivityTest.java @@ -0,0 +1,92 @@ +/* + * 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.app.activity; + +import android.test.suitebuilder.annotation.Suppress; +import android.content.ComponentName; + +@Suppress +public class SubActivityTest extends ActivityTestsBase { + + public void testPendingResult() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.PENDING_RESULT_MODE); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + public void testNoResult() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.NO_RESULT_MODE); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + public void testResult() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.RESULT_MODE); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + public void testFinishSub() throws Exception { + mIntent.putExtra("component", + new ComponentName(getContext(), RemoteSubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.FINISH_SUB_MODE); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + public void testRemoteNoResult() throws Exception { + mIntent.putExtra("component", + new ComponentName(getContext(), RemoteSubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.NO_RESULT_MODE); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + public void testRemoteResult() throws Exception { + mIntent.putExtra("component", + new ComponentName(getContext(), RemoteSubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.RESULT_MODE); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + public void testRemoteFinishSub() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.FINISH_SUB_MODE); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + public void testRemoteRestartNoResult() throws Exception { + mIntent.putExtra("component", + new ComponentName(getContext(), RemoteSubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.NO_RESULT_MODE); + mIntent.putExtra("kill", true); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + public void testRemoteRestartResult() throws Exception { + mIntent.putExtra("component", + new ComponentName(getContext(), RemoteSubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.RESULT_MODE); + mIntent.putExtra("kill", true); + runLaunchpad(LaunchpadActivity.LAUNCH); + } + + public void testRemoteRestartFinishSub() throws Exception { + mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class)); + mIntent.putExtra("mode", SubActivityScreen.FINISH_SUB_MODE); + mIntent.putExtra("kill", true); + runLaunchpad(LaunchpadActivity.LAUNCH); + } +} diff --git a/core/tests/coretests/src/android/app/activity/TestedActivity.java b/core/tests/coretests/src/android/app/activity/TestedActivity.java new file mode 100644 index 0000000..3a1c15f --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/TestedActivity.java @@ -0,0 +1,77 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.os.Bundle; + +public class TestedActivity extends Activity +{ + public TestedActivity() + { + } + + public void onCreate(Bundle icicle) + { + super.onCreate(icicle); + } + + protected void onRestoreInstanceState(Bundle state) + { + super.onRestoreInstanceState(state); + } + + protected void onResume() + { + super.onResume(); + Looper.myLooper().myQueue().addIdleHandler(new Idler()); + } + + protected void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + } + + protected void onStop() + { + super.onStop(); + } + + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + setResult(RESULT_OK); + finish(); + } + }; + + private class Idler implements MessageQueue.IdleHandler + { + public final boolean queueIdle() + { + //Message m = Message.obtain(); + //mHandler.sendMessageAtTime(m, SystemClock.uptimeMillis()+1000); + setResult(RESULT_OK); + finish(); + return false; + } + } +} + diff --git a/core/tests/coretests/src/android/app/activity/TestedScreen.java b/core/tests/coretests/src/android/app/activity/TestedScreen.java new file mode 100644 index 0000000..1682d1a --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/TestedScreen.java @@ -0,0 +1,128 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.os.SystemClock; +import android.os.Bundle; +import android.util.Log; + +public class TestedScreen extends Activity +{ + public static final String WAIT_BEFORE_FINISH = "TestedScreen.WAIT_BEFORE_FINISH"; + public static final String DELIVER_RESULT = "TestedScreen.DELIVER_RESULT"; + public static final String CLEAR_TASK = "TestedScreen.CLEAR_TASK"; + + public TestedScreen() { + } + + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "CREATE tested " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + if (LaunchpadActivity.FORWARD_RESULT.equals(getIntent().getAction())) { + Intent intent = new Intent(getIntent()); + intent.setAction(DELIVER_RESULT); + intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + startActivity(intent); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "Finishing tested " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + finish(); + } else if (DELIVER_RESULT.equals(getIntent().getAction())) { + setResult(RESULT_OK, (new Intent()).setAction( + LaunchpadActivity.RETURNED_RESULT)); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "Finishing tested " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + finish(); + } else if (CLEAR_TASK.equals(getIntent().getAction())) { + if (!getIntent().getBooleanExtra(ClearTop.WAIT_CLEAR_TASK, false)) { + launchClearTask(); + } + } + } + + protected void onRestoreInstanceState(Bundle state) { + super.onRestoreInstanceState(state); + } + + protected void onResume() { + super.onResume(); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "RESUME tested " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + if (CLEAR_TASK.equals(getIntent().getAction())) { + if (getIntent().getBooleanExtra(ClearTop.WAIT_CLEAR_TASK, false)) { + Looper.myLooper().myQueue().addIdleHandler(new Idler()); + } + } else { + Looper.myLooper().myQueue().addIdleHandler(new Idler()); + } + } + + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + } + + protected void onStop() { + super.onStop(); + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "STOP tested " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + } + + private void launchClearTask() { + Intent intent = new Intent(getIntent()). + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP). + setClass(this, ClearTop.class); + startActivity(intent); + } + + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + if (CLEAR_TASK.equals(getIntent().getAction())) { + launchClearTask(); + } else { + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "Finishing tested " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + setResult(RESULT_OK); + finish(); + } + } + }; + + private class Idler implements MessageQueue.IdleHandler { + public final boolean queueIdle() { + if (WAIT_BEFORE_FINISH.equals(getIntent().getAction())) { + Message m = Message.obtain(); + mHandler.sendMessageAtTime(m, SystemClock.uptimeMillis()+1000); + } else if (CLEAR_TASK.equals(getIntent().getAction())) { + Message m = Message.obtain(); + mHandler.sendMessageAtTime(m, SystemClock.uptimeMillis()+1000); + } else { + if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "Finishing tested " + + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent()); + setResult(RESULT_OK); + finish(); + } + return false; + } + } +} + diff --git a/core/tests/coretests/src/android/content/AssetTest.java b/core/tests/coretests/src/android/content/AssetTest.java new file mode 100644 index 0000000..b66574c --- /dev/null +++ b/core/tests/coretests/src/android/content/AssetTest.java @@ -0,0 +1,74 @@ +/* + * 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. + */ + +package android.content; + +import android.content.res.AssetManager; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.IOException; +import java.io.InputStream; + +public class AssetTest extends AndroidTestCase { + private AssetManager mAssets; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mAssets = mContext.getAssets(); + } + + public static void verifyTextAsset(InputStream is) throws IOException { + String expectedString = "OneTwoThreeFourFiveSixSevenEightNineTen"; + byte[] buffer = new byte[10]; + + int readCount; + int curIndex = 0; + while ((readCount = is.read(buffer, 0, buffer.length)) > 0) { + for (int i = 0; i < readCount; i++) { + assertEquals("At index " + curIndex + + " expected " + expectedString.charAt(curIndex) + + " but found " + ((char) buffer[i]), + buffer[i], expectedString.charAt(curIndex)); + curIndex++; + } + } + + readCount = is.read(buffer, 0, buffer.length); + assertEquals("Reading end of buffer: expected readCount=-1 but got " + readCount, + -1, readCount); + + readCount = is.read(buffer, buffer.length, 0); + assertEquals("Reading end of buffer length 0: expected readCount=0 but got " + readCount, + 0, readCount); + + is.close(); + } + + @SmallTest + public void testReadToEnd() throws Exception { + InputStream is = mAssets.open("text.txt"); + verifyTextAsset(is); + } + + // XXX failing + public void xxtestListDir() throws Exception { + String[] files = mAssets.list(""); + assertEquals(1, files.length); + assertEquals("test.txt", files[0]); + } +} diff --git a/core/tests/coretests/src/android/content/ContentQueryMapTest.java b/core/tests/coretests/src/android/content/ContentQueryMapTest.java new file mode 100644 index 0000000..d1b8c24 --- /dev/null +++ b/core/tests/coretests/src/android/content/ContentQueryMapTest.java @@ -0,0 +1,104 @@ +/* + * 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. + */ + +package android.content; + +import android.content.ContentQueryMap; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +import java.util.Observable; +import java.util.Observer; + +/** Test of {@link ContentQueryMap} */ +public class ContentQueryMapTest extends AndroidTestCase { + /** Helper class to run test code in a new thread with a Looper. */ + private abstract class LooperThread extends Thread { + public Throwable mError = null; + public boolean mSuccess = false; + + abstract void go(); + + public void run() { + try { + Looper.prepare(); + go(); + Looper.loop(); + } catch (Throwable e) { + mError = e; + } + } + } + + @MediumTest + public void testContentQueryMap() throws Throwable { + LooperThread thread = new LooperThread() { + void go() { + ContentResolver r = getContext().getContentResolver(); + Settings.System.putString(r, "test", "Value"); + Cursor cursor = r.query( + Settings.System.CONTENT_URI, + new String[] { + Settings.System.NAME, + Settings.System.VALUE, + }, null, null, null); + + final ContentQueryMap cqm = new ContentQueryMap( + cursor, Settings.System.NAME, true, null); + // Get the current state of the CQM. This forces a requery and means that the + // call to getValues() below won't do a requery(). + cqm.getRows(); + + // The cache won't notice changes until the loop runs. + Settings.System.putString(r, "test", "New Value"); + ContentValues v = cqm.getValues("test"); + String value = v.getAsString(Settings.System.VALUE); + assertEquals("Value", value); + + // Use an Observer to find out when the cache does update. + cqm.addObserver(new Observer() { + public void update(Observable o, Object arg) { + // Should have the new values by now. + ContentValues v = cqm.getValues("test"); + String value = v.getAsString(Settings.System.VALUE); + assertEquals("New Value", value); + Looper.myLooper().quit(); + cqm.close(); + mSuccess = true; + } + }); + + // Give up after a few seconds, if it doesn't. + new Handler().postDelayed(new Runnable() { + public void run() { + fail("Timed out"); + } + }, 5000); + } + }; + + thread.start(); + thread.join(); + if (thread.mError != null) throw thread.mError; + assertTrue(thread.mSuccess); + } +} diff --git a/core/tests/coretests/src/android/content/ContentTests.java b/core/tests/coretests/src/android/content/ContentTests.java new file mode 100644 index 0000000..a1299e3 --- /dev/null +++ b/core/tests/coretests/src/android/content/ContentTests.java @@ -0,0 +1,28 @@ +/* + * 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.content; + +import junit.framework.TestSuite; + +public class ContentTests { + public static TestSuite suite() { + TestSuite suite = new TestSuite(ContentTests.class.getName()); + + suite.addTestSuite(AssetTest.class); + return suite; + } +} diff --git a/core/tests/coretests/src/android/content/MemoryFileProvider.java b/core/tests/coretests/src/android/content/MemoryFileProvider.java new file mode 100644 index 0000000..c4bc767 --- /dev/null +++ b/core/tests/coretests/src/android/content/MemoryFileProvider.java @@ -0,0 +1,211 @@ +/* + * 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.content; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.os.MemoryFile; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; + +/** Simple test provider that runs in the local process. */ +public class MemoryFileProvider extends ContentProvider { + private static final String TAG = "MemoryFileProvider"; + + private static final String DATA_FILE = "data.bin"; + + // some random data + public static final byte[] TEST_BLOB = new byte[] { + -12, 127, 0, 3, 1, 2, 3, 4, 5, 6, 1, -128, -1, -54, -65, 35, + -53, -96, -74, -74, -55, -43, -69, 3, 52, -58, + -121, 127, 87, -73, 16, -13, -103, -65, -128, -36, + 107, 24, 118, -17, 97, 97, -88, 19, -94, -54, + 53, 43, 44, -27, -124, 28, -74, 26, 35, -36, + 16, -124, -31, -31, -128, -79, 108, 116, 43, -17 }; + + private SQLiteOpenHelper mOpenHelper; + + private static final int DATA_ID_BLOB = 1; + private static final int HUGE = 2; + private static final int FILE = 3; + + private static final UriMatcher sURLMatcher = new UriMatcher( + UriMatcher.NO_MATCH); + + static { + sURLMatcher.addURI("*", "data/#/blob", DATA_ID_BLOB); + sURLMatcher.addURI("*", "huge", HUGE); + sURLMatcher.addURI("*", "file", FILE); + } + + private static class DatabaseHelper extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "local.db"; + private static final int DATABASE_VERSION = 1; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE data (" + + "_id INTEGER PRIMARY KEY," + + "_blob TEXT, " + + "integer INTEGER);"); + + // insert alarms + ContentValues values = new ContentValues(); + values.put("_id", 1); + values.put("_blob", TEST_BLOB); + values.put("integer", 100); + db.insert("data", null, values); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { + Log.w(TAG, "Upgrading test database from version " + + oldVersion + " to " + currentVersion + + ", which will destroy all old data"); + db.execSQL("DROP TABLE IF EXISTS data"); + onCreate(db); + } + } + + + public MemoryFileProvider() { + } + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + try { + OutputStream out = getContext().openFileOutput(DATA_FILE, Context.MODE_PRIVATE); + out.write(TEST_BLOB); + out.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + return true; + } + + @Override + public Cursor query(Uri url, String[] projectionIn, String selection, + String[] selectionArgs, String sort) { + throw new UnsupportedOperationException("query not supported"); + } + + @Override + public String getType(Uri url) { + int match = sURLMatcher.match(url); + switch (match) { + case DATA_ID_BLOB: + return "application/octet-stream"; + case FILE: + return "application/octet-stream"; + default: + throw new IllegalArgumentException("Unknown URL"); + } + } + + @Override + public AssetFileDescriptor openAssetFile(Uri url, String mode) throws FileNotFoundException { + int match = sURLMatcher.match(url); + switch (match) { + case DATA_ID_BLOB: + String sql = "SELECT _blob FROM data WHERE _id=" + url.getPathSegments().get(1); + return getBlobColumnAsAssetFile(url, mode, sql); + case HUGE: + try { + MemoryFile memoryFile = new MemoryFile(null, 5000000); + memoryFile.writeBytes(TEST_BLOB, 0, 1000000, TEST_BLOB.length); + memoryFile.deactivate(); + return AssetFileDescriptor.fromMemoryFile(memoryFile); + } catch (IOException ex) { + throw new FileNotFoundException("Error reading " + url + ":" + ex.toString()); + } + case FILE: + File file = getContext().getFileStreamPath(DATA_FILE); + ParcelFileDescriptor fd = + ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + return new AssetFileDescriptor(fd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); + default: + throw new FileNotFoundException("No files supported by provider at " + url); + } + } + + private AssetFileDescriptor getBlobColumnAsAssetFile(Uri url, String mode, String sql) + throws FileNotFoundException { + if (!"r".equals(mode)) { + throw new FileNotFoundException("Mode " + mode + " not supported for " + url); + } + try { + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + MemoryFile file = simpleQueryForBlobMemoryFile(db, sql); + if (file == null) throw new FileNotFoundException("No such entry: " + url); + AssetFileDescriptor afd = AssetFileDescriptor.fromMemoryFile(file); + file.deactivate(); + // need to dup and then close? openFileHelper() doesn't do that though + return afd; + } catch (IOException ex) { + throw new FileNotFoundException("Error reading " + url + ":" + ex.toString()); + } + } + + private MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql) throws IOException { + Cursor cursor = db.rawQuery(sql, null); + try { + if (!cursor.moveToFirst()) { + return null; + } + byte[] bytes = cursor.getBlob(0); + MemoryFile file = new MemoryFile(null, bytes.length); + file.writeBytes(bytes, 0, 0, bytes.length); + return file; + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + @Override + public int update(Uri url, ContentValues values, String where, String[] whereArgs) { + throw new UnsupportedOperationException("update not supported"); + } + + @Override + public Uri insert(Uri url, ContentValues initialValues) { + throw new UnsupportedOperationException("insert not supported"); + } + + @Override + public int delete(Uri url, String where, String[] whereArgs) { + throw new UnsupportedOperationException("delete not supported"); + } +} diff --git a/core/tests/coretests/src/android/content/MemoryFileProviderTest.java b/core/tests/coretests/src/android/content/MemoryFileProviderTest.java new file mode 100644 index 0000000..6708af6 --- /dev/null +++ b/core/tests/coretests/src/android/content/MemoryFileProviderTest.java @@ -0,0 +1,82 @@ +/* + * 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.content; + +import android.content.ContentResolver; +import android.net.Uri; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +import java.io.InputStream; +import java.util.Arrays; + +/** + * Tests reading a MemoryFile-based AssestFile from a ContentProvider running + * in a different process. + */ +public class MemoryFileProviderTest extends AndroidTestCase { + + // reads from a cross-process AssetFileDescriptor for a MemoryFile + @MediumTest + public void testRead() throws Exception { + ContentResolver resolver = getContext().getContentResolver(); + Uri uri = Uri.parse("content://android.content.MemoryFileProvider/data/1/blob"); + byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length]; + InputStream in = resolver.openInputStream(uri); + assertNotNull(in); + int count = in.read(buf); + assertEquals(buf.length, count); + assertEquals(-1, in.read()); + in.close(); + assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf)); + } + + // tests that we don't leak file descriptors or virtual address space + @MediumTest + public void testClose() throws Exception { + ContentResolver resolver = getContext().getContentResolver(); + // open enough file descriptors that we will crash something if we leak FDs + // or address space + for (int i = 0; i < 1025; i++) { + Uri uri = Uri.parse("content://android.content.MemoryFileProvider/huge"); + InputStream in = resolver.openInputStream(uri); + assertNotNull("Failed to open stream number " + i, in); + assertEquals(1000000, in.skip(1000000)); + byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length]; + int count = in.read(buf); + assertEquals(buf.length, count); + assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf)); + in.close(); + } + } + + // tests that we haven't broken AssestFileDescriptors for normal files. + @MediumTest + public void testFile() throws Exception { + ContentResolver resolver = getContext().getContentResolver(); + Uri uri = Uri.parse("content://android.content.MemoryFileProvider/file"); + byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length]; + InputStream in = resolver.openInputStream(uri); + assertNotNull(in); + int count = in.read(buf); + assertEquals(buf.length, count); + assertEquals(-1, in.read()); + in.close(); + assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf)); + } + +} diff --git a/core/tests/coretests/src/android/database/CursorWindowTest.java b/core/tests/coretests/src/android/database/CursorWindowTest.java new file mode 100644 index 0000000..07e75cb --- /dev/null +++ b/core/tests/coretests/src/android/database/CursorWindowTest.java @@ -0,0 +1,173 @@ +/* + * 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. + */ + +package android.database; + +import android.database.AbstractCursor; +import android.test.suitebuilder.annotation.SmallTest; +import com.android.common.ArrayListCursor; +import android.database.CursorWindow; +import android.test.PerformanceTestCase; + +import com.google.android.collect.Lists; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Random; + +import junit.framework.TestCase; + +public class CursorWindowTest extends TestCase implements PerformanceTestCase { + public boolean isPerformanceOnly() { + return false; + } + + // These test can only be run once. + public int startPerformance(Intermediates intermediates) { + return 1; + } + + @SmallTest + public void testWriteCursorToWindow() throws Exception { + // create cursor + String[] colNames = new String[]{"name", "number", "profit"}; + int colsize = colNames.length; + ArrayList<ArrayList> list = createTestList(10, colsize); + AbstractCursor cursor = new ArrayListCursor(colNames, (ArrayList<ArrayList>) list); + + // fill window + CursorWindow window = new CursorWindow(false); + cursor.fillWindow(0, window); + + // read from cursor window + for (int i = 0; i < list.size(); i++) { + ArrayList<Integer> col = list.get(i); + for (int j = 0; j < colsize; j++) { + String s = window.getString(i, j); + int r2 = col.get(j); + int r1 = Integer.parseInt(s); + assertEquals(r2, r1); + } + } + + // test cursor window handle startpos != 0 + window.clear(); + cursor.fillWindow(1, window); + // read from cursor from window + for (int i = 1; i < list.size(); i++) { + ArrayList<Integer> col = list.get(i); + for (int j = 0; j < colsize; j++) { + String s = window.getString(i, j); + int r2 = col.get(j); + int r1 = Integer.parseInt(s); + assertEquals(r2, r1); + } + } + + // Clear the window and make sure it's empty + window.clear(); + assertEquals(0, window.getNumRows()); + } + + @SmallTest + public void testValuesLocalWindow() { + doTestValues(new CursorWindow(true)); + } + + @SmallTest + public void testValuesRemoteWindow() { + doTestValues(new CursorWindow(false)); + } + + private void doTestValues(CursorWindow window) { + assertTrue(window.setNumColumns(7)); + assertTrue(window.allocRow()); + double db1 = 1.26; + assertTrue(window.putDouble(db1, 0, 0)); + double db2 = window.getDouble(0, 0); + assertEquals(db1, db2); + + long int1 = Long.MAX_VALUE; + assertTrue(window.putLong(int1, 0, 1)); + long int2 = window.getLong(0, 1); + assertEquals(int1, int2); + + assertTrue(window.putString("1198032740000", 0, 3)); + assertEquals("1198032740000", window.getString(0, 3)); + assertEquals(1198032740000L, window.getLong(0, 3)); + + assertTrue(window.putString(Long.toString(1198032740000L), 0, 3)); + assertEquals(Long.toString(1198032740000L), window.getString(0, 3)); + assertEquals(1198032740000L, window.getLong(0, 3)); + + assertTrue(window.putString(Double.toString(42.0), 0, 4)); + assertEquals(Double.toString(42.0), window.getString(0, 4)); + assertEquals(42.0, window.getDouble(0, 4)); + + // put blob + byte[] blob = new byte[1000]; + byte value = 99; + Arrays.fill(blob, value); + assertTrue(window.putBlob(blob, 0, 6)); + assertTrue(Arrays.equals(blob, window.getBlob(0, 6))); + } + + @SmallTest + public void testNull() { + CursorWindow window = getOneByOneWindow(); + + // Put in a null value and read it back as various types + assertTrue(window.putNull(0, 0)); + assertNull(window.getString(0, 0)); + assertEquals(0, window.getLong(0, 0)); + assertEquals(0.0, window.getDouble(0, 0)); + assertNull(window.getBlob(0, 0)); + } + + @SmallTest + public void testEmptyString() { + CursorWindow window = getOneByOneWindow(); + + // put size 0 string and read it back as various types + assertTrue(window.putString("", 0, 0)); + assertEquals("", window.getString(0, 0)); + assertEquals(0, window.getLong(0, 0)); + assertEquals(0.0, window.getDouble(0, 0)); + } + + private CursorWindow getOneByOneWindow() { + CursorWindow window = new CursorWindow(false); + assertTrue(window.setNumColumns(1)); + assertTrue(window.allocRow()); + return window; + } + + private static ArrayList<ArrayList> createTestList(int rows, int cols) { + ArrayList<ArrayList> list = Lists.newArrayList(); + Random generator = new Random(); + + for (int i = 0; i < rows; i++) { + ArrayList<Integer> col = Lists.newArrayList(); + list.add(col); + for (int j = 0; j < cols; j++) { + // generate random number + Integer r = generator.nextInt(); + col.add(r); + } + } + return list; + } +} diff --git a/core/tests/coretests/src/android/database/DatabaseCursorTest.java b/core/tests/coretests/src/android/database/DatabaseCursorTest.java new file mode 100644 index 0000000..fad4349 --- /dev/null +++ b/core/tests/coretests/src/android/database/DatabaseCursorTest.java @@ -0,0 +1,630 @@ +/* + * 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.database; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.CursorIndexOutOfBoundsException; +import android.database.DataSetObserver; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteCursor; +import android.database.sqlite.SQLiteCursorDriver; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQuery; +import android.database.sqlite.SQLiteStatement; +import android.os.Looper; +import android.test.AndroidTestCase; +import android.test.PerformanceTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; +import android.util.Log; + +import java.io.File; +import java.util.Arrays; +import java.util.Random; + +import junit.framework.TestCase; + +public class DatabaseCursorTest extends AndroidTestCase implements PerformanceTestCase { + + private static final String sString1 = "this is a test"; + private static final String sString2 = "and yet another test"; + private static final String sString3 = "this string is a little longer, but still a test"; + + private static final int CURRENT_DATABASE_VERSION = 42; + private SQLiteDatabase mDatabase; + private File mDatabaseFile; + + @Override + protected void setUp() throws Exception { + super.setUp(); + File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE); + mDatabaseFile = new File(dbDir, "database_test.db"); + + if (mDatabaseFile.exists()) { + mDatabaseFile.delete(); + } + mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); + assertNotNull(mDatabase); + mDatabase.setVersion(CURRENT_DATABASE_VERSION); + } + + @Override + protected void tearDown() throws Exception { + mDatabase.close(); + mDatabaseFile.delete(); + super.tearDown(); + } + + public boolean isPerformanceOnly() { + return false; + } + + // These test can only be run once. + public int startPerformance(Intermediates intermediates) { + return 1; + } + + private void populateDefaultTable() { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);"); + + mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString1 + "');"); + mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString2 + "');"); + mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');"); + } + + @MediumTest + public void testCursorUpdate() { + mDatabase.execSQL( + "CREATE TABLE test (_id INTEGER PRIMARY KEY, d INTEGER, s INTEGER);"); + for(int i = 0; i < 20; i++) { + mDatabase.execSQL("INSERT INTO test (d, s) VALUES (" + i + + "," + i%2 + ");"); + } + + Cursor c = mDatabase.query("test", null, "s = 0", null, null, null, null); + int dCol = c.getColumnIndexOrThrow("d"); + int sCol = c.getColumnIndexOrThrow("s"); + + int count = 0; + while (c.moveToNext()) { + assertTrue(c.updateInt(dCol, 3)); + count++; + } + assertEquals(10, count); + + assertTrue(c.commitUpdates()); + + assertTrue(c.requery()); + + count = 0; + while (c.moveToNext()) { + assertEquals(3, c.getInt(dCol)); + count++; + } + + assertEquals(10, count); + assertTrue(c.moveToFirst()); + assertTrue(c.deleteRow()); + assertEquals(9, c.getCount()); + c.close(); + } + + @MediumTest + public void testBlob() throws Exception { + // create table + mDatabase.execSQL( + "CREATE TABLE test (_id INTEGER PRIMARY KEY, s TEXT, d REAL, l INTEGER, b BLOB);"); + // insert blob + Object[] args = new Object[4]; + + byte[] blob = new byte[1000]; + byte value = 99; + Arrays.fill(blob, value); + args[3] = blob; + + String s = new String("text"); + args[0] = s; + Double d = 99.9; + args[1] = d; + Long l = (long)1000; + args[2] = l; + + String sql = "INSERT INTO test (s, d, l, b) VALUES (?,?,?,?)"; + mDatabase.execSQL(sql, args); + // use cursor to access blob + Cursor c = mDatabase.query("test", null, null, null, null, null, null); + c.moveToNext(); + ContentValues cv = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(c, cv); + + int bCol = c.getColumnIndexOrThrow("b"); + int sCol = c.getColumnIndexOrThrow("s"); + int dCol = c.getColumnIndexOrThrow("d"); + int lCol = c.getColumnIndexOrThrow("l"); + byte[] cBlob = c.getBlob(bCol); + assertTrue(Arrays.equals(blob, cBlob)); + assertEquals(s, c.getString(sCol)); + assertEquals((double)d, c.getDouble(dCol)); + assertEquals((long)l, c.getLong(lCol)); + + // new byte[] + byte[] newblob = new byte[1000]; + value = 98; + Arrays.fill(blob, value); + + c.updateBlob(bCol, newblob); + cBlob = c.getBlob(bCol); + assertTrue(Arrays.equals(newblob, cBlob)); + + // commit + assertTrue(c.commitUpdates()); + assertTrue(c.requery()); + c.moveToNext(); + cBlob = c.getBlob(bCol); + assertTrue(Arrays.equals(newblob, cBlob)); + c.close(); + } + + @MediumTest + public void testRealColumns() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data REAL);"); + ContentValues values = new ContentValues(); + values.put("data", 42.11); + long id = mDatabase.insert("test", "data", values); + assertTrue(id > 0); + Cursor c = mDatabase.rawQuery("SELECT data FROM test", null); + assertNotNull(c); + assertTrue(c.moveToFirst()); + assertEquals(42.11, c.getDouble(0)); + c.close(); + } + + @MediumTest + public void testCursor1() throws Exception { + populateDefaultTable(); + + Cursor c = mDatabase.query("test", null, null, null, null, null, null); + + int dataColumn = c.getColumnIndexOrThrow("data"); + + // The cursor should ignore text before the last period when looking for a column. (This + // is a temporary hack in all implementations of getColumnIndex.) + int dataColumn2 = c.getColumnIndexOrThrow("junk.data"); + assertEquals(dataColumn, dataColumn2); + + assertSame(3, c.getCount()); + + assertTrue(c.isBeforeFirst()); + + try { + c.getInt(0); + fail("CursorIndexOutOfBoundsException expected"); + } catch (CursorIndexOutOfBoundsException ex) { + // expected + } + + c.moveToNext(); + assertEquals(1, c.getInt(0)); + + String s = c.getString(dataColumn); + assertEquals(sString1, s); + + c.moveToNext(); + s = c.getString(dataColumn); + assertEquals(sString2, s); + + c.moveToNext(); + s = c.getString(dataColumn); + assertEquals(sString3, s); + + c.moveToPosition(-1); + c.moveToNext(); + s = c.getString(dataColumn); + assertEquals(sString1, s); + + c.moveToPosition(2); + s = c.getString(dataColumn); + assertEquals(sString3, s); + + int i; + + for (c.moveToFirst(), i = 0; !c.isAfterLast(); c.moveToNext(), i++) { + c.getInt(0); + } + + assertEquals(3, i); + + try { + c.getInt(0); + fail("CursorIndexOutOfBoundsException expected"); + } catch (CursorIndexOutOfBoundsException ex) { + // expected + } + c.close(); + } + + @MediumTest + public void testCursor2() throws Exception { + populateDefaultTable(); + + Cursor c = mDatabase.query("test", null, "_id > 1000", null, null, null, null); + assertEquals(0, c.getCount()); + assertTrue(c.isBeforeFirst()); + + try { + c.getInt(0); + fail("CursorIndexOutOfBoundsException expected"); + } catch (CursorIndexOutOfBoundsException ex) { + // expected + } + + int i; + for (c.moveToFirst(), i = 0; !c.isAfterLast(); c.moveToNext(), i++) { + c.getInt(0); + } + assertEquals(0, i); + try { + c.getInt(0); + fail("CursorIndexOutOfBoundsException expected"); + } catch (CursorIndexOutOfBoundsException ex) { + // expected + } + c.close(); + } + + @MediumTest + public void testLargeField() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);"); + + StringBuilder sql = new StringBuilder(2100); + sql.append("INSERT INTO test (data) VALUES ('"); + Random random = new Random(System.currentTimeMillis()); + StringBuilder randomString = new StringBuilder(1979); + for (int i = 0; i < 1979; i++) { + randomString.append((random.nextInt() & 0xf) % 10); + } + sql.append(randomString); + sql.append("');"); + mDatabase.execSQL(sql.toString()); + + Cursor c = mDatabase.query("test", null, null, null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + + assertTrue(c.moveToFirst()); + assertEquals(0, c.getPosition()); + String largeString = c.getString(c.getColumnIndexOrThrow("data")); + assertNotNull(largeString); + assertEquals(randomString.toString(), largeString); + c.close(); + } + + class TestObserver extends DataSetObserver { + int total; + SQLiteCursor c; + boolean quit = false; + public TestObserver(int total_, SQLiteCursor cursor) { + c = cursor; + total = total_; + } + + @Override + public void onChanged() { + int count = c.getCount(); + if (total == count) { + int i = 0; + while (c.moveToNext()) { + assertEquals(i, c.getInt(1)); + i++; + } + assertEquals(count, i); + quit = true; + Looper.myLooper().quit(); + } + } + + @Override + public void onInvalidated() { + } + } + + //@Large + @Suppress + public void testLoadingThreadDelayRegisterData() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);"); + + final int count = 505; + String sql = "INSERT INTO test (data) VALUES (?);"; + SQLiteStatement s = mDatabase.compileStatement(sql); + for (int i = 0; i < count; i++) { + s.bindLong(1, i); + s.execute(); + } + + int maxRead = 500; + int initialRead = 5; + SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;", + null, initialRead, maxRead); + + TestObserver observer = new TestObserver(count, c); + c.getCount(); + c.registerDataSetObserver(observer); + if (!observer.quit) { + Looper.loop(); + } + c.close(); + } + + @LargeTest + public void testLoadingThread() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);"); + + final int count = 50000; + String sql = "INSERT INTO test (data) VALUES (?);"; + SQLiteStatement s = mDatabase.compileStatement(sql); + for (int i = 0; i < count; i++) { + s.bindLong(1, i); + s.execute(); + } + + int maxRead = 1000; + int initialRead = 5; + SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;", + null, initialRead, maxRead); + + TestObserver observer = new TestObserver(count, c); + c.registerDataSetObserver(observer); + c.getCount(); + + Looper.loop(); + c.close(); + } + + @LargeTest + public void testLoadingThreadClose() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);"); + + final int count = 1000; + String sql = "INSERT INTO test (data) VALUES (?);"; + SQLiteStatement s = mDatabase.compileStatement(sql); + for (int i = 0; i < count; i++) { + s.bindLong(1, i); + s.execute(); + } + + int maxRead = 11; + int initialRead = 5; + SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;", + null, initialRead, maxRead); + + TestObserver observer = new TestObserver(count, c); + c.registerDataSetObserver(observer); + c.getCount(); + c.close(); + } + + @LargeTest + public void testLoadingThreadDeactivate() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);"); + + final int count = 1000; + String sql = "INSERT INTO test (data) VALUES (?);"; + SQLiteStatement s = mDatabase.compileStatement(sql); + for (int i = 0; i < count; i++) { + s.bindLong(1, i); + s.execute(); + } + + int maxRead = 11; + int initialRead = 5; + SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;", + null, initialRead, maxRead); + + TestObserver observer = new TestObserver(count, c); + c.registerDataSetObserver(observer); + c.getCount(); + c.deactivate(); + c.close(); + } + + @LargeTest + public void testManyRowsLong() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);"); + + final int count = 36799; + for (int i = 0; i < count; i++) { + mDatabase.execSQL("INSERT INTO test (data) VALUES (" + i + ");"); + } + + Cursor c = mDatabase.query("test", new String[]{"data"}, null, null, null, null, null); + assertNotNull(c); + + int i = 0; + while (c.moveToNext()) { + assertEquals(i, c.getInt(0)); + i++; + } + assertEquals(count, i); + assertEquals(count, c.getCount()); + + Log.d("testManyRows", "count " + Integer.toString(i)); + c.close(); + } + + @LargeTest + public void testManyRowsTxt() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);"); + StringBuilder sql = new StringBuilder(2100); + sql.append("INSERT INTO test (data) VALUES ('"); + Random random = new Random(System.currentTimeMillis()); + StringBuilder randomString = new StringBuilder(1979); + for (int i = 0; i < 1979; i++) { + randomString.append((random.nextInt() & 0xf) % 10); + } + sql.append(randomString); + sql.append("');"); + + // if cursor window size changed, adjust this value too + final int count = 600; // more than two fillWindow needed + for (int i = 0; i < count; i++) { + mDatabase.execSQL(sql.toString()); + } + + Cursor c = mDatabase.query("test", new String[]{"data"}, null, null, null, null, null); + assertNotNull(c); + + int i = 0; + while (c.moveToNext()) { + assertEquals(randomString.toString(), c.getString(0)); + i++; + } + assertEquals(count, i); + assertEquals(count, c.getCount()); + c.close(); + } + + @LargeTest + public void testManyRowsTxtLong() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, txt TEXT, data INT);"); + + Random random = new Random(System.currentTimeMillis()); + StringBuilder randomString = new StringBuilder(1979); + for (int i = 0; i < 1979; i++) { + randomString.append((random.nextInt() & 0xf) % 10); + } + + // if cursor window size changed, adjust this value too + final int count = 600; + for (int i = 0; i < count; i++) { + StringBuilder sql = new StringBuilder(2100); + sql.append("INSERT INTO test (txt, data) VALUES ('"); + sql.append(randomString); + sql.append("','"); + sql.append(i); + sql.append("');"); + mDatabase.execSQL(sql.toString()); + } + + Cursor c = mDatabase.query("test", new String[]{"txt", "data"}, null, null, null, null, null); + assertNotNull(c); + + int i = 0; + while (c.moveToNext()) { + assertEquals(randomString.toString(), c.getString(0)); + assertEquals(i, c.getInt(1)); + i++; + } + assertEquals(count, i); + assertEquals(count, c.getCount()); + c.close(); + } + + @MediumTest + public void testRequery() throws Exception { + populateDefaultTable(); + + Cursor c = mDatabase.rawQuery("SELECT * FROM test", null); + assertNotNull(c); + assertEquals(3, c.getCount()); + c.deactivate(); + c.requery(); + assertEquals(3, c.getCount()); + c.close(); + } + + @MediumTest + public void testRequeryWithSelection() throws Exception { + populateDefaultTable(); + + Cursor c = mDatabase.rawQuery("SELECT data FROM test WHERE data = '" + sString1 + "'", + null); + assertNotNull(c); + assertEquals(1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals(sString1, c.getString(0)); + c.deactivate(); + c.requery(); + assertEquals(1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals(sString1, c.getString(0)); + c.close(); + } + + @MediumTest + public void testRequeryWithSelectionArgs() throws Exception { + populateDefaultTable(); + + Cursor c = mDatabase.rawQuery("SELECT data FROM test WHERE data = ?", + new String[]{sString1}); + assertNotNull(c); + assertEquals(1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals(sString1, c.getString(0)); + c.deactivate(); + c.requery(); + assertEquals(1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals(sString1, c.getString(0)); + c.close(); + } + + @MediumTest + public void testRequeryWithAlteredSelectionArgs() throws Exception { + /** + * Test the ability of a subclass of SQLiteCursor to change its query arguments. + */ + populateDefaultTable(); + + SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() { + public Cursor newCursor( + SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, + SQLiteQuery query) { + return new SQLiteCursor(db, masterQuery, editTable, query) { + @Override + public boolean requery() { + setSelectionArguments(new String[]{"2"}); + return super.requery(); + } + }; + } + }; + Cursor c = mDatabase.rawQueryWithFactory( + factory, "SELECT data FROM test WHERE _id <= ?", new String[]{"1"}, + null); + assertNotNull(c); + assertEquals(1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals(sString1, c.getString(0)); + + // Our hacked requery() changes the query arguments in the cursor. + c.requery(); + + assertEquals(2, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals(sString1, c.getString(0)); + assertTrue(c.moveToNext()); + assertEquals(sString2, c.getString(0)); + + // Test that setting query args on a deactivated cursor also works. + c.deactivate(); + c.requery(); + } +} diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java new file mode 100644 index 0000000..ca650e0 --- /dev/null +++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java @@ -0,0 +1,1008 @@ +/* + * 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; + +import android.content.ContentValues; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.CharArrayBuffer; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.os.Environment; +import android.os.Handler; +import android.os.Parcel; +import android.test.AndroidTestCase; +import android.test.PerformanceTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.text.Collator; +import java.util.Arrays; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import static android.database.DatabaseUtils.InsertHelper.TABLE_INFO_PRAGMA_COLUMNNAME_INDEX; +import static android.database.DatabaseUtils.InsertHelper.TABLE_INFO_PRAGMA_DEFAULT_INDEX; + +public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceTestCase { + + private static final String sString1 = "this is a test"; + private static final String sString2 = "and yet another test"; + private static final String sString3 = "this string is a little longer, but still a test"; + private static final String PHONE_NUMBER = "16175551212"; + + private static final int CURRENT_DATABASE_VERSION = 42; + private SQLiteDatabase mDatabase; + private File mDatabaseFile; + + @Override + protected void setUp() throws Exception { + super.setUp(); + File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); + mDatabaseFile = new File(dbDir, "database_test.db"); + if (mDatabaseFile.exists()) { + mDatabaseFile.delete(); + } + mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); + assertNotNull(mDatabase); + mDatabase.setVersion(CURRENT_DATABASE_VERSION); + } + + @Override + protected void tearDown() throws Exception { + mDatabase.close(); + mDatabaseFile.delete(); + super.tearDown(); + } + + public boolean isPerformanceOnly() { + return false; + } + + // These test can only be run once. + public int startPerformance(Intermediates intermediates) { + return 1; + } + + private void populateDefaultTable() { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);"); + + mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString1 + "');"); + mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString2 + "');"); + mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');"); + } + + @MediumTest + public void testVersion() throws Exception { + assertEquals(CURRENT_DATABASE_VERSION, mDatabase.getVersion()); + mDatabase.setVersion(11); + assertEquals(11, mDatabase.getVersion()); + } + + @MediumTest + public void testUpdate() throws Exception { + populateDefaultTable(); + + ContentValues values = new ContentValues(1); + values.put("data", "this is an updated test"); + assertEquals(1, mDatabase.update("test", values, "_id=1", null)); + Cursor c = mDatabase.query("test", null, "_id=1", null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + c.moveToFirst(); + String value = c.getString(c.getColumnIndexOrThrow("data")); + assertEquals("this is an updated test", value); + } + + @MediumTest + public void testPhoneNumbersEqual() throws Exception { + mDatabase.execSQL("CREATE TABLE phones (num TEXT);"); + mDatabase.execSQL("INSERT INTO phones (num) VALUES ('911');"); + mDatabase.execSQL("INSERT INTO phones (num) VALUES ('5555');"); + mDatabase.execSQL("INSERT INTO phones (num) VALUES ('+" + PHONE_NUMBER + "');"); + + String number; + Cursor c; + + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '504-555-7683')", null, null, null, null); + assertTrue(c == null || c.getCount() == 0); + c.close(); + + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '911')", null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + c.moveToFirst(); + number = c.getString(c.getColumnIndexOrThrow("num")); + assertEquals("911", number); + c.close(); + + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '5555')", null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + c.moveToFirst(); + number = c.getString(c.getColumnIndexOrThrow("num")); + assertEquals("5555", number); + c.close(); + + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '180055555555')", null, null, null, null); + assertTrue(c == null || c.getCount() == 0); + c.close(); + + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '+" + PHONE_NUMBER + "')", null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + c.moveToFirst(); + number = c.getString(c.getColumnIndexOrThrow("num")); + assertEquals("+" + PHONE_NUMBER, number); + c.close(); + + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '+1 (617).555-1212')", null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + c.moveToFirst(); + number = c.getString(c.getColumnIndexOrThrow("num")); + assertEquals("+" + PHONE_NUMBER, number); + c.close(); + + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '" + PHONE_NUMBER + "')", null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + c.moveToFirst(); + number = c.getString(c.getColumnIndexOrThrow("num")); + assertEquals("+" + PHONE_NUMBER, number); + c.close(); + + /* + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '5551212')", null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + c.moveToFirst(); + number = c.getString(c.getColumnIndexOrThrow("num")); + assertEquals("+" + PHONE_NUMBER, number); + c.close(); + */ + + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '011" + PHONE_NUMBER + "')", null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + c.moveToFirst(); + number = c.getString(c.getColumnIndexOrThrow("num")); + assertEquals("+" + PHONE_NUMBER, number); + c.close(); + + c = mDatabase.query("phones", null, + "PHONE_NUMBERS_EQUAL(num, '00" + PHONE_NUMBER + "')", null, null, null, null); + assertNotNull(c); + assertEquals(1, c.getCount()); + c.moveToFirst(); + number = c.getString(c.getColumnIndexOrThrow("num")); + assertEquals("+" + PHONE_NUMBER, number); + c.close(); + } + + private void phoneNumberCompare(String phone1, String phone2, boolean equal, + boolean useStrictComparation) { + String[] temporalPhoneNumbers = new String[2]; + temporalPhoneNumbers[0] = phone1; + temporalPhoneNumbers[1] = phone2; + + Cursor cursor = mDatabase.rawQuery( + String.format( + "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?, %d) " + + "THEN 'equal' ELSE 'not equal' END", + (useStrictComparation ? 1 : 0)), + temporalPhoneNumbers); + try { + assertNotNull(cursor); + assertTrue(cursor.moveToFirst()); + if (equal) { + assertEquals(String.format("Unexpectedly, \"%s != %s\".", phone1, phone2), + "equal", cursor.getString(0)); + } else { + assertEquals(String.format("Unexpectedly, \"%s\" == \"%s\".", phone1, phone2), + "not equal", cursor.getString(0)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + private void assertPhoneNumberEqual(String phone1, String phone2) throws Exception { + assertPhoneNumberEqual(phone1, phone2, true); + assertPhoneNumberEqual(phone1, phone2, false); + } + + private void assertPhoneNumberEqual(String phone1, String phone2, boolean useStrict) + throws Exception { + phoneNumberCompare(phone1, phone2, true, useStrict); + } + + private void assertPhoneNumberNotEqual(String phone1, String phone2) throws Exception { + assertPhoneNumberNotEqual(phone1, phone2, true); + assertPhoneNumberNotEqual(phone1, phone2, false); + } + + private void assertPhoneNumberNotEqual(String phone1, String phone2, boolean useStrict) + throws Exception { + phoneNumberCompare(phone1, phone2, false, useStrict); + } + + /** + * Tests international matching issues for the PHONE_NUMBERS_EQUAL function. + * + * @throws Exception + */ + @SmallTest + public void testPhoneNumbersEqualInternationl() throws Exception { + assertPhoneNumberEqual("1", "1"); + assertPhoneNumberEqual("123123", "123123"); + assertPhoneNumberNotEqual("123123", "923123"); + assertPhoneNumberNotEqual("123123", "123129"); + assertPhoneNumberNotEqual("123123", "1231234"); + assertPhoneNumberNotEqual("123123", "0123123", false); + assertPhoneNumberNotEqual("123123", "0123123", true); + assertPhoneNumberEqual("650-253-0000", "6502530000"); + assertPhoneNumberEqual("650-253-0000", "650 253 0000"); + assertPhoneNumberEqual("650 253 0000", "6502530000"); + assertPhoneNumberEqual("+1 650-253-0000", "6502530000"); + assertPhoneNumberEqual("001 650-253-0000", "6502530000"); + assertPhoneNumberEqual("0111 650-253-0000", "6502530000"); + + // Russian trunk digit + assertPhoneNumberEqual("+79161234567", "89161234567"); + + // French trunk digit + assertPhoneNumberEqual("+33123456789", "0123456789"); + + // Trunk digit for city codes in the Netherlands + assertPhoneNumberEqual("+31771234567", "0771234567"); + + // Test broken caller ID seen on call from Thailand to the US + assertPhoneNumberEqual("+66811234567", "166811234567"); + + // Test the same in-country number with different country codes + assertPhoneNumberNotEqual("+33123456789", "+1123456789"); + + // Test one number with country code and the other without + assertPhoneNumberEqual("5125551212", "+15125551212"); + + // Test two NANP numbers that only differ in the area code + assertPhoneNumberNotEqual("5125551212", "6505551212"); + + // Japanese phone numbers + assertPhoneNumberEqual("090-1234-5678", "+819012345678"); + assertPhoneNumberEqual("090(1234)5678", "+819012345678"); + assertPhoneNumberEqual("090-1234-5678", "+81-90-1234-5678"); + + // Equador + assertPhoneNumberEqual("+593(800)123-1234", "8001231234"); + assertPhoneNumberEqual("+593-2-1234-123", "21234123"); + + // Two continuous 0 at the beginning of the phone string should not be + // treated as trunk prefix in the strict comparation. + assertPhoneNumberEqual("008001231234", "8001231234", false); + assertPhoneNumberNotEqual("008001231234", "8001231234", true); + + // Confirm that the bug found before does not re-appear in the strict compalation + assertPhoneNumberEqual("080-1234-5678", "+819012345678", false); + assertPhoneNumberNotEqual("080-1234-5678", "+819012345678", true); + } + + @MediumTest + public void testCopyString() throws Exception { + mDatabase.execSQL("CREATE TABLE guess (numi INTEGER, numf FLOAT, str TEXT);"); + mDatabase.execSQL( + "INSERT INTO guess (numi,numf,str) VALUES (0,0.0,'ZoomZoomZoomZoom');"); + mDatabase.execSQL("INSERT INTO guess (numi,numf,str) VALUES (2000000000,3.1415926535,'');"); + String chinese = "\u4eac\u4ec5 \u5c3d\u5f84\u60ca"; + String[] arr = new String[1]; + arr[0] = chinese; + mDatabase.execSQL("INSERT INTO guess (numi,numf,str) VALUES (-32768,-1.0,?)", arr); + + Cursor c; + + c = mDatabase.rawQuery("SELECT * FROM guess", null); + + c.moveToFirst(); + + CharArrayBuffer buf = new CharArrayBuffer(14); + + String compareTo = c.getString(c.getColumnIndexOrThrow("numi")); + int numiIdx = c.getColumnIndexOrThrow("numi"); + int numfIdx = c.getColumnIndexOrThrow("numf"); + int strIdx = c.getColumnIndexOrThrow("str"); + + c.copyStringToBuffer(numiIdx, buf); + assertEquals(1, buf.sizeCopied); + assertEquals(compareTo, new String(buf.data, 0, buf.sizeCopied)); + + c.copyStringToBuffer(strIdx, buf); + assertEquals("ZoomZoomZoomZoom", new String(buf.data, 0, buf.sizeCopied)); + + c.moveToNext(); + compareTo = c.getString(numfIdx); + + c.copyStringToBuffer(numfIdx, buf); + assertEquals(compareTo, new String(buf.data, 0, buf.sizeCopied)); + c.copyStringToBuffer(strIdx, buf); + assertEquals(0, buf.sizeCopied); + + c.moveToNext(); + c.copyStringToBuffer(numfIdx, buf); + assertEquals(-1.0, Double.valueOf( + new String(buf.data, 0, buf.sizeCopied)).doubleValue()); + + c.copyStringToBuffer(strIdx, buf); + compareTo = c.getString(strIdx); + assertEquals(chinese, compareTo); + + assertEquals(chinese, new String(buf.data, 0, buf.sizeCopied)); + c.close(); + } + + @MediumTest + public void testSchemaChange1() throws Exception { + SQLiteDatabase db1 = mDatabase; + Cursor cursor; + + db1.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);"); + + cursor = db1.query("db1", null, null, null, null, null, null); + assertNotNull("Cursor is null", cursor); + + db1.execSQL("CREATE TABLE db2 (_id INTEGER PRIMARY KEY, data TEXT);"); + + assertEquals(0, cursor.getCount()); + cursor.deactivate(); + } + + @MediumTest + public void testSchemaChange2() throws Exception { + SQLiteDatabase db1 = mDatabase; + SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile, null); + Cursor cursor; + + db1.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);"); + + cursor = db1.query("db1", null, null, null, null, null, null); + assertNotNull("Cursor is null", cursor); + assertEquals(0, cursor.getCount()); + cursor.deactivate(); + // this cause exception because we're still using sqlite_prepate16 and not + // sqlite_prepare16_v2. The v2 variant added the ability to check the + // schema version and handle the case when the schema has changed + // Marco Nelissen claim it was 2x slower to compile SQL statements so + // I reverted back to the v1 variant. + /* db2.execSQL("CREATE TABLE db2 (_id INTEGER PRIMARY KEY, data TEXT);"); + + cursor = db1.query("db1", null, null, null, null, null, null); + assertNotNull("Cursor is null", cursor); + assertEquals(0, cursor.count()); + cursor.deactivate(); + */ + } + + @MediumTest + public void testSchemaChange3() throws Exception { + SQLiteDatabase db1 = mDatabase; + SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile, null); + Cursor cursor; + + + db1.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);"); + db1.execSQL("INSERT INTO db1 (data) VALUES ('test');"); + + cursor = db1.query("db1", null, null, null, null, null, null); + // this cause exception because we're still using sqlite_prepate16 and not + // sqlite_prepare16_v2. The v2 variant added the ability to check the + // schema version and handle the case when the schema has changed + // Marco Nelissen claim it was 2x slower to compile SQL statements so + // I reverted back to the v1 variant. + /* db2.execSQL("CREATE TABLE db2 (_id INTEGER PRIMARY KEY, data TEXT);"); + + assertNotNull("Cursor is null", cursor); + assertEquals(1, cursor.count()); + assertTrue(cursor.first()); + assertEquals("test", cursor.getString(cursor.getColumnIndexOrThrow("data"))); + cursor.deactivate(); + */ + } + + private class ChangeObserver extends ContentObserver { + private int mCursorNotificationCount = 0; + private int mNotificationCount = 0; + + public int getCursorNotificationCount() { + return mCursorNotificationCount; + } + + public int getNotificationCount() { + return mNotificationCount; + } + + public ChangeObserver(boolean cursor) { + super(new Handler()); + mCursor = cursor; + } + + @Override + public boolean deliverSelfNotifications() { + return true; + } + + @Override + public void onChange(boolean selfChange) { + if (mCursor) { + mCursorNotificationCount++; + } else { + mNotificationCount++; + } + } + + boolean mCursor; + } + + @MediumTest + public void testNotificationTest1() throws Exception { + /* + Cursor c = mContentResolver.query(Notes.CONTENT_URI, + new String[] {Notes._ID, Notes.NOTE}, + null, null); + c.registerContentObserver(new MyContentObserver(true)); + int count = c.count(); + + MyContentObserver observer = new MyContentObserver(false); + mContentResolver.registerContentObserver(Notes.CONTENT_URI, true, observer); + + Uri uri; + + HashMap<String, String> values = new HashMap<String, String>(); + values.put(Notes.NOTE, "test note1"); + uri = mContentResolver.insert(Notes.CONTENT_URI, values); + assertEquals(1, mCursorNotificationCount); + assertEquals(1, mNotificationCount); + + c.requery(); + assertEquals(count + 1, c.count()); + c.first(); + assertEquals("test note1", c.getString(c.getColumnIndex(Notes.NOTE))); + c.updateString(c.getColumnIndex(Notes.NOTE), "test note2"); + c.commitUpdates(); + + assertEquals(2, mCursorNotificationCount); + assertEquals(2, mNotificationCount); + + mContentResolver.delete(uri, null); + + assertEquals(3, mCursorNotificationCount); + assertEquals(3, mNotificationCount); + + mContentResolver.unregisterContentObserver(observer); + */ + } + + @MediumTest + public void testSelectionArgs() throws Exception { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);"); + ContentValues values = new ContentValues(1); + values.put("data", "don't forget to handled 's"); + mDatabase.insert("test", "data", values); + values.clear(); + values.put("data", "no apostrophes here"); + mDatabase.insert("test", "data", values); + Cursor c = mDatabase.query( + "test", null, "data GLOB ?", new String[]{"*'*"}, null, null, null); + assertEquals(1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals("don't forget to handled 's", c.getString(1)); + c.deactivate(); + + // make sure code should checking null string properly so that + // it won't crash + try { + mDatabase.query("test", new String[]{"_id"}, + "_id=?", new String[]{null}, null, null, null); + fail("expected exception not thrown"); + } catch (IllegalArgumentException e) { + // expected + } + } + + @MediumTest + public void testTokenize() throws Exception { + Cursor c; + mDatabase.execSQL("CREATE TABLE tokens (" + + "token TEXT COLLATE unicode," + + "source INTEGER," + + "token_index INTEGER," + + "tag TEXT" + + ");"); + mDatabase.execSQL("CREATE TABLE tokens_no_index (" + + "token TEXT COLLATE unicode," + + "source INTEGER" + + ");"); + + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE(NULL, NULL, NULL, NULL)", null)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE('tokens', NULL, NULL, NULL)", null)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE('tokens', 10, NULL, NULL)", null)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE('tokens', 10, 'some string', NULL)", null)); + + Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE('tokens', 11, 'some string ok', ' ', 1, 'foo')", null)); + Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE('tokens', 11, 'second field', ' ', 1, 'bar')", null)); + + Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE('tokens_no_index', 20, 'some string ok', ' ')", null)); + Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE('tokens_no_index', 21, 'foo bar baz', ' ', 0)", null)); + + // test Chinese + String chinese = new String("\u4eac\u4ec5 \u5c3d\u5f84\u60ca"); + Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE('tokens', 12,'" + chinese + "', ' ', 1)", null)); + + String icustr = new String("Fr\u00e9d\u00e9ric Hj\u00f8nnev\u00e5g"); + + Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, + "SELECT _TOKENIZE('tokens', 13, '" + icustr + "', ' ', 1)", null)); + + Assert.assertEquals(9, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens;", null)); + + String key = DatabaseUtils.getHexCollationKey("Frederic Hjonneva"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + key = DatabaseUtils.getHexCollationKey("Hjonneva"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + + key = DatabaseUtils.getHexCollationKey("some string ok"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, + "SELECT tag from tokens where token GLOB '" + key + "*'", null)); + key = DatabaseUtils.getHexCollationKey("string"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, + "SELECT tag from tokens where token GLOB '" + key + "*'", null)); + key = DatabaseUtils.getHexCollationKey("ok"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, + "SELECT tag from tokens where token GLOB '" + key + "*'", null)); + + key = DatabaseUtils.getHexCollationKey("second field"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase, + "SELECT tag from tokens where token GLOB '" + key + "*'", null)); + key = DatabaseUtils.getHexCollationKey("field"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase, + "SELECT tag from tokens where token GLOB '" + key + "*'", null)); + + key = DatabaseUtils.getHexCollationKey(chinese); + String[] a = new String[1]; + a[0] = key; + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token= ?", a)); + Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token= ?", a)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token= ?", a)); + a[0] += "*"; + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB ?", a)); + Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB ?", a)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB ?", a)); + + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token= '" + key + "'", null)); + Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token= '" + key + "'", null)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token= '" + key + "'", null)); + + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + + key = DatabaseUtils.getHexCollationKey("\u4eac\u4ec5"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + + key = DatabaseUtils.getHexCollationKey("\u5c3d\u5f84\u60ca"); + Log.d("DatabaseGeneralTest", "key = " + key); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens where token GLOB '" + key + "*'", null)); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); + + Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens where token GLOB 'ab*'", null)); + + key = DatabaseUtils.getHexCollationKey("some string ok"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null)); + Assert.assertEquals(20, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null)); + + key = DatabaseUtils.getHexCollationKey("bar"); + Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, + "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null)); + Assert.assertEquals(21, DatabaseUtils.longForQuery(mDatabase, + "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null)); + } + + @MediumTest + public void testTransactions() throws Exception { + mDatabase.execSQL("CREATE TABLE test (num INTEGER);"); + mDatabase.execSQL("INSERT INTO test (num) VALUES (0)"); + + // Make sure that things work outside an explicit transaction. + setNum(1); + checkNum(1); + + // Test a single-level transaction. + setNum(0); + mDatabase.beginTransaction(); + setNum(1); + mDatabase.setTransactionSuccessful(); + mDatabase.endTransaction(); + checkNum(1); + Assert.assertFalse(mDatabase.isDbLockedByCurrentThread()); + + // Test a rolled-back transaction. + setNum(0); + mDatabase.beginTransaction(); + setNum(1); + mDatabase.endTransaction(); + checkNum(0); + Assert.assertFalse(mDatabase.isDbLockedByCurrentThread()); + + // We should get an error if we end a non-existent transaction. + assertThrowsIllegalState(new Runnable() { public void run() { + mDatabase.endTransaction(); + }}); + + // We should get an error if a set a non-existent transaction as clean. + assertThrowsIllegalState(new Runnable() { public void run() { + mDatabase.setTransactionSuccessful(); + }}); + + mDatabase.beginTransaction(); + mDatabase.setTransactionSuccessful(); + // We should get an error if we mark a transaction as clean twice. + assertThrowsIllegalState(new Runnable() { public void run() { + mDatabase.setTransactionSuccessful(); + }}); + // We should get an error if we begin a transaction after marking the parent as clean. + assertThrowsIllegalState(new Runnable() { public void run() { + mDatabase.beginTransaction(); + }}); + mDatabase.endTransaction(); + Assert.assertFalse(mDatabase.isDbLockedByCurrentThread()); + + // Test a two-level transaction. + setNum(0); + mDatabase.beginTransaction(); + mDatabase.beginTransaction(); + setNum(1); + mDatabase.setTransactionSuccessful(); + mDatabase.endTransaction(); + mDatabase.setTransactionSuccessful(); + mDatabase.endTransaction(); + checkNum(1); + Assert.assertFalse(mDatabase.isDbLockedByCurrentThread()); + + // Test rolling back an inner transaction. + setNum(0); + mDatabase.beginTransaction(); + mDatabase.beginTransaction(); + setNum(1); + mDatabase.endTransaction(); + mDatabase.setTransactionSuccessful(); + mDatabase.endTransaction(); + checkNum(0); + Assert.assertFalse(mDatabase.isDbLockedByCurrentThread()); + + // Test rolling back an outer transaction. + setNum(0); + mDatabase.beginTransaction(); + mDatabase.beginTransaction(); + setNum(1); + mDatabase.setTransactionSuccessful(); + mDatabase.endTransaction(); + mDatabase.endTransaction(); + checkNum(0); + Assert.assertFalse(mDatabase.isDbLockedByCurrentThread()); + } + + private void setNum(int num) { + mDatabase.execSQL("UPDATE test SET num = " + num); + } + + private void checkNum(int num) { + Assert.assertEquals( + num, DatabaseUtils.longForQuery(mDatabase, "SELECT num FROM test", null)); + } + + private void assertThrowsIllegalState(Runnable r) { + boolean ok = false; + try { + r.run(); + } catch (IllegalStateException e) { + ok = true; + } + Assert.assertTrue(ok); + } + + // Disable these until we can explicitly mark them as stress tests + public void xxtestMem1() throws Exception { + populateDefaultTable(); + + for (int i = 0; i < 50000; i++) { + Cursor cursor = mDatabase.query("test", null, null, null, null, null, null); + cursor.moveToFirst(); + cursor.close(); +// Log.i("~~~~", "Finished round " + i); + } + } + + // Disable these until we can explicitly mark them as stress tests + public void xxtestMem2() throws Exception { + populateDefaultTable(); + + for (int i = 0; i < 50000; i++) { + Cursor cursor = mDatabase.query("test", null, null, null, null, null, null); + cursor.close(); +// Log.i("~~~~", "Finished round " + i); + } + } + + // Disable these until we can explicitly mark them as stress tests + public void xxtestMem3() throws Exception { + populateDefaultTable(); + + for (int i = 0; i < 50000; i++) { + Cursor cursor = mDatabase.query("test", null, null, null, null, null, null); + cursor.deactivate(); +// Log.i("~~~~", "Finished round " + i); + } + } + + @MediumTest + public void testContentValues() throws Exception { + ContentValues values = new ContentValues(); + values.put("string", "value"); + assertEquals("value", values.getAsString("string")); + byte[] bytes = new byte[42]; + Arrays.fill(bytes, (byte) 0x28); + values.put("byteArray", bytes); + assertTrue(Arrays.equals(bytes, values.getAsByteArray("byteArray"))); + + // Write the ContentValues to a Parcel and then read them out + Parcel p = Parcel.obtain(); + values.writeToParcel(p, 0); + p.setDataPosition(0); + values = ContentValues.CREATOR.createFromParcel(p); + + // Read the values out again and make sure they're the same + assertTrue(Arrays.equals(bytes, values.getAsByteArray("byteArray"))); + assertEquals("value", values.get("string")); + } + + @MediumTest + public void testTableInfoPragma() throws Exception { + mDatabase.execSQL("CREATE TABLE pragma_test (" + + "i INTEGER DEFAULT 1234, " + + "j INTEGER, " + + "s TEXT DEFAULT 'hello', " + + "t TEXT, " + + "'select' TEXT DEFAULT \"hello\")"); + try { + Cursor cur = mDatabase.rawQuery("PRAGMA table_info(pragma_test)", null); + Assert.assertEquals(5, cur.getCount()); + + Assert.assertTrue(cur.moveToNext()); + Assert.assertEquals("i", + cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX)); + Assert.assertEquals("1234", + cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX)); + + Assert.assertTrue(cur.moveToNext()); + Assert.assertEquals("j", + cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX)); + Assert.assertNull(cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX)); + + Assert.assertTrue(cur.moveToNext()); + Assert.assertEquals("s", + cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX)); + Assert.assertEquals("'hello'", + cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX)); + + Assert.assertTrue(cur.moveToNext()); + Assert.assertEquals("t", + cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX)); + Assert.assertNull(cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX)); + + Assert.assertTrue(cur.moveToNext()); + Assert.assertEquals("select", + cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX)); + Assert.assertEquals("\"hello\"", + cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX)); + + cur.close(); + } catch (Throwable t) { + throw new RuntimeException( + "If you see this test fail, it's likely that something about " + + "sqlite's PRAGMA table_info(...) command has changed.", t); + } + } + + @MediumTest + public void testInsertHelper() throws Exception { + Cursor cur; + ContentValues cv; + long row; + + mDatabase.execSQL("CREATE TABLE insert_test (" + + "_id INTEGER PRIMARY KEY, " + + "s TEXT NOT NULL UNIQUE, " + + "t TEXT NOT NULL DEFAULT 'hello world', " + + "i INTEGER, " + + "j INTEGER NOT NULL DEFAULT 1234, " + + "'select' TEXT)"); + + DatabaseUtils.InsertHelper ih = + new DatabaseUtils.InsertHelper(mDatabase, "insert_test"); + + cv = new ContentValues(); + cv.put("s", "one"); + row = ih.insert(cv); + cur = mDatabase.rawQuery("SELECT * FROM insert_test WHERE _id == " + row, null); + Assert.assertTrue(cur.moveToFirst()); + Assert.assertEquals("one", cur.getString(1)); + Assert.assertEquals("hello world", cur.getString(2)); + Assert.assertNull(cur.getString(3)); + Assert.assertEquals(1234, cur.getLong(4)); + Assert.assertNull(cur.getString(5)); + + cv = new ContentValues(); + cv.put("s", "two"); + cv.put("t", "goodbye world"); + row = ih.insert(cv); + cur = mDatabase.rawQuery("SELECT * FROM insert_test WHERE _id == " + row, null); + Assert.assertTrue(cur.moveToFirst()); + Assert.assertEquals("two", cur.getString(1)); + Assert.assertEquals("goodbye world", cur.getString(2)); + Assert.assertNull(cur.getString(3)); + Assert.assertEquals(1234, cur.getLong(4)); + Assert.assertNull(cur.getString(5)); + + cv = new ContentValues(); + cv.put("t", "goodbye world"); + row = ih.insert(cv); + Assert.assertEquals(-1, row); + + cv = new ContentValues(); + cv.put("s", "three"); + cv.put("i", 2345); + cv.put("j", 3456); + cv.put("select", "tricky"); + row = ih.insert(cv); + cur = mDatabase.rawQuery("SELECT * FROM insert_test WHERE _id == " + row, null); + Assert.assertTrue(cur.moveToFirst()); + Assert.assertEquals("three", cur.getString(1)); + Assert.assertEquals("hello world", cur.getString(2)); + Assert.assertEquals(2345, cur.getLong(3)); + Assert.assertEquals(3456, cur.getLong(4)); + Assert.assertEquals("tricky", cur.getString(5)); + + cv = new ContentValues(); + cv.put("s", "three"); + cv.put("i", 6789); + row = ih.insert(cv); + Assert.assertEquals(-1, row); + row = ih.replace(cv); + cur = mDatabase.rawQuery("SELECT * FROM insert_test WHERE _id == " + row, null); + Assert.assertTrue(cur.moveToFirst()); + Assert.assertEquals("three", cur.getString(1)); + Assert.assertEquals("hello world", cur.getString(2)); + Assert.assertEquals(6789, cur.getLong(3)); + + ih.close(); + } + + @MediumTest + public void testDbCloseReleasingAllCachedSql() { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " + + "num1 INTEGER, num2 INTEGER, image BLOB);"); + final String statement = "DELETE FROM test WHERE _id=?;"; + SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement); + assertTrue(statementDoNotClose.getUniqueId() > 0); + int nStatement = statementDoNotClose.getUniqueId(); + assertTrue(statementDoNotClose.getUniqueId() == nStatement); + /* do not close statementDoNotClose object. + * That should leave it in SQLiteDatabase.mPrograms. + * mDatabase.close() in tearDown() should release it. + */ + } +} diff --git a/core/tests/coretests/src/android/database/DatabaseLocaleTest.java b/core/tests/coretests/src/android/database/DatabaseLocaleTest.java new file mode 100644 index 0000000..b3282941 --- /dev/null +++ b/core/tests/coretests/src/android/database/DatabaseLocaleTest.java @@ -0,0 +1,129 @@ +/* + * 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.database; + +import android.database.sqlite.SQLiteDatabase; +import android.database.Cursor; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; +import android.test.MoreAsserts; + +import java.util.ArrayList; +import java.util.Locale; + +import junit.framework.TestCase; + +public class DatabaseLocaleTest extends TestCase { + + private SQLiteDatabase mDatabase; + + private static final String[] STRINGS = { + "c\u00f4t\u00e9", + "cote", + "c\u00f4te", + "cot\u00e9", + "boy", + "dog", + "COTE", + }; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mDatabase = SQLiteDatabase.create(null); + mDatabase.execSQL( + "CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT COLLATE LOCALIZED);"); + } + + private void insertStrings() { + for (String s : STRINGS) { + mDatabase.execSQL("INSERT INTO test (data) VALUES('" + s + "');"); + } + } + + @Override + protected void tearDown() throws Exception { + mDatabase.close(); + super.tearDown(); + } + + private String[] query(String sql) { + Log.i("LocaleTest", "Querying: " + sql); + Cursor c = mDatabase.rawQuery(sql, null); + assertNotNull(c); + ArrayList<String> items = new ArrayList<String>(); + while (c.moveToNext()) { + items.add(c.getString(0)); + Log.i("LocaleTest", "...." + c.getString(0)); + } + String[] result = items.toArray(new String[items.size()]); + assertEquals(STRINGS.length, result.length); + c.close(); + return result; + } + + @MediumTest + public void testLocaleInsertOrder() throws Exception { + insertStrings(); + String[] results = query("SELECT data FROM test"); + MoreAsserts.assertEquals(STRINGS, results); + } + + @MediumTest + public void testLocaleenUS() throws Exception { + insertStrings(); + Log.i("LocaleTest", "about to call setLocale en_US"); + mDatabase.setLocale(new Locale("en", "US")); + String[] results; + results = query("SELECT data FROM test ORDER BY data COLLATE LOCALIZED ASC"); + + // The database code currently uses PRIMARY collation strength, + // meaning that all versions of a character compare equal (regardless + // of case or accents), leaving the "cote" flavors in database order. + MoreAsserts.assertEquals(results, new String[] { + STRINGS[4], // "boy" + STRINGS[0], // sundry forms of "cote" + STRINGS[1], + STRINGS[2], + STRINGS[3], + STRINGS[6], // "COTE" + STRINGS[5], // "dog" + }); + } + + @SmallTest + public void testHoge() throws Exception { + Cursor cursor = null; + try { + String expectedString = new String(new int[] {0xFE000}, 0, 1); + mDatabase.execSQL("INSERT INTO test(id, data) VALUES(1, '" + expectedString + "')"); + cursor = mDatabase.rawQuery("SELECT data FROM test WHERE id = 1", null); + + assertNotNull(cursor); + assertTrue(cursor.moveToFirst()); + String actualString = cursor.getString(0); + assertEquals(expectedString.length(), actualString.length()); + for (int i = 0; i < expectedString.length(); i++) { + assertEquals((int)expectedString.charAt(i), (int)actualString.charAt(i)); + } + assertEquals(expectedString, actualString); + } finally { + if (cursor != null) cursor.close(); + } + } +} diff --git a/core/tests/coretests/src/android/database/DatabaseLockTest.java b/core/tests/coretests/src/android/database/DatabaseLockTest.java new file mode 100644 index 0000000..f7a9f8a --- /dev/null +++ b/core/tests/coretests/src/android/database/DatabaseLockTest.java @@ -0,0 +1,175 @@ +/* + * 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.database; + +import android.app.Activity; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.Suppress; +import android.util.Log; +import java.io.File; +import java.util.concurrent.atomic.AtomicInteger; +import android.test.AndroidTestCase; + +import junit.framework.TestCase; + +/* + * This is a series of unit tests for database locks. + * + * Suppress these tests for now, since they have has inconsistent results. + * This should be turned into a performance tracking test. + */ +@Suppress +public class DatabaseLockTest extends AndroidTestCase { + + private static final int NUM_ITERATIONS = 100; + private static final int SLEEP_TIME = 30; + private static final int MAX_ALLOWED_LATENCY_TIME = 30; + private SQLiteDatabase mDatabase; + private File mDatabaseFile; + private AtomicInteger mCounter = new AtomicInteger(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + File parentDir = getContext().getFilesDir(); + mDatabaseFile = new File(parentDir, "database_test.db"); + + if (mDatabaseFile.exists()) { + mDatabaseFile.delete(); + } + mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); + assertNotNull(mDatabase); + } + + @Override + protected void tearDown() throws Exception { + mDatabase.close(); + mDatabaseFile.delete(); + super.tearDown(); + } + + /* + * testLockFairness() tests the fairness of prioritizing multiple threads + * attempting to access a database concurrently. + * This test is intended to verify that, when two threads are accessing the + * same database at the same time with the same prioritization, neither thread + * is locked out and prevented from accessing the database. + */ + @Suppress + public void testLockFairness() { + startDatabaseFairnessThread(); + int previous = 0; + for (int i = 0; i < NUM_ITERATIONS; i++) { + mDatabase.beginTransaction(); + int val = mCounter.get(); + if (i == 0) { + previous = val - i; + } + assertTrue(previous == (val - i)); + try { + Thread.currentThread().sleep(SLEEP_TIME); + } catch (InterruptedException e) { + // ignore + } + mDatabase.endTransaction(); + } + } + + /* + * This function is to create the second thread for testLockFairness() test. + */ + private void startDatabaseFairnessThread() { + Thread thread = new DatabaseFairnessThread(); + thread.start(); + } + + private class DatabaseFairnessThread extends Thread { + @Override + public void run() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + mDatabase.beginTransaction(); + int val = mCounter.incrementAndGet(); + try { + Thread.currentThread().sleep(SLEEP_TIME); + } catch (InterruptedException e) { + // ignore + } + mDatabase.endTransaction(); + } + } + } + + /* + * testLockLatency() tests the latency of database locks. + * This test is intended to verify that, even when two threads are accessing + * the same database, the locking/unlocking of the database is done within an + * appropriate amount of time (MAX_ALLOWED_LATENCY_TIME). + */ + @Suppress + public void testLockLatency() { + startDatabaseLatencyThread(); + int previous = 0; + long sumTime = 0; + long maxTime = 0; + for (int i = 0; i < NUM_ITERATIONS; i++) { + long startTime = System.currentTimeMillis(); + mDatabase.beginTransaction(); + long endTime = System.currentTimeMillis(); + long elapsedTime = endTime - startTime; + if (maxTime < elapsedTime) { + maxTime = elapsedTime; + } + sumTime += elapsedTime; + try { + Thread.currentThread().sleep(SLEEP_TIME); + } catch (InterruptedException e) { + // ignore + } + mDatabase.endTransaction(); + } + long averageTime = sumTime/NUM_ITERATIONS; + Log.i("DatabaseLockLatency", "AverageTime: " + averageTime); + Log.i("DatabaseLockLatency", "MaxTime: " + maxTime); + assertTrue( (averageTime - SLEEP_TIME) <= MAX_ALLOWED_LATENCY_TIME); + } + + /* + * This function is to create the second thread for testLockLatency() test. + */ + private void startDatabaseLatencyThread() { + Thread thread = new DatabaseLatencyThread(); + thread.start(); + } + + private class DatabaseLatencyThread extends Thread { + @Override + public void run() { + for (int i = 0; i < NUM_ITERATIONS; i++) + { + mDatabase.beginTransaction(); + try { + Thread.currentThread().sleep(SLEEP_TIME); + } catch (InterruptedException e) { + // ignore + } + mDatabase.endTransaction(); + } + } + } +} diff --git a/core/tests/coretests/src/android/database/DatabasePerformanceTests.java b/core/tests/coretests/src/android/database/DatabasePerformanceTests.java new file mode 100644 index 0000000..b8ebcc4 --- /dev/null +++ b/core/tests/coretests/src/android/database/DatabasePerformanceTests.java @@ -0,0 +1,1353 @@ +/* + * 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.database; + +import junit.framework.Assert; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.Contacts; +import android.provider.Contacts.People; +import android.test.PerformanceTestCase; +import android.test.TestCase; + +import java.io.File; +import java.util.Random; + +/** + * Database Performance Tests + * + */ + +public class DatabasePerformanceTests { + + public static String[] children() { + return new String[] { + ContactReadingTest1.class.getName(), + Perf1Test.class.getName(), + Perf2Test.class.getName(), + Perf3Test.class.getName(), + Perf4Test.class.getName(), + Perf5Test.class.getName(), + Perf6Test.class.getName(), + Perf7Test.class.getName(), + Perf8Test.class.getName(), + Perf9Test.class.getName(), + Perf10Test.class.getName(), + Perf11Test.class.getName(), + Perf12Test.class.getName(), + Perf13Test.class.getName(), + Perf14Test.class.getName(), + Perf15Test.class.getName(), + Perf16Test.class.getName(), + Perf17Test.class.getName(), + Perf18Test.class.getName(), + Perf19Test.class.getName(), + Perf20Test.class.getName(), + Perf21Test.class.getName(), + Perf22Test.class.getName(), + Perf23Test.class.getName(), + Perf24Test.class.getName(), + Perf25Test.class.getName(), + Perf26Test.class.getName(), + Perf27Test.class.getName(), + Perf28Test.class.getName(), + Perf29Test.class.getName(), + Perf30Test.class.getName(), + Perf31Test.class.getName(), + }; + } + + public static abstract class PerformanceBase implements TestCase, + PerformanceTestCase { + protected static final int CURRENT_DATABASE_VERSION = 42; + protected SQLiteDatabase mDatabase; + protected File mDatabaseFile; + protected Context mContext; + + public void setUp(Context c) { + mContext = c; + mDatabaseFile = new File("/tmp", "perf_database_test.db"); + if (mDatabaseFile.exists()) { + mDatabaseFile.delete(); + } + mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); + Assert.assertTrue(mDatabase != null); + mDatabase.setVersion(CURRENT_DATABASE_VERSION); + } + + public void tearDown() { + mDatabase.close(); + mDatabaseFile.delete(); + } + + public boolean isPerformanceOnly() { + return true; + } + + // These test can only be run once. + public int startPerformance(Intermediates intermediates) { + return 0; + } + + public void run() { + } + + public String numberName(int number) { + String result = ""; + + if (number >= 1000) { + result += numberName((number / 1000)) + " thousand"; + number = (number % 1000); + + if (number > 0) result += " "; + } + + if (number >= 100) { + result += ONES[(number / 100)] + " hundred"; + number = (number % 100); + + if (number > 0) result += " "; + } + + if (number >= 20) { + result += TENS[(number / 10)]; + number = (number % 10); + + if (number > 0) result += " "; + } + + if (number > 0) { + result += ONES[number]; + } + + return result; + } + } + + /** + * Test reading all contact data. + */ + public static class ContactReadingTest1 implements TestCase, PerformanceTestCase { + private static final String[] PEOPLE_PROJECTION = new String[] { + Contacts.People._ID, // 0 + Contacts.People.PRIMARY_PHONE_ID, // 1 + Contacts.People.TYPE, // 2 + Contacts.People.NUMBER, // 3 + Contacts.People.LABEL, // 4 + Contacts.People.NAME, // 5 + Contacts.People.PRESENCE_STATUS, // 6 + }; + + private Cursor mCursor; + + public void setUp(Context c) { + mCursor = c.getContentResolver().query(People.CONTENT_URI, PEOPLE_PROJECTION, null, + null, People.DEFAULT_SORT_ORDER); + } + + public void tearDown() { + mCursor.close(); + } + + public boolean isPerformanceOnly() { + return true; + } + + public int startPerformance(Intermediates intermediates) { + // This test can only be run once. + return 0; + } + + public void run() { + while (mCursor.moveToNext()) { + // Read out all of the data + mCursor.getLong(0); + mCursor.getLong(1); + mCursor.getLong(2); + mCursor.getString(3); + mCursor.getString(4); + mCursor.getString(5); + mCursor.getLong(6); + } + } + } + + /** + * Test 1000 inserts + */ + + public static class Perf1Test extends PerformanceBase { + private static final int SIZE = 1000; + + private String[] statements = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + statements[i] = + "INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"; + } + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.execSQL(statements[i]); + } + } + } + + /** + * Test 1000 inserts into and indexed table + */ + + public static class Perf2Test extends PerformanceBase { + private static final int SIZE = 1000; + + private String[] statements = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + statements[i] = + "INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"; + } + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i1c ON t1(c)"); + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.execSQL(statements[i]); + } + } + } + + /** + * 100 SELECTs without an index + */ + + public static class Perf3Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"count(*)", "avg(b)"}; + + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int lower = i * 100; + int upper = (i + 10) * 100; + where[i] = "b >= " + lower + " AND b < " + upper; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase + .query("t1", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * 100 SELECTs on a string comparison + */ + + public static class Perf4Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"count(*)", "avg(b)"}; + + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + where[i] = "c LIKE '" + numberName(i) + "'"; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase + .query("t1", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * 100 SELECTs with an index + */ + + public static class Perf5Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"count(*)", "avg(b)"}; + + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i1b ON t1(b)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int lower = i * 100; + int upper = (i + 10) * 100; + where[i] = "b >= " + lower + " AND b < " + upper; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase + .query("t1", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * INNER JOIN without an index + */ + + public static class Perf6Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"t1.a"}; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase + .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + } + + @Override + public void run() { + mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null, + null, null, null, null); + } + } + + /** + * INNER JOIN without an index on one side + */ + + public static class Perf7Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"t1.a"}; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase + .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))"); + + mDatabase.execSQL("CREATE INDEX i1b ON t1(b)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + } + + @Override + public void run() { + mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null, + null, null, null, null); + } + } + + /** + * INNER JOIN without an index on one side + */ + + public static class Perf8Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"t1.a"}; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase + .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))"); + + mDatabase.execSQL("CREATE INDEX i1b ON t1(b)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + } + + @Override + public void run() { + mDatabase.query("t1 INNER JOIN t2 ON t1.c = t2.c", COLUMNS, null, + null, null, null, null); + } + } + + /** + * 100 SELECTs with subqueries. Subquery is using an index + */ + + public static class Perf9Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"t1.a"}; + + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase + .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))"); + + mDatabase.execSQL("CREATE INDEX i2b ON t2(b)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int lower = i * 100; + int upper = (i + 10) * 100; + where[i] = + "t1.b IN (SELECT t2.b FROM t2 WHERE t2.b >= " + lower + + " AND t2.b < " + upper + ")"; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase + .query("t1", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * 100 SELECTs on string comparison with Index + */ + + public static class Perf10Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"count(*)", "avg(b)"}; + + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i3c ON t1(c)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + where[i] = "c LIKE '" + numberName(i) + "'"; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase + .query("t1", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * 100 SELECTs on integer + */ + + public static class Perf11Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"b"}; + + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t1", COLUMNS, null, null, null, null, null); + } + } + } + + /** + * 100 SELECTs on String + */ + + public static class Perf12Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"c"}; + + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t1", COLUMNS, null, null, null, null, null); + } + } + } + + /** + * 100 SELECTs on integer with index + */ + + public static class Perf13Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"b"}; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i1b on t1(b)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t1", COLUMNS, null, null, null, null, null); + } + } + } + + /** + * 100 SELECTs on String with index + */ + + public static class Perf14Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"c"}; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i1c ON t1(c)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t1", COLUMNS, null, null, null, null, null); + } + } + } + + /** + * 100 SELECTs on String with starts with + */ + + public static class Perf15Test extends PerformanceBase { + private static final int SIZE = 100; + private static final String[] COLUMNS = {"c"}; + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i1c ON t1(c)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + where[i] = "c LIKE '" + numberName(r).substring(0, 1) + "*'"; + + } + + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase + .query("t1", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * 1000 Deletes on an indexed table + */ + + public static class Perf16Test extends PerformanceBase { + private static final int SIZE = 1000; + private static final String[] COLUMNS = {"c"}; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i3c ON t1(c)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.delete("t1", null, null); + } + } + } + + /** + * 1000 Deletes + */ + + public static class Perf17Test extends PerformanceBase { + private static final int SIZE = 1000; + private static final String[] COLUMNS = {"c"}; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.delete("t1", null, null); + } + } + } + + /** + * 1000 DELETE's without an index with where clause + */ + + public static class Perf18Test extends PerformanceBase { + private static final int SIZE = 1000; + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int lower = i * 100; + int upper = (i + 10) * 100; + where[i] = "b >= " + lower + " AND b < " + upper; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.delete("t1", where[i], null); + } + } + } + + /** + * 1000 DELETE's with an index with where clause + */ + + public static class Perf19Test extends PerformanceBase { + private static final int SIZE = 1000; + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i1b ON t1(b)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int lower = i * 100; + int upper = (i + 10) * 100; + where[i] = "b >= " + lower + " AND b < " + upper; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.delete("t1", where[i], null); + } + } + } + + /** + * 1000 update's with an index with where clause + */ + + public static class Perf20Test extends PerformanceBase { + private static final int SIZE = 1000; + private String[] where = new String[SIZE]; + ContentValues[] mValues = new ContentValues[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i1b ON t1(b)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + + int lower = i * 100; + int upper = (i + 10) * 100; + where[i] = "b >= " + lower + " AND b < " + upper; + ContentValues b = new ContentValues(1); + b.put("b", upper); + mValues[i] = b; + + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.update("t1", mValues[i], where[i], null); + } + } + } + + /** + * 1000 update's without an index with where clause + */ + + public static class Perf21Test extends PerformanceBase { + private static final int SIZE = 1000; + private String[] where = new String[SIZE]; + ContentValues[] mValues = new ContentValues[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + + int lower = i * 100; + int upper = (i + 10) * 100; + where[i] = "b >= " + lower + " AND b < " + upper; + ContentValues b = new ContentValues(1); + b.put("b", upper); + mValues[i] = b; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.update("t1", mValues[i], where[i], null); + } + } + } + + /** + * 10000 inserts for an integer + */ + + public static class Perf22Test extends PerformanceBase { + private static final int SIZE = 10000; + ContentValues[] mValues = new ContentValues[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + ContentValues b = new ContentValues(1); + b.put("a", r); + mValues[i] = b; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.insert("t1", null, mValues[i]); + } + } + } + + /** + * 10000 inserts for an integer -indexed table + */ + + public static class Perf23Test extends PerformanceBase { + private static final int SIZE = 10000; + ContentValues[] mValues = new ContentValues[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a INTEGER)"); + mDatabase.execSQL("CREATE INDEX i1a ON t1(a)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + ContentValues b = new ContentValues(1); + b.put("a", r); + mValues[i] = b; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.insert("t1", null, mValues[i]); + } + } + } + + /** + * 10000 inserts for a String + */ + + public static class Perf24Test extends PerformanceBase { + private static final int SIZE = 10000; + ContentValues[] mValues = new ContentValues[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + ContentValues b = new ContentValues(1); + b.put("a", numberName(r)); + mValues[i] = b; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.insert("t1", null, mValues[i]); + } + } + } + + /** + * 10000 inserts for a String - indexed table + */ + + public static class Perf25Test extends PerformanceBase { + private static final int SIZE = 10000; + ContentValues[] mValues = new ContentValues[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t1(a VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i1a ON t1(a)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + ContentValues b = new ContentValues(1); + b.put("a", numberName(r)); + mValues[i] = b; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.insert("t1", null, mValues[i]); + } + } + } + + + /** + * 10000 selects for a String -starts with + */ + + public static class Perf26Test extends PerformanceBase { + private static final int SIZE = 10000; + private static final String[] COLUMNS = {"t3.a"}; + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t3(a VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t3 VALUES('" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'"; + + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t3", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * 10000 selects for a String - indexed table -starts with + */ + + public static class Perf27Test extends PerformanceBase { + private static final int SIZE = 10000; + private static final String[] COLUMNS = {"t3.a"}; + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t3(a VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i3a ON t3(a)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t3 VALUES('" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'"; + + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t3", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * 10000 selects for an integer - + */ + + public static class Perf28Test extends PerformanceBase { + private static final int SIZE = 10000; + private static final String[] COLUMNS = {"t4.a"}; + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t4(a INTEGER)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")"); + int lower = i * 100; + int upper = (i + 10) * 100; + where[i] = "a >= " + lower + " AND a < " + upper; + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t4", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * 10000 selects for an integer -indexed table + */ + + public static class Perf29Test extends PerformanceBase { + private static final int SIZE = 10000; + private static final String[] COLUMNS = {"t4.a"}; + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t4(a INTEGER)"); + mDatabase.execSQL("CREATE INDEX i4a ON t4(a)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")"); + + int lower = i * 100; + int upper = (i + 10) * 100; + where[i] = "a >= " + lower + " AND a < " + upper; + } + + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t4", COLUMNS, where[i], null, null, null, null); + } + } + } + + + /** + * 10000 selects for a String - contains 'e' + */ + + public static class Perf30Test extends PerformanceBase { + private static final int SIZE = 10000; + private static final String[] COLUMNS = {"t3.a"}; + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t3(a VARCHAR(100))"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t3 VALUES('" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + where[i] = "a LIKE '*e*'"; + + } + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t3", COLUMNS, where[i], null, null, null, null); + } + } + } + + /** + * 10000 selects for a String - contains 'e'-indexed table + */ + + public static class Perf31Test extends PerformanceBase { + private static final int SIZE = 10000; + private static final String[] COLUMNS = {"t3.a"}; + private String[] where = new String[SIZE]; + + @Override + public void setUp(Context c) { + super.setUp(c); + Random random = new Random(42); + + mDatabase + .execSQL("CREATE TABLE t3(a VARCHAR(100))"); + mDatabase.execSQL("CREATE INDEX i3a ON t3(a)"); + + for (int i = 0; i < SIZE; i++) { + int r = random.nextInt(100000); + mDatabase.execSQL("INSERT INTO t3 VALUES('" + + numberName(r) + "')"); + } + + for (int i = 0; i < SIZE; i++) { + where[i] = "a LIKE '*e*'"; + + } + + } + + @Override + public void run() { + for (int i = 0; i < SIZE; i++) { + mDatabase.query("t3", COLUMNS, where[i], null, null, null, null); + } + } + } + + public static final String[] ONES = + {"zero", "one", "two", "three", "four", "five", "six", "seven", + "eight", "nine", "ten", "eleven", "twelve", "thirteen", + "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", + "nineteen"}; + + public static final String[] TENS = + {"", "ten", "twenty", "thirty", "forty", "fifty", "sixty", + "seventy", "eighty", "ninety"}; +} diff --git a/core/tests/coretests/src/android/database/DatabaseStatementTest.java b/core/tests/coretests/src/android/database/DatabaseStatementTest.java new file mode 100644 index 0000000..71dc3ae --- /dev/null +++ b/core/tests/coretests/src/android/database/DatabaseStatementTest.java @@ -0,0 +1,324 @@ +/* + * 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.database; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteConstraintException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDoneException; +import android.database.sqlite.SQLiteStatement; +import android.test.AndroidTestCase; +import android.test.PerformanceTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import junit.framework.TestCase; + +import java.io.File; + +public class DatabaseStatementTest extends AndroidTestCase implements PerformanceTestCase { + + private static final String sString1 = "this is a test"; + private static final String sString2 = "and yet another test"; + private static final String sString3 = "this string is a little longer, but still a test"; + + private static final int CURRENT_DATABASE_VERSION = 42; + private SQLiteDatabase mDatabase; + private File mDatabaseFile; + + @Override + protected void setUp() throws Exception { + super.setUp(); + File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE); + mDatabaseFile = new File(dbDir, "database_test.db"); + + if (mDatabaseFile.exists()) { + mDatabaseFile.delete(); + } + mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); + assertNotNull(mDatabase); + mDatabase.setVersion(CURRENT_DATABASE_VERSION); + } + + @Override + protected void tearDown() throws Exception { + mDatabase.close(); + mDatabaseFile.delete(); + super.tearDown(); + } + + public boolean isPerformanceOnly() { + return false; + } + + // These test can only be run once. + public int startPerformance(Intermediates intermediates) { + return 1; + } + + private void populateDefaultTable() { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);"); + + mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString1 + "');"); + mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString2 + "');"); + mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');"); + } + + @MediumTest + public void testExecuteStatement() throws Exception { + populateDefaultTable(); + SQLiteStatement statement = mDatabase.compileStatement("DELETE FROM test"); + statement.execute(); + + Cursor c = mDatabase.query("test", null, null, null, null, null, null); + assertEquals(0, c.getCount()); + c.deactivate(); + statement.close(); + } + + @MediumTest + public void testSimpleQuery() throws Exception { + mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);"); + mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');"); + SQLiteStatement statement1 = + mDatabase.compileStatement("SELECT num FROM test WHERE str = ?"); + SQLiteStatement statement2 = + mDatabase.compileStatement("SELECT str FROM test WHERE num = ?"); + + try { + statement1.bindString(1, "hello"); + long value = statement1.simpleQueryForLong(); + assertEquals(1234, value); + + statement1.bindString(1, "world"); + statement1.simpleQueryForLong(); + fail("shouldn't get here"); + } catch (SQLiteDoneException e) { + // expected + } + + try { + statement2.bindLong(1, 1234); + String value = statement1.simpleQueryForString(); + assertEquals("hello", value); + + statement2.bindLong(1, 5678); + statement1.simpleQueryForString(); + fail("shouldn't get here"); + } catch (SQLiteDoneException e) { + // expected + } + + statement1.close(); + statement2.close(); + } + + @MediumTest + public void testStatementLongBinding() throws Exception { + mDatabase.execSQL("CREATE TABLE test (num INTEGER);"); + SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)"); + + for (int i = 0; i < 10; i++) { + statement.bindLong(1, i); + statement.execute(); + } + statement.close(); + + Cursor c = mDatabase.query("test", null, null, null, null, null, null); + int numCol = c.getColumnIndexOrThrow("num"); + c.moveToFirst(); + for (long i = 0; i < 10; i++) { + long num = c.getLong(numCol); + assertEquals(i, num); + c.moveToNext(); + } + c.close(); + } + + @MediumTest + public void testStatementStringBinding() throws Exception { + mDatabase.execSQL("CREATE TABLE test (num TEXT);"); + SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)"); + + for (long i = 0; i < 10; i++) { + statement.bindString(1, Long.toHexString(i)); + statement.execute(); + } + statement.close(); + + Cursor c = mDatabase.query("test", null, null, null, null, null, null); + int numCol = c.getColumnIndexOrThrow("num"); + c.moveToFirst(); + for (long i = 0; i < 10; i++) { + String num = c.getString(numCol); + assertEquals(Long.toHexString(i), num); + c.moveToNext(); + } + c.close(); + } + + @MediumTest + public void testStatementClearBindings() throws Exception { + mDatabase.execSQL("CREATE TABLE test (num INTEGER);"); + SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)"); + + for (long i = 0; i < 10; i++) { + statement.bindLong(1, i); + statement.clearBindings(); + statement.execute(); + } + statement.close(); + + Cursor c = mDatabase.query("test", null, null, null, null, null, "ROWID"); + int numCol = c.getColumnIndexOrThrow("num"); + assertTrue(c.moveToFirst()); + for (long i = 0; i < 10; i++) { + assertTrue(c.isNull(numCol)); + c.moveToNext(); + } + c.close(); + } + + @MediumTest + public void testSimpleStringBinding() throws Exception { + mDatabase.execSQL("CREATE TABLE test (num TEXT, value TEXT);"); + String statement = "INSERT INTO test (num, value) VALUES (?,?)"; + + String[] args = new String[2]; + for (int i = 0; i < 2; i++) { + args[i] = Integer.toHexString(i); + } + + mDatabase.execSQL(statement, args); + + Cursor c = mDatabase.query("test", null, null, null, null, null, null); + int numCol = c.getColumnIndexOrThrow("num"); + int valCol = c.getColumnIndexOrThrow("value"); + c.moveToFirst(); + String num = c.getString(numCol); + assertEquals(Integer.toHexString(0), num); + + String val = c.getString(valCol); + assertEquals(Integer.toHexString(1), val); + c.close(); + } + + @MediumTest + public void testStatementMultipleBindings() throws Exception { + mDatabase.execSQL("CREATE TABLE test (num INTEGER, str TEXT);"); + SQLiteStatement statement = + mDatabase.compileStatement("INSERT INTO test (num, str) VALUES (?, ?)"); + + for (long i = 0; i < 10; i++) { + statement.bindLong(1, i); + statement.bindString(2, Long.toHexString(i)); + statement.execute(); + } + statement.close(); + + Cursor c = mDatabase.query("test", null, null, null, null, null, "ROWID"); + int numCol = c.getColumnIndexOrThrow("num"); + int strCol = c.getColumnIndexOrThrow("str"); + assertTrue(c.moveToFirst()); + for (long i = 0; i < 10; i++) { + long num = c.getLong(numCol); + String str = c.getString(strCol); + assertEquals(i, num); + assertEquals(Long.toHexString(i), str); + c.moveToNext(); + } + c.close(); + } + + private static class StatementTestThread extends Thread { + private SQLiteDatabase mDatabase; + private SQLiteStatement mStatement; + + public StatementTestThread(SQLiteDatabase db, SQLiteStatement statement) { + super(); + mDatabase = db; + mStatement = statement; + } + + @Override + public void run() { + mDatabase.beginTransaction(); + for (long i = 0; i < 10; i++) { + mStatement.bindLong(1, i); + mStatement.bindString(2, Long.toHexString(i)); + mStatement.execute(); + } + mDatabase.setTransactionSuccessful(); + mDatabase.endTransaction(); + + Cursor c = mDatabase.query("test", null, null, null, null, null, "ROWID"); + int numCol = c.getColumnIndexOrThrow("num"); + int strCol = c.getColumnIndexOrThrow("str"); + assertTrue(c.moveToFirst()); + for (long i = 0; i < 10; i++) { + long num = c.getLong(numCol); + String str = c.getString(strCol); + assertEquals(i, num); + assertEquals(Long.toHexString(i), str); + c.moveToNext(); + } + c.close(); + } + } + + @MediumTest + public void testStatementMultiThreaded() throws Exception { + mDatabase.execSQL("CREATE TABLE test (num INTEGER, str TEXT);"); + SQLiteStatement statement = + mDatabase.compileStatement("INSERT INTO test (num, str) VALUES (?, ?)"); + + StatementTestThread thread = new StatementTestThread(mDatabase, statement); + thread.start(); + try { + thread.join(); + } finally { + statement.close(); + } + } + + @MediumTest + public void testStatementConstraint() throws Exception { + mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL);"); + SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)"); + + // Try to insert NULL, which violates the constraint + try { + statement.clearBindings(); + statement.execute(); + fail("expected exception not thrown"); + } catch (SQLiteConstraintException e) { + // expected + } + + // Make sure the statement can still be used + statement.bindLong(1, 1); + statement.execute(); + statement.close(); + + Cursor c = mDatabase.query("test", null, null, null, null, null, null); + int numCol = c.getColumnIndexOrThrow("num"); + c.moveToFirst(); + long num = c.getLong(numCol); + assertEquals(1, num); + c.close(); + } +} diff --git a/core/tests/coretests/src/android/database/DatabaseStressTest.java b/core/tests/coretests/src/android/database/DatabaseStressTest.java new file mode 100644 index 0000000..30e46e7 --- /dev/null +++ b/core/tests/coretests/src/android/database/DatabaseStressTest.java @@ -0,0 +1,99 @@ +/* + * 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. + */ + +package android.database; + +import android.content.Context; +import android.database.sqlite.*; +import android.util.Log; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.Suppress; + +import java.io.File; + +// This test suite is too desctructive and takes too long to be included in the +// automated suite. +@Suppress +public class DatabaseStressTest extends AndroidTestCase { + private static final String TAG = "DatabaseStressTest"; + private static final int CURRENT_DATABASE_VERSION = 1; + private SQLiteDatabase mDatabase; + private File mDatabaseFile; + + @Override + protected void setUp() throws Exception { + super.setUp(); + Context c = getContext(); + + mDatabaseFile = c.getDatabasePath("database_test.db"); + if (mDatabaseFile.exists()) { + mDatabaseFile.delete(); + } + + mDatabase = c.openOrCreateDatabase("database_test.db", 0, null); + + assertNotNull(mDatabase); + mDatabase.setVersion(CURRENT_DATABASE_VERSION); + + mDatabase.execSQL("CREATE TABLE IF NOT EXISTS test (_id INTEGER PRIMARY KEY, data TEXT);"); + + } + + @Override + protected void tearDown() throws Exception { + mDatabase.close(); + mDatabaseFile.delete(); + super.tearDown(); + } + + public void testSingleThreadInsertDelete() { + int i = 0; + char[] ch = new char[100000]; + String str = new String(ch); + String[] strArr = new String[1]; + strArr[0] = str; + for (; i < 10000; ++i) { + try { + mDatabase.execSQL("INSERT INTO test (data) VALUES (?)", strArr); + mDatabase.execSQL("delete from test;"); + } catch (Exception e) { + Log.e(TAG, "exception " + e.getMessage()); + } + } + } + + /** + * use fillup -p 90 before run the test + * and when disk run out + * start delete some fillup files + * and see if db recover + */ + public void testOutOfSpace() { + int i = 0; + char[] ch = new char[100000]; + String str = new String(ch); + String[] strArr = new String[1]; + strArr[0] = str; + for (; i < 10000; ++i) { + try { + mDatabase.execSQL("INSERT INTO test (data) VALUES (?)", strArr); + } catch (Exception e) { + Log.e(TAG, "exception " + e.getMessage()); + } + } + } +} diff --git a/core/tests/coretests/src/android/net/LocalSocketTest.java b/core/tests/coretests/src/android/net/LocalSocketTest.java new file mode 100644 index 0000000..1349844 --- /dev/null +++ b/core/tests/coretests/src/android/net/LocalSocketTest.java @@ -0,0 +1,171 @@ +/* + * 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.net; + +import android.net.Credentials; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.test.MoreAsserts; +import android.test.suitebuilder.annotation.SmallTest; +import junit.framework.TestCase; + +import java.io.FileDescriptor; +import java.io.IOException; + +public class LocalSocketTest extends TestCase { + + @SmallTest + public void testBasic() throws Exception { + LocalServerSocket ss; + LocalSocket ls; + LocalSocket ls1; + + ss = new LocalServerSocket("android.net.LocalSocketTest"); + + ls = new LocalSocket(); + + ls.connect(new LocalSocketAddress("android.net.LocalSocketTest")); + + ls1 = ss.accept(); + + // Test trivial read and write + ls.getOutputStream().write(42); + + assertEquals(42, ls1.getInputStream().read()); + + // Test getting credentials + Credentials c = ls1.getPeerCredentials(); + + MoreAsserts.assertNotEqual(0, c.getPid()); + + // Test sending and receiving file descriptors + ls.setFileDescriptorsForSend( + new FileDescriptor[]{FileDescriptor.in}); + + ls.getOutputStream().write(42); + + assertEquals(42, ls1.getInputStream().read()); + + FileDescriptor[] out = ls1.getAncillaryFileDescriptors(); + + assertEquals(1, out.length); + + // Test multible byte write and available() + ls1.getOutputStream().write(new byte[]{0, 1, 2, 3, 4, 5}, 1, 5); + + assertEquals(1, ls.getInputStream().read()); + assertEquals(4, ls.getInputStream().available()); + + byte[] buffer = new byte[16]; + int countRead; + + countRead = ls.getInputStream().read(buffer, 1, 15); + + assertEquals(4, countRead); + assertEquals(2, buffer[1]); + assertEquals(3, buffer[2]); + assertEquals(4, buffer[3]); + assertEquals(5, buffer[4]); + + // Try various array-out-of-bound cases + try { + ls.getInputStream().read(buffer, 1, 16); + fail("expected exception"); + } catch (ArrayIndexOutOfBoundsException ex) { + // excpected + } + + try { + ls.getOutputStream().write(buffer, 1, 16); + fail("expected exception"); + } catch (ArrayIndexOutOfBoundsException ex) { + // excpected + } + + try { + ls.getOutputStream().write(buffer, -1, 15); + fail("expected exception"); + } catch (ArrayIndexOutOfBoundsException ex) { + // excpected + } + + try { + ls.getOutputStream().write(buffer, 0, -1); + fail("expected exception"); + } catch (ArrayIndexOutOfBoundsException ex) { + // excpected + } + + try { + ls.getInputStream().read(buffer, -1, 15); + fail("expected exception"); + } catch (ArrayIndexOutOfBoundsException ex) { + // excpected + } + + try { + ls.getInputStream().read(buffer, 0, -1); + fail("expected exception"); + } catch (ArrayIndexOutOfBoundsException ex) { + // excpected + } + + // Try read of length 0 + ls.getOutputStream().write(42); + countRead = ls1.getInputStream().read(buffer, 0, 0); + assertEquals(0, countRead); + assertEquals(42, ls1.getInputStream().read()); + + ss.close(); + + ls.close(); + + // Try write on closed socket + + try { + ls.getOutputStream().write(42); + fail("expected exception"); + } catch (IOException ex) { + // Expected + } + + // Try read on closed socket + + try { + ls.getInputStream().read(); + fail("expected exception"); + } catch (IOException ex) { + // Expected + } + + // Try write on socket whose peer has closed + + try { + ls1.getOutputStream().write(42); + fail("expected exception"); + } catch (IOException ex) { + // Expected + } + + // Try read on socket whose peer has closed + + assertEquals(-1, ls1.getInputStream().read()); + + ls1.close(); + } +} diff --git a/core/tests/coretests/src/android/net/SSLTest.java b/core/tests/coretests/src/android/net/SSLTest.java new file mode 100644 index 0000000..810ed0d --- /dev/null +++ b/core/tests/coretests/src/android/net/SSLTest.java @@ -0,0 +1,52 @@ +/* + * 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.net; + +import android.net.SSLCertificateSocketFactory; +import android.test.suitebuilder.annotation.Suppress; +import junit.framework.TestCase; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +//This test relies on network resources. +@Suppress +public class SSLTest extends TestCase { + public void testCertificate() throws Exception { + // test www.fortify.net/sslcheck.html + Socket ssl = SSLCertificateSocketFactory.getDefault().createSocket("www.fortify.net",443); + assertNotNull(ssl); + + OutputStream out = ssl.getOutputStream(); + assertNotNull(out); + + InputStream in = ssl.getInputStream(); + assertNotNull(in); + + String get = "GET /sslcheck.html HTTP/1.1\r\nHost: 68.178.217.222\r\n\r\n"; + + // System.out.println("going for write..."); + out.write(get.getBytes()); + + byte[] b = new byte[1024]; + // System.out.println("going for read..."); + int ret = in.read(b); + + // System.out.println(new String(b)); + } +} diff --git a/core/tests/coretests/src/android/net/UriMatcherTest.java b/core/tests/coretests/src/android/net/UriMatcherTest.java new file mode 100644 index 0000000..2872144 --- /dev/null +++ b/core/tests/coretests/src/android/net/UriMatcherTest.java @@ -0,0 +1,95 @@ +/* + * 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.net; + +import android.content.UriMatcher; +import android.net.Uri; +import android.test.suitebuilder.annotation.SmallTest; +import junit.framework.TestCase; + +public class UriMatcherTest extends TestCase +{ + static final int ROOT = 0; + static final int PEOPLE = 1; + static final int PEOPLE_ID = 2; + static final int PEOPLE_PHONES = 3; + static final int PEOPLE_PHONES_ID = 4; + static final int PEOPLE_ADDRESSES = 5; + static final int PEOPLE_ADDRESSES_ID = 6; + static final int PEOPLE_CONTACTMETH = 7; + static final int PEOPLE_CONTACTMETH_ID = 8; + static final int CALLS = 9; + static final int CALLS_ID = 10; + static final int CALLERID = 11; + static final int CALLERID_TEXT = 12; + static final int FILTERRECENT = 13; + + @SmallTest + public void testContentUris() { + check("content://asdf", UriMatcher.NO_MATCH); + check("content://people", PEOPLE); + check("content://people/1", PEOPLE_ID); + check("content://people/asdf", UriMatcher.NO_MATCH); + check("content://people/2/phones", PEOPLE_PHONES); + check("content://people/2/phones/3", PEOPLE_PHONES_ID); + check("content://people/2/phones/asdf", UriMatcher.NO_MATCH); + check("content://people/2/addresses", PEOPLE_ADDRESSES); + check("content://people/2/addresses/3", PEOPLE_ADDRESSES_ID); + check("content://people/2/addresses/asdf", UriMatcher.NO_MATCH); + check("content://people/2/contact-methods", PEOPLE_CONTACTMETH); + check("content://people/2/contact-methods/3", PEOPLE_CONTACTMETH_ID); + check("content://people/2/contact-methods/asdf", UriMatcher.NO_MATCH); + check("content://calls", CALLS); + check("content://calls/1", CALLS_ID); + check("content://calls/asdf", UriMatcher.NO_MATCH); + check("content://caller-id", CALLERID); + check("content://caller-id/asdf", CALLERID_TEXT); + check("content://caller-id/1", CALLERID_TEXT); + check("content://filter-recent", FILTERRECENT); + } + + private static final UriMatcher mURLMatcher = new UriMatcher(ROOT); + + static + { + mURLMatcher.addURI("people", null, PEOPLE); + mURLMatcher.addURI("people", "#", PEOPLE_ID); + mURLMatcher.addURI("people", "#/phones", PEOPLE_PHONES); + mURLMatcher.addURI("people", "#/phones/blah", PEOPLE_PHONES_ID); + mURLMatcher.addURI("people", "#/phones/#", PEOPLE_PHONES_ID); + mURLMatcher.addURI("people", "#/addresses", PEOPLE_ADDRESSES); + mURLMatcher.addURI("people", "#/addresses/#", PEOPLE_ADDRESSES_ID); + mURLMatcher.addURI("people", "#/contact-methods", PEOPLE_CONTACTMETH); + mURLMatcher.addURI("people", "#/contact-methods/#", PEOPLE_CONTACTMETH_ID); + mURLMatcher.addURI("calls", null, CALLS); + mURLMatcher.addURI("calls", "#", CALLS_ID); + mURLMatcher.addURI("caller-id", null, CALLERID); + mURLMatcher.addURI("caller-id", "*", CALLERID_TEXT); + mURLMatcher.addURI("filter-recent", null, FILTERRECENT); + } + + void check(String uri, int expected) + { + int result = mURLMatcher.match(Uri.parse(uri)); + if (result != expected) { + String msg = "failed on " + uri; + msg += " expected " + expected + " got " + result; + throw new RuntimeException(msg); + } + } +} + diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java new file mode 100644 index 0000000..ad71fcb --- /dev/null +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -0,0 +1,578 @@ +/* + * 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.net; + +import android.net.Uri; +import android.content.ContentUris; +import android.os.Parcel; +import android.test.suitebuilder.annotation.SmallTest; +import junit.framework.TestCase; + +import java.io.File; +import java.util.Arrays; + +public class UriTest extends TestCase { + + @SmallTest + public void testToStringWithPathOnly() { + Uri.Builder builder = new Uri.Builder(); + + // Not a valid path, but this came from a user's test case. + builder.path("//foo"); + Uri uri = builder.build(); + assertEquals("//foo", uri.toString()); + } + + @SmallTest + public void testParcelling() { + parcelAndUnparcel(Uri.parse("foo:bob%20lee")); + parcelAndUnparcel(Uri.fromParts("foo", "bob lee", "fragment")); + parcelAndUnparcel(new Uri.Builder() + .scheme("http") + .authority("crazybob.org") + .path("/rss/") + .encodedQuery("a=b") + .fragment("foo") + .build()); + } + + private void parcelAndUnparcel(Uri u) { + Parcel p = Parcel.obtain(); + try { + Uri.writeToParcel(p, u); + p.setDataPosition(0); + assertEquals(u, Uri.CREATOR.createFromParcel(p)); + + p.setDataPosition(0); + u = u.buildUpon().build(); + Uri.writeToParcel(p, u); + p.setDataPosition(0); + assertEquals(u, Uri.CREATOR.createFromParcel(p)); + } + finally { + p.recycle(); + } + } + + @SmallTest + public void testBuildUponOpaqueStringUri() { + Uri u = Uri.parse("bob:lee").buildUpon().scheme("robert").build(); + assertEquals("robert", u.getScheme()); + assertEquals("lee", u.getEncodedSchemeSpecificPart()); + assertEquals("lee", u.getSchemeSpecificPart()); + assertNull(u.getQuery()); + assertNull(u.getPath()); + assertNull(u.getAuthority()); + assertNull(u.getHost()); + } + + @SmallTest + public void testStringUri() { + assertEquals("bob lee", + Uri.parse("foo:bob%20lee").getSchemeSpecificPart()); + assertEquals("bob%20lee", + Uri.parse("foo:bob%20lee").getEncodedSchemeSpecificPart()); + assertEquals("/bob%20lee", + Uri.parse("foo:/bob%20lee").getEncodedPath()); + assertNull(Uri.parse("foo:bob%20lee").getPath()); + assertEquals("bob%20lee", + Uri.parse("foo:?bob%20lee").getEncodedQuery()); + assertNull(Uri.parse("foo:bar#?bob%20lee").getQuery()); + assertEquals("bob%20lee", + Uri.parse("foo:#bob%20lee").getEncodedFragment()); + } + + @SmallTest + public void testStringUriIsHierarchical() { + assertTrue(Uri.parse("bob").isHierarchical()); + assertFalse(Uri.parse("bob:").isHierarchical()); + } + + @SmallTest + public void testNullUriString() { + try { + Uri.parse(null); + fail(); + } catch (NullPointerException e) {} + } + + @SmallTest + public void testNullFile() { + try { + Uri.fromFile(null); + fail(); + } catch (NullPointerException e) {} + } + + @SmallTest + public void testCompareTo() { + Uri a = Uri.parse("foo:a"); + Uri b = Uri.parse("foo:b"); + Uri b2 = Uri.parse("foo:b"); + + assertTrue(a.compareTo(b) < 0); + assertTrue(b.compareTo(a) > 0); + assertEquals(0, b.compareTo(b2)); + } + + @SmallTest + public void testEqualsAndHashCode() { + + Uri a = Uri.parse("http://crazybob.org/test/?foo=bar#tee"); + + Uri b = new Uri.Builder() + .scheme("http") + .authority("crazybob.org") + .path("/test/") + .encodedQuery("foo=bar") + .fragment("tee") + .build(); + + // Try alternate builder methods. + Uri c = new Uri.Builder() + .scheme("http") + .encodedAuthority("crazybob.org") + .encodedPath("/test/") + .encodedQuery("foo=bar") + .encodedFragment("tee") + .build(); + + assertFalse(Uri.EMPTY.equals(null)); + + assertEquals(a, b); + assertEquals(b, c); + assertEquals(c, a); + + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b.hashCode(), c.hashCode()); + } + + @SmallTest + public void testAuthorityParsing() { + Uri uri = Uri.parse("http://localhost:42"); + assertEquals("localhost", uri.getHost()); + assertEquals(42, uri.getPort()); + + uri = Uri.parse("http://bob@localhost:42"); + assertEquals("bob", uri.getUserInfo()); + assertEquals("localhost", uri.getHost()); + assertEquals(42, uri.getPort()); + + uri = Uri.parse("http://bob%20lee@localhost:42"); + assertEquals("bob lee", uri.getUserInfo()); + assertEquals("bob%20lee", uri.getEncodedUserInfo()); + + uri = Uri.parse("http://bob%40lee%3ajr@local%68ost:4%32"); + assertEquals("bob@lee:jr", uri.getUserInfo()); + assertEquals("localhost", uri.getHost()); + assertEquals(42, uri.getPort()); + + uri = Uri.parse("http://localhost"); + assertEquals("localhost", uri.getHost()); + assertEquals(-1, uri.getPort()); + } + + @SmallTest + public void testBuildUponOpaqueUri() { + Uri a = Uri.fromParts("foo", "bar", "tee"); + Uri b = a.buildUpon().fragment("new").build(); + assertEquals("new", b.getFragment()); + assertEquals("bar", b.getSchemeSpecificPart()); + assertEquals("foo", b.getScheme()); + } + + @SmallTest + public void testBuildUponEncodedOpaqueUri() { + Uri a = new Uri.Builder() + .scheme("foo") + .encodedOpaquePart("bar") + .fragment("tee") + .build(); + Uri b = a.buildUpon().fragment("new").build(); + assertEquals("new", b.getFragment()); + assertEquals("bar", b.getSchemeSpecificPart()); + assertEquals("foo", b.getScheme()); + } + + @SmallTest + public void testPathSegmentDecoding() { + Uri uri = Uri.parse("foo://bar/a%20a/b%20b"); + assertEquals("a a", uri.getPathSegments().get(0)); + assertEquals("b b", uri.getPathSegments().get(1)); + } + + @SmallTest + public void testSms() { + Uri base = Uri.parse("content://sms"); + Uri appended = base.buildUpon() + .appendEncodedPath("conversations/addr=555-1212") + .build(); + assertEquals("content://sms/conversations/addr=555-1212", + appended.toString()); + assertEquals(2, appended.getPathSegments().size()); + assertEquals("conversations", appended.getPathSegments().get(0)); + assertEquals("addr=555-1212", appended.getPathSegments().get(1)); + } + + @SmallTest + public void testEncodeWithAllowedChars() { + String encoded = Uri.encode("Bob:/", "/"); + assertEquals(-1, encoded.indexOf(':')); + assertTrue(encoded.indexOf('/') > -1); + } + + @SmallTest + public void testEncodeDecode() { + code(null); + code(""); + code("Bob"); + code(":Bob"); + code("::Bob"); + code("Bob::Lee"); + code("Bob:Lee"); + code("Bob::"); + code("Bob:"); + code("::Bob::"); + } + + private void code(String s) { + assertEquals(s, Uri.decode(Uri.encode(s, null))); + } + + @SmallTest + public void testFile() { + File f = new File("/tmp/bob"); + + Uri uri = Uri.fromFile(f); + + assertEquals("file:///tmp/bob", uri.toString()); + } + + @SmallTest + public void testQueryParameters() { + Uri uri = Uri.parse("content://user"); + + assertEquals(null, uri.getQueryParameter("a")); + + uri = uri.buildUpon().appendQueryParameter("a", "b").build(); + + assertEquals("b", uri.getQueryParameter("a")); + + uri = uri.buildUpon().appendQueryParameter("a", "b2").build(); + + assertEquals(Arrays.asList("b", "b2"), uri.getQueryParameters("a")); + + uri = uri.buildUpon().appendQueryParameter("c", "d").build(); + + assertEquals(Arrays.asList("b", "b2"), uri.getQueryParameters("a")); + assertEquals("d", uri.getQueryParameter("c")); + } + + @SmallTest + public void testSchemeOnly() { + Uri uri = Uri.parse("empty:"); + assertEquals("empty", uri.getScheme()); + assertTrue(uri.isAbsolute()); + assertNull(uri.getPath()); + } + + @SmallTest + public void testEmptyPath() { + Uri uri = Uri.parse("content://user"); + assertEquals(0, uri.getPathSegments().size()); + } + + @SmallTest + public void testPathOperations() { + Uri uri = Uri.parse("content://user/a/b"); + + assertEquals(2, uri.getPathSegments().size()); + assertEquals("b", uri.getLastPathSegment()); + + Uri first = uri; + uri = uri.buildUpon().appendPath("c").build(); + + assertEquals(3, uri.getPathSegments().size()); + assertEquals("c", uri.getLastPathSegment()); + assertEquals("content://user/a/b/c", uri.toString()); + + uri = ContentUris.withAppendedId(uri, 100); + + assertEquals(4, uri.getPathSegments().size()); + assertEquals("100", uri.getLastPathSegment()); + assertEquals(100, ContentUris.parseId(uri)); + assertEquals("content://user/a/b/c/100", uri.toString()); + + // Make sure the original URI is still intact. + assertEquals(2, first.getPathSegments().size()); + assertEquals("b", first.getLastPathSegment()); + + try { + first.getPathSegments().get(2); + fail(); + } catch (IndexOutOfBoundsException e) {} + + assertEquals(null, Uri.EMPTY.getLastPathSegment()); + + Uri withC = Uri.parse("foo:/a/b/").buildUpon().appendPath("c").build(); + assertEquals("/a/b/c", withC.getPath()); + } + + @SmallTest + public void testOpaqueUri() { + Uri uri = Uri.parse("mailto:nobody"); + testOpaqueUri(uri); + + uri = uri.buildUpon().build(); + testOpaqueUri(uri); + + uri = Uri.fromParts("mailto", "nobody", null); + testOpaqueUri(uri); + + uri = uri.buildUpon().build(); + testOpaqueUri(uri); + + uri = new Uri.Builder() + .scheme("mailto") + .opaquePart("nobody") + .build(); + testOpaqueUri(uri); + + uri = uri.buildUpon().build(); + testOpaqueUri(uri); + } + + private void testOpaqueUri(Uri uri) { + assertEquals("mailto", uri.getScheme()); + assertEquals("nobody", uri.getSchemeSpecificPart()); + assertEquals("nobody", uri.getEncodedSchemeSpecificPart()); + + assertNull(uri.getFragment()); + assertTrue(uri.isAbsolute()); + assertTrue(uri.isOpaque()); + assertFalse(uri.isRelative()); + assertFalse(uri.isHierarchical()); + + assertNull(uri.getAuthority()); + assertNull(uri.getEncodedAuthority()); + assertNull(uri.getPath()); + assertNull(uri.getEncodedPath()); + assertNull(uri.getUserInfo()); + assertNull(uri.getEncodedUserInfo()); + assertNull(uri.getQuery()); + assertNull(uri.getEncodedQuery()); + assertNull(uri.getHost()); + assertEquals(-1, uri.getPort()); + + assertTrue(uri.getPathSegments().isEmpty()); + assertNull(uri.getLastPathSegment()); + + assertEquals("mailto:nobody", uri.toString()); + + Uri withFragment = uri.buildUpon().fragment("top").build(); + assertEquals("mailto:nobody#top", withFragment.toString()); + } + + @SmallTest + public void testHierarchicalUris() { + testHierarchical("http", "google.com", "/p1/p2", "query", "fragment"); + testHierarchical("file", null, "/p1/p2", null, null); + testHierarchical("content", "contact", "/p1/p2", null, null); + testHierarchical("http", "google.com", "/p1/p2", null, "fragment"); + testHierarchical("http", "google.com", "", null, "fragment"); + testHierarchical("http", "google.com", "", "query", "fragment"); + testHierarchical("http", "google.com", "", "query", null); + testHierarchical("http", null, "/", "query", null); + } + + private static void testHierarchical(String scheme, String authority, + String path, String query, String fragment) { + StringBuilder sb = new StringBuilder(); + + if (authority != null) { + sb.append("//").append(authority); + } + if (path != null) { + sb.append(path); + } + if (query != null) { + sb.append('?').append(query); + } + + String ssp = sb.toString(); + + if (scheme != null) { + sb.insert(0, scheme + ":"); + } + if (fragment != null) { + sb.append('#').append(fragment); + } + + String uriString = sb.toString(); + + Uri uri = Uri.parse(uriString); + + // Run these twice to test caching. + compareHierarchical( + uriString, ssp, uri, scheme, authority, path, query, fragment); + compareHierarchical( + uriString, ssp, uri, scheme, authority, path, query, fragment); + + // Test rebuilt version. + uri = uri.buildUpon().build(); + + // Run these twice to test caching. + compareHierarchical( + uriString, ssp, uri, scheme, authority, path, query, fragment); + compareHierarchical( + uriString, ssp, uri, scheme, authority, path, query, fragment); + + // The decoded and encoded versions of the inputs are all the same. + // We'll test the actual encoding decoding separately. + + // Test building with encoded versions. + Uri built = new Uri.Builder() + .scheme(scheme) + .encodedAuthority(authority) + .encodedPath(path) + .encodedQuery(query) + .encodedFragment(fragment) + .build(); + + compareHierarchical( + uriString, ssp, built, scheme, authority, path, query, fragment); + compareHierarchical( + uriString, ssp, built, scheme, authority, path, query, fragment); + + // Test building with decoded versions. + built = new Uri.Builder() + .scheme(scheme) + .authority(authority) + .path(path) + .query(query) + .fragment(fragment) + .build(); + + compareHierarchical( + uriString, ssp, built, scheme, authority, path, query, fragment); + compareHierarchical( + uriString, ssp, built, scheme, authority, path, query, fragment); + + // Rebuild. + built = built.buildUpon().build(); + + compareHierarchical( + uriString, ssp, built, scheme, authority, path, query, fragment); + compareHierarchical( + uriString, ssp, built, scheme, authority, path, query, fragment); + } + + private static void compareHierarchical(String uriString, String ssp, + Uri uri, + String scheme, String authority, String path, String query, + String fragment) { + assertEquals(scheme, uri.getScheme()); + assertEquals(authority, uri.getAuthority()); + assertEquals(authority, uri.getEncodedAuthority()); + assertEquals(path, uri.getPath()); + assertEquals(path, uri.getEncodedPath()); + assertEquals(query, uri.getQuery()); + assertEquals(query, uri.getEncodedQuery()); + assertEquals(fragment, uri.getFragment()); + assertEquals(fragment, uri.getEncodedFragment()); + assertEquals(ssp, uri.getSchemeSpecificPart()); + + if (scheme != null) { + assertTrue(uri.isAbsolute()); + assertFalse(uri.isRelative()); + } else { + assertFalse(uri.isAbsolute()); + assertTrue(uri.isRelative()); + } + + assertFalse(uri.isOpaque()); + assertTrue(uri.isHierarchical()); + + assertEquals(uriString, uri.toString()); + } + + public void testEmptyToStringNotNull() { + assertNotNull(Uri.EMPTY.toString()); + } + + @SmallTest + public void testParcellingWithoutFragment() { + parcelAndUnparcel(Uri.parse("foo:bob%20lee")); + parcelAndUnparcel(Uri.fromParts("foo", "bob lee", "fragment")); + parcelAndUnparcel(new Uri.Builder() + .scheme("http") + .authority("crazybob.org") + .path("/rss/") + .encodedQuery("a=b") + .build()); + } + + public void testGetQueryParameter() { + String nestedUrl = "http://crazybob.org/?a=1&b=2"; + Uri uri = Uri.parse("http://test/").buildUpon() + .appendQueryParameter("foo", "bar") + .appendQueryParameter("nested", nestedUrl).build(); + assertEquals(nestedUrl, uri.getQueryParameter("nested")); + assertEquals(nestedUrl, uri.getQueryParameters("nested").get(0)); + } + + public void testGetQueryParameterWorkaround() { + // This was a workaround for a bug where getQueryParameter called + // getQuery() instead of getEncodedQuery(). + String nestedUrl = "http://crazybob.org/?a=1&b=2"; + Uri uri = Uri.parse("http://test/").buildUpon() + .appendQueryParameter("foo", "bar") + .appendQueryParameter("nested", Uri.encode(nestedUrl)).build(); + assertEquals(nestedUrl, Uri.decode(uri.getQueryParameter("nested"))); + assertEquals(nestedUrl, + Uri.decode(uri.getQueryParameters("nested").get(0))); + } + + public void testGetQueryParameterEdgeCases() { + Uri uri; + + // key at beginning of URL + uri = Uri.parse("http://test/").buildUpon() + .appendQueryParameter("key", "a b") + .appendQueryParameter("keya", "c d") + .appendQueryParameter("bkey", "e f") + .build(); + assertEquals("a b", uri.getQueryParameter("key")); + + // key in middle of URL + uri = Uri.parse("http://test/").buildUpon() + .appendQueryParameter("akeyb", "a b") + .appendQueryParameter("keya", "c d") + .appendQueryParameter("key", "e f") + .appendQueryParameter("bkey", "g h") + .build(); + assertEquals("e f", uri.getQueryParameter("key")); + + // key at end of URL + uri = Uri.parse("http://test/").buildUpon() + .appendQueryParameter("akeyb", "a b") + .appendQueryParameter("keya", "c d") + .appendQueryParameter("key", "y z") + .build(); + assertEquals("y z", uri.getQueryParameter("key")); + } +} diff --git a/core/tests/coretests/src/android/os/AidlTest.aidl b/core/tests/coretests/src/android/os/AidlTest.aidl new file mode 100644 index 0000000..6004f4b --- /dev/null +++ b/core/tests/coretests/src/android/os/AidlTest.aidl @@ -0,0 +1,20 @@ +/* //device/apps/AndroidTests/src/com.android.unit_tests/AidlTest.aidl +** +** Copyright 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.os; + +parcelable AidlTest.TestParcelable; diff --git a/core/tests/coretests/src/android/os/AidlTest.java b/core/tests/coretests/src/android/os/AidlTest.java new file mode 100644 index 0000000..bf11d56 --- /dev/null +++ b/core/tests/coretests/src/android/os/AidlTest.java @@ -0,0 +1,422 @@ +/* + * 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.os; + +import android.os.IInterface; +import android.os.Parcel; +import android.os.Parcelable; +import android.test.suitebuilder.annotation.SmallTest; +import com.google.android.collect.Lists; +import junit.framework.TestCase; + +import java.util.List; + +public class AidlTest extends TestCase { + + private IAidlTest mRemote; + + @Override + protected void setUp() throws Exception { + super.setUp(); + AidlObject mLocal = new AidlObject(); + mRemote = IAidlTest.Stub.asInterface(mLocal); + } + + private static boolean check(TestParcelable p, int n, String s) { + return p.mAnInt == n && + ((s == null && p.mAString == null) || s.equals(p.mAString)); + } + + public static class TestParcelable implements Parcelable { + public int mAnInt; + public String mAString; + + public TestParcelable() { + } + + public TestParcelable(int i, String s) { + mAnInt = i; + mAString = s; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mAnInt); + parcel.writeString(mAString); + } + + public void readFromParcel(Parcel parcel) { + mAnInt = parcel.readInt(); + mAString = parcel.readString(); + } + + public static final Parcelable.Creator<TestParcelable> CREATOR + = new Parcelable.Creator<TestParcelable>() { + public TestParcelable createFromParcel(Parcel parcel) { + return new TestParcelable(parcel.readInt(), + parcel.readString()); + } + + public TestParcelable[] newArray(int size) { + return new TestParcelable[size]; + } + }; + + public String toString() { + return super.toString() + " {" + mAnInt + "/" + mAString + "}"; + } + } + + private static class AidlObject extends IAidlTest.Stub { + public IInterface queryLocalInterface(String descriptor) { + // overriding this to return null makes asInterface always + // generate a proxy + return null; + } + + public int intMethod(int a) { + return a; + } + + public TestParcelable parcelableIn(TestParcelable p) { + p.mAnInt++; + return p; + } + + public TestParcelable parcelableOut(TestParcelable p) { + p.mAnInt = 44; + return p; + } + + public TestParcelable parcelableInOut(TestParcelable p) { + p.mAnInt++; + return p; + } + + public TestParcelable listParcelableLonger(List<TestParcelable> list, int index) { + list.add(list.get(index)); + return list.get(index); + } + + public int listParcelableShorter(List<TestParcelable> list, int index) { + list.remove(index); + return list.size(); + } + + public boolean[] booleanArray(boolean[] a0, boolean[] a1, boolean[] a2) { + for (int i = 0; i < a0.length && i < a2.length; i++) { + a2[i] = a0[i]; + } + for (int i = 0; i < a0.length && i < a1.length; i++) { + a1[i] = a0[i]; + } + return a0; + } + + public char[] charArray(char[] a0, char[] a1, char[] a2) { + for (int i = 0; i < a0.length && i < a2.length; i++) { + a2[i] = a0[i]; + } + for (int i = 0; i < a0.length && i < a1.length; i++) { + a1[i] = a0[i]; + } + return a0; + } + + public int[] intArray(int[] a0, int[] a1, int[] a2) { + for (int i = 0; i < a0.length && i < a2.length; i++) { + a2[i] = a0[i]; + } + for (int i = 0; i < a0.length && i < a1.length; i++) { + a1[i] = a0[i]; + } + return a0; + } + + public long[] longArray(long[] a0, long[] a1, long[] a2) { + for (int i = 0; i < a0.length && i < a2.length; i++) { + a2[i] = a0[i]; + } + for (int i = 0; i < a0.length && i < a1.length; i++) { + a1[i] = a0[i]; + } + return a0; + } + + public float[] floatArray(float[] a0, float[] a1, float[] a2) { + for (int i = 0; i < a0.length && i < a2.length; i++) { + a2[i] = a0[i]; + } + for (int i = 0; i < a0.length && i < a1.length; i++) { + a1[i] = a0[i]; + } + return a0; + } + + public double[] doubleArray(double[] a0, double[] a1, double[] a2) { + for (int i = 0; i < a0.length && i < a2.length; i++) { + a2[i] = a0[i]; + } + for (int i = 0; i < a0.length && i < a1.length; i++) { + a1[i] = a0[i]; + } + return a0; + } + + public String[] stringArray(String[] a0, String[] a1, String[] a2) { + for (int i = 0; i < a0.length && i < a2.length; i++) { + a2[i] = a0[i]; + } + for (int i = 0; i < a0.length && i < a1.length; i++) { + a1[i] = a0[i]; + } + return a0; + } + + public TestParcelable[] parcelableArray(TestParcelable[] a0, + TestParcelable[] a1, TestParcelable[] a2) { + return null; + } + + public void voidSecurityException() { + throw new SecurityException("gotcha!"); + } + + public int intSecurityException() { + throw new SecurityException("gotcha!"); + } + } + + @SmallTest + public void testInt() throws Exception { + int result = mRemote.intMethod(42); + assertEquals(42, result); + } + + @SmallTest + public void testParcelableIn() throws Exception { + TestParcelable arg = new TestParcelable(43, "hi"); + TestParcelable result = mRemote.parcelableIn(arg); + assertNotSame(arg, result); + + assertEquals(43, arg.mAnInt); + assertEquals(44, result.mAnInt); + } + + @SmallTest + public void testParcelableOut() throws Exception { + TestParcelable arg = new TestParcelable(43, "hi"); + TestParcelable result = mRemote.parcelableOut(arg); + assertNotSame(arg, result); + assertEquals(44, arg.mAnInt); + } + + @SmallTest + public void testParcelableInOut() throws Exception { + TestParcelable arg = new TestParcelable(43, "hi"); + TestParcelable result = mRemote.parcelableInOut(arg); + assertNotSame(arg, result); + assertEquals(44, arg.mAnInt); + } + + @SmallTest + public void testListParcelableLonger() throws Exception { + List<TestParcelable> list = Lists.newArrayList(); + list.add(new TestParcelable(33, "asdf")); + list.add(new TestParcelable(34, "jkl;")); + + TestParcelable result = mRemote.listParcelableLonger(list, 1); + +// System.out.println("result=" + result); +// for (TestParcelable p : list) { +// System.out.println("longer: " + p); +// } + + assertEquals("jkl;", result.mAString); + assertEquals(34, result.mAnInt); + + assertEquals(3, list.size()); + assertTrue("out parameter 0: " + list.get(0), check(list.get(0), 33, "asdf")); + assertTrue("out parameter 1: " + list.get(1), check(list.get(1), 34, "jkl;")); + assertTrue("out parameter 2: " + list.get(2), check(list.get(2), 34, "jkl;")); + + assertNotSame(list.get(1), list.get(2)); + } + + @SmallTest + public void testListParcelableShorter() throws Exception { + List<TestParcelable> list = Lists.newArrayList(); + list.add(new TestParcelable(33, "asdf")); + list.add(new TestParcelable(34, "jkl;")); + list.add(new TestParcelable(35, "qwerty")); + + int result = mRemote.listParcelableShorter(list, 2); + +// System.out.println("result=" + result); +// for (TestParcelable p : list) { +// System.out.println("shorter: " + p); +// } + + assertEquals(2, result); + assertEquals(2, list.size()); + assertTrue("out parameter 0: " + list.get(0), check(list.get(0), 33, "asdf")); + assertTrue("out parameter 1: " + list.get(1), check(list.get(1), 34, "jkl;")); + + assertNotSame(list.get(0), list.get(1)); + } + + @SmallTest + public void testArrays() throws Exception { + // boolean + boolean[] b0 = new boolean[]{true}; + boolean[] b1 = new boolean[]{false, true}; + boolean[] b2 = new boolean[]{true, false, true}; + boolean[] br = mRemote.booleanArray(b0, b1, b2); + + assertEquals(1, br.length); + assertTrue(br[0]); + + assertTrue(b1[0]); + assertFalse(b1[1]); + + assertTrue(b2[0]); + assertFalse(b2[1]); + assertTrue(b2[2]); + + // char + char[] c0 = new char[]{'a'}; + char[] c1 = new char[]{'b', 'c'}; + char[] c2 = new char[]{'d', 'e', 'f'}; + char[] cr = mRemote.charArray(c0, c1, c2); + + assertEquals(1, cr.length); + assertEquals('a', cr[0]); + + assertEquals('a', c1[0]); + assertEquals('\0', c1[1]); + + assertEquals('a', c2[0]); + assertEquals('e', c2[1]); + assertEquals('f', c2[2]); + + // int + int[] i0 = new int[]{34}; + int[] i1 = new int[]{38, 39}; + int[] i2 = new int[]{42, 43, 44}; + int[] ir = mRemote.intArray(i0, i1, i2); + + assertEquals(1, ir.length); + assertEquals(34, ir[0]); + + assertEquals(34, i1[0]); + assertEquals(0, i1[1]); + + assertEquals(34, i2[0]); + assertEquals(43, i2[1]); + assertEquals(44, i2[2]); + + // long + long[] l0 = new long[]{50}; + long[] l1 = new long[]{51, 52}; + long[] l2 = new long[]{53, 54, 55}; + long[] lr = mRemote.longArray(l0, l1, l2); + + assertEquals(1, lr.length); + assertEquals(50, lr[0]); + + assertEquals(50, l1[0]); + assertEquals(0, l1[1]); + + assertEquals(50, l2[0]); + assertEquals(54, l2[1]); + assertEquals(55, l2[2]); + + // float + float[] f0 = new float[]{90.1f}; + float[] f1 = new float[]{90.2f, 90.3f}; + float[] f2 = new float[]{90.4f, 90.5f, 90.6f}; + float[] fr = mRemote.floatArray(f0, f1, f2); + + assertEquals(1, fr.length); + assertEquals(90.1f, fr[0]); + + assertEquals(90.1f, f1[0]); + assertEquals(0f, f1[1], 0.0f); + + assertEquals(90.1f, f2[0]); + assertEquals(90.5f, f2[1]); + assertEquals(90.6f, f2[2]); + + // double + double[] d0 = new double[]{100.1}; + double[] d1 = new double[]{100.2, 100.3}; + double[] d2 = new double[]{100.4, 100.5, 100.6}; + double[] dr = mRemote.doubleArray(d0, d1, d2); + + assertEquals(1, dr.length); + assertEquals(100.1, dr[0]); + + assertEquals(100.1, d1[0]); + assertEquals(0, d1[1], 0.0); + + assertEquals(100.1, d2[0]); + assertEquals(100.5, d2[1]); + assertEquals(100.6, d2[2]); + + // String + String[] s0 = new String[]{"s0[0]"}; + String[] s1 = new String[]{"s1[0]", "s1[1]"}; + String[] s2 = new String[]{"s2[0]", "s2[1]", "s2[2]"}; + String[] sr = mRemote.stringArray(s0, s1, s2); + + assertEquals(1, sr.length); + assertEquals("s0[0]", sr[0]); + + assertEquals("s0[0]", s1[0]); + assertNull(s1[1]); + + assertEquals("s0[0]", s2[0]); + assertEquals("s2[1]", s2[1]); + assertEquals("s2[2]", s2[2]); + } + + @SmallTest + public void testVoidSecurityException() throws Exception { + boolean good = false; + try { + mRemote.voidSecurityException(); + } catch (SecurityException e) { + good = true; + } + assertEquals(good, true); + } + + @SmallTest + public void testIntSecurityException() throws Exception { + boolean good = false; + try { + mRemote.intSecurityException(); + } catch (SecurityException e) { + good = true; + } + assertEquals(good, true); + } +} + diff --git a/core/tests/coretests/src/android/os/BroadcasterTest.java b/core/tests/coretests/src/android/os/BroadcasterTest.java new file mode 100644 index 0000000..551ea8d --- /dev/null +++ b/core/tests/coretests/src/android/os/BroadcasterTest.java @@ -0,0 +1,232 @@ +/* + * 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.os; + +import android.os.Broadcaster; +import android.os.Handler; +import android.os.Message; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import junit.framework.TestCase; + +public class BroadcasterTest extends TestCase { + private static final int MESSAGE_A = 23234; + private static final int MESSAGE_B = 3; + private static final int MESSAGE_C = 14; + private static final int MESSAGE_D = 95; + + @MediumTest + public void test1() throws Exception { + /* + * One handler requestes one message, with a translation + */ + HandlerTester tester = new HandlerTester() { + Handler h; + + public void go() { + Broadcaster b = new Broadcaster(); + h = new H(); + + b.request(MESSAGE_A, h, MESSAGE_B); + + Message msg = new Message(); + msg.what = MESSAGE_A; + + b.broadcast(msg); + } + + public void handleMessage(Message msg) { + if (msg.what == MESSAGE_B) { + success(); + } else { + failure(); + } + } + }; + tester.doTest(1000); + } + + private static class Tests2and3 extends HandlerTester { + Tests2and3(int n) { + N = n; + } + + int N; + Handler mHandlers[]; + boolean mSuccess[]; + + public void go() { + Broadcaster b = new Broadcaster(); + mHandlers = new Handler[N]; + mSuccess = new boolean[N]; + for (int i = 0; i < N; i++) { + mHandlers[i] = new H(); + mSuccess[i] = false; + b.request(MESSAGE_A, mHandlers[i], MESSAGE_B + i); + } + + Message msg = new Message(); + msg.what = MESSAGE_A; + + b.broadcast(msg); + } + + public void handleMessage(Message msg) { + int index = msg.what - MESSAGE_B; + if (index < 0 || index >= N) { + failure(); + } else { + if (msg.getTarget() == mHandlers[index]) { + mSuccess[index] = true; + } + } + boolean winner = true; + for (int i = 0; i < N; i++) { + if (!mSuccess[i]) { + winner = false; + } + } + if (winner) { + success(); + } + } + } + + @MediumTest + public void test2() throws Exception { + /* + * 2 handlers request the same message, with different translations + */ + HandlerTester tester = new Tests2and3(2); + tester.doTest(1000); + } + + @MediumTest + public void test3() throws Exception { + /* + * 1000 handlers request the same message, with different translations + */ + HandlerTester tester = new Tests2and3(10); + tester.doTest(1000); + } + + @MediumTest + public void test4() throws Exception { + /* + * Two handlers request different messages, with translations, sending + * only one. The other one should never get sent. + */ + HandlerTester tester = new HandlerTester() { + Handler h1; + Handler h2; + + public void go() { + Broadcaster b = new Broadcaster(); + h1 = new H(); + h2 = new H(); + + b.request(MESSAGE_A, h1, MESSAGE_C); + b.request(MESSAGE_B, h2, MESSAGE_D); + + Message msg = new Message(); + msg.what = MESSAGE_A; + + b.broadcast(msg); + } + + public void handleMessage(Message msg) { + if (msg.what == MESSAGE_C && msg.getTarget() == h1) { + success(); + } else { + failure(); + } + } + }; + tester.doTest(1000); + } + + @MediumTest + public void test5() throws Exception { + /* + * Two handlers request different messages, with translations, sending + * only one. The other one should never get sent. + */ + HandlerTester tester = new HandlerTester() { + Handler h1; + Handler h2; + + public void go() { + Broadcaster b = new Broadcaster(); + h1 = new H(); + h2 = new H(); + + b.request(MESSAGE_A, h1, MESSAGE_C); + b.request(MESSAGE_B, h2, MESSAGE_D); + + Message msg = new Message(); + msg.what = MESSAGE_B; + + b.broadcast(msg); + } + + public void handleMessage(Message msg) { + if (msg.what == MESSAGE_D && msg.getTarget() == h2) { + success(); + } else { + failure(); + } + } + }; + tester.doTest(1000); + } + + @MediumTest + public void test6() throws Exception { + /* + * Two handlers request same message. Cancel the request for the + * 2nd handler, make sure the first still works. + */ + HandlerTester tester = new HandlerTester() { + Handler h1; + Handler h2; + + public void go() { + Broadcaster b = new Broadcaster(); + h1 = new H(); + h2 = new H(); + + b.request(MESSAGE_A, h1, MESSAGE_C); + b.request(MESSAGE_A, h2, MESSAGE_D); + b.cancelRequest(MESSAGE_A, h2, MESSAGE_D); + + Message msg = new Message(); + msg.what = MESSAGE_A; + + b.broadcast(msg); + } + + public void handleMessage(Message msg) { + if (msg.what == MESSAGE_C && msg.getTarget() == h1) { + success(); + } else { + failure(); + } + } + }; + tester.doTest(1000); + } +} diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java new file mode 100644 index 0000000..3758627 --- /dev/null +++ b/core/tests/coretests/src/android/os/BuildTest.java @@ -0,0 +1,76 @@ +/* + * 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. + */ + +package android.os; + +import android.os.Build; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; +import junit.framework.Assert; +import junit.framework.TestCase; + +/** + * Provides test cases for android.os.Build and, in turn, many of the + * system properties set by the build system. + */ +public class BuildTest extends TestCase { + + private static final String TAG = "BuildTest"; + + /** + * Asserts that a String is non-null and non-empty. If it is not, + * an AssertionFailedError is thrown with the given message. + */ + private static void assertNotEmpty(String message, String string) { + //Log.i(TAG, "" + message + ": " + string); + assertNotNull(message, string); + assertFalse(message, string.equals("")); + } + + /** + * Asserts that a String is non-null and non-empty. If it is not, + * an AssertionFailedError is thrown. + */ + private static void assertNotEmpty(String string) { + assertNotEmpty(null, string); + } + + /** + * Asserts that all android.os.Build fields are non-empty and/or in a valid range. + */ + @SmallTest + public void testBuildFields() throws Exception { + assertNotEmpty("ID", Build.ID); + assertNotEmpty("DISPLAY", Build.DISPLAY); + assertNotEmpty("PRODUCT", Build.PRODUCT); + assertNotEmpty("DEVICE", Build.DEVICE); + assertNotEmpty("BOARD", Build.BOARD); + assertNotEmpty("BRAND", Build.BRAND); + assertNotEmpty("MODEL", Build.MODEL); + assertNotEmpty("VERSION.INCREMENTAL", Build.VERSION.INCREMENTAL); + assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE); + assertNotEmpty("TYPE", Build.TYPE); + Assert.assertNotNull("TAGS", Build.TAGS); // TAGS is allowed to be empty. + assertNotEmpty("FINGERPRINT", Build.FINGERPRINT); + Assert.assertTrue("TIME", Build.TIME > 0); + assertNotEmpty("USER", Build.USER); + assertNotEmpty("HOST", Build.HOST); + + // TODO: if any of the android.os.Build fields have additional constraints + // (e.g., must be a C identifier, must be a valid filename, must not contain any spaces) + // add tests for them. + } +} diff --git a/core/tests/coretests/src/android/os/FileObserverTest.java b/core/tests/coretests/src/android/os/FileObserverTest.java new file mode 100644 index 0000000..ca4e0d6 --- /dev/null +++ b/core/tests/coretests/src/android/os/FileObserverTest.java @@ -0,0 +1,152 @@ +/* + * 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.os; + +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + +import android.os.FileObserver; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class FileObserverTest extends AndroidTestCase { + private Observer mObserver; + private File mTestFile; + + private static class Observer extends FileObserver { + public List<Map> events = Lists.newArrayList(); + public int totalEvents = 0; + + public Observer(String path) { + super(path); + } + + public void onEvent(int event, String path) { + synchronized (this) { + totalEvents++; + Map<String, Object> map = Maps.newHashMap(); + + map.put("event", event); + map.put("path", path); + + events.add(map); + + this.notifyAll(); + } + } + } + + @Override + protected void setUp() throws Exception { + mTestFile = File.createTempFile(".file_observer_test", ".txt"); + } + + @Override + protected void tearDown() throws Exception { + if (mTestFile != null && mTestFile.exists()) { + mTestFile.delete(); + } + } + + @LargeTest + public void testRun() throws Exception { + // make file changes and wait for them + assertTrue(mTestFile.exists()); + assertNotNull(mTestFile.getParent()); + + mObserver = new Observer(mTestFile.getParent()); + mObserver.startWatching(); + + FileOutputStream out = new FileOutputStream(mTestFile); + try { + out.write(0x20); + waitForEvent(); // open + waitForEvent(); // modify + + mTestFile.delete(); + waitForEvent(); // modify + waitForEvent(); // delete + + mObserver.stopWatching(); + + // Ensure that we have seen at least 3 events. + assertTrue(mObserver.totalEvents > 3); + } finally { + out.close(); + } + } + + private void waitForEvent() { + synchronized (mObserver) { + boolean done = false; + while (!done) { + try { + mObserver.wait(2000); + done = true; + } catch (InterruptedException e) { + } + } + + Iterator<Map> it = mObserver.events.iterator(); + + while (it.hasNext()) { + Map map = it.next(); + Log.i("FileObserverTest", "event: " + getEventString((Integer)map.get("event")) + " path: " + map.get("path")); + } + + mObserver.events.clear(); + } + } + + private String getEventString(int event) { + switch (event) { + case FileObserver.ACCESS: + return "ACCESS"; + case FileObserver.MODIFY: + return "MODIFY"; + case FileObserver.ATTRIB: + return "ATTRIB"; + case FileObserver.CLOSE_WRITE: + return "CLOSE_WRITE"; + case FileObserver.CLOSE_NOWRITE: + return "CLOSE_NOWRITE"; + case FileObserver.OPEN: + return "OPEN"; + case FileObserver.MOVED_FROM: + return "MOVED_FROM"; + case FileObserver.MOVED_TO: + return "MOVED_TO"; + case FileObserver.CREATE: + return "CREATE"; + case FileObserver.DELETE: + return "DELETE"; + case FileObserver.DELETE_SELF: + return "DELETE_SELF"; + case FileObserver.MOVE_SELF: + return "MOVE_SELF"; + default: + return "UNKNOWN"; + } + } +} diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java new file mode 100644 index 0000000..f12cbe1 --- /dev/null +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -0,0 +1,171 @@ +/* + * 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. + */ + +package android.os; + +import android.content.Context; +import android.os.FileUtils; +import android.os.FileUtils.FileStatus; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +import junit.framework.Assert; + +public class FileUtilsTest extends AndroidTestCase { + private static final String TEST_DATA = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + private File mTestFile; + private File mCopyFile; + + @Override + protected void setUp() throws Exception { + super.setUp(); + File testDir = getContext().getDir("testing", Context.MODE_PRIVATE); + mTestFile = new File(testDir, "test.file"); + mCopyFile = new File(testDir, "copy.file"); + FileWriter writer = new FileWriter(mTestFile); + try { + writer.write(TEST_DATA, 0, TEST_DATA.length()); + } finally { + writer.close(); + } + } + + @Override + protected void tearDown() throws Exception { + if (mTestFile.exists()) mTestFile.delete(); + if (mCopyFile.exists()) mCopyFile.delete(); + } + + @LargeTest + public void testGetFileStatus() { + final byte[] MAGIC = { 0xB, 0xE, 0x0, 0x5 }; + + try { + // truncate test file and write MAGIC (4 bytes) to it. + FileOutputStream os = new FileOutputStream(mTestFile, false); + os.write(MAGIC, 0, 4); + os.flush(); + os.close(); + } catch (FileNotFoundException e) { + Assert.fail("File was removed durning test" + e); + } catch (IOException e) { + Assert.fail("Unexpected IOException: " + e); + } + + Assert.assertTrue(mTestFile.exists()); + Assert.assertTrue(FileUtils.getFileStatus(mTestFile.getPath(), null)); + + FileStatus status1 = new FileStatus(); + FileUtils.getFileStatus(mTestFile.getPath(), status1); + + Assert.assertEquals(4, status1.size); + + // Sleep for at least one second so that the modification time will be different. + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + + try { + // append so we don't change the creation time. + FileOutputStream os = new FileOutputStream(mTestFile, true); + os.write(MAGIC, 0, 4); + os.flush(); + os.close(); + } catch (FileNotFoundException e) { + Assert.fail("File was removed durning test" + e); + } catch (IOException e) { + Assert.fail("Unexpected IOException: " + e); + } + + FileStatus status2 = new FileStatus(); + FileUtils.getFileStatus(mTestFile.getPath(), status2); + + Assert.assertEquals(8, status2.size); + Assert.assertTrue(status2.mtime > status1.mtime); + + mTestFile.delete(); + + Assert.assertFalse(mTestFile.exists()); + Assert.assertFalse(FileUtils.getFileStatus(mTestFile.getPath(), null)); + } + + // TODO: test setPermissions(), getPermissions() + + @MediumTest + public void testCopyFile() throws Exception { + assertFalse(mCopyFile.exists()); + FileUtils.copyFile(mTestFile, mCopyFile); + assertTrue(mCopyFile.exists()); + assertEquals(TEST_DATA, FileUtils.readTextFile(mCopyFile, 0, null)); + } + + @MediumTest + public void testCopyToFile() throws Exception { + final String s = "Foo Bar"; + assertFalse(mCopyFile.exists()); + FileUtils.copyToFile(new ByteArrayInputStream(s.getBytes()), mCopyFile); assertTrue(mCopyFile.exists()); + assertEquals(s, FileUtils.readTextFile(mCopyFile, 0, null)); + } + + @MediumTest + public void testIsFilenameSafe() throws Exception { + assertTrue(FileUtils.isFilenameSafe(new File("foobar"))); + assertTrue(FileUtils.isFilenameSafe(new File("a_b-c=d.e/0,1+23"))); + assertFalse(FileUtils.isFilenameSafe(new File("foo*bar"))); + assertFalse(FileUtils.isFilenameSafe(new File("foo\nbar"))); + } + + @MediumTest + public void testReadTextFile() throws Exception { + assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 0, null)); + + assertEquals("ABCDE", FileUtils.readTextFile(mTestFile, 5, null)); + assertEquals("ABCDE<>", FileUtils.readTextFile(mTestFile, 5, "<>")); + assertEquals(TEST_DATA.substring(0, 51) + "<>", + FileUtils.readTextFile(mTestFile, 51, "<>")); + assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 52, "<>")); + assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 100, "<>")); + + assertEquals("vwxyz", FileUtils.readTextFile(mTestFile, -5, null)); + assertEquals("<>vwxyz", FileUtils.readTextFile(mTestFile, -5, "<>")); + assertEquals("<>" + TEST_DATA.substring(1, 52), + FileUtils.readTextFile(mTestFile, -51, "<>")); + assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, -52, "<>")); + assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, -100, "<>")); + } + + @MediumTest + public void testReadTextFileWithZeroLengthFile() throws Exception { + new FileOutputStream(mTestFile).close(); // Zero out the file + assertEquals("", FileUtils.readTextFile(mTestFile, 0, null)); + assertEquals("", FileUtils.readTextFile(mTestFile, 1, "<>")); + assertEquals("", FileUtils.readTextFile(mTestFile, 10, "<>")); + assertEquals("", FileUtils.readTextFile(mTestFile, -1, "<>")); + assertEquals("", FileUtils.readTextFile(mTestFile, -10, "<>")); + } +} diff --git a/core/tests/coretests/src/android/os/HandlerTester.java b/core/tests/coretests/src/android/os/HandlerTester.java new file mode 100644 index 0000000..a216a0b --- /dev/null +++ b/core/tests/coretests/src/android/os/HandlerTester.java @@ -0,0 +1,89 @@ +/* + * 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.os; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +public abstract class HandlerTester extends Thread { + public abstract void go(); + public abstract void handleMessage(Message msg); + + public HandlerTester() { + } + + public void doTest(long timeout) { + start(); + + synchronized (this) { + try { + wait(timeout); + quit(); + } + catch (InterruptedException e) { + } + } + + if (!mDone) { + throw new RuntimeException("test timed out"); + } + if (!mSuccess) { + throw new RuntimeException("test failed"); + } + } + + public void success() { + mDone = true; + mSuccess = true; + } + + public void failure() { + mDone = true; + mSuccess = false; + } + + public void run() { + Looper.prepare(); + mLooper = Looper.myLooper(); + go(); + Looper.loop(); + } + + protected class H extends Handler { + public void handleMessage(Message msg) { + synchronized (HandlerTester.this) { + // Call into them with our monitor locked, so they don't have + // to deal with other races. + HandlerTester.this.handleMessage(msg); + if (mDone) { + HandlerTester.this.notify(); + quit(); + } + } + } + } + + private void quit() { + mLooper.quit(); + } + + private boolean mDone = false; + private boolean mSuccess = false; + private Looper mLooper; +} + diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java new file mode 100644 index 0000000..9772aa4 --- /dev/null +++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java @@ -0,0 +1,102 @@ +/* + * 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.os; + +import junit.framework.TestCase; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.Process; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; + +public class HandlerThreadTest extends TestCase { + private static final int TEST_WHAT = 1; + + private boolean mGotMessage = false; + private int mGotMessageWhat = -1; + private volatile boolean mDidSetup = false; + private volatile int mLooperTid = -1; + + @MediumTest + public void testHandlerThread() throws Exception { + HandlerThread th1 = new HandlerThread("HandlerThreadTest") { + protected void onLooperPrepared() { + synchronized (HandlerThreadTest.this) { + mDidSetup = true; + mLooperTid = Process.myTid(); + HandlerThreadTest.this.notify(); + } + } + }; + + assertFalse(th1.isAlive()); + assertNull(th1.getLooper()); + + th1.start(); + + assertTrue(th1.isAlive()); + assertNotNull(th1.getLooper()); + + // The call to getLooper() internally blocks until the looper is + // available, but will call onLooperPrepared() after that. So we + // need to block here to wait for our onLooperPrepared() to complete + // and fill in the values we expect. + synchronized (this) { + while (!mDidSetup) { + try { + wait(); + } catch (InterruptedException e) { + } + } + } + + // Make sure that the process was set. + assertNotSame(-1, mLooperTid); + // Make sure that the onLooperPrepared() was called on a different thread. + assertNotSame(Process.myTid(), mLooperTid); + + final Handler h1 = new Handler(th1.getLooper()) { + public void handleMessage(Message msg) { + assertEquals(TEST_WHAT, msg.what); + // Ensure that we are running on the same thread in which the looper was setup on. + assertEquals(mLooperTid, Process.myTid()); + + mGotMessageWhat = msg.what; + mGotMessage = true; + synchronized(this) { + notifyAll(); + } + } + }; + + Message msg = h1.obtainMessage(TEST_WHAT); + + synchronized (h1) { + // wait until we have the lock before sending the message. + h1.sendMessage(msg); + try { + // wait for the message to be handled + h1.wait(); + } catch (InterruptedException e) { + } + } + + assertTrue(mGotMessage); + assertEquals(TEST_WHAT, mGotMessageWhat); + } +} diff --git a/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java new file mode 100644 index 0000000..6e50c7e --- /dev/null +++ b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java @@ -0,0 +1,1392 @@ +/** + * 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.os; + +import junit.framework.TestCase; + +import android.os.Debug; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.test.suitebuilder.annotation.SmallTest; + +import android.util.Log; + +import com.android.internal.util.HierarchicalStateMachine; +import com.android.internal.util.HierarchicalState; +import com.android.internal.util.ProcessedMessages; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Test for HierarchicalStateMachine. + * + * @author wink@google.com (Wink Saville) + */ +public class HierarchicalStateMachineTest extends TestCase { + private static final int TEST_CMD_1 = 1; + private static final int TEST_CMD_2 = 2; + private static final int TEST_CMD_3 = 3; + private static final int TEST_CMD_4 = 4; + private static final int TEST_CMD_5 = 5; + private static final int TEST_CMD_6 = 6; + + private static final boolean DBG = true; + private static final boolean WAIT_FOR_DEBUGGER = false; + private static final String TAG = "HierarchicalStateMachineTest"; + + /** + * Tests that ProcessedMessage works as a circular buffer. + */ + class StateMachine0 extends HierarchicalStateMachine { + StateMachine0(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + setProcessedMessagesSize(3); + + // Setup state machine with 1 state + addState(mS1); + + // Set the initial state + setInitialState(mS1); + } + + class S1 extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + if (message.what == TEST_CMD_6) { + transitionToHaltingState(); + } + return true; + } + } + + @Override + protected void halting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachine0 mThisSm; + private S1 mS1 = new S1(); + } + + @SmallTest + public void testStateMachine0() throws Exception { + if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); + + StateMachine0 sm0 = new StateMachine0("sm0"); + sm0.start(); + if (sm0.isDbg()) Log.d(TAG, "testStateMachine0 E"); + + synchronized (sm0) { + // Send 6 messages + for (int i = 1; i <= 6; i++) { + sm0.sendMessage(sm0.obtainMessage(i)); + } + + try { + // wait for the messages to be handled + sm0.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachine0: exception while waiting " + e.getMessage()); + } + } + + assertTrue(sm0.getProcessedMessagesCount() == 6); + assertTrue(sm0.getProcessedMessagesSize() == 3); + + ProcessedMessages.Info pmi; + pmi = sm0.getProcessedMessage(0); + assertEquals(TEST_CMD_4, pmi.getWhat()); + assertEquals(sm0.mS1, pmi.getState()); + assertEquals(sm0.mS1, pmi.getOriginalState()); + + pmi = sm0.getProcessedMessage(1); + assertEquals(TEST_CMD_5, pmi.getWhat()); + assertEquals(sm0.mS1, pmi.getState()); + assertEquals(sm0.mS1, pmi.getOriginalState()); + + pmi = sm0.getProcessedMessage(2); + assertEquals(TEST_CMD_6, pmi.getWhat()); + assertEquals(sm0.mS1, pmi.getState()); + assertEquals(sm0.mS1, pmi.getOriginalState()); + + if (sm0.isDbg()) Log.d(TAG, "testStateMachine0 X"); + } + + /** + * This tests enter/exit and transitions to the same state. + * The state machine has one state, it receives two messages + * in state mS1. With the first message it transitions to + * itself which causes it to be exited and reentered. + */ + class StateMachine1 extends HierarchicalStateMachine { + StateMachine1(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + + // Setup state machine with 1 state + addState(mS1); + + // Set the initial state + setInitialState(mS1); + if (DBG) Log.d(TAG, "StateMachine1: ctor X"); + } + + class S1 extends HierarchicalState { + @Override protected void enter() { + mEnterCount++; + } + + @Override protected boolean processMessage(Message message) { + if (message.what == TEST_CMD_1) { + assertEquals(1, mEnterCount); + assertEquals(0, mExitCount); + transitionTo(mS1); + } else if (message.what == TEST_CMD_2) { + assertEquals(2, mEnterCount); + assertEquals(1, mExitCount); + transitionToHaltingState(); + } + return true; + } + + @Override protected void exit() { + mExitCount++; + } + } + + @Override + protected void halting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachine1 mThisSm; + private S1 mS1 = new S1(); + + private int mEnterCount; + private int mExitCount; + } + + @SmallTest + public void testStateMachine1() throws Exception { + StateMachine1 sm1 = new StateMachine1("sm1"); + sm1.start(); + if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 E"); + + synchronized (sm1) { + // Send two messages + sm1.sendMessage(sm1.obtainMessage(TEST_CMD_1)); + sm1.sendMessage(sm1.obtainMessage(TEST_CMD_2)); + + try { + // wait for the messages to be handled + sm1.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachine1: exception while waiting " + e.getMessage()); + } + } + + assertEquals(2, sm1.mEnterCount); + assertEquals(2, sm1.mExitCount); + + assertTrue(sm1.getProcessedMessagesSize() == 2); + + ProcessedMessages.Info pmi; + pmi = sm1.getProcessedMessage(0); + assertEquals(TEST_CMD_1, pmi.getWhat()); + assertEquals(sm1.mS1, pmi.getState()); + assertEquals(sm1.mS1, pmi.getOriginalState()); + + pmi = sm1.getProcessedMessage(1); + assertEquals(TEST_CMD_2, pmi.getWhat()); + assertEquals(sm1.mS1, pmi.getState()); + assertEquals(sm1.mS1, pmi.getOriginalState()); + + assertEquals(2, sm1.mEnterCount); + assertEquals(2, sm1.mExitCount); + + if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 X"); + } + + /** + * Test deferring messages and states with no parents. The state machine + * has two states, it receives two messages in state mS1 deferring them + * until what == TEST_CMD_2 and then transitions to state mS2. State + * mS2 then receives both of the deferred messages first TEST_CMD_1 and + * then TEST_CMD_2. + */ + class StateMachine2 extends HierarchicalStateMachine { + StateMachine2(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + + // Setup the hierarchy + addState(mS1); + addState(mS2); + + // Set the initial state + setInitialState(mS1); + if (DBG) Log.d(TAG, "StateMachine2: ctor X"); + } + + class S1 extends HierarchicalState { + @Override protected void enter() { + mDidEnter = true; + } + + @Override protected boolean processMessage(Message message) { + deferMessage(message); + if (message.what == TEST_CMD_2) { + transitionTo(mS2); + } + return true; + } + + @Override protected void exit() { + mDidExit = true; + } + } + + class S2 extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + if (message.what == TEST_CMD_2) { + transitionToHaltingState(); + } + return true; + } + } + + @Override + protected void halting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachine2 mThisSm; + private S1 mS1 = new S1(); + private S2 mS2 = new S2(); + + private boolean mDidEnter = false; + private boolean mDidExit = false; + } + + @SmallTest + public void testStateMachine2() throws Exception { + StateMachine2 sm2 = new StateMachine2("sm2"); + sm2.start(); + if (sm2.isDbg()) Log.d(TAG, "testStateMachine2 E"); + + synchronized (sm2) { + // Send two messages + sm2.sendMessage(sm2.obtainMessage(TEST_CMD_1)); + sm2.sendMessage(sm2.obtainMessage(TEST_CMD_2)); + + try { + // wait for the messages to be handled + sm2.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachine2: exception while waiting " + e.getMessage()); + } + } + + assertTrue(sm2.getProcessedMessagesSize() == 4); + + ProcessedMessages.Info pmi; + pmi = sm2.getProcessedMessage(0); + assertEquals(TEST_CMD_1, pmi.getWhat()); + assertEquals(sm2.mS1, pmi.getState()); + + pmi = sm2.getProcessedMessage(1); + assertEquals(TEST_CMD_2, pmi.getWhat()); + assertEquals(sm2.mS1, pmi.getState()); + + pmi = sm2.getProcessedMessage(2); + assertEquals(TEST_CMD_1, pmi.getWhat()); + assertEquals(sm2.mS2, pmi.getState()); + + pmi = sm2.getProcessedMessage(3); + assertEquals(TEST_CMD_2, pmi.getWhat()); + assertEquals(sm2.mS2, pmi.getState()); + + assertTrue(sm2.mDidEnter); + assertTrue(sm2.mDidExit); + + if (sm2.isDbg()) Log.d(TAG, "testStateMachine2 X"); + } + + /** + * Test that unhandled messages in a child are handled by the parent. + * When TEST_CMD_2 is received. + */ + class StateMachine3 extends HierarchicalStateMachine { + StateMachine3(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + + // Setup the simplest hierarchy of two states + // mParentState and mChildState. + // (Use indentation to help visualize hierarchy) + addState(mParentState); + addState(mChildState, mParentState); + + // Set the initial state will be the child + setInitialState(mChildState); + if (DBG) Log.d(TAG, "StateMachine3: ctor X"); + } + + class ParentState extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + if (message.what == TEST_CMD_2) { + transitionToHaltingState(); + } + return true; + } + } + + class ChildState extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + return false; + } + } + + @Override + protected void halting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachine3 mThisSm; + private ParentState mParentState = new ParentState(); + private ChildState mChildState = new ChildState(); + } + + @SmallTest + public void testStateMachine3() throws Exception { + StateMachine3 sm3 = new StateMachine3("sm3"); + sm3.start(); + if (sm3.isDbg()) Log.d(TAG, "testStateMachine3 E"); + + synchronized (sm3) { + // Send two messages + sm3.sendMessage(sm3.obtainMessage(TEST_CMD_1)); + sm3.sendMessage(sm3.obtainMessage(TEST_CMD_2)); + + try { + // wait for the messages to be handled + sm3.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachine3: exception while waiting " + e.getMessage()); + } + } + + assertTrue(sm3.getProcessedMessagesSize() == 2); + + ProcessedMessages.Info pmi; + pmi = sm3.getProcessedMessage(0); + assertEquals(TEST_CMD_1, pmi.getWhat()); + assertEquals(sm3.mParentState, pmi.getState()); + assertEquals(sm3.mChildState, pmi.getOriginalState()); + + pmi = sm3.getProcessedMessage(1); + assertEquals(TEST_CMD_2, pmi.getWhat()); + assertEquals(sm3.mParentState, pmi.getState()); + assertEquals(sm3.mChildState, pmi.getOriginalState()); + + if (sm3.isDbg()) Log.d(TAG, "testStateMachine3 X"); + } + + /** + * Test a hierarchy of 3 states a parent and two children + * with transition from child 1 to child 2 and child 2 + * lets the parent handle the messages. + */ + class StateMachine4 extends HierarchicalStateMachine { + StateMachine4(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + + // Setup a hierarchy of three states + // mParentState, mChildState1 & mChildState2 + // (Use indentation to help visualize hierarchy) + addState(mParentState); + addState(mChildState1, mParentState); + addState(mChildState2, mParentState); + + // Set the initial state will be child 1 + setInitialState(mChildState1); + if (DBG) Log.d(TAG, "StateMachine4: ctor X"); + } + + class ParentState extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + if (message.what == TEST_CMD_2) { + transitionToHaltingState(); + } + return true; + } + } + + class ChildState1 extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + transitionTo(mChildState2); + return true; + } + } + + class ChildState2 extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + return false; + } + } + + @Override + protected void halting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachine4 mThisSm; + private ParentState mParentState = new ParentState(); + private ChildState1 mChildState1 = new ChildState1(); + private ChildState2 mChildState2 = new ChildState2(); + } + + @SmallTest + public void testStateMachine4() throws Exception { + StateMachine4 sm4 = new StateMachine4("sm4"); + sm4.start(); + if (sm4.isDbg()) Log.d(TAG, "testStateMachine4 E"); + + synchronized (sm4) { + // Send two messages + sm4.sendMessage(sm4.obtainMessage(TEST_CMD_1)); + sm4.sendMessage(sm4.obtainMessage(TEST_CMD_2)); + + try { + // wait for the messages to be handled + sm4.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachine4: exception while waiting " + e.getMessage()); + } + } + + + assertTrue(sm4.getProcessedMessagesSize() == 2); + + ProcessedMessages.Info pmi; + pmi = sm4.getProcessedMessage(0); + assertEquals(TEST_CMD_1, pmi.getWhat()); + assertEquals(sm4.mChildState1, pmi.getState()); + assertEquals(sm4.mChildState1, pmi.getOriginalState()); + + pmi = sm4.getProcessedMessage(1); + assertEquals(TEST_CMD_2, pmi.getWhat()); + assertEquals(sm4.mParentState, pmi.getState()); + assertEquals(sm4.mChildState2, pmi.getOriginalState()); + + if (sm4.isDbg()) Log.d(TAG, "testStateMachine4 X"); + } + + /** + * Test transition from one child to another of a "complex" + * hierarchy with two parents and multiple children. + */ + class StateMachine5 extends HierarchicalStateMachine { + StateMachine5(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + + // Setup a hierarchy with two parents and some children. + // (Use indentation to help visualize hierarchy) + addState(mParentState1); + addState(mChildState1, mParentState1); + addState(mChildState2, mParentState1); + + addState(mParentState2); + addState(mChildState3, mParentState2); + addState(mChildState4, mParentState2); + addState(mChildState5, mChildState4); + + // Set the initial state will be the child + setInitialState(mChildState1); + if (DBG) Log.d(TAG, "StateMachine5: ctor X"); + } + + class ParentState1 extends HierarchicalState { + @Override protected void enter() { + mParentState1EnterCount += 1; + } + @Override protected boolean processMessage(Message message) { + return true; + } + @Override protected void exit() { + mParentState1ExitCount += 1; + } + } + + class ChildState1 extends HierarchicalState { + @Override protected void enter() { + mChildState1EnterCount += 1; + } + @Override protected boolean processMessage(Message message) { + assertEquals(1, mParentState1EnterCount); + assertEquals(0, mParentState1ExitCount); + assertEquals(1, mChildState1EnterCount); + assertEquals(0, mChildState1ExitCount); + assertEquals(0, mChildState2EnterCount); + assertEquals(0, mChildState2ExitCount); + assertEquals(0, mParentState2EnterCount); + assertEquals(0, mParentState2ExitCount); + assertEquals(0, mChildState3EnterCount); + assertEquals(0, mChildState3ExitCount); + assertEquals(0, mChildState4EnterCount); + assertEquals(0, mChildState4ExitCount); + assertEquals(0, mChildState5EnterCount); + assertEquals(0, mChildState5ExitCount); + + transitionTo(mChildState2); + return true; + } + @Override protected void exit() { + mChildState1ExitCount += 1; + } + } + + class ChildState2 extends HierarchicalState { + @Override protected void enter() { + mChildState2EnterCount += 1; + } + @Override protected boolean processMessage(Message message) { + assertEquals(1, mParentState1EnterCount); + assertEquals(0, mParentState1ExitCount); + assertEquals(1, mChildState1EnterCount); + assertEquals(1, mChildState1ExitCount); + assertEquals(1, mChildState2EnterCount); + assertEquals(0, mChildState2ExitCount); + assertEquals(0, mParentState2EnterCount); + assertEquals(0, mParentState2ExitCount); + assertEquals(0, mChildState3EnterCount); + assertEquals(0, mChildState3ExitCount); + assertEquals(0, mChildState4EnterCount); + assertEquals(0, mChildState4ExitCount); + assertEquals(0, mChildState5EnterCount); + assertEquals(0, mChildState5ExitCount); + + transitionTo(mChildState5); + return true; + } + @Override protected void exit() { + mChildState2ExitCount += 1; + } + } + + class ParentState2 extends HierarchicalState { + @Override protected void enter() { + mParentState2EnterCount += 1; + } + @Override protected boolean processMessage(Message message) { + assertEquals(1, mParentState1EnterCount); + assertEquals(1, mParentState1ExitCount); + assertEquals(1, mChildState1EnterCount); + assertEquals(1, mChildState1ExitCount); + assertEquals(1, mChildState2EnterCount); + assertEquals(1, mChildState2ExitCount); + assertEquals(2, mParentState2EnterCount); + assertEquals(1, mParentState2ExitCount); + assertEquals(1, mChildState3EnterCount); + assertEquals(1, mChildState3ExitCount); + assertEquals(2, mChildState4EnterCount); + assertEquals(2, mChildState4ExitCount); + assertEquals(1, mChildState5EnterCount); + assertEquals(1, mChildState5ExitCount); + + transitionToHaltingState(); + return true; + } + @Override protected void exit() { + mParentState2ExitCount += 1; + } + } + + class ChildState3 extends HierarchicalState { + @Override protected void enter() { + mChildState3EnterCount += 1; + } + @Override protected boolean processMessage(Message message) { + assertEquals(1, mParentState1EnterCount); + assertEquals(1, mParentState1ExitCount); + assertEquals(1, mChildState1EnterCount); + assertEquals(1, mChildState1ExitCount); + assertEquals(1, mChildState2EnterCount); + assertEquals(1, mChildState2ExitCount); + assertEquals(1, mParentState2EnterCount); + assertEquals(0, mParentState2ExitCount); + assertEquals(1, mChildState3EnterCount); + assertEquals(0, mChildState3ExitCount); + assertEquals(1, mChildState4EnterCount); + assertEquals(1, mChildState4ExitCount); + assertEquals(1, mChildState5EnterCount); + assertEquals(1, mChildState5ExitCount); + + transitionTo(mChildState4); + return true; + } + @Override protected void exit() { + mChildState3ExitCount += 1; + } + } + + class ChildState4 extends HierarchicalState { + @Override protected void enter() { + mChildState4EnterCount += 1; + } + @Override protected boolean processMessage(Message message) { + assertEquals(1, mParentState1EnterCount); + assertEquals(1, mParentState1ExitCount); + assertEquals(1, mChildState1EnterCount); + assertEquals(1, mChildState1ExitCount); + assertEquals(1, mChildState2EnterCount); + assertEquals(1, mChildState2ExitCount); + assertEquals(1, mParentState2EnterCount); + assertEquals(0, mParentState2ExitCount); + assertEquals(1, mChildState3EnterCount); + assertEquals(1, mChildState3ExitCount); + assertEquals(2, mChildState4EnterCount); + assertEquals(1, mChildState4ExitCount); + assertEquals(1, mChildState5EnterCount); + assertEquals(1, mChildState5ExitCount); + + transitionTo(mParentState2); + return true; + } + @Override protected void exit() { + mChildState4ExitCount += 1; + } + } + + class ChildState5 extends HierarchicalState { + @Override protected void enter() { + mChildState5EnterCount += 1; + } + @Override protected boolean processMessage(Message message) { + assertEquals(1, mParentState1EnterCount); + assertEquals(1, mParentState1ExitCount); + assertEquals(1, mChildState1EnterCount); + assertEquals(1, mChildState1ExitCount); + assertEquals(1, mChildState2EnterCount); + assertEquals(1, mChildState2ExitCount); + assertEquals(1, mParentState2EnterCount); + assertEquals(0, mParentState2ExitCount); + assertEquals(0, mChildState3EnterCount); + assertEquals(0, mChildState3ExitCount); + assertEquals(1, mChildState4EnterCount); + assertEquals(0, mChildState4ExitCount); + assertEquals(1, mChildState5EnterCount); + assertEquals(0, mChildState5ExitCount); + + transitionTo(mChildState3); + return true; + } + @Override protected void exit() { + mChildState5ExitCount += 1; + } + } + + @Override + protected void halting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachine5 mThisSm; + private ParentState1 mParentState1 = new ParentState1(); + private ChildState1 mChildState1 = new ChildState1(); + private ChildState2 mChildState2 = new ChildState2(); + private ParentState2 mParentState2 = new ParentState2(); + private ChildState3 mChildState3 = new ChildState3(); + private ChildState4 mChildState4 = new ChildState4(); + private ChildState5 mChildState5 = new ChildState5(); + + private int mParentState1EnterCount = 0; + private int mParentState1ExitCount = 0; + private int mChildState1EnterCount = 0; + private int mChildState1ExitCount = 0; + private int mChildState2EnterCount = 0; + private int mChildState2ExitCount = 0; + private int mParentState2EnterCount = 0; + private int mParentState2ExitCount = 0; + private int mChildState3EnterCount = 0; + private int mChildState3ExitCount = 0; + private int mChildState4EnterCount = 0; + private int mChildState4ExitCount = 0; + private int mChildState5EnterCount = 0; + private int mChildState5ExitCount = 0; + } + + @SmallTest + public void testStateMachine5() throws Exception { + StateMachine5 sm5 = new StateMachine5("sm5"); + sm5.start(); + if (sm5.isDbg()) Log.d(TAG, "testStateMachine5 E"); + + synchronized (sm5) { + // Send 6 messages + sm5.sendMessage(sm5.obtainMessage(TEST_CMD_1)); + sm5.sendMessage(sm5.obtainMessage(TEST_CMD_2)); + sm5.sendMessage(sm5.obtainMessage(TEST_CMD_3)); + sm5.sendMessage(sm5.obtainMessage(TEST_CMD_4)); + sm5.sendMessage(sm5.obtainMessage(TEST_CMD_5)); + sm5.sendMessage(sm5.obtainMessage(TEST_CMD_6)); + + try { + // wait for the messages to be handled + sm5.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachine5: exception while waiting " + e.getMessage()); + } + } + + + assertTrue(sm5.getProcessedMessagesSize() == 6); + + assertEquals(1, sm5.mParentState1EnterCount); + assertEquals(1, sm5.mParentState1ExitCount); + assertEquals(1, sm5.mChildState1EnterCount); + assertEquals(1, sm5.mChildState1ExitCount); + assertEquals(1, sm5.mChildState2EnterCount); + assertEquals(1, sm5.mChildState2ExitCount); + assertEquals(2, sm5.mParentState2EnterCount); + assertEquals(2, sm5.mParentState2ExitCount); + assertEquals(1, sm5.mChildState3EnterCount); + assertEquals(1, sm5.mChildState3ExitCount); + assertEquals(2, sm5.mChildState4EnterCount); + assertEquals(2, sm5.mChildState4ExitCount); + assertEquals(1, sm5.mChildState5EnterCount); + assertEquals(1, sm5.mChildState5ExitCount); + + ProcessedMessages.Info pmi; + pmi = sm5.getProcessedMessage(0); + assertEquals(TEST_CMD_1, pmi.getWhat()); + assertEquals(sm5.mChildState1, pmi.getState()); + assertEquals(sm5.mChildState1, pmi.getOriginalState()); + + pmi = sm5.getProcessedMessage(1); + assertEquals(TEST_CMD_2, pmi.getWhat()); + assertEquals(sm5.mChildState2, pmi.getState()); + assertEquals(sm5.mChildState2, pmi.getOriginalState()); + + pmi = sm5.getProcessedMessage(2); + assertEquals(TEST_CMD_3, pmi.getWhat()); + assertEquals(sm5.mChildState5, pmi.getState()); + assertEquals(sm5.mChildState5, pmi.getOriginalState()); + + pmi = sm5.getProcessedMessage(3); + assertEquals(TEST_CMD_4, pmi.getWhat()); + assertEquals(sm5.mChildState3, pmi.getState()); + assertEquals(sm5.mChildState3, pmi.getOriginalState()); + + pmi = sm5.getProcessedMessage(4); + assertEquals(TEST_CMD_5, pmi.getWhat()); + assertEquals(sm5.mChildState4, pmi.getState()); + assertEquals(sm5.mChildState4, pmi.getOriginalState()); + + pmi = sm5.getProcessedMessage(5); + assertEquals(TEST_CMD_6, pmi.getWhat()); + assertEquals(sm5.mParentState2, pmi.getState()); + assertEquals(sm5.mParentState2, pmi.getOriginalState()); + + if (sm5.isDbg()) Log.d(TAG, "testStateMachine5 X"); + } + + /** + * Test that the initial state enter is invoked immediately + * after construction and before any other messages arrive and that + * sendMessageDelayed works. + */ + class StateMachine6 extends HierarchicalStateMachine { + StateMachine6(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + + // Setup state machine with 1 state + addState(mS1); + + // Set the initial state + setInitialState(mS1); + if (DBG) Log.d(TAG, "StateMachine6: ctor X"); + } + + class S1 extends HierarchicalState { + + @Override protected void enter() { + sendMessage(obtainMessage(TEST_CMD_1)); + } + + @Override protected boolean processMessage(Message message) { + if (message.what == TEST_CMD_1) { + mArrivalTimeMsg1 = SystemClock.elapsedRealtime(); + } else if (message.what == TEST_CMD_2) { + mArrivalTimeMsg2 = SystemClock.elapsedRealtime(); + transitionToHaltingState(); + } + return true; + } + + @Override protected void exit() { + } + } + + @Override + protected void halting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachine6 mThisSm; + private S1 mS1 = new S1(); + + private long mArrivalTimeMsg1; + private long mArrivalTimeMsg2; + } + + @SmallTest + public void testStateMachine6() throws Exception { + long sentTimeMsg2; + final int DELAY_TIME = 250; + final int DELAY_FUDGE = 20; + + StateMachine6 sm6 = new StateMachine6("sm6"); + sm6.start(); + if (sm6.isDbg()) Log.d(TAG, "testStateMachine6 E"); + + synchronized (sm6) { + // Send a message + sentTimeMsg2 = SystemClock.elapsedRealtime(); + sm6.sendMessageDelayed(sm6.obtainMessage(TEST_CMD_2), DELAY_TIME); + + try { + // wait for the messages to be handled + sm6.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachine6: exception while waiting " + e.getMessage()); + } + } + + /** + * TEST_CMD_1 was sent in enter and must always have been processed + * immediately after construction and hence the arrival time difference + * should always >= to the DELAY_TIME + */ + long arrivalTimeDiff = sm6.mArrivalTimeMsg2 - sm6.mArrivalTimeMsg1; + long expectedDelay = DELAY_TIME - DELAY_FUDGE; + if (sm6.isDbg()) Log.d(TAG, "testStateMachine6: expect " + arrivalTimeDiff + + " >= " + expectedDelay); + assertTrue(arrivalTimeDiff >= expectedDelay); + + if (sm6.isDbg()) Log.d(TAG, "testStateMachine6 X"); + } + + /** + * Test that enter is invoked immediately after exit. This validates + * that enter can be used to send a watch dog message for its state. + */ + class StateMachine7 extends HierarchicalStateMachine { + private final int SM7_DELAY_TIME = 250; + + StateMachine7(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + + // Setup state machine with 1 state + addState(mS1); + addState(mS2); + + // Set the initial state + setInitialState(mS1); + if (DBG) Log.d(TAG, "StateMachine7: ctor X"); + } + + class S1 extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + transitionTo(mS2); + return true; + } + @Override protected void exit() { + sendMessage(obtainMessage(TEST_CMD_2)); + } + } + + class S2 extends HierarchicalState { + + @Override protected void enter() { + // Send a delayed message as a watch dog + sendMessageDelayed(obtainMessage(TEST_CMD_3), SM7_DELAY_TIME); + } + + @Override protected boolean processMessage(Message message) { + if (message.what == TEST_CMD_2) { + mMsgCount += 1; + mArrivalTimeMsg2 = SystemClock.elapsedRealtime(); + } else if (message.what == TEST_CMD_3) { + mMsgCount += 1; + mArrivalTimeMsg3 = SystemClock.elapsedRealtime(); + } + + if (mMsgCount == 2) { + transitionToHaltingState(); + } + return true; + } + + @Override protected void exit() { + } + } + + @Override + protected void halting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachine7 mThisSm; + private S1 mS1 = new S1(); + private S2 mS2 = new S2(); + + private int mMsgCount = 0; + private long mArrivalTimeMsg2; + private long mArrivalTimeMsg3; + } + + @SmallTest + public void testStateMachine7() throws Exception { + long sentTimeMsg2; + final int SM7_DELAY_FUDGE = 20; + + StateMachine7 sm7 = new StateMachine7("sm7"); + sm7.start(); + if (sm7.isDbg()) Log.d(TAG, "testStateMachine7 E"); + + synchronized (sm7) { + // Send a message + sentTimeMsg2 = SystemClock.elapsedRealtime(); + sm7.sendMessage(sm7.obtainMessage(TEST_CMD_1)); + + try { + // wait for the messages to be handled + sm7.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachine7: exception while waiting " + e.getMessage()); + } + } + + /** + * TEST_CMD_3 was sent in S2.enter with a delay and must always have been + * processed immediately after S1.exit. Since S1.exit sent TEST_CMD_2 + * without a delay the arrival time difference should always >= to SM7_DELAY_TIME. + */ + long arrivalTimeDiff = sm7.mArrivalTimeMsg3 - sm7.mArrivalTimeMsg2; + long expectedDelay = sm7.SM7_DELAY_TIME - SM7_DELAY_FUDGE; + if (sm7.isDbg()) Log.d(TAG, "testStateMachine7: expect " + arrivalTimeDiff + + " >= " + expectedDelay); + assertTrue(arrivalTimeDiff >= expectedDelay); + + if (sm7.isDbg()) Log.d(TAG, "testStateMachine7 X"); + } + + /** + * Test unhandledMessage. + */ + class StateMachineUnhandledMessage extends HierarchicalStateMachine { + StateMachineUnhandledMessage(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + + // Setup state machine with 1 state + addState(mS1); + + // Set the initial state + setInitialState(mS1); + } + + @Override protected void unhandledMessage(Message message) { + mUnhandledMessageCount += 1; + } + + class S1 extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + if (message.what == TEST_CMD_2) { + transitionToHaltingState(); + } + return false; + } + } + + @Override + protected void halting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachineUnhandledMessage mThisSm; + private int mUnhandledMessageCount; + private S1 mS1 = new S1(); + } + + @SmallTest + public void testStateMachineUnhandledMessage() throws Exception { + + StateMachineUnhandledMessage sm = new StateMachineUnhandledMessage("sm"); + sm.start(); + if (sm.isDbg()) Log.d(TAG, "testStateMachineUnhandledMessage E"); + + synchronized (sm) { + // Send 2 messages + for (int i = 1; i <= 2; i++) { + sm.sendMessage(sm.obtainMessage(i)); + } + + try { + // wait for the messages to be handled + sm.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachineUnhandledMessage: exception while waiting " + + e.getMessage()); + } + } + + assertTrue(sm.getProcessedMessagesCount() == 2); + assertEquals(2, sm.mUnhandledMessageCount); + + if (sm.isDbg()) Log.d(TAG, "testStateMachineUnhandledMessage X"); + } + + /** + * Test state machines sharing the same thread/looper. Multiple instances + * of the same state machine will be created. They will all share the + * same thread and thus each can update <code>sharedCounter</code> which + * will be used to notify testStateMachineSharedThread that the test is + * complete. + */ + class StateMachineSharedThread extends HierarchicalStateMachine { + StateMachineSharedThread(Looper looper, String name, int maxCount) { + super(looper, name); + mMaxCount = maxCount; + setDbg(DBG); + + // Setup state machine with 1 state + addState(mS1); + + // Set the initial state + setInitialState(mS1); + } + + class S1 extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + if (message.what == TEST_CMD_4) { + transitionToHaltingState(); + } + return true; + } + } + + @Override + protected void halting() { + // Update the shared counter, which is OK since all state + // machines are using the same thread. + sharedCounter += 1; + if (sharedCounter == mMaxCount) { + synchronized (waitObject) { + waitObject.notifyAll(); + } + } + } + + private int mMaxCount; + private S1 mS1 = new S1(); + } + private static int sharedCounter = 0; + private static Object waitObject = new Object(); + + @SmallTest + public void testStateMachineSharedThread() throws Exception { + if (DBG) Log.d(TAG, "testStateMachineSharedThread E"); + + // Create and start the handler thread + HandlerThread smThread = new HandlerThread("testStateMachineSharedThread"); + smThread.start(); + + // Create the state machines + StateMachineSharedThread sms[] = new StateMachineSharedThread[10]; + for (int i = 0; i < sms.length; i++) { + sms[i] = new StateMachineSharedThread(smThread.getLooper(), "sm", sms.length); + sms[i].start(); + } + + synchronized (waitObject) { + // Send messages to each of the state machines + for (StateMachineSharedThread sm : sms) { + for (int i = 1; i <= 4; i++) { + sm.sendMessage(sm.obtainMessage(i)); + } + } + + // Wait for the last state machine to notify its done + try { + waitObject.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachineSharedThread: exception while waiting " + + e.getMessage()); + } + } + + for (StateMachineSharedThread sm : sms) { + assertTrue(sm.getProcessedMessagesCount() == 4); + for (int i = 0; i < sm.getProcessedMessagesCount(); i++) { + ProcessedMessages.Info pmi = sm.getProcessedMessage(i); + assertEquals(i+1, pmi.getWhat()); + assertEquals(sm.mS1, pmi.getState()); + assertEquals(sm.mS1, pmi.getOriginalState()); + } + } + + if (DBG) Log.d(TAG, "testStateMachineSharedThread X"); + } + + @SmallTest + public void testHsm1() throws Exception { + if (DBG) Log.d(TAG, "testHsm1 E"); + + Hsm1 sm = Hsm1.makeHsm1(); + + // Send messages + sm.sendMessage(sm.obtainMessage(Hsm1.CMD_1)); + sm.sendMessage(sm.obtainMessage(Hsm1.CMD_2)); + + synchronized (sm) { + // Wait for the last state machine to notify its done + try { + sm.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testHsm1: exception while waiting " + e.getMessage()); + } + } + + assertEquals(7, sm.getProcessedMessagesCount()); + ProcessedMessages.Info pmi = sm.getProcessedMessage(0); + assertEquals(Hsm1.CMD_1, pmi.getWhat()); + assertEquals(sm.mS1, pmi.getState()); + assertEquals(sm.mS1, pmi.getOriginalState()); + + pmi = sm.getProcessedMessage(1); + assertEquals(Hsm1.CMD_2, pmi.getWhat()); + assertEquals(sm.mP1, pmi.getState()); + assertEquals(sm.mS1, pmi.getOriginalState()); + + pmi = sm.getProcessedMessage(2); + assertEquals(Hsm1.CMD_2, pmi.getWhat()); + assertEquals(sm.mS2, pmi.getState()); + assertEquals(sm.mS2, pmi.getOriginalState()); + + pmi = sm.getProcessedMessage(3); + assertEquals(Hsm1.CMD_3, pmi.getWhat()); + assertEquals(sm.mS2, pmi.getState()); + assertEquals(sm.mS2, pmi.getOriginalState()); + + pmi = sm.getProcessedMessage(4); + assertEquals(Hsm1.CMD_3, pmi.getWhat()); + assertEquals(sm.mP2, pmi.getState()); + assertEquals(sm.mP2, pmi.getOriginalState()); + + pmi = sm.getProcessedMessage(5); + assertEquals(Hsm1.CMD_4, pmi.getWhat()); + assertEquals(sm.mP2, pmi.getState()); + assertEquals(sm.mP2, pmi.getOriginalState()); + + pmi = sm.getProcessedMessage(6); + assertEquals(Hsm1.CMD_5, pmi.getWhat()); + assertEquals(sm.mP2, pmi.getState()); + assertEquals(sm.mP2, pmi.getOriginalState()); + + if (DBG) Log.d(TAG, "testStateMachineSharedThread X"); + } +} + +class Hsm1 extends HierarchicalStateMachine { + private static final String TAG = "hsm1"; + + public static final int CMD_1 = 1; + public static final int CMD_2 = 2; + public static final int CMD_3 = 3; + public static final int CMD_4 = 4; + public static final int CMD_5 = 5; + + public static Hsm1 makeHsm1() { + Log.d(TAG, "makeHsm1 E"); + Hsm1 sm = new Hsm1("hsm1"); + sm.start(); + Log.d(TAG, "makeHsm1 X"); + return sm; + } + + Hsm1(String name) { + super(name); + Log.d(TAG, "ctor E"); + + // Add states, use indentation to show hierarchy + addState(mP1); + addState(mS1, mP1); + addState(mS2, mP1); + addState(mP2); + + // Set the initial state + setInitialState(mS1); + Log.d(TAG, "ctor X"); + } + + class P1 extends HierarchicalState { + @Override protected void enter() { + Log.d(TAG, "P1.enter"); + } + @Override protected boolean processMessage(Message message) { + boolean retVal; + Log.d(TAG, "P1.processMessage what=" + message.what); + switch(message.what) { + case CMD_2: + // CMD_2 will arrive in mS2 before CMD_3 + sendMessage(obtainMessage(CMD_3)); + deferMessage(message); + transitionTo(mS2); + retVal = true; + break; + default: + // Any message we don't understand in this state invokes unhandledMessage + retVal = false; + break; + } + return retVal; + } + @Override protected void exit() { + Log.d(TAG, "P1.exit"); + } + } + + class S1 extends HierarchicalState { + @Override protected void enter() { + Log.d(TAG, "S1.enter"); + } + @Override protected boolean processMessage(Message message) { + Log.d(TAG, "S1.processMessage what=" + message.what); + if (message.what == CMD_1) { + // Transition to ourself to show that enter/exit is called + transitionTo(mS1); + return true; + } else { + // Let parent process all other messages + return false; + } + } + @Override protected void exit() { + Log.d(TAG, "S1.exit"); + } + } + + class S2 extends HierarchicalState { + @Override protected void enter() { + Log.d(TAG, "S2.enter"); + } + @Override protected boolean processMessage(Message message) { + boolean retVal; + Log.d(TAG, "S2.processMessage what=" + message.what); + switch(message.what) { + case(CMD_2): + sendMessage(obtainMessage(CMD_4)); + retVal = true; + break; + case(CMD_3): + deferMessage(message); + transitionTo(mP2); + retVal = true; + break; + default: + retVal = false; + break; + } + return retVal; + } + @Override protected void exit() { + Log.d(TAG, "S2.exit"); + } + } + + class P2 extends HierarchicalState { + @Override protected void enter() { + Log.d(TAG, "P2.enter"); + sendMessage(obtainMessage(CMD_5)); + } + @Override protected boolean processMessage(Message message) { + Log.d(TAG, "P2.processMessage what=" + message.what); + switch(message.what) { + case(CMD_3): + break; + case(CMD_4): + break; + case(CMD_5): + transitionToHaltingState(); + break; + } + return true; + } + @Override protected void exit() { + Log.d(TAG, "P2.exit"); + } + } + + @Override + protected void halting() { + Log.d(TAG, "halting"); + synchronized (this) { + this.notifyAll(); + } + } + + P1 mP1 = new P1(); + S1 mS1 = new S1(); + S2 mS2 = new S2(); + P2 mP2 = new P2(); +} diff --git a/core/tests/coretests/src/android/os/IAidlTest.aidl b/core/tests/coretests/src/android/os/IAidlTest.aidl new file mode 100644 index 0000000..a09022e --- /dev/null +++ b/core/tests/coretests/src/android/os/IAidlTest.aidl @@ -0,0 +1,47 @@ +/* //device/apps/AndroidTests/src/com.android.unit_tests/IAidlTest.aidl +** +** Copyright 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.os; + +import android.os.AidlTest; + +interface IAidlTest { + int intMethod(int a); + + AidlTest.TestParcelable parcelableIn(in AidlTest.TestParcelable p); + AidlTest.TestParcelable parcelableOut(out AidlTest.TestParcelable p); + AidlTest.TestParcelable parcelableInOut(inout AidlTest.TestParcelable p); + + AidlTest.TestParcelable listParcelableLonger( + inout List<AidlTest.TestParcelable> list, int index); + int listParcelableShorter( + inout List<AidlTest.TestParcelable> list, int index); + + boolean[] booleanArray(in boolean[] a0, out boolean[] a1, inout boolean[] a2); + char[] charArray(in char[] a0, out char[] a1, inout char[] a2); + int[] intArray(in int[] a0, out int[] a1, inout int[] a2); + long[] longArray(in long[] a0, out long[] a1, inout long[] a2); + float[] floatArray(in float[] a0, out float[] a1, inout float[] a2); + double[] doubleArray(in double[] a0, out double[] a1, inout double[] a2); + String[] stringArray(in String[] a0, out String[] a1, inout String[] a2); + AidlTest.TestParcelable[] parcelableArray(in AidlTest.TestParcelable[] a0, + out AidlTest.TestParcelable[] a1, + inout AidlTest.TestParcelable[] a2); + + void voidSecurityException(); + int intSecurityException(); +} diff --git a/core/tests/coretests/src/android/os/IdleHandlerTest.java b/core/tests/coretests/src/android/os/IdleHandlerTest.java new file mode 100644 index 0000000..6c0a862 --- /dev/null +++ b/core/tests/coretests/src/android/os/IdleHandlerTest.java @@ -0,0 +1,199 @@ +/* + * 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.os; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue.IdleHandler; +import android.test.suitebuilder.annotation.MediumTest; +import junit.framework.TestCase; + +public class IdleHandlerTest extends TestCase { + + private static class BaseTestHandler extends TestHandlerThread { + Handler mHandler; + + public BaseTestHandler() { + } + + public void go() { + mHandler = new Handler() { + public void handleMessage(Message msg) { + BaseTestHandler.this.handleMessage(msg); + } + }; + } + + public void addIdleHandler() { + Looper.myQueue().addIdleHandler(new IdleHandler() { + public boolean queueIdle() { + return BaseTestHandler.this.queueIdle(); + } + }); + } + + public void handleMessage(Message msg) { + } + + public boolean queueIdle() { + return false; + } + } + + @MediumTest + public void testOneShotFirst() throws Exception { + TestHandlerThread tester = new BaseTestHandler() { + int mCount; + + public void go() { + super.go(); + mCount = 0; + mHandler.sendMessageDelayed(mHandler.obtainMessage(0), 100); + addIdleHandler(); + } + + public void handleMessage(Message msg) { + if (msg.what == 0) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(1), 100); + } else if (msg.what == 1) { + if (mCount == 1) { + success(); + } else { + failure(new RuntimeException( + "Idle handler called " + mCount + " times")); + } + } + } + + public boolean queueIdle() { + mCount++; + return false; + } + }; + + tester.doTest(1000); + } + + @MediumTest + public void testOneShotLater() throws Exception { + TestHandlerThread tester = new BaseTestHandler() { + int mCount; + + public void go() { + super.go(); + mCount = 0; + mHandler.sendMessage(mHandler.obtainMessage(0)); + } + + public void handleMessage(Message msg) { + if (msg.what == 0) { + addIdleHandler(); + mHandler.sendMessageDelayed(mHandler.obtainMessage(1), 100); + } else if (msg.what == 1) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(2), 100); + } else if (msg.what == 2) { + if (mCount == 1) { + success(); + } else { + failure(new RuntimeException( + "Idle handler called " + mCount + " times")); + } + } + } + + public boolean queueIdle() { + mCount++; + return false; + } + }; + + tester.doTest(1000); + } + + + @MediumTest + public void testRepeatedFirst() throws Exception { + TestHandlerThread tester = new BaseTestHandler() { + int mCount; + + public void go() { + super.go(); + mCount = 0; + mHandler.sendMessageDelayed(mHandler.obtainMessage(0), 100); + addIdleHandler(); + } + + public void handleMessage(Message msg) { + if (msg.what == 0) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(1), 100); + } else if (msg.what == 1) { + if (mCount == 2) { + success(); + } else { + failure(new RuntimeException( + "Idle handler called " + mCount + " times")); + } + } + } + + public boolean queueIdle() { + mCount++; + return true; + } + }; + + tester.doTest(1000); + } + + @MediumTest + public void testRepeatedLater() throws Exception { + TestHandlerThread tester = new BaseTestHandler() { + int mCount; + + public void go() { + super.go(); + mCount = 0; + mHandler.sendMessage(mHandler.obtainMessage(0)); + } + + public void handleMessage(Message msg) { + if (msg.what == 0) { + addIdleHandler(); + mHandler.sendMessageDelayed(mHandler.obtainMessage(1), 100); + } else if (msg.what == 1) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(2), 100); + } else if (msg.what == 2) { + if (mCount == 2) { + success(); + } else { + failure(new RuntimeException( + "Idle handler called " + mCount + " times")); + } + } + } + + public boolean queueIdle() { + mCount++; + return true; + } + }; + + tester.doTest(1000); + } +} + diff --git a/core/tests/coretests/src/android/os/MemoryFileTest.java b/core/tests/coretests/src/android/os/MemoryFileTest.java new file mode 100644 index 0000000..411bdaa --- /dev/null +++ b/core/tests/coretests/src/android/os/MemoryFileTest.java @@ -0,0 +1,427 @@ +/* + * 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. + */ + +package android.os; + +import android.os.MemoryFile; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +public class MemoryFileTest extends AndroidTestCase { + + private void compareBuffers(byte[] buffer1, byte[] buffer2, int length) throws Exception { + for (int i = 0; i < length; i++) { + if (buffer1[i] != buffer2[i]) { + throw new Exception("readBytes did not read back what writeBytes wrote"); + } + } + } + + /** + * Keep allocating new files till the system purges them. + */ + @MediumTest + public void testPurge() throws Exception { + List<MemoryFile> files = new ArrayList<MemoryFile>(); + while (true) { + MemoryFile newFile = new MemoryFile("MemoryFileTest", 1000000); + newFile.allowPurging(true); + newFile.writeBytes(testString, 0, 0, testString.length); + files.add(newFile); + for (MemoryFile file : files) { + try { + file.readBytes(testString, 0, 0, testString.length); + } catch (IOException e) { + // Expected + for (MemoryFile fileToClose : files) { + fileToClose.close(); + } + return; + } + } + } + } + + @SmallTest + public void testRun() throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); + + byte[] buffer = new byte[testString.length]; + + // check low level accessors + file.writeBytes(testString, 0, 2000, testString.length); + file.readBytes(buffer, 2000, 0, testString.length); + compareBuffers(testString, buffer, testString.length); + + // check streams + buffer = new byte[testString.length]; + + OutputStream os = file.getOutputStream(); + os.write(testString); + + InputStream is = file.getInputStream(); + is.mark(testString.length); + is.read(buffer); + compareBuffers(testString, buffer, testString.length); + + // test mark/reset + buffer = new byte[testString.length]; + is.reset(); + is.read(buffer); + compareBuffers(testString, buffer, testString.length); + + file.close(); + } + + // Tests for the IndexOutOfBoundsException cases in read(). + + private void readIndexOutOfBoundsException(int offset, int count, String msg) + throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", testString.length); + try { + file.writeBytes(testString, 0, 0, testString.length); + InputStream is = file.getInputStream(); + byte[] buffer = new byte[testString.length + 10]; + try { + is.read(buffer, offset, count); + fail(msg); + } catch (IndexOutOfBoundsException ex) { + // this is what should happen + } finally { + is.close(); + } + } finally { + file.close(); + } + } + + @SmallTest + public void testReadNegativeOffset() throws Exception { + readIndexOutOfBoundsException(-1, 5, + "read() with negative offset should throw IndexOutOfBoundsException"); + } + + @SmallTest + public void testReadNegativeCount() throws Exception { + readIndexOutOfBoundsException(5, -1, + "read() with negative length should throw IndexOutOfBoundsException"); + } + + @SmallTest + public void testReadOffsetOverflow() throws Exception { + readIndexOutOfBoundsException(testString.length + 10, 5, + "read() with offset outside buffer should throw IndexOutOfBoundsException"); + } + + @SmallTest + public void testReadOffsetCountOverflow() throws Exception { + readIndexOutOfBoundsException(testString.length, 11, + "read() with offset + count outside buffer should throw IndexOutOfBoundsException"); + } + + // Test behavior of read() at end of file + @SmallTest + public void testReadEOF() throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", testString.length); + try { + file.writeBytes(testString, 0, 0, testString.length); + InputStream is = file.getInputStream(); + try { + byte[] buffer = new byte[testString.length + 10]; + // read() with count larger than data should succeed, and return # of bytes read + assertEquals(testString.length, is.read(buffer)); + compareBuffers(testString, buffer, testString.length); + // Read at EOF should return -1 + assertEquals(-1, is.read()); + } finally { + is.close(); + } + } finally { + file.close(); + } + } + + // Tests that close() is idempotent + @SmallTest + public void testCloseClose() throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); + byte[] data = new byte[512]; + file.writeBytes(data, 0, 0, 128); + file.close(); + file.close(); + } + + // Tests that we can't read from a closed memory file + @SmallTest + public void testCloseRead() throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); + file.close(); + + try { + byte[] data = new byte[512]; + assertEquals(128, file.readBytes(data, 0, 0, 128)); + fail("readBytes() after close() did not throw IOException."); + } catch (IOException e) { + // this is what should happen + } + } + + // Tests that we can't write to a closed memory file + @SmallTest + public void testCloseWrite() throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); + file.close(); + + try { + byte[] data = new byte[512]; + file.writeBytes(data, 0, 0, 128); + fail("writeBytes() after close() did not throw IOException."); + } catch (IOException e) { + // this is what should happen + } + } + + // Tests that we can't call allowPurging() after close() + @SmallTest + public void testCloseAllowPurging() throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); + byte[] data = new byte[512]; + file.writeBytes(data, 0, 0, 128); + file.close(); + + try { + file.allowPurging(true); + fail("allowPurging() after close() did not throw IOException."); + } catch (IOException e) { + // this is what should happen + } + } + + // Tests that we don't leak file descriptors or mmap areas + @LargeTest + public void testCloseLeak() throws Exception { + // open enough memory files that we should run out of + // file descriptors or address space if we leak either. + for (int i = 0; i < 1025; i++) { + MemoryFile file = new MemoryFile("MemoryFileTest", 5000000); + file.writeBytes(testString, 0, 0, testString.length); + file.close(); + } + } + + @SmallTest + public void testIsMemoryFile() throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); + FileDescriptor fd = file.getFileDescriptor(); + assertNotNull(fd); + assertTrue(fd.valid()); + assertTrue(MemoryFile.isMemoryFile(fd)); + file.close(); + + assertFalse(MemoryFile.isMemoryFile(FileDescriptor.in)); + assertFalse(MemoryFile.isMemoryFile(FileDescriptor.out)); + assertFalse(MemoryFile.isMemoryFile(FileDescriptor.err)); + + File tempFile = File.createTempFile("MemoryFileTest",".tmp", getContext().getFilesDir()); + assertNotNull(file); + FileOutputStream out = null; + try { + out = new FileOutputStream(tempFile); + FileDescriptor fileFd = out.getFD(); + assertNotNull(fileFd); + assertFalse(MemoryFile.isMemoryFile(fileFd)); + } finally { + if (out != null) { + out.close(); + } + tempFile.delete(); + } + } + + @SmallTest + public void testFileDescriptor() throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); + MemoryFile ref = new MemoryFile(file.getFileDescriptor(), file.length(), "r"); + byte[] buffer; + + // write to original, read from reference + file.writeBytes(testString, 0, 2000, testString.length); + buffer = new byte[testString.length]; + ref.readBytes(buffer, 2000, 0, testString.length); + compareBuffers(testString, buffer, testString.length); + + file.close(); + ref.close(); // Doesn't actually do anything, since the file descriptor is not dup(2):ed + } + + private static final byte[] testString = new byte[] { + 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3, 2, 7, 9, 5, 0, 2, 8, 8, 4, 1, 9, 7, 1, 6, 9, 3, 9, 9, 3, 7, 5, 1, 0, 5, 8, 2, 0, 9, 7, 4, 9, 4, 4, 5, 9, 2, 3, 0, 7, 8, 1, 6, 4, + 0, 6, 2, 8, 6, 2, 0, 8, 9, 9, 8, 6, 2, 8, 0, 3, 4, 8, 2, 5, 3, 4, 2, 1, 1, 7, 0, 6, 7, 9, 8, 2, 1, 4, 8, 0, 8, 6, 5, 1, 3, 2, 8, 2, 3, 0, 6, 6, 4, 7, 0, 9, 3, 8, 4, 4, 6, 0, 9, 5, 5, 0, 5, 8, 2, 2, 3, 1, 7, 2, + 5, 3, 5, 9, 4, 0, 8, 1, 2, 8, 4, 8, 1, 1, 1, 7, 4, 5, 0, 2, 8, 4, 1, 0, 2, 7, 0, 1, 9, 3, 8, 5, 2, 1, 1, 0, 5, 5, 5, 9, 6, 4, 4, 6, 2, 2, 9, 4, 8, 9, 5, 4, 9, 3, 0, 3, 8, 1, 9, 6, 4, 4, 2, 8, 8, 1, 0, 9, 7, 5, + 6, 6, 5, 9, 3, 3, 4, 4, 6, 1, 2, 8, 4, 7, 5, 6, 4, 8, 2, 3, 3, 7, 8, 6, 7, 8, 3, 1, 6, 5, 2, 7, 1, 2, 0, 1, 9, 0, 9, 1, 4, 5, 6, 4, 8, 5, 6, 6, 9, 2, 3, 4, 6, 0, 3, 4, 8, 6, 1, 0, 4, 5, 4, 3, 2, 6, 6, 4, 8, 2, + 1, 3, 3, 9, 3, 6, 0, 7, 2, 6, 0, 2, 4, 9, 1, 4, 1, 2, 7, 3, 7, 2, 4, 5, 8, 7, 0, 0, 6, 6, 0, 6, 3, 1, 5, 5, 8, 8, 1, 7, 4, 8, 8, 1, 5, 2, 0, 9, 2, 0, 9, 6, 2, 8, 2, 9, 2, 5, 4, 0, 9, 1, 7, 1, 5, 3, 6, 4, 3, 6, + 7, 8, 9, 2, 5, 9, 0, 3, 6, 0, 0, 1, 1, 3, 3, 0, 5, 3, 0, 5, 4, 8, 8, 2, 0, 4, 6, 6, 5, 2, 1, 3, 8, 4, 1, 4, 6, 9, 5, 1, 9, 4, 1, 5, 1, 1, 6, 0, 9, 4, 3, 3, 0, 5, 7, 2, 7, 0, 3, 6, 5, 7, 5, 9, 5, 9, 1, 9, 5, 3, + 0, 9, 2, 1, 8, 6, 1, 1, 7, 3, 8, 1, 9, 3, 2, 6, 1, 1, 7, 9, 3, 1, 0, 5, 1, 1, 8, 5, 4, 8, 0, 7, 4, 4, 6, 2, 3, 7, 9, 9, 6, 2, 7, 4, 9, 5, 6, 7, 3, 5, 1, 8, 8, 5, 7, 5, 2, 7, 2, 4, 8, 9, 1, 2, 2, 7, 9, 3, 8, 1, + 8, 3, 0, 1, 1, 9, 4, 9, 1, 2, 9, 8, 3, 3, 6, 7, 3, 3, 6, 2, 4, 4, 0, 6, 5, 6, 6, 4, 3, 0, 8, 6, 0, 2, 1, 3, 9, 4, 9, 4, 6, 3, 9, 5, 2, 2, 4, 7, 3, 7, 1, 9, 0, 7, 0, 2, 1, 7, 9, 8, 6, 0, 9, 4, 3, 7, 0, 2, 7, 7, + 0, 5, 3, 9, 2, 1, 7, 1, 7, 6, 2, 9, 3, 1, 7, 6, 7, 5, 2, 3, 8, 4, 6, 7, 4, 8, 1, 8, 4, 6, 7, 6, 6, 9, 4, 0, 5, 1, 3, 2, 0, 0, 0, 5, 6, 8, 1, 2, 7, 1, 4, 5, 2, 6, 3, 5, 6, 0, 8, 2, 7, 7, 8, 5, 7, 7, 1, 3, 4, 2, + 7, 5, 7, 7, 8, 9, 6, 0, 9, 1, 7, 3, 6, 3, 7, 1, 7, 8, 7, 2, 1, 4, 6, 8, 4, 4, 0, 9, 0, 1, 2, 2, 4, 9, 5, 3, 4, 3, 0, 1, 4, 6, 5, 4, 9, 5, 8, 5, 3, 7, 1, 0, 5, 0, 7, 9, 2, 2, 7, 9, 6, 8, 9, 2, 5, 8, 9, 2, 3, 5, + 4, 2, 0, 1, 9, 9, 5, 6, 1, 1, 2, 1, 2, 9, 0, 2, 1, 9, 6, 0, 8, 6, 4, 0, 3, 4, 4, 1, 8, 1, 5, 9, 8, 1, 3, 6, 2, 9, 7, 7, 4, 7, 7, 1, 3, 0, 9, 9, 6, 0, 5, 1, 8, 7, 0, 7, 2, 1, 1, 3, 4, 9, 9, 9, 9, 9, 9, 8, 3, 7, + 2, 9, 7, 8, 0, 4, 9, 9, 5, 1, 0, 5, 9, 7, 3, 1, 7, 3, 2, 8, 1, 6, 0, 9, 6, 3, 1, 8, 5, 9, 5, 0, 2, 4, 4, 5, 9, 4, 5, 5, 3, 4, 6, 9, 0, 8, 3, 0, 2, 6, 4, 2, 5, 2, 2, 3, 0, 8, 2, 5, 3, 3, 4, 4, 6, 8, 5, 0, 3, 5, + 2, 6, 1, 9, 3, 1, 1, 8, 8, 1, 7, 1, 0, 1, 0, 0, 0, 3, 1, 3, 7, 8, 3, 8, 7, 5, 2, 8, 8, 6, 5, 8, 7, 5, 3, 3, 2, 0, 8, 3, 8, 1, 4, 2, 0, 6, 1, 7, 1, 7, 7, 6, 6, 9, 1, 4, 7, 3, 0, 3, 5, 9, 8, 2, 5, 3, 4, 9, 0, 4, + 2, 8, 7, 5, 5, 4, 6, 8, 7, 3, 1, 1, 5, 9, 5, 6, 2, 8, 6, 3, 8, 8, 2, 3, 5, 3, 7, 8, 7, 5, 9, 3, 7, 5, 1, 9, 5, 7, 7, 8, 1, 8, 5, 7, 7, 8, 0, 5, 3, 2, 1, 7, 1, 2, 2, 6, 8, 0, 6, 6, 1, 3, 0, 0, 1, 9, 2, 7, 8, 7, + 6, 6, 1, 1, 1, 9, 5, 9, 0, 9, 2, 1, 6, 4, 2, 0, 1, 9, 8, 9, 3, 8, 0, 9, 5, 2, 5, 7, 2, 0, 1, 0, 6, 5, 4, 8, 5, 8, 6, 3, 2, 7, 8, 8, 6, 5, 9, 3, 6, 1, 5, 3, 3, 8, 1, 8, 2, 7, 9, 6, 8, 2, 3, 0, 3, 0, 1, 9, 5, 2, + 0, 3, 5, 3, 0, 1, 8, 5, 2, 9, 6, 8, 9, 9, 5, 7, 7, 3, 6, 2, 2, 5, 9, 9, 4, 1, 3, 8, 9, 1, 2, 4, 9, 7, 2, 1, 7, 7, 5, 2, 8, 3, 4, 7, 9, 1, 3, 1, 5, 1, 5, 5, 7, 4, 8, 5, 7, 2, 4, 2, 4, 5, 4, 1, 5, 0, 6, 9, 5, 9, + 5, 0, 8, 2, 9, 5, 3, 3, 1, 1, 6, 8, 6, 1, 7, 2, 7, 8, 5, 5, 8, 8, 9, 0, 7, 5, 0, 9, 8, 3, 8, 1, 7, 5, 4, 6, 3, 7, 4, 6, 4, 9, 3, 9, 3, 1, 9, 2, 5, 5, 0, 6, 0, 4, 0, 0, 9, 2, 7, 7, 0, 1, 6, 7, 1, 1, 3, 9, 0, 0, + 9, 8, 4, 8, 8, 2, 4, 0, 1, 2, 8, 5, 8, 3, 6, 1, 6, 0, 3, 5, 6, 3, 7, 0, 7, 6, 6, 0, 1, 0, 4, 7, 1, 0, 1, 8, 1, 9, 4, 2, 9, 5, 5, 5, 9, 6, 1, 9, 8, 9, 4, 6, 7, 6, 7, 8, 3, 7, 4, 4, 9, 4, 4, 8, 2, 5, 5, 3, 7, 9, + 7, 7, 4, 7, 2, 6, 8, 4, 7, 1, 0, 4, 0, 4, 7, 5, 3, 4, 6, 4, 6, 2, 0, 8, 0, 4, 6, 6, 8, 4, 2, 5, 9, 0, 6, 9, 4, 9, 1, 2, 9, 3, 3, 1, 3, 6, 7, 7, 0, 2, 8, 9, 8, 9, 1, 5, 2, 1, 0, 4, 7, 5, 2, 1, 6, 2, 0, 5, 6, 9, + 6, 6, 0, 2, 4, 0, 5, 8, 0, 3, 8, 1, 5, 0, 1, 9, 3, 5, 1, 1, 2, 5, 3, 3, 8, 2, 4, 3, 0, 0, 3, 5, 5, 8, 7, 6, 4, 0, 2, 4, 7, 4, 9, 6, 4, 7, 3, 2, 6, 3, 9, 1, 4, 1, 9, 9, 2, 7, 2, 6, 0, 4, 2, 6, 9, 9, 2, 2, 7, 9, + 6, 7, 8, 2, 3, 5, 4, 7, 8, 1, 6, 3, 6, 0, 0, 9, 3, 4, 1, 7, 2, 1, 6, 4, 1, 2, 1, 9, 9, 2, 4, 5, 8, 6, 3, 1, 5, 0, 3, 0, 2, 8, 6, 1, 8, 2, 9, 7, 4, 5, 5, 5, 7, 0, 6, 7, 4, 9, 8, 3, 8, 5, 0, 5, 4, 9, 4, 5, 8, 8, + 5, 8, 6, 9, 2, 6, 9, 9, 5, 6, 9, 0, 9, 2, 7, 2, 1, 0, 7, 9, 7, 5, 0, 9, 3, 0, 2, 9, 5, 5, 3, 2, 1, 1, 6, 5, 3, 4, 4, 9, 8, 7, 2, 0, 2, 7, 5, 5, 9, 6, 0, 2, 3, 6, 4, 8, 0, 6, 6, 5, 4, 9, 9, 1, 1, 9, 8, 8, 1, 8, + 3, 4, 7, 9, 7, 7, 5, 3, 5, 6, 6, 3, 6, 9, 8, 0, 7, 4, 2, 6, 5, 4, 2, 5, 2, 7, 8, 6, 2, 5, 5, 1, 8, 1, 8, 4, 1, 7, 5, 7, 4, 6, 7, 2, 8, 9, 0, 9, 7, 7, 7, 7, 2, 7, 9, 3, 8, 0, 0, 0, 8, 1, 6, 4, 7, 0, 6, 0, 0, 1, + 6, 1, 4, 5, 2, 4, 9, 1, 9, 2, 1, 7, 3, 2, 1, 7, 2, 1, 4, 7, 7, 2, 3, 5, 0, 1, 4, 1, 4, 4, 1, 9, 7, 3, 5, 6, 8, 5, 4, 8, 1, 6, 1, 3, 6, 1, 1, 5, 7, 3, 5, 2, 5, 5, 2, 1, 3, 3, 4, 7, 5, 7, 4, 1, 8, 4, 9, 4, 6, 8, + 4, 3, 8, 5, 2, 3, 3, 2, 3, 9, 0, 7, 3, 9, 4, 1, 4, 3, 3, 3, 4, 5, 4, 7, 7, 6, 2, 4, 1, 6, 8, 6, 2, 5, 1, 8, 9, 8, 3, 5, 6, 9, 4, 8, 5, 5, 6, 2, 0, 9, 9, 2, 1, 9, 2, 2, 2, 1, 8, 4, 2, 7, 2, 5, 5, 0, 2, 5, 4, 2, + 5, 6, 8, 8, 7, 6, 7, 1, 7, 9, 0, 4, 9, 4, 6, 0, 1, 6, 5, 3, 4, 6, 6, 8, 0, 4, 9, 8, 8, 6, 2, 7, 2, 3, 2, 7, 9, 1, 7, 8, 6, 0, 8, 5, 7, 8, 4, 3, 8, 3, 8, 2, 7, 9, 6, 7, 9, 7, 6, 6, 8, 1, 4, 5, 4, 1, 0, 0, 9, 5, + 3, 8, 8, 3, 7, 8, 6, 3, 6, 0, 9, 5, 0, 6, 8, 0, 0, 6, 4, 2, 2, 5, 1, 2, 5, 2, 0, 5, 1, 1, 7, 3, 9, 2, 9, 8, 4, 8, 9, 6, 0, 8, 4, 1, 2, 8, 4, 8, 8, 6, 2, 6, 9, 4, 5, 6, 0, 4, 2, 4, 1, 9, 6, 5, 2, 8, 5, 0, 2, 2, + 2, 1, 0, 6, 6, 1, 1, 8, 6, 3, 0, 6, 7, 4, 4, 2, 7, 8, 6, 2, 2, 0, 3, 9, 1, 9, 4, 9, 4, 5, 0, 4, 7, 1, 2, 3, 7, 1, 3, 7, 8, 6, 9, 6, 0, 9, 5, 6, 3, 6, 4, 3, 7, 1, 9, 1, 7, 2, 8, 7, 4, 6, 7, 7, 6, 4, 6, 5, 7, 5, + 7, 3, 9, 6, 2, 4, 1, 3, 8, 9, 0, 8, 6, 5, 8, 3, 2, 6, 4, 5, 9, 9, 5, 8, 1, 3, 3, 9, 0, 4, 7, 8, 0, 2, 7, 5, 9, 0, 0, 9, 9, 4, 6, 5, 7, 6, 4, 0, 7, 8, 9, 5, 1, 2, 6, 9, 4, 6, 8, 3, 9, 8, 3, 5, 2, 5, 9, 5, 7, 0, + 9, 8, 2, 5, 8, 2, 2, 6, 2, 0, 5, 2, 2, 4, 8, 9, 4, 0, 7, 7, 2, 6, 7, 1, 9, 4, 7, 8, 2, 6, 8, 4, 8, 2, 6, 0, 1, 4, 7, 6, 9, 9, 0, 9, 0, 2, 6, 4, 0, 1, 3, 6, 3, 9, 4, 4, 3, 7, 4, 5, 5, 3, 0, 5, 0, 6, 8, 2, 0, 3, + 4, 9, 6, 2, 5, 2, 4, 5, 1, 7, 4, 9, 3, 9, 9, 6, 5, 1, 4, 3, 1, 4, 2, 9, 8, 0, 9, 1, 9, 0, 6, 5, 9, 2, 5, 0, 9, 3, 7, 2, 2, 1, 6, 9, 6, 4, 6, 1, 5, 1, 5, 7, 0, 9, 8, 5, 8, 3, 8, 7, 4, 1, 0, 5, 9, 7, 8, 8, 5, 9, + 5, 9, 7, 7, 2, 9, 7, 5, 4, 9, 8, 9, 3, 0, 1, 6, 1, 7, 5, 3, 9, 2, 8, 4, 6, 8, 1, 3, 8, 2, 6, 8, 6, 8, 3, 8, 6, 8, 9, 4, 2, 7, 7, 4, 1, 5, 5, 9, 9, 1, 8, 5, 5, 9, 2, 5, 2, 4, 5, 9, 5, 3, 9, 5, 9, 4, 3, 1, 0, 4, + 9, 9, 7, 2, 5, 2, 4, 6, 8, 0, 8, 4, 5, 9, 8, 7, 2, 7, 3, 6, 4, 4, 6, 9, 5, 8, 4, 8, 6, 5, 3, 8, 3, 6, 7, 3, 6, 2, 2, 2, 6, 2, 6, 0, 9, 9, 1, 2, 4, 6, 0, 8, 0, 5, 1, 2, 4, 3, 8, 8, 4, 3, 9, 0, 4, 5, 1, 2, 4, 4, + 1, 3, 6, 5, 4, 9, 7, 6, 2, 7, 8, 0, 7, 9, 7, 7, 1, 5, 6, 9, 1, 4, 3, 5, 9, 9, 7, 7, 0, 0, 1, 2, 9, 6, 1, 6, 0, 8, 9, 4, 4, 1, 6, 9, 4, 8, 6, 8, 5, 5, 5, 8, 4, 8, 4, 0, 6, 3, 5, 3, 4, 2, 2, 0, 7, 2, 2, 2, 5, 8, + 2, 8, 4, 8, 8, 6, 4, 8, 1, 5, 8, 4, 5, 6, 0, 2, 8, 5, 0, 6, 0, 1, 6, 8, 4, 2, 7, 3, 9, 4, 5, 2, 2, 6, 7, 4, 6, 7, 6, 7, 8, 8, 9, 5, 2, 5, 2, 1, 3, 8, 5, 2, 2, 5, 4, 9, 9, 5, 4, 6, 6, 6, 7, 2, 7, 8, 2, 3, 9, 8, + 6, 4, 5, 6, 5, 9, 6, 1, 1, 6, 3, 5, 4, 8, 8, 6, 2, 3, 0, 5, 7, 7, 4, 5, 6, 4, 9, 8, 0, 3, 5, 5, 9, 3, 6, 3, 4, 5, 6, 8, 1, 7, 4, 3, 2, 4, 1, 1, 2, 5, 1, 5, 0, 7, 6, 0, 6, 9, 4, 7, 9, 4, 5, 1, 0, 9, 6, 5, 9, 6, + 0, 9, 4, 0, 2, 5, 2, 2, 8, 8, 7, 9, 7, 1, 0, 8, 9, 3, 1, 4, 5, 6, 6, 9, 1, 3, 6, 8, 6, 7, 2, 2, 8, 7, 4, 8, 9, 4, 0, 5, 6, 0, 1, 0, 1, 5, 0, 3, 3, 0, 8, 6, 1, 7, 9, 2, 8, 6, 8, 0, 9, 2, 0, 8, 7, 4, 7, 6, 0, 9, + 1, 7, 8, 2, 4, 9, 3, 8, 5, 8, 9, 0, 0, 9, 7, 1, 4, 9, 0, 9, 6, 7, 5, 9, 8, 5, 2, 6, 1, 3, 6, 5, 5, 4, 9, 7, 8, 1, 8, 9, 3, 1, 2, 9, 7, 8, 4, 8, 2, 1, 6, 8, 2, 9, 9, 8, 9, 4, 8, 7, 2, 2, 6, 5, 8, 8, 0, 4, 8, 5, + 7, 5, 6, 4, 0, 1, 4, 2, 7, 0, 4, 7, 7, 5, 5, 5, 1, 3, 2, 3, 7, 9, 6, 4, 1, 4, 5, 1, 5, 2, 3, 7, 4, 6, 2, 3, 4, 3, 6, 4, 5, 4, 2, 8, 5, 8, 4, 4, 4, 7, 9, 5, 2, 6, 5, 8, 6, 7, 8, 2, 1, 0, 5, 1, 1, 4, 1, 3, 5, 4, + 7, 3, 5, 7, 3, 9, 5, 2, 3, 1, 1, 3, 4, 2, 7, 1, 6, 6, 1, 0, 2, 1, 3, 5, 9, 6, 9, 5, 3, 6, 2, 3, 1, 4, 4, 2, 9, 5, 2, 4, 8, 4, 9, 3, 7, 1, 8, 7, 1, 1, 0, 1, 4, 5, 7, 6, 5, 4, 0, 3, 5, 9, 0, 2, 7, 9, 9, 3, 4, 4, + 0, 3, 7, 4, 2, 0, 0, 7, 3, 1, 0, 5, 7, 8, 5, 3, 9, 0, 6, 2, 1, 9, 8, 3, 8, 7, 4, 4, 7, 8, 0, 8, 4, 7, 8, 4, 8, 9, 6, 8, 3, 3, 2, 1, 4, 4, 5, 7, 1, 3, 8, 6, 8, 7, 5, 1, 9, 4, 3, 5, 0, 6, 4, 3, 0, 2, 1, 8, 4, 5, + 3, 1, 9, 1, 0, 4, 8, 4, 8, 1, 0, 0, 5, 3, 7, 0, 6, 1, 4, 6, 8, 0, 6, 7, 4, 9, 1, 9, 2, 7, 8, 1, 9, 1, 1, 9, 7, 9, 3, 9, 9, 5, 2, 0, 6, 1, 4, 1, 9, 6, 6, 3, 4, 2, 8, 7, 5, 4, 4, 4, 0, 6, 4, 3, 7, 4, 5, 1, 2, 3, + 7, 1, 8, 1, 9, 2, 1, 7, 9, 9, 9, 8, 3, 9, 1, 0, 1, 5, 9, 1, 9, 5, 6, 1, 8, 1, 4, 6, 7, 5, 1, 4, 2, 6, 9, 1, 2, 3, 9, 7, 4, 8, 9, 4, 0, 9, 0, 7, 1, 8, 6, 4, 9, 4, 2, 3, 1, 9, 6, 1, 5, 6, 7, 9, 4, 5, 2, 0, 8, 0, + 9, 5, 1, 4, 6, 5, 5, 0, 2, 2, 5, 2, 3, 1, 6, 0, 3, 8, 8, 1, 9, 3, 0, 1, 4, 2, 0, 9, 3, 7, 6, 2, 1, 3, 7, 8, 5, 5, 9, 5, 6, 6, 3, 8, 9, 3, 7, 7, 8, 7, 0, 8, 3, 0, 3, 9, 0, 6, 9, 7, 9, 2, 0, 7, 7, 3, 4, 6, 7, 2, + 2, 1, 8, 2, 5, 6, 2, 5, 9, 9, 6, 6, 1, 5, 0, 1, 4, 2, 1, 5, 0, 3, 0, 6, 8, 0, 3, 8, 4, 4, 7, 7, 3, 4, 5, 4, 9, 2, 0, 2, 6, 0, 5, 4, 1, 4, 6, 6, 5, 9, 2, 5, 2, 0, 1, 4, 9, 7, 4, 4, 2, 8, 5, 0, 7, 3, 2, 5, 1, 8, + 6, 6, 6, 0, 0, 2, 1, 3, 2, 4, 3, 4, 0, 8, 8, 1, 9, 0, 7, 1, 0, 4, 8, 6, 3, 3, 1, 7, 3, 4, 6, 4, 9, 6, 5, 1, 4, 5, 3, 9, 0, 5, 7, 9, 6, 2, 6, 8, 5, 6, 1, 0, 0, 5, 5, 0, 8, 1, 0, 6, 6, 5, 8, 7, 9, 6, 9, 9, 8, 1, + 6, 3, 5, 7, 4, 7, 3, 6, 3, 8, 4, 0, 5, 2, 5, 7, 1, 4, 5, 9, 1, 0, 2, 8, 9, 7, 0, 6, 4, 1, 4, 0, 1, 1, 0, 9, 7, 1, 2, 0, 6, 2, 8, 0, 4, 3, 9, 0, 3, 9, 7, 5, 9, 5, 1, 5, 6, 7, 7, 1, 5, 7, 7, 0, 0, 4, 2, 0, 3, 3, + 7, 8, 6, 9, 9, 3, 6, 0, 0, 7, 2, 3, 0, 5, 5, 8, 7, 6, 3, 1, 7, 6, 3, 5, 9, 4, 2, 1, 8, 7, 3, 1, 2, 5, 1, 4, 7, 1, 2, 0, 5, 3, 2, 9, 2, 8, 1, 9, 1, 8, 2, 6, 1, 8, 6, 1, 2, 5, 8, 6, 7, 3, 2, 1, 5, 7, 9, 1, 9, 8, + 4, 1, 4, 8, 4, 8, 8, 2, 9, 1, 6, 4, 4, 7, 0, 6, 0, 9, 5, 7, 5, 2, 7, 0, 6, 9, 5, 7, 2, 2, 0, 9, 1, 7, 5, 6, 7, 1, 1, 6, 7, 2, 2, 9, 1, 0, 9, 8, 1, 6, 9, 0, 9, 1, 5, 2, 8, 0, 1, 7, 3, 5, 0, 6, 7, 1, 2, 7, 4, 8, + 5, 8, 3, 2, 2, 2, 8, 7, 1, 8, 3, 5, 2, 0, 9, 3, 5, 3, 9, 6, 5, 7, 2, 5, 1, 2, 1, 0, 8, 3, 5, 7, 9, 1, 5, 1, 3, 6, 9, 8, 8, 2, 0, 9, 1, 4, 4, 4, 2, 1, 0, 0, 6, 7, 5, 1, 0, 3, 3, 4, 6, 7, 1, 1, 0, 3, 1, 4, 1, 2, + 6, 7, 1, 1, 1, 3, 6, 9, 9, 0, 8, 6, 5, 8, 5, 1, 6, 3, 9, 8, 3, 1, 5, 0, 1, 9, 7, 0, 1, 6, 5, 1, 5, 1, 1, 6, 8, 5, 1, 7, 1, 4, 3, 7, 6, 5, 7, 6, 1, 8, 3, 5, 1, 5, 5, 6, 5, 0, 8, 8, 4, 9, 0, 9, 9, 8, 9, 8, 5, 9, + 9, 8, 2, 3, 8, 7, 3, 4, 5, 5, 2, 8, 3, 3, 1, 6, 3, 5, 5, 0, 7, 6, 4, 7, 9, 1, 8, 5, 3, 5, 8, 9, 3, 2, 2, 6, 1, 8, 5, 4, 8, 9, 6, 3, 2, 1, 3, 2, 9, 3, 3, 0, 8, 9, 8, 5, 7, 0, 6, 4, 2, 0, 4, 6, 7, 5, 2, 5, 9, 0, + 7, 0, 9, 1, 5, 4, 8, 1, 4, 1, 6, 5, 4, 9, 8, 5, 9, 4, 6, 1, 6, 3, 7, 1, 8, 0, 2, 7, 0, 9, 8, 1, 9, 9, 4, 3, 0, 9, 9, 2, 4, 4, 8, 8, 9, 5, 7, 5, 7, 1, 2, 8, 2, 8, 9, 0, 5, 9, 2, 3, 2, 3, 3, 2, 6, 0, 9, 7, 2, 9, + 9, 7, 1, 2, 0, 8, 4, 4, 3, 3, 5, 7, 3, 2, 6, 5, 4, 8, 9, 3, 8, 2, 3, 9, 1, 1, 9, 3, 2, 5, 9, 7, 4, 6, 3, 6, 6, 7, 3, 0, 5, 8, 3, 6, 0, 4, 1, 4, 2, 8, 1, 3, 8, 8, 3, 0, 3, 2, 0, 3, 8, 2, 4, 9, 0, 3, 7, 5, 8, 9, + 8, 5, 2, 4, 3, 7, 4, 4, 1, 7, 0, 2, 9, 1, 3, 2, 7, 6, 5, 6, 1, 8, 0, 9, 3, 7, 7, 3, 4, 4, 4, 0, 3, 0, 7, 0, 7, 4, 6, 9, 2, 1, 1, 2, 0, 1, 9, 1, 3, 0, 2, 0, 3, 3, 0, 3, 8, 0, 1, 9, 7, 6, 2, 1, 1, 0, 1, 1, 0, 0, + 4, 4, 9, 2, 9, 3, 2, 1, 5, 1, 6, 0, 8, 4, 2, 4, 4, 4, 8, 5, 9, 6, 3, 7, 6, 6, 9, 8, 3, 8, 9, 5, 2, 2, 8, 6, 8, 4, 7, 8, 3, 1, 2, 3, 5, 5, 2, 6, 5, 8, 2, 1, 3, 1, 4, 4, 9, 5, 7, 6, 8, 5, 7, 2, 6, 2, 4, 3, 3, 4, + 4, 1, 8, 9, 3, 0, 3, 9, 6, 8, 6, 4, 2, 6, 2, 4, 3, 4, 1, 0, 7, 7, 3, 2, 2, 6, 9, 7, 8, 0, 2, 8, 0, 7, 3, 1, 8, 9, 1, 5, 4, 4, 1, 1, 0, 1, 0, 4, 4, 6, 8, 2, 3, 2, 5, 2, 7, 1, 6, 2, 0, 1, 0, 5, 2, 6, 5, 2, 2, 7, + 2, 1, 1, 1, 6, 6, 0, 3, 9, 6, 6, 6, 5, 5, 7, 3, 0, 9, 2, 5, 4, 7, 1, 1, 0, 5, 5, 7, 8, 5, 3, 7, 6, 3, 4, 6, 6, 8, 2, 0, 6, 5, 3, 1, 0, 9, 8, 9, 6, 5, 2, 6, 9, 1, 8, 6, 2, 0, 5, 6, 4, 7, 6, 9, 3, 1, 2, 5, 7, 0, + 5, 8, 6, 3, 5, 6, 6, 2, 0, 1, 8, 5, 5, 8, 1, 0, 0, 7, 2, 9, 3, 6, 0, 6, 5, 9, 8, 7, 6, 4, 8, 6, 1, 1, 7, 9, 1, 0, 4, 5, 3, 3, 4, 8, 8, 5, 0, 3, 4, 6, 1, 1, 3, 6, 5, 7, 6, 8, 6, 7, 5, 3, 2, 4, 9, 4, 4, 1, 6, 6, + 8, 0, 3, 9, 6, 2, 6, 5, 7, 9, 7, 8, 7, 7, 1, 8, 5, 5, 6, 0, 8, 4, 5, 5, 2, 9, 6, 5, 4, 1, 2, 6, 6, 5, 4, 0, 8, 5, 3, 0, 6, 1, 4, 3, 4, 4, 4, 3, 1, 8, 5, 8, 6, 7, 6, 9, 7, 5, 1, 4, 5, 6, 6, 1, 4, 0, 6, 8, 0, 0, + 7, 0, 0, 2, 3, 7, 8, 7, 7, 6, 5, 9, 1, 3, 4, 4, 0, 1, 7, 1, 2, 7, 4, 9, 4, 7, 0, 4, 2, 0, 5, 6, 2, 2, 3, 0, 5, 3, 8, 9, 9, 4, 5, 6, 1, 3, 1, 4, 0, 7, 1, 1, 2, 7, 0, 0, 0, 4, 0, 7, 8, 5, 4, 7, 3, 3, 2, 6, 9, 9, + 3, 9, 0, 8, 1, 4, 5, 4, 6, 6, 4, 6, 4, 5, 8, 8, 0, 7, 9, 7, 2, 7, 0, 8, 2, 6, 6, 8, 3, 0, 6, 3, 4, 3, 2, 8, 5, 8, 7, 8, 5, 6, 9, 8, 3, 0, 5, 2, 3, 5, 8, 0, 8, 9, 3, 3, 0, 6, 5, 7, 5, 7, 4, 0, 6, 7, 9, 5, 4, 5, + 7, 1, 6, 3, 7, 7, 5, 2, 5, 4, 2, 0, 2, 1, 1, 4, 9, 5, 5, 7, 6, 1, 5, 8, 1, 4, 0, 0, 2, 5, 0, 1, 2, 6, 2, 2, 8, 5, 9, 4, 1, 3, 0, 2, 1, 6, 4, 7, 1, 5, 5, 0, 9, 7, 9, 2, 5, 9, 2, 3, 0, 9, 9, 0, 7, 9, 6, 5, 4, 7, + 3, 7, 6, 1, 2, 5, 5, 1, 7, 6, 5, 6, 7, 5, 1, 3, 5, 7, 5, 1, 7, 8, 2, 9, 6, 6, 6, 4, 5, 4, 7, 7, 9, 1, 7, 4, 5, 0, 1, 1, 2, 9, 9, 6, 1, 4, 8, 9, 0, 3, 0, 4, 6, 3, 9, 9, 4, 7, 1, 3, 2, 9, 6, 2, 1, 0, 7, 3, 4, 0, + 4, 3, 7, 5, 1, 8, 9, 5, 7, 3, 5, 9, 6, 1, 4, 5, 8, 9, 0, 1, 9, 3, 8, 9, 7, 1, 3, 1, 1, 1, 7, 9, 0, 4, 2, 9, 7, 8, 2, 8, 5, 6, 4, 7, 5, 0, 3, 2, 0, 3, 1, 9, 8, 6, 9, 1, 5, 1, 4, 0, 2, 8, 7, 0, 8, 0, 8, 5, 9, 9, + 0, 4, 8, 0, 1, 0, 9, 4, 1, 2, 1, 4, 7, 2, 2, 1, 3, 1, 7, 9, 4, 7, 6, 4, 7, 7, 7, 2, 6, 2, 2, 4, 1, 4, 2, 5, 4, 8, 5, 4, 5, 4, 0, 3, 3, 2, 1, 5, 7, 1, 8, 5, 3, 0, 6, 1, 4, 2, 2, 8, 8, 1, 3, 7, 5, 8, 5, 0, 4, 3, + 0, 6, 3, 3, 2, 1, 7, 5, 1, 8, 2, 9, 7, 9, 8, 6, 6, 2, 2, 3, 7, 1, 7, 2, 1, 5, 9, 1, 6, 0, 7, 7, 1, 6, 6, 9, 2, 5, 4, 7, 4, 8, 7, 3, 8, 9, 8, 6, 6, 5, 4, 9, 4, 9, 4, 5, 0, 1, 1, 4, 6, 5, 4, 0, 6, 2, 8, 4, 3, 3, + 6, 6, 3, 9, 3, 7, 9, 0, 0, 3, 9, 7, 6, 9, 2, 6, 5, 6, 7, 2, 1, 4, 6, 3, 8, 5, 3, 0, 6, 7, 3, 6, 0, 9, 6, 5, 7, 1, 2, 0, 9, 1, 8, 0, 7, 6, 3, 8, 3, 2, 7, 1, 6, 6, 4, 1, 6, 2, 7, 4, 8, 8, 8, 8, 0, 0, 7, 8, 6, 9, + 2, 5, 6, 0, 2, 9, 0, 2, 2, 8, 4, 7, 2, 1, 0, 4, 0, 3, 1, 7, 2, 1, 1, 8, 6, 0, 8, 2, 0, 4, 1, 9, 0, 0, 0, 4, 2, 2, 9, 6, 6, 1, 7, 1, 1, 9, 6, 3, 7, 7, 9, 2, 1, 3, 3, 7, 5, 7, 5, 1, 1, 4, 9, 5, 9, 5, 0, 1, 5, 6, + 6, 0, 4, 9, 6, 3, 1, 8, 6, 2, 9, 4, 7, 2, 6, 5, 4, 7, 3, 6, 4, 2, 5, 2, 3, 0, 8, 1, 7, 7, 0, 3, 6, 7, 5, 1, 5, 9, 0, 6, 7, 3, 5, 0, 2, 3, 5, 0, 7, 2, 8, 3, 5, 4, 0, 5, 6, 7, 0, 4, 0, 3, 8, 6, 7, 4, 3, 5, 1, 3, + 6, 2, 2, 2, 2, 4, 7, 7, 1, 5, 8, 9, 1, 5, 0, 4, 9, 5, 3, 0, 9, 8, 4, 4, 4, 8, 9, 3, 3, 3, 0, 9, 6, 3, 4, 0, 8, 7, 8, 0, 7, 6, 9, 3, 2, 5, 9, 9, 3, 9, 7, 8, 0, 5, 4, 1, 9, 3, 4, 1, 4, 4, 7, 3, 7, 7, 4, 4, 1, 8, + 4, 2, 6, 3, 1, 2, 9, 8, 6, 0, 8, 0, 9, 9, 8, 8, 8, 6, 8, 7, 4, 1, 3, 2, 6, 0, 4, 7, 2, 1, 5, 6, 9, 5, 1, 6, 2, 3, 9, 6, 5, 8, 6, 4, 5, 7, 3, 0, 2, 1, 6, 3, 1, 5, 9, 8, 1, 9, 3, 1, 9, 5, 1, 6, 7, 3, 5, 3, 8, 1, + 2, 9, 7, 4, 1, 6, 7, 7, 2, 9, 4, 7, 8, 6, 7, 2, 4, 2, 2, 9, 2, 4, 6, 5, 4, 3, 6, 6, 8, 0, 0, 9, 8, 0, 6, 7, 6, 9, 2, 8, 2, 3, 8, 2, 8, 0, 6, 8, 9, 9, 6, 4, 0, 0, 4, 8, 2, 4, 3, 5, 4, 0, 3, 7, 0, 1, 4, 1, 6, 3, + 1, 4, 9, 6, 5, 8, 9, 7, 9, 4, 0, 9, 2, 4, 3, 2, 3, 7, 8, 9, 6, 9, 0, 7, 0, 6, 9, 7, 7, 9, 4, 2, 2, 3, 6, 2, 5, 0, 8, 2, 2, 1, 6, 8, 8, 9, 5, 7, 3, 8, 3, 7, 9, 8, 6, 2, 3, 0, 0, 1, 5, 9, 3, 7, 7, 6, 4, 7, 1, 6, + 5, 1, 2, 2, 8, 9, 3, 5, 7, 8, 6, 0, 1, 5, 8, 8, 1, 6, 1, 7, 5, 5, 7, 8, 2, 9, 7, 3, 5, 2, 3, 3, 4, 4, 6, 0, 4, 2, 8, 1, 5, 1, 2, 6, 2, 7, 2, 0, 3, 7, 3, 4, 3, 1, 4, 6, 5, 3, 1, 9, 7, 7, 7, 7, 4, 1, 6, 0, 3, 1, + 9, 9, 0, 6, 6, 5, 5, 4, 1, 8, 7, 6, 3, 9, 7, 9, 2, 9, 3, 3, 4, 4, 1, 9, 5, 2, 1, 5, 4, 1, 3, 4, 1, 8, 9, 9, 4, 8, 5, 4, 4, 4, 7, 3, 4, 5, 6, 7, 3, 8, 3, 1, 6, 2, 4, 9, 9, 3, 4, 1, 9, 1, 3, 1, 8, 1, 4, 8, 0, 9, + 2, 7, 7, 7, 7, 1, 0, 3, 8, 6, 3, 8, 7, 7, 3, 4, 3, 1, 7, 7, 2, 0, 7, 5, 4, 5, 6, 5, 4, 5, 3, 2, 2, 0, 7, 7, 7, 0, 9, 2, 1, 2, 0, 1, 9, 0, 5, 1, 6, 6, 0, 9, 6, 2, 8, 0, 4, 9, 0, 9, 2, 6, 3, 6, 0, 1, 9, 7, 5, 9, + 8, 8, 2, 8, 1, 6, 1, 3, 3, 2, 3, 1, 6, 6, 6, 3, 6, 5, 2, 8, 6, 1, 9, 3, 2, 6, 6, 8, 6, 3, 3, 6, 0, 6, 2, 7, 3, 5, 6, 7, 6, 3, 0, 3, 5, 4, 4, 7, 7, 6, 2, 8, 0, 3, 5, 0, 4, 5, 0, 7, 7, 7, 2, 3, 5, 5, 4, 7, 1, 0, + 5, 8, 5, 9, 5, 4, 8, 7, 0, 2, 7, 9, 0, 8, 1, 4, 3, 5, 6, 2, 4, 0, 1, 4, 5, 1, 7, 1, 8, 0, 6, 2, 4, 6, 4, 3, 6, 2, 6, 7, 9, 4, 5, 6, 1, 2, 7, 5, 3, 1, 8, 1, 3, 4, 0, 7, 8, 3, 3, 0, 3, 3, 6, 2, 5, 4, 2, 3, 2, 7, + 8, 3, 9, 4, 4, 9, 7, 5, 3, 8, 2, 4, 3, 7, 2, 0, 5, 8, 3, 5, 3, 1, 1, 4, 7, 7, 1, 1, 9, 9, 2, 6, 0, 6, 3, 8, 1, 3, 3, 4, 6, 7, 7, 6, 8, 7, 9, 6, 9, 5, 9, 7, 0, 3, 0, 9, 8, 3, 3, 9, 1, 3, 0, 7, 7, 1, 0, 9, 8, 7, + 0, 4, 0, 8, 5, 9, 1, 3, 3, 7, 4, 6, 4, 1, 4, 4, 2, 8, 2, 2, 7, 7, 2, 6, 3, 4, 6, 5, 9, 4, 7, 0, 4, 7, 4, 5, 8, 7, 8, 4, 7, 7, 8, 7, 2, 0, 1, 9, 2, 7, 7, 1, 5, 2, 8, 0, 7, 3, 1, 7, 6, 7, 9, 0, 7, 7, 0, 7, 1, 5, + 7, 2, 1, 3, 4, 4, 4, 7, 3, 0, 6, 0, 5, 7, 0, 0, 7, 3, 3, 4, 9, 2, 4, 3, 6, 9, 3, 1, 1, 3, 8, 3, 5, 0, 4, 9, 3, 1, 6, 3, 1, 2, 8, 4, 0, 4, 2, 5, 1, 2, 1, 9, 2, 5, 6, 5, 1, 7, 9, 8, 0, 6, 9, 4, 1, 1, 3, 5, 2, 8, + 0, 1, 3, 1, 4, 7, 0, 1, 3, 0, 4, 7, 8, 1, 6, 4, 3, 7, 8, 8, 5, 1, 8, 5, 2, 9, 0, 9, 2, 8, 5, 4, 5, 2, 0, 1, 1, 6, 5, 8, 3, 9, 3, 4, 1, 9, 6, 5, 6, 2, 1, 3, 4, 9, 1, 4, 3, 4, 1, 5, 9, 5, 6, 2, 5, 8, 6, 5, 8, 6, + 5, 5, 7, 0, 5, 5, 2, 6, 9, 0, 4, 9, 6, 5, 2, 0, 9, 8, 5, 8, 0, 3, 3, 8, 5, 0, 7, 2, 2, 4, 2, 6, 4, 8, 2, 9, 3, 9, 7, 2, 8, 5, 8, 4, 7, 8, 3, 1, 6, 3, 0, 5, 7, 7, 7, 7, 5, 6, 0, 6, 8, 8, 8, 7, 6, 4, 4, 6, 2, 4, + 8, 2, 4, 6, 8, 5, 7, 9, 2, 6, 0, 3, 9, 5, 3, 5, 2, 7, 7, 3, 4, 8, 0, 3, 0, 4, 8, 0, 2, 9, 0, 0, 5, 8, 7, 6, 0, 7, 5, 8, 2, 5, 1, 0, 4, 7, 4, 7, 0, 9, 1, 6, 4, 3, 9, 6, 1, 3, 6, 2, 6, 7, 6, 0, 4, 4, 9, 2, 5, 6, + 2, 7, 4, 2, 0, 4, 2, 0, 8, 3, 2, 0, 8, 5, 6, 6, 1, 1, 9, 0, 6, 2, 5, 4, 5, 4, 3, 3, 7, 2, 1, 3, 1, 5, 3, 5, 9, 5, 8, 4, 5, 0, 6, 8, 7, 7, 2, 4, 6, 0, 2, 9, 0, 1, 6, 1, 8, 7, 6, 6, 7, 9, 5, 2, 4, 0, 6, 1, 6, 3, + 4, 2, 5, 2, 2, 5, 7, 7, 1, 9, 5, 4, 2, 9, 1, 6, 2, 9, 9, 1, 9, 3, 0, 6, 4, 5, 5, 3, 7, 7, 9, 9, 1, 4, 0, 3, 7, 3, 4, 0, 4, 3, 2, 8, 7, 5, 2, 6, 2, 8, 8, 8, 9, 6, 3, 9, 9, 5, 8, 7, 9, 4, 7, 5, 7, 2, 9, 1, 7, 4, + 6, 4, 2, 6, 3, 5, 7, 4, 5, 5, 2, 5, 4, 0, 7, 9, 0, 9, 1, 4, 5, 1, 3, 5, 7, 1, 1, 1, 3, 6, 9, 4, 1, 0, 9, 1, 1, 9, 3, 9, 3, 2, 5, 1, 9, 1, 0, 7, 6, 0, 2, 0, 8, 2, 5, 2, 0, 2, 6, 1, 8, 7, 9, 8, 5, 3, 1, 8, 8, 7, + 7, 0, 5, 8, 4, 2, 9, 7, 2, 5, 9, 1, 6, 7, 7, 8, 1, 3, 1, 4, 9, 6, 9, 9, 0, 0, 9, 0, 1, 9, 2, 1, 1, 6, 9, 7, 1, 7, 3, 7, 2, 7, 8, 4, 7, 6, 8, 4, 7, 2, 6, 8, 6, 0, 8, 4, 9, 0, 0, 3, 3, 7, 7, 0, 2, 4, 2, 4, 2, 9, + 1, 6, 5, 1, 3, 0, 0, 5, 0, 0, 5, 1, 6, 8, 3, 2, 3, 3, 6, 4, 3, 5, 0, 3, 8, 9, 5, 1, 7, 0, 2, 9, 8, 9, 3, 9, 2, 2, 3, 3, 4, 5, 1, 7, 2, 2, 0, 1, 3, 8, 1, 2, 8, 0, 6, 9, 6, 5, 0, 1, 1, 7, 8, 4, 4, 0, 8, 7, 4, 5, + 1, 9, 6, 0, 1, 2, 1, 2, 2, 8, 5, 9, 9, 3, 7, 1, 6, 2, 3, 1, 3, 0, 1, 7, 1, 1, 4, 4, 4, 8, 4, 6, 4, 0, 9, 0, 3, 8, 9, 0, 6, 4, 4, 9, 5, 4, 4, 4, 0, 0, 6, 1, 9, 8, 6, 9, 0, 7, 5, 4, 8, 5, 1, 6, 0, 2, 6, 3, 2, 7, + 5, 0, 5, 2, 9, 8, 3, 4, 9, 1, 8, 7, 4, 0, 7, 8, 6, 6, 8, 0, 8, 8, 1, 8, 3, 3, 8, 5, 1, 0, 2, 2, 8, 3, 3, 4, 5, 0, 8, 5, 0, 4, 8, 6, 0, 8, 2, 5, 0, 3, 9, 3, 0, 2, 1, 3, 3, 2, 1, 9, 7, 1, 5, 5, 1, 8, 4, 3, 0, 6, + 3, 5, 4, 5, 5, 0, 0, 7, 6, 6, 8, 2, 8, 2, 9, 4, 9, 3, 0, 4, 1, 3, 7, 7, 6, 5, 5, 2, 7, 9, 3, 9, 7, 5, 1, 7, 5, 4, 6, 1, 3, 9, 5, 3, 9, 8, 4, 6, 8, 3, 3, 9, 3, 6, 3, 8, 3, 0, 4, 7, 4, 6, 1, 1, 9, 9, 6, 6, 5, 3, + 8, 5, 8, 1, 5, 3, 8, 4, 2, 0, 5, 6, 8, 5, 3, 3, 8, 6, 2, 1, 8, 6, 7, 2, 5, 2, 3, 3, 4, 0, 2, 8, 3, 0, 8, 7, 1, 1, 2, 3, 2, 8, 2, 7, 8, 9, 2, 1, 2, 5, 0, 7, 7, 1, 2, 6, 2, 9, 4, 6, 3, 2, 2, 9, 5, 6, 3, 9, 8, 9, + 8, 9, 8, 9, 3, 5, 8, 2, 1, 1, 6, 7, 4, 5, 6, 2, 7, 0, 1, 0, 2, 1, 8, 3, 5, 6, 4, 6, 2, 2, 0, 1, 3, 4, 9, 6, 7, 1, 5, 1, 8, 8, 1, 9, 0, 9, 7, 3, 0, 3, 8, 1, 1, 9, 8, 0, 0, 4, 9, 7, 3, 4, 0, 7, 2, 3, 9, 6, 1, 0, + 3, 6, 8, 5, 4, 0, 6, 6, 4, 3, 1, 9, 3, 9, 5, 0, 9, 7, 9, 0, 1, 9, 0, 6, 9, 9, 6, 3, 9, 5, 5, 2, 4, 5, 3, 0, 0, 5, 4, 5, 0, 5, 8, 0, 6, 8, 5, 5, 0, 1, 9, 5, 6, 7, 3, 0, 2, 2, 9, 2, 1, 9, 1, 3, 9, 3, 3, 9, 1, 8, + 5, 6, 8, 0, 3, 4, 4, 9, 0, 3, 9, 8, 2, 0, 5, 9, 5, 5, 1, 0, 0, 2, 2, 6, 3, 5, 3, 5, 3, 6, 1, 9, 2, 0, 4, 1, 9, 9, 4, 7, 4, 5, 5, 3, 8, 5, 9, 3, 8, 1, 0, 2, 3, 4, 3, 9, 5, 5, 4, 4, 9, 5, 9, 7, 7, 8, 3, 7, 7, 9, + 0, 2, 3, 7, 4, 2, 1, 6, 1, 7, 2, 7, 1, 1, 1, 7, 2, 3, 6, 4, 3, 4, 3, 5, 4, 3, 9, 4, 7, 8, 2, 2, 1, 8, 1, 8, 5, 2, 8, 6, 2, 4, 0, 8, 5, 1, 4, 0, 0, 6, 6, 6, 0, 4, 4, 3, 3, 2, 5, 8, 8, 8, 5, 6, 9, 8, 6, 7, 0, 5, + 4, 3, 1, 5, 4, 7, 0, 6, 9, 6, 5, 7, 4, 7, 4, 5, 8, 5, 5, 0, 3, 3, 2, 3, 2, 3, 3, 4, 2, 1, 0, 7, 3, 0, 1, 5, 4, 5, 9, 4, 0, 5, 1, 6, 5, 5, 3, 7, 9, 0, 6, 8, 6, 6, 2, 7, 3, 3, 3, 7, 9, 9, 5, 8, 5, 1, 1, 5, 6, 2, + 5, 7, 8, 4, 3, 2, 2, 9, 8, 8, 2, 7, 3, 7, 2, 3, 1, 9, 8, 9, 8, 7, 5, 7, 1, 4, 1, 5, 9, 5, 7, 8, 1, 1, 1, 9, 6, 3, 5, 8, 3, 3, 0, 0, 5, 9, 4, 0, 8, 7, 3, 0, 6, 8, 1, 2, 1, 6, 0, 2, 8, 7, 6, 4, 9, 6, 2, 8, 6, 7, + 4, 4, 6, 0, 4, 7, 7, 4, 6, 4, 9, 1, 5, 9, 9, 5, 0, 5, 4, 9, 7, 3, 7, 4, 2, 5, 6, 2, 6, 9, 0, 1, 0, 4, 9, 0, 3, 7, 7, 8, 1, 9, 8, 6, 8, 3, 5, 9, 3, 8, 1, 4, 6, 5, 7, 4, 1, 2, 6, 8, 0, 4, 9, 2, 5, 6, 4, 8, 7, 9, + 8, 5, 5, 6, 1, 4, 5, 3, 7, 2, 3, 4, 7, 8, 6, 7, 3, 3, 0, 3, 9, 0, 4, 6, 8, 8, 3, 8, 3, 4, 3, 6, 3, 4, 6, 5, 5, 3, 7, 9, 4, 9, 8, 6, 4, 1, 9, 2, 7, 0, 5, 6, 3, 8, 7, 2, 9, 3, 1, 7, 4, 8, 7, 2, 3, 3, 2, 0, 8, 3, + 7, 6, 0, 1, 1, 2, 3, 0, 2, 9, 9, 1, 1, 3, 6, 7, 9, 3, 8, 6, 2, 7, 0, 8, 9, 4, 3, 8, 7, 9, 9, 3, 6, 2, 0, 1, 6, 2, 9, 5, 1, 5, 4, 1, 3, 3, 7, 1, 4, 2, 4, 8, 9, 2, 8, 3, 0, 7, 2, 2, 0, 1, 2, 6, 9, 0, 1, 4, 7, 5, + 4, 6, 6, 8, 4, 7, 6, 5, 3, 5, 7, 6, 1, 6, 4, 7, 7, 3, 7, 9, 4, 6, 7, 5, 2, 0, 0, 4, 9, 0, 7, 5, 7, 1, 5, 5, 5, 2, 7, 8, 1, 9, 6, 5, 3, 6, 2, 1, 3, 2, 3, 9, 2, 6, 4, 0, 6, 1, 6, 0, 1, 3, 6, 3, 5, 8, 1, 5, 5, 9, + 0, 7, 4, 2, 2, 0, 2, 0, 2, 0, 3, 1, 8, 7, 2, 7, 7, 6, 0, 5, 2, 7, 7, 2, 1, 9, 0, 0, 5, 5, 6, 1, 4, 8, 4, 2, 5, 5, 5, 1, 8, 7, 9, 2, 5, 3, 0, 3, 4, 3, 5, 1, 3, 9, 8, 4, 4, 2, 5, 3, 2, 2, 3, 4, 1, 5, 7, 6, 2, 3, + 3, 6, 1, 0, 6, 4, 2, 5, 0, 6, 3, 9, 0, 4, 9, 7, 5, 0, 0, 8, 6, 5, 6, 2, 7, 1, 0, 9, 5, 3, 5, 9, 1, 9, 4, 6, 5, 8, 9, 7, 5, 1, 4, 1, 3, 1, 0, 3, 4, 8, 2, 2, 7, 6, 9, 3, 0, 6, 2, 4, 7, 4, 3, 5, 3, 6, 3, 2, 5, 6, + 9, 1, 6, 0, 7, 8, 1, 5, 4, 7, 8, 1, 8, 1, 1, 5, 2, 8, 4, 3, 6, 6, 7, 9, 5, 7, 0, 6, 1, 1, 0, 8, 6, 1, 5, 3, 3, 1, 5, 0, 4, 4, 5, 2, 1, 2, 7, 4, 7, 3, 9, 2, 4, 5, 4, 4, 9, 4, 5, 4, 2, 3, 6, 8, 2, 8, 8, 6, 0, 6, + 1, 3, 4, 0, 8, 4, 1, 4, 8, 6, 3, 7, 7, 6, 7, 0, 0, 9, 6, 1, 2, 0, 7, 1, 5, 1, 2, 4, 9, 1, 4, 0, 4, 3, 0, 2, 7, 2, 5, 3, 8, 6, 0, 7, 6, 4, 8, 2, 3, 6, 3, 4, 1, 4, 3, 3, 4, 6, 2, 3, 5, 1, 8, 9, 7, 5, 7, 6, 6, 4, + 5, 2, 1, 6, 4, 1, 3, 7, 6, 7, 9, 6, 9, 0, 3, 1, 4, 9, 5, 0, 1, 9, 1, 0, 8, 5, 7, 5, 9, 8, 4, 4, 2, 3, 9, 1, 9, 8, 6, 2, 9, 1, 6, 4, 2, 1, 9, 3, 9, 9, 4, 9, 0, 7, 2, 3, 6, 2, 3, 4, 6, 4, 6, 8, 4, 4, 1, 1, 7, 3, + 9, 4, 0, 3, 2, 6, 5, 9, 1, 8, 4, 0, 4, 4, 3, 7, 8, 0, 5, 1, 3, 3, 3, 8, 9, 4, 5, 2, 5, 7, 4, 2, 3, 9, 9, 5, 0, 8, 2, 9, 6, 5, 9, 1, 2, 2, 8, 5, 0, 8, 5, 5, 5, 8, 2, 1, 5, 7, 2, 5, 0, 3, 1, 0, 7, 1, 2, 5, 7, 0, + 1, 2, 6, 6, 8, 3, 0, 2, 4, 0, 2, 9, 2, 9, 5, 2, 5, 2, 2, 0, 1, 1, 8, 7, 2, 6, 7, 6, 7, 5, 6, 2, 2, 0, 4, 1, 5, 4, 2, 0, 5, 1, 6, 1, 8, 4, 1, 6, 3, 4, 8, 4, 7, 5, 6, 5, 1, 6, 9, 9, 9, 8, 1, 1, 6, 1, 4, 1, 0, 1, + 0, 0, 2, 9, 9, 6, 0, 7, 8, 3, 8, 6, 9, 0, 9, 2, 9, 1, 6, 0, 3, 0, 2, 8, 8, 4, 0, 0, 2, 6, 9, 1, 0, 4, 1, 4, 0, 7, 9, 2, 8, 8, 6, 2, 1, 5, 0, 7, 8, 4, 2, 4, 5, 1, 6, 7, 0, 9, 0, 8, 7, 0, 0, 0, 6, 9, 9, 2, 8, 2, + 1, 2, 0, 6, 6, 0, 4, 1, 8, 3, 7, 1, 8, 0, 6, 5, 3, 5, 5, 6, 7, 2, 5, 2, 5, 3, 2, 5, 6, 7, 5, 3, 2, 8, 6, 1, 2, 9, 1, 0, 4, 2, 4, 8, 7, 7, 6, 1, 8, 2, 5, 8, 2, 9, 7, 6, 5, 1, 5, 7, 9, 5, 9, 8, 4, 7, 0, 3, 5, 6, + 2, 2, 2, 6, 2, 9, 3, 4, 8, 6, 0, 0, 3, 4, 1, 5, 8, 7, 2, 2, 9, 8, 0, 5, 3, 4, 9, 8, 9, 6, 5, 0, 2, 2, 6, 2, 9, 1, 7, 4, 8, 7, 8, 8, 2, 0, 2, 7, 3, 4, 2, 0, 9, 2, 2, 2, 2, 4, 5, 3, 3, 9, 8, 5, 6, 2, 6, 4, 7, 6, + 6, 9, 1, 4, 9, 0, 5, 5, 6, 2, 8, 4, 2, 5, 0, 3, 9, 1, 2, 7, 5, 7, 7, 1, 0, 2, 8, 4, 0, 2, 7, 9, 9, 8, 0, 6, 6, 3, 6, 5, 8, 2, 5, 4, 8, 8, 9, 2, 6, 4, 8, 8, 0, 2, 5, 4, 5, 6, 6, 1, 0, 1, 7, 2, 9, 6, 7, 0, 2, 6, + 6, 4, 0, 7, 6, 5, 5, 9, 0, 4, 2, 9, 0, 9, 9, 4, 5, 6, 8, 1, 5, 0, 6, 5, 2, 6, 5, 3, 0, 5, 3, 7, 1, 8, 2, 9, 4, 1, 2, 7, 0, 3, 3, 6, 9, 3, 1, 3, 7, 8, 5, 1, 7, 8, 6, 0, 9, 0, 4, 0, 7, 0, 8, 6, 6, 7, 1, 1, 4, 9, + 6, 5, 5, 8, 3, 4, 3, 4, 3, 4, 7, 6, 9, 3, 3, 8, 5, 7, 8, 1, 7, 1, 1, 3, 8, 6, 4, 5, 5, 8, 7, 3, 6, 7, 8, 1, 2, 3, 0, 1, 4, 5, 8, 7, 6, 8, 7, 1, 2, 6, 6, 0, 3, 4, 8, 9, 1, 3, 9, 0, 9, 5, 6, 2, 0, 0, 9, 9, 3, 9, + 3, 6, 1, 0, 3, 1, 0, 2, 9, 1, 6, 1, 6, 1, 5, 2, 8, 8, 1, 3, 8, 4, 3, 7, 9, 0, 9, 9, 0, 4, 2, 3, 1, 7, 4, 7, 3, 3, 6, 3, 9, 4, 8, 0, 4, 5, 7, 5, 9, 3, 1, 4, 9, 3, 1, 4, 0, 5, 2, 9, 7, 6, 3, 4, 7, 5, 7, 4, 8, 1, + 1, 9, 3, 5, 6, 7, 0, 9, 1, 1, 0, 1, 3, 7, 7, 5, 1, 7, 2, 1, 0, 0, 8, 0, 3, 1, 5, 5, 9, 0, 2, 4, 8, 5, 3, 0, 9, 0, 6, 6, 9, 2, 0, 3, 7, 6, 7, 1, 9, 2, 2, 0, 3, 3, 2, 2, 9, 0, 9, 4, 3, 3, 4, 6, 7, 6, 8, 5, 1, 4, + 2, 2, 1, 4, 4, 7, 7, 3, 7, 9, 3, 9, 3, 7, 5, 1, 7, 0, 3, 4, 4, 3, 6, 6, 1, 9, 9, 1, 0, 4, 0, 3, 3, 7, 5, 1, 1, 1, 7, 3, 5, 4, 7, 1, 9, 1, 8, 5, 5, 0, 4, 6, 4, 4, 9, 0, 2, 6, 3, 6, 5, 5, 1, 2, 8, 1, 6, 2, 2, 8, + 8, 2, 4, 4, 6, 2, 5, 7, 5, 9, 1, 6, 3, 3, 3, 0, 3, 9, 1, 0, 7, 2, 2, 5, 3, 8, 3, 7, 4, 2, 1, 8, 2, 1, 4, 0, 8, 8, 3, 5, 0, 8, 6, 5, 7, 3, 9, 1, 7, 7, 1, 5, 0, 9, 6, 8, 2, 8, 8, 7, 4, 7, 8, 2, 6, 5, 6, 9, 9, 5, + 9, 9, 5, 7, 4, 4, 9, 0, 6, 6, 1, 7, 5, 8, 3, 4, 4, 1, 3, 7, 5, 2, 2, 3, 9, 7, 0, 9, 6, 8, 3, 4, 0, 8, 0, 0, 5, 3, 5, 5, 9, 8, 4, 9, 1, 7, 5, 4, 1, 7, 3, 8, 1, 8, 8, 3, 9, 9, 9, 4, 4, 6, 9, 7, 4, 8, 6, 7, 6, 2, + 6, 5, 5, 1, 6, 5, 8, 2, 7, 6, 5, 8, 4, 8, 3, 5, 8, 8, 4, 5, 3, 1, 4, 2, 7, 7, 5, 6, 8, 7, 9, 0, 0, 2, 9, 0, 9, 5, 1, 7, 0, 2, 8, 3, 5, 2, 9, 7, 1, 6, 3, 4, 4, 5, 6, 2, 1, 2, 9, 6, 4, 0, 4, 3, 5, 2, 3, 1, 1, 7, + 6, 0, 0, 6, 6, 5, 1, 0, 1, 2, 4, 1, 2, 0, 0, 6, 5, 9, 7, 5, 5, 8, 5, 1, 2, 7, 6, 1, 7, 8, 5, 8, 3, 8, 2, 9, 2, 0, 4, 1, 9, 7, 4, 8, 4, 4, 2, 3, 6, 0, 8, 0, 0, 7, 1, 9, 3, 0, 4, 5, 7, 6, 1, 8, 9, 3, 2, 3, 4, 9, + 2, 2, 9, 2, 7, 9, 6, 5, 0, 1, 9, 8, 7, 5, 1, 8, 7, 2, 1, 2, 7, 2, 6, 7, 5, 0, 7, 9, 8, 1, 2, 5, 5, 4, 7, 0, 9, 5, 8, 9, 0, 4, 5, 5, 6, 3, 5, 7, 9, 2, 1, 2, 2, 1, 0, 3, 3, 3, 4, 6, 6, 9, 7, 4, 9, 9, 2, 3, 5, 6, + 3, 0, 2, 5, 4, 9, 4, 7, 8, 0, 2, 4, 9, 0, 1, 1, 4, 1, 9, 5, 2, 1, 2, 3, 8, 2, 8, 1, 5, 3, 0, 9, 1, 1, 4, 0, 7, 9, 0, 7, 3, 8, 6, 0, 2, 5, 1, 5, 2, 2, 7, 4, 2, 9, 9, 5, 8, 1, 8, 0, 7, 2, 4, 7, 1, 6, 2, 5, 9, 1, + 6, 6, 8, 5, 4, 5, 1, 3, 3, 3, 1, 2, 3, 9, 4, 8, 0, 4, 9, 4, 7, 0, 7, 9, 1, 1, 9, 1, 5, 3, 2, 6, 7, 3, 4, 3, 0, 2, 8, 2, 4, 4, 1, 8, 6, 0, 4, 1, 4, 2, 6, 3, 6, 3, 9, 5, 4, 8, 0, 0, 0, 4, 4, 8, 0, 0, 2, 6, 7, 0, + 4, 9, 6, 2, 4, 8, 2, 0, 1, 7, 9, 2, 8, 9, 6, 4, 7, 6, 6, 9, 7, 5, 8, 3, 1, 8, 3, 2, 7, 1, 3, 1, 4, 2, 5, 1, 7, 0, 2, 9, 6, 9, 2, 3, 4, 8, 8, 9, 6, 2, 7, 6, 6, 8, 4, 4, 0, 3, 2, 3, 2, 6, 0, 9, 2, 7, 5, 2, 4, 9, + 6, 0, 3, 5, 7, 9, 9, 6, 4, 6, 9, 2, 5, 6, 5, 0, 4, 9, 3, 6, 8, 1, 8, 3, 6, 0, 9, 0, 0, 3, 2, 3, 8, 0, 9, 2, 9, 3, 4, 5, + 9, 5, 8, 8, 9, 7, 0, 6, 9, 5, 3, 6, 5, 3, 4, 9, 4, 0, 6, 0, 3, 4, 0, 2, 1, 6, 6, 5, 4, 4, 3, 7, 5, 5, 8, 9, 0, 0, 4, 5, 6, 3, 2, 8, 8, 2, 2, 5, 0, 5, 4, 5, 2, 5, 5, 6, 4, 0, 5, 6, 4, 4, 8, 2, 4, 6, 5, 1, 5, 1, + 8, 7, 5, 4, 7, 1, 1, 9, 6, 2, 1, 8, 4, 4, 3, 9, 6, 5, 8, 2, 5, 3, 3, 7, 5, 4, 3, 8, 8, 5, 6, 9, 0, 9, 4, 1, 1, 3, 0, 3, 1, 5, 0, 9, 5, 2, 6, 1, 7, 9, 3, 7, 8, 0, 0, 2, 9, 7, 4, 1, 2, 0, 7, 6, 6, 5, 1, 4, 7, 9, + 3, 9, 4, 2, 5, 9, 0, 2, 9, 8, 9, 6, 9, 5, 9, 4, 6, 9, 9, 5, 5, 6, 5, 7, 6, 1, 2, 1, 8, 6, 5, 6, 1, 9, 6, 7, 3, 3, 7, 8, 6, 2, 3, 6, 2, 5, 6, 1, 2, 5, 2, 1, 6, 3, 2, 0, 8, 6, 2, 8, 6, 9, 2, 2, 2, 1, 0, 3, 2, 7, + 4, 8, 8, 9, 2, 1, 8, 6, 5, 4, 3, 6, 4, 8, 0, 2, 2, 9, 6, 7, 8, 0, 7, 0, 5, 7, 6, 5, 6, 1, 5, 1, 4, 4, 6, 3, 2, 0, 4, 6, 9, 2, 7, 9, 0, 6, 8, 2, 1, 2, 0, 7, 3, 8, 8, 3, 7, 7, 8, 1, 4, 2, 3, 3, 5, 6, 2, 8, 2, 3, + 6, 0, 8, 9, 6, 3, 2, 0, 8, 0, 6, 8, 2, 2, 2, 4, 6, 8, 0, 1, 2, 2, 4, 8, 2, 6, 1, 1, 7, 7, 1, 8, 5, 8, 9, 6, 3, 8, 1, 4, 0, 9, 1, 8, 3, 9, 0, 3, 6, 7, 3, 6, 7, 2, 2, 2, 0, 8, 8, 8, 3, 2, 1, 5, 1, 3, 7, 5, 5, 6, + 0, 0, 3, 7, 2, 7, 9, 8, 3, 9, 4, 0, 0, 4, 1, 5, 2, 9, 7, 0, 0, 2, 8, 7, 8, 3, 0, 7, 6, 6, 7, 0, 9, 4, 4, 4, 7, 4, 5, 6, 0, 1, 3, 4, 5, 5, 6, 4, 1, 7, 2, 5, 4, 3, 7, 0, 9, 0, 6, 9, 7, 9, 3, 9, 6, 1, 2, 2, 5, 7, + 1, 4, 2, 9, 8, 9, 4, 6, 7, 1, 5, 4, 3, 5, 7, 8, 4, 6, 8, 7, 8, 8, 6, 1, 4, 4, 4, 5, 8, 1, 2, 3, 1, 4, 5, 9, 3, 5, 7, 1, 9, 8, 4, 9, 2, 2, 5, 2, 8, 4, 7, 1, 6, 0, 5, 0, 4, 9, 2, 2, 1, 2, 4, 2, 4, 7, 0, 1, 4, 1, + 2, 1, 4, 7, 8, 0, 5, 7, 3, 4, 5, 5, 1, 0, 5, 0, 0, 8, 0, 1, 9, 0, 8, 6, 9, 9, 6, 0, 3, 3, 0, 2, 7, 6, 3, 4, 7, 8, 7, 0, 8, 1, 0, 8, 1, 7, 5, 4, 5, 0, 1, 1, 9, 3, 0, 7, 1, 4, 1, 2, 2, 3, 3, 9, 0, 8, 6, 6, 3, 9, + 3, 8, 3, 3, 9, 5, 2, 9, 4, 2, 5, 7, 8, 6, 9, 0, 5, 0, 7, 6, 4, 3, 1, 0, 0, 6, 3, 8, 3, 5, 1, 9, 8, 3, 4, 3, 8, 9, 3, 4, 1, 5, 9, 6, 1, 3, 1, 8, 5, 4, 3, 4, 7, 5, 4, 6, 4, 9, 5, 5, 6, 9, 7, 8, 1, 0, 3, 8, 2, 9, + 3, 0, 9, 7, 1, 6, 4, 6, 5, 1, 4, 3, 8, 4, 0, 7, 0, 0, 7, 0, 7, 3, 6, 0, 4, 1, 1, 2, 3, 7, 3, 5, 9, 9, 8, 4, 3, 4, 5, 2, 2, 5, 1, 6, 1, 0, 5, 0, 7, 0, 2, 7, 0, 5, 6, 2, 3, 5, 2, 6, 6, 0, 1, 2, 7, 6, 4, 8, 4, 8, + 3, 0, 8, 4, 0, 7, 6, 1, 1, 8, 3, 0, 1, 3, 0, 5, 2, 7, 9, 3, 2, 0, 5, 4, 2, 7, 4, 6, 2, 8, 6, 5, 4, 0, 3, 6, 0, 3, 6, 7, 4, 5, 3, 2, 8, 6, 5, 1, 0, 5, 7, 0, 6, 5, 8, 7, 4, 8, 8, 2, 2, 5, 6, 9, 8, 1, 5, 7, 9, 3, + 6, 7, 8, 9, 7, 6, 6, 9, 7, 4, 2, 2, 0, 5, 7, 5, 0, 5, 9, 6, 8, 3, 4, 4, 0, 8, 6, 9, 7, 3, 5, 0, 2, 0, 1, 4, 1, 0, 2, 0, 6, 7, 2, 3, 5, 8, 5, 0, 2, 0, 0, 7, 2, 4, 5, 2, 2, 5, 6, 3, 2, 6, 5, 1, 3, 4, 1, 0, 5, 5, + 9, 2, 4, 0, 1, 9, 0, 2, 7, 4, 2, 1, 6, 2, 4, 8, 4, 3, 9, 1, 4, 0, 3, 5, 9, 9, 8, 9, 5, 3, 5, 3, 9, 4, 5, 9, 0, 9, 4, 4, 0, 7, 0, 4, 6, 9, 1, 2, 0, 9, 1, 4, 0, 9, 3, 8, 7, 0, 0, 1, 2, 6, 4, 5, 6, 0, 0, 1, 6, 2, + 3, 7, 4, 2, 8, 8, 0, 2, 1, 0, 9, 2, 7, 6, 4, 5, 7, 9, 3, 1, 0, 6, 5, 7, 9, 2, 2, 9, 5, 5, 2, 4, 9, 8, 8, 7, 2, 7, 5, 8, 4, 6, 1, 0, 1, 2, 6, 4, 8, 3, 6, 9, 9, 9, 8, 9, 2, 2, 5, 6, 9, 5, 9, 6, 8, 8, 1, 5, 9, 2, + 0, 5, 6, 0, 0, 1, 0, 1, 6, 5, 5, 2, 5, 6, 3, 7, 5, 6, 7, 8 + }; +} diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java new file mode 100644 index 0000000..b7c2d1f --- /dev/null +++ b/core/tests/coretests/src/android/os/MessageQueueTest.java @@ -0,0 +1,103 @@ +/* + * 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.os; + +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.test.suitebuilder.annotation.MediumTest; +import junit.framework.TestCase; + +public class MessageQueueTest extends TestCase { + + private static class BaseTestHandler extends TestHandlerThread { + Handler mHandler; + int mLastMessage; + int mCount; + + public BaseTestHandler() { + } + + public void go() { + mHandler = new Handler() { + public void handleMessage(Message msg) { + BaseTestHandler.this.handleMessage(msg); + } + }; + } + + public void handleMessage(Message msg) { + if (mCount <= mLastMessage) { + if (msg.what != mCount) { + failure(new RuntimeException( + "Expected message #" + mCount + + ", received #" + msg.what)); + } else if (mCount == mLastMessage) { + success(); + } + mCount++; + } else { + failure(new RuntimeException( + "Message received after done, #" + msg.what)); + } + } + } + + @MediumTest + public void testMessageOrder() throws Exception { + TestHandlerThread tester = new BaseTestHandler() { + public void go() { + super.go(); + long now = SystemClock.uptimeMillis() + 200; + mLastMessage = 4; + mCount = 0; + mHandler.sendMessageAtTime(mHandler.obtainMessage(2), now + 1); + mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now + 2); + mHandler.sendMessageAtTime(mHandler.obtainMessage(4), now + 2); + mHandler.sendMessageAtTime(mHandler.obtainMessage(0), now + 0); + mHandler.sendMessageAtTime(mHandler.obtainMessage(1), now + 0); + } + }; + + tester.doTest(1000); + } + + @MediumTest + public void testAtFrontOfQueue() throws Exception { + TestHandlerThread tester = new BaseTestHandler() { + public void go() { + super.go(); + long now = SystemClock.uptimeMillis() + 200; + mLastMessage = 3; + mCount = 0; + mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now); + mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(2)); + mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(0)); + } + + public void handleMessage(Message msg) { + super.handleMessage(msg); + if (msg.what == 0) { + mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(1)); + } + } + }; + + tester.doTest(1000); + } +} + diff --git a/core/tests/coretests/src/android/os/MessengerService.java b/core/tests/coretests/src/android/os/MessengerService.java new file mode 100644 index 0000000..f15e134 --- /dev/null +++ b/core/tests/coretests/src/android/os/MessengerService.java @@ -0,0 +1,50 @@ +/* + * 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.os; + +import android.app.Service; +import android.content.Intent; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; + +public class MessengerService extends Service { + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Message reply = Message.obtain(); + reply.copyFrom(msg); + try { + msg.replyTo.send(reply); + } catch (RemoteException e) { + } + } + }; + + private final Messenger mMessenger = new Messenger(mHandler); + + public MessengerService() { + } + + @Override + public IBinder onBind(Intent intent) { + return mMessenger.getBinder(); + } +} + diff --git a/core/tests/coretests/src/android/os/MessengerTest.java b/core/tests/coretests/src/android/os/MessengerTest.java new file mode 100644 index 0000000..473ffe2 --- /dev/null +++ b/core/tests/coretests/src/android/os/MessengerTest.java @@ -0,0 +1,119 @@ +/* + * 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.os; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.RemoteException; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +public class MessengerTest extends AndroidTestCase { + private Messenger mServiceMessenger; + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (MessengerTest.this) { + mServiceMessenger = new Messenger(service); + MessengerTest.this.notifyAll(); + } + } + public void onServiceDisconnected(ComponentName name) { + mServiceMessenger = null; + } + }; + + private class TestThread extends TestHandlerThread { + private Handler mTestHandler; + private Messenger mTestMessenger; + + public void go() { + synchronized (MessengerTest.this) { + mTestHandler = new Handler() { + public void handleMessage(Message msg) { + TestThread.this.handleMessage(msg); + } + }; + mTestMessenger = new Messenger(mTestHandler); + TestThread.this.executeTest(); + } + } + + public void executeTest() { + Message msg = Message.obtain(); + msg.arg1 = 100; + msg.arg2 = 1000; + msg.replyTo = mTestMessenger; + try { + mServiceMessenger.send(msg); + } catch (RemoteException e) { + } + } + + public void handleMessage(Message msg) { + if (msg.arg1 != 100) { + failure(new RuntimeException( + "Message.arg1 is not 100: " + msg.arg1)); + return; + } + if (msg.arg2 != 1000) { + failure(new RuntimeException( + "Message.arg2 is not 1000: " + msg.arg2)); + return; + } + if (!mTestMessenger.equals(msg.replyTo)) { + failure(new RuntimeException( + "Message.replyTo is not me: " + msg.replyTo)); + return; + } + success(); + } + }; + + @Override + protected void setUp() throws Exception { + super.setUp(); + getContext().bindService(new Intent(mContext, MessengerService.class), + mConnection, Context.BIND_AUTO_CREATE); + synchronized (this) { + while (mServiceMessenger == null) { + try { + wait(); + } catch (InterruptedException e) { + } + } + } + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + getContext().unbindService(mConnection); + } + + @MediumTest + public void testSend() { + (new TestThread()).doTest(1000); + + } +} diff --git a/core/tests/coretests/src/android/os/OsTests.java b/core/tests/coretests/src/android/os/OsTests.java new file mode 100644 index 0000000..582bf1a --- /dev/null +++ b/core/tests/coretests/src/android/os/OsTests.java @@ -0,0 +1,39 @@ +/* + * 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.os; + +import com.google.android.collect.Lists; +import junit.framework.TestSuite; + +import java.util.Enumeration; +import java.util.List; + +public class OsTests { + public static TestSuite suite() { + TestSuite suite = new TestSuite(OsTests.class.getName()); + + suite.addTestSuite(AidlTest.class); + suite.addTestSuite(BroadcasterTest.class); + suite.addTestSuite(FileObserverTest.class); + suite.addTestSuite(IdleHandlerTest.class); + suite.addTestSuite(MessageQueueTest.class); + suite.addTestSuite(MessengerTest.class); + suite.addTestSuite(SystemPropertiesTest.class); + + return suite; + } +} diff --git a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java new file mode 100644 index 0000000..a382239 --- /dev/null +++ b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java @@ -0,0 +1,528 @@ +/* + * 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.os; + +import android.os.Bundle; +import android.os.Parcelable; +import android.os.PerformanceCollector; +import android.os.Process; +import android.os.PerformanceCollector.PerformanceResultsWriter; +import android.test.suitebuilder.annotation.SmallTest; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Random; + +import junit.framework.TestCase; + +public class PerformanceCollectorTest extends TestCase { + + private PerformanceCollector mPerfCollector; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mPerfCollector = new PerformanceCollector(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + mPerfCollector = null; + } + + @SmallTest + public void testBeginSnapshotNoWriter() throws Exception { + mPerfCollector.beginSnapshot("testBeginSnapshotNoWriter"); + + assertTrue((Long)readPrivateField("mSnapshotCpuTime", mPerfCollector) > 0); + assertTrue((Long)readPrivateField("mSnapshotExecTime", mPerfCollector) > 0); + Bundle snapshot = (Bundle)readPrivateField("mPerfSnapshot", mPerfCollector); + assertNotNull(snapshot); + assertEquals(2, snapshot.size()); + } + + @SmallTest + public void testEndSnapshotNoWriter() throws Exception { + mPerfCollector.beginSnapshot("testEndSnapshotNoWriter"); + workForRandomLongPeriod(); + Bundle snapshot = mPerfCollector.endSnapshot(); + + verifySnapshotBundle(snapshot); + } + + @SmallTest + public void testStartTimingNoWriter() throws Exception { + mPerfCollector.startTiming("testStartTimingNoWriter"); + + assertTrue((Long)readPrivateField("mCpuTime", mPerfCollector) > 0); + assertTrue((Long)readPrivateField("mExecTime", mPerfCollector) > 0); + Bundle measurement = (Bundle)readPrivateField("mPerfMeasurement", mPerfCollector); + assertNotNull(measurement); + verifyTimingBundle(measurement, new ArrayList<String>()); + } + + @SmallTest + public void testAddIterationNoWriter() throws Exception { + mPerfCollector.startTiming("testAddIterationNoWriter"); + workForRandomTinyPeriod(); + Bundle iteration = mPerfCollector.addIteration("timing1"); + + verifyIterationBundle(iteration, "timing1"); + } + + @SmallTest + public void testStopTimingNoWriter() throws Exception { + mPerfCollector.startTiming("testStopTimingNoWriter"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("timing2"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("timing3"); + workForRandomShortPeriod(); + Bundle timing = mPerfCollector.stopTiming("timing4"); + + ArrayList<String> labels = new ArrayList<String>(); + labels.add("timing2"); + labels.add("timing3"); + labels.add("timing4"); + verifyTimingBundle(timing, labels); + } + + @SmallTest + public void testBeginSnapshot() throws Exception { + MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); + mPerfCollector.setPerformanceResultsWriter(writer); + mPerfCollector.beginSnapshot("testBeginSnapshot"); + + assertEquals("testBeginSnapshot", writer.snapshotLabel); + assertTrue((Long)readPrivateField("mSnapshotCpuTime", mPerfCollector) > 0); + assertTrue((Long)readPrivateField("mSnapshotExecTime", mPerfCollector) > 0); + Bundle snapshot = (Bundle)readPrivateField("mPerfSnapshot", mPerfCollector); + assertNotNull(snapshot); + assertEquals(2, snapshot.size()); + } + + @SmallTest + public void testEndSnapshot() throws Exception { + MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); + mPerfCollector.setPerformanceResultsWriter(writer); + mPerfCollector.beginSnapshot("testEndSnapshot"); + workForRandomLongPeriod(); + Bundle snapshot1 = mPerfCollector.endSnapshot(); + Bundle snapshot2 = writer.snapshotResults; + + assertEqualsBundle(snapshot1, snapshot2); + verifySnapshotBundle(snapshot1); + } + + @SmallTest + public void testStartTiming() throws Exception { + MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); + mPerfCollector.setPerformanceResultsWriter(writer); + mPerfCollector.startTiming("testStartTiming"); + + assertEquals("testStartTiming", writer.timingLabel); + assertTrue((Long)readPrivateField("mCpuTime", mPerfCollector) > 0); + assertTrue((Long)readPrivateField("mExecTime", mPerfCollector) > 0); + Bundle measurement = (Bundle)readPrivateField("mPerfMeasurement", mPerfCollector); + assertNotNull(measurement); + verifyTimingBundle(measurement, new ArrayList<String>()); + } + + @SmallTest + public void testAddIteration() throws Exception { + mPerfCollector.startTiming("testAddIteration"); + workForRandomTinyPeriod(); + Bundle iteration = mPerfCollector.addIteration("timing5"); + + verifyIterationBundle(iteration, "timing5"); + } + + @SmallTest + public void testStopTiming() throws Exception { + mPerfCollector.startTiming("testStopTiming"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("timing6"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("timing7"); + workForRandomShortPeriod(); + Bundle timing = mPerfCollector.stopTiming("timing8"); + + ArrayList<String> labels = new ArrayList<String>(); + labels.add("timing6"); + labels.add("timing7"); + labels.add("timing8"); + verifyTimingBundle(timing, labels); + } + + @SmallTest + public void testAddMeasurementLong() throws Exception { + MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); + mPerfCollector.setPerformanceResultsWriter(writer); + mPerfCollector.startTiming("testAddMeasurementLong"); + mPerfCollector.addMeasurement("testAddMeasurementLongZero", 0); + mPerfCollector.addMeasurement("testAddMeasurementLongPos", 348573); + mPerfCollector.addMeasurement("testAddMeasurementLongNeg", -19354); + mPerfCollector.stopTiming(""); + + assertEquals("testAddMeasurementLong", writer.timingLabel); + Bundle results = writer.timingResults; + assertEquals(4, results.size()); + assertTrue(results.containsKey("testAddMeasurementLongZero")); + assertEquals(0, results.getLong("testAddMeasurementLongZero")); + assertTrue(results.containsKey("testAddMeasurementLongPos")); + assertEquals(348573, results.getLong("testAddMeasurementLongPos")); + assertTrue(results.containsKey("testAddMeasurementLongNeg")); + assertEquals(-19354, results.getLong("testAddMeasurementLongNeg")); + } + + @SmallTest + public void testAddMeasurementFloat() throws Exception { + MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); + mPerfCollector.setPerformanceResultsWriter(writer); + mPerfCollector.startTiming("testAddMeasurementFloat"); + mPerfCollector.addMeasurement("testAddMeasurementFloatZero", 0.0f); + mPerfCollector.addMeasurement("testAddMeasurementFloatPos", 348573.345f); + mPerfCollector.addMeasurement("testAddMeasurementFloatNeg", -19354.093f); + mPerfCollector.stopTiming(""); + + assertEquals("testAddMeasurementFloat", writer.timingLabel); + Bundle results = writer.timingResults; + assertEquals(4, results.size()); + assertTrue(results.containsKey("testAddMeasurementFloatZero")); + assertEquals(0.0f, results.getFloat("testAddMeasurementFloatZero")); + assertTrue(results.containsKey("testAddMeasurementFloatPos")); + assertEquals(348573.345f, results.getFloat("testAddMeasurementFloatPos")); + assertTrue(results.containsKey("testAddMeasurementFloatNeg")); + assertEquals(-19354.093f, results.getFloat("testAddMeasurementFloatNeg")); + } + + @SmallTest + public void testAddMeasurementString() throws Exception { + MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); + mPerfCollector.setPerformanceResultsWriter(writer); + mPerfCollector.startTiming("testAddMeasurementString"); + mPerfCollector.addMeasurement("testAddMeasurementStringNull", null); + mPerfCollector.addMeasurement("testAddMeasurementStringEmpty", ""); + mPerfCollector.addMeasurement("testAddMeasurementStringNonEmpty", "Hello World"); + mPerfCollector.stopTiming(""); + + assertEquals("testAddMeasurementString", writer.timingLabel); + Bundle results = writer.timingResults; + assertEquals(4, results.size()); + assertTrue(results.containsKey("testAddMeasurementStringNull")); + assertNull(results.getString("testAddMeasurementStringNull")); + assertTrue(results.containsKey("testAddMeasurementStringEmpty")); + assertEquals("", results.getString("testAddMeasurementStringEmpty")); + assertTrue(results.containsKey("testAddMeasurementStringNonEmpty")); + assertEquals("Hello World", results.getString("testAddMeasurementStringNonEmpty")); + } + + @SmallTest + public void testSimpleSequence() throws Exception { + MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); + mPerfCollector.setPerformanceResultsWriter(writer); + mPerfCollector.beginSnapshot("testSimpleSequence"); + mPerfCollector.startTiming("testSimpleSequenceTiming"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration1"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration2"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration3"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration4"); + workForRandomShortPeriod(); + Bundle timing = mPerfCollector.stopTiming("iteration5"); + workForRandomLongPeriod(); + Bundle snapshot1 = mPerfCollector.endSnapshot(); + Bundle snapshot2 = writer.snapshotResults; + + assertEqualsBundle(snapshot1, snapshot2); + verifySnapshotBundle(snapshot1); + + ArrayList<String> labels = new ArrayList<String>(); + labels.add("iteration1"); + labels.add("iteration2"); + labels.add("iteration3"); + labels.add("iteration4"); + labels.add("iteration5"); + verifyTimingBundle(timing, labels); + } + + @SmallTest + public void testLongSequence() throws Exception { + MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); + mPerfCollector.setPerformanceResultsWriter(writer); + mPerfCollector.beginSnapshot("testLongSequence"); + mPerfCollector.startTiming("testLongSequenceTiming1"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration1"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration2"); + workForRandomShortPeriod(); + Bundle timing1 = mPerfCollector.stopTiming("iteration3"); + workForRandomLongPeriod(); + + mPerfCollector.startTiming("testLongSequenceTiming2"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration4"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration5"); + workForRandomShortPeriod(); + Bundle timing2 = mPerfCollector.stopTiming("iteration6"); + workForRandomLongPeriod(); + + mPerfCollector.startTiming("testLongSequenceTiming3"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration7"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration8"); + workForRandomShortPeriod(); + Bundle timing3 = mPerfCollector.stopTiming("iteration9"); + workForRandomLongPeriod(); + + mPerfCollector.startTiming("testLongSequenceTiming4"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration10"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration11"); + workForRandomShortPeriod(); + Bundle timing4 = mPerfCollector.stopTiming("iteration12"); + workForRandomLongPeriod(); + + mPerfCollector.startTiming("testLongSequenceTiming5"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration13"); + workForRandomTinyPeriod(); + mPerfCollector.addIteration("iteration14"); + workForRandomShortPeriod(); + Bundle timing5 = mPerfCollector.stopTiming("iteration15"); + workForRandomLongPeriod(); + Bundle snapshot1 = mPerfCollector.endSnapshot(); + Bundle snapshot2 = writer.snapshotResults; + + assertEqualsBundle(snapshot1, snapshot2); + verifySnapshotBundle(snapshot1); + + ArrayList<String> labels1 = new ArrayList<String>(); + labels1.add("iteration1"); + labels1.add("iteration2"); + labels1.add("iteration3"); + verifyTimingBundle(timing1, labels1); + ArrayList<String> labels2 = new ArrayList<String>(); + labels2.add("iteration4"); + labels2.add("iteration5"); + labels2.add("iteration6"); + verifyTimingBundle(timing2, labels2); + ArrayList<String> labels3 = new ArrayList<String>(); + labels3.add("iteration7"); + labels3.add("iteration8"); + labels3.add("iteration9"); + verifyTimingBundle(timing3, labels3); + ArrayList<String> labels4 = new ArrayList<String>(); + labels4.add("iteration10"); + labels4.add("iteration11"); + labels4.add("iteration12"); + verifyTimingBundle(timing4, labels4); + ArrayList<String> labels5 = new ArrayList<String>(); + labels5.add("iteration13"); + labels5.add("iteration14"); + labels5.add("iteration15"); + verifyTimingBundle(timing5, labels5); + } + + /* + * Verify that snapshotting and timing do not interfere w/ each other, + * by staggering calls to snapshot and timing functions. + */ + @SmallTest + public void testOutOfOrderSequence() { + MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); + mPerfCollector.setPerformanceResultsWriter(writer); + mPerfCollector.startTiming("testOutOfOrderSequenceTiming"); + workForRandomShortPeriod(); + mPerfCollector.beginSnapshot("testOutOfOrderSequenceSnapshot"); + workForRandomShortPeriod(); + Bundle timing1 = mPerfCollector.stopTiming("timing1"); + workForRandomShortPeriod(); + Bundle snapshot1 = mPerfCollector.endSnapshot(); + + Bundle timing2 = writer.timingResults; + Bundle snapshot2 = writer.snapshotResults; + + assertEqualsBundle(snapshot1, snapshot2); + verifySnapshotBundle(snapshot1); + + assertEqualsBundle(timing1, timing2); + ArrayList<String> labels = new ArrayList<String>(); + labels.add("timing1"); + verifyTimingBundle(timing1, labels); + } + + private void workForRandomPeriod(int minDuration, int maxDuration) { + Random random = new Random(); + int period = minDuration + random.nextInt(maxDuration - minDuration); + long start = Process.getElapsedCpuTime(); + // Generate positive amount of work, so cpu time is measurable in + // milliseconds + while (Process.getElapsedCpuTime() - start < period) { + for (int i = 0, temp = 0; i < 50; i++ ) { + temp += i; + } + } + } + + private void workForRandomTinyPeriod() { + workForRandomPeriod(2, 5); + } + + private void workForRandomShortPeriod() { + workForRandomPeriod(10, 25); + } + + private void workForRandomLongPeriod() { + workForRandomPeriod(50, 100); + } + + private void verifySnapshotBundle(Bundle snapshot) { + assertTrue("At least 26 metrics collected", 26 <= snapshot.size()); + + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_CPU_TIME)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_EXECUTION_TIME)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME) > 0); + + assertTrue(snapshot.containsKey( + PerformanceCollector.METRIC_KEY_PRE_RECEIVED_TRANSACTIONS)); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_PRE_SENT_TRANSACTIONS)); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_RECEIVED_TRANSACTIONS)); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_SENT_TRANSACTIONS)); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GC_INVOCATION_COUNT)); + + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_ALLOCATED)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_ALLOCATED) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_FREE)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_FREE) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_PRIVATE_DIRTY)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_PRIVATE_DIRTY) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_PSS)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_PSS) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_SHARED_DIRTY)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_SHARED_DIRTY) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_SIZE)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_SIZE) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_ALLOCATED)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_ALLOCATED) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_FREE)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_FREE) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_PRIVATE_DIRTY)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_PRIVATE_DIRTY) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_PSS)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_PSS) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_SHARED_DIRTY)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_SHARED_DIRTY) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_SIZE)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_SIZE) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_COUNT)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_COUNT) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_SIZE)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_SIZE) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_COUNT)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_COUNT) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_SIZE)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_SIZE) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_OTHER_PRIVATE_DIRTY)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_OTHER_PRIVATE_DIRTY) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_OTHER_PSS)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_OTHER_PSS) > 0); + assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_OTHER_SHARED_DIRTY)); + assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_OTHER_SHARED_DIRTY) > 0); + } + + private void verifyIterationBundle(Bundle iteration, String label) { + assertEquals(3, iteration.size()); + assertTrue(iteration.containsKey(PerformanceCollector.METRIC_KEY_LABEL)); + assertEquals(label, iteration.getString(PerformanceCollector.METRIC_KEY_LABEL)); + assertTrue(iteration.containsKey(PerformanceCollector.METRIC_KEY_CPU_TIME)); + assertTrue(iteration.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME) > 0); + assertTrue(iteration.containsKey(PerformanceCollector.METRIC_KEY_EXECUTION_TIME)); + assertTrue(iteration.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME) > 0); + } + + private void verifyTimingBundle(Bundle timing, ArrayList<String> labels) { + assertEquals(1, timing.size()); + assertTrue(timing.containsKey(PerformanceCollector.METRIC_KEY_ITERATIONS)); + ArrayList<Parcelable> iterations = timing.getParcelableArrayList( + PerformanceCollector.METRIC_KEY_ITERATIONS); + assertNotNull(iterations); + assertEquals(labels.size(), iterations.size()); + for (int i = 0; i < labels.size(); i ++) { + Bundle iteration = (Bundle)iterations.get(i); + verifyIterationBundle(iteration, labels.get(i)); + } + } + + private void assertEqualsBundle(Bundle b1, Bundle b2) { + assertEquals(b1.keySet(), b2.keySet()); + for (String key : b1.keySet()) { + assertEquals(b1.get(key), b2.get(key)); + } + } + + private Object readPrivateField(String fieldName, Object object) throws Exception { + Field f = object.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + return f.get(object); + } + + private class MockPerformanceResultsWriter implements PerformanceResultsWriter { + + public String snapshotLabel; + public Bundle snapshotResults = new Bundle(); + public String timingLabel; + public Bundle timingResults = new Bundle(); + + public void writeBeginSnapshot(String label) { + snapshotLabel = label; + } + + public void writeEndSnapshot(Bundle results) { + snapshotResults.putAll(results); + } + + public void writeStartTiming(String label) { + timingLabel = label; + } + + public void writeStopTiming(Bundle results) { + timingResults.putAll(results); + } + + public void writeMeasurement(String label, long value) { + timingResults.putLong(label, value); + } + + public void writeMeasurement(String label, float value) { + timingResults.putFloat(label, value); + } + + public void writeMeasurement(String label, String value) { + timingResults.putString(label, value); + } + } +} diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java new file mode 100644 index 0000000..e089b3e --- /dev/null +++ b/core/tests/coretests/src/android/os/PowerManagerTest.java @@ -0,0 +1,142 @@ +/* + * 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. + */ + +package android.os; + +import android.content.Context; +import android.os.PowerManager; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +public class PowerManagerTest extends AndroidTestCase { + + private PowerManager mPm; + + /** + * Setup any common data for the upcoming tests. + */ + @Override + public void setUp() throws Exception { + super.setUp(); + mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + } + + /** + * Confirm that the setup is good. + * + * @throws Exception + */ + @MediumTest + public void testPreconditions() throws Exception { + assertNotNull(mPm); + } + + /** + * Confirm that we can create functional wakelocks. + * + * @throws Exception + */ + @MediumTest + public void testNewWakeLock() throws Exception { + PowerManager.WakeLock wl = mPm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "FULL_WAKE_LOCK"); + doTestWakeLock(wl); + + wl = mPm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "SCREEN_BRIGHT_WAKE_LOCK"); + doTestWakeLock(wl); + + wl = mPm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "SCREEN_DIM_WAKE_LOCK"); + doTestWakeLock(wl); + + wl = mPm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PARTIAL_WAKE_LOCK"); + doTestWakeLock(wl); + + doTestSetBacklightBrightness(); + + // TODO: Some sort of functional test (maybe not in the unit test here?) + // that confirms that things are really happening e.g. screen power, keyboard power. +} + + /** + * Confirm that we can't create dysfunctional wakelocks. + * + * @throws Exception + */ + @MediumTest + public void testBadNewWakeLock() throws Exception { + + final int badFlags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK + | PowerManager.SCREEN_DIM_WAKE_LOCK; + // wrap in try because we want the error here + try { + PowerManager.WakeLock wl = mPm.newWakeLock(badFlags, "foo"); + } catch (IllegalArgumentException e) { + return; + } + fail("Bad WakeLock flag was not caught."); + } + + /** + * Apply a few tests to a wakelock to make sure it's healthy. + * + * @param wl The wakelock to be tested. + */ + private void doTestWakeLock(PowerManager.WakeLock wl) { + // First try simple acquire/release + wl.acquire(); + assertTrue(wl.isHeld()); + wl.release(); + assertFalse(wl.isHeld()); + + // Try ref-counted acquire/release + wl.setReferenceCounted(true); + wl.acquire(); + assertTrue(wl.isHeld()); + wl.acquire(); + assertTrue(wl.isHeld()); + wl.release(); + assertTrue(wl.isHeld()); + wl.release(); + assertFalse(wl.isHeld()); + + // Try non-ref-counted + wl.setReferenceCounted(false); + wl.acquire(); + assertTrue(wl.isHeld()); + wl.acquire(); + assertTrue(wl.isHeld()); + wl.release(); + assertFalse(wl.isHeld()); + + // TODO: Threaded test (needs handler) to make sure timed wakelocks work too + } + + + /** + * Test that calling {@link android.os.IHardwareService#setBacklights(int)} requires + * permissions. + * <p>Tests permission: + * {@link android.Manifest.permission#DEVICE_POWER} + */ + private void doTestSetBacklightBrightness() { + try { + mPm.setBacklightBrightness(0); + fail("setBacklights did not throw SecurityException as expected"); + } catch (SecurityException e) { + // expected + } + } + +} diff --git a/core/tests/coretests/src/android/os/SystemPropertiesTest.java b/core/tests/coretests/src/android/os/SystemPropertiesTest.java new file mode 100644 index 0000000..25868ce --- /dev/null +++ b/core/tests/coretests/src/android/os/SystemPropertiesTest.java @@ -0,0 +1,51 @@ +/* + * 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.os; + +import static junit.framework.Assert.assertEquals; +import junit.framework.TestCase; + +import android.os.SystemProperties; +import android.test.suitebuilder.annotation.SmallTest; + +public class SystemPropertiesTest extends TestCase { + private static final String KEY = "com.android.frameworks.coretests"; + @SmallTest + public void testProperties() throws Exception { + if (false) { + String value; + + SystemProperties.set(KEY, ""); + value = SystemProperties.get(KEY, "default"); + assertEquals("default", value); + + SystemProperties.set(KEY, "AAA"); + value = SystemProperties.get(KEY, "default"); + assertEquals("AAA", value); + + value = SystemProperties.get(KEY); + assertEquals("AAA", value); + + SystemProperties.set(KEY, ""); + value = SystemProperties.get(KEY, "default"); + assertEquals("default", value); + + value = SystemProperties.get(KEY); + assertEquals("", value); + } + } +} diff --git a/core/tests/coretests/src/android/os/TestHandlerThread.java b/core/tests/coretests/src/android/os/TestHandlerThread.java new file mode 100644 index 0000000..7e84af3 --- /dev/null +++ b/core/tests/coretests/src/android/os/TestHandlerThread.java @@ -0,0 +1,104 @@ +/* + * 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.os; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue.IdleHandler; + +abstract class TestHandlerThread { + private boolean mDone = false; + private boolean mSuccess = false; + private RuntimeException mFailure = null; + private Looper mLooper; + + public abstract void go(); + + public TestHandlerThread() { + } + + public void doTest(long timeout) { + (new LooperThread()).start(); + + synchronized (this) { + long now = System.currentTimeMillis(); + long endTime = now + timeout; + while (!mDone && now < endTime) { + try { + wait(endTime-now); + } + catch (InterruptedException e) { + } + now = System.currentTimeMillis(); + } + } + + mLooper.quit(); + + if (!mDone) { + throw new RuntimeException("test timed out"); + } + if (!mSuccess) { + throw mFailure; + } + } + + public Looper getLooper() { + return mLooper; + } + + public void success() { + synchronized (this) { + mSuccess = true; + quit(); + } + } + + public void failure(RuntimeException failure) { + synchronized (this) { + mSuccess = false; + mFailure = failure; + quit(); + } + } + + class LooperThread extends Thread { + public void run() { + Looper.prepare(); + mLooper = Looper.myLooper(); + go(); + Looper.loop(); + + synchronized (TestHandlerThread.this) { + mDone = true; + if (!mSuccess && mFailure == null) { + mFailure = new RuntimeException("no failure exception set"); + } + TestHandlerThread.this.notifyAll(); + } + } + + } + + private void quit() { + synchronized (this) { + mDone = true; + notifyAll(); + } + } +} diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java new file mode 100644 index 0000000..7a788ee --- /dev/null +++ b/core/tests/coretests/src/android/os/TraceTest.java @@ -0,0 +1,215 @@ +/* + * 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.os; + +import android.os.Debug; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; +import android.util.Log; + +/** + * This class is used to test the native tracing support. Run this test + * while tracing on the emulator and then run traceview to view the trace. + */ +public class TraceTest extends AndroidTestCase +{ + private static final String TAG = "TraceTest"; + private int eMethodCalls = 0; + private int fMethodCalls = 0; + private int gMethodCalls = 0; + + @SmallTest + public void testNativeTracingFromJava() + { + long start = System.currentTimeMillis(); + Debug.startNativeTracing(); + //nativeMethod(); + int count = 0; + for (int ii = 0; ii < 20; ii++) { + count = eMethod(); + } + Debug.stopNativeTracing(); + long end = System.currentTimeMillis(); + long elapsed = end - start; + Log.i(TAG, "elapsed millis: " + elapsed); + Log.i(TAG, "eMethod calls: " + eMethodCalls + + " fMethod calls: " + fMethodCalls + + " gMethod calls: " + gMethodCalls); + } + + // This should not run in the automated suite. + @Suppress + public void disableTestNativeTracingFromC() + { + long start = System.currentTimeMillis(); + nativeMethodAndStartTracing(); + long end = System.currentTimeMillis(); + long elapsed = end - start; + Log.i(TAG, "elapsed millis: " + elapsed); + } + + native void nativeMethod(); + native void nativeMethodAndStartTracing(); + + @LargeTest + public void testMethodTracing() + { + long start = System.currentTimeMillis(); + Debug.startMethodTracing("traceTest"); + topMethod(); + Debug.stopMethodTracing(); + long end = System.currentTimeMillis(); + long elapsed = end - start; + Log.i(TAG, "elapsed millis: " + elapsed); + } + + private void topMethod() { + aMethod(); + bMethod(); + cMethod(); + dMethod(5); + + Thread t1 = new aThread(); + t1.start(); + Thread t2 = new aThread(); + t2.start(); + Thread t3 = new aThread(); + t3.start(); + try { + t1.join(); + t2.join(); + t3.join(); + } catch (InterruptedException e) { + } + } + + private class aThread extends Thread { + @Override + public void run() { + aMethod(); + bMethod(); + cMethod(); + } + } + + /** Calls other methods to make some interesting trace data. + * + * @return a meaningless value + */ + private int aMethod() { + int count = 0; + for (int ii = 0; ii < 6; ii++) { + count += bMethod(); + } + for (int ii = 0; ii < 5; ii++) { + count += cMethod(); + } + for (int ii = 0; ii < 4; ii++) { + count += dMethod(ii); + } + return count; + } + + /** Calls another method to make some interesting trace data. + * + * @return a meaningless value + */ + private int bMethod() { + int count = 0; + for (int ii = 0; ii < 4; ii++) { + count += cMethod(); + } + return count; + } + + /** Executes a simple loop to make some interesting trace data. + * + * @return a meaningless value + */ + private int cMethod() { + int count = 0; + for (int ii = 0; ii < 1000; ii++) { + count += ii; + } + return count; + } + + /** Calls itself recursively to make some interesting trace data. + * + * @return a meaningless value + */ + private int dMethod(int level) { + int count = 0; + if (level > 0) { + count = dMethod(level - 1); + } + for (int ii = 0; ii < 100; ii++) { + count += ii; + } + if (level == 0) { + return count; + } + return dMethod(level - 1); + } + + public int eMethod() { + eMethodCalls += 1; + int count = fMethod(); + count += gMethod(3); + return count; + } + + public int fMethod() { + fMethodCalls += 1; + int count = 0; + for (int ii = 0; ii < 10; ii++) { + count += ii; + } + return count; + } + + public int gMethod(int level) { + gMethodCalls += 1; + int count = level; + if (level > 1) + count += gMethod(level - 1); + return count; + } + + /* + * This causes the native shared library to be loaded when the + * class is first used. The library is only loaded once, even if + * multiple classes include this line. + * + * The library must be in java.library.path, which is derived from + * LD_LIBRARY_PATH. The actual library name searched for will be + * "libtrace_test.so" under Linux, but may be different on other + * platforms. + */ + static { + Log.i(TAG, "Loading trace_test native library..."); + try { + System.loadLibrary("trace_test"); + Log.i(TAG, "Successfully loaded trace_test native library"); + } + catch (UnsatisfiedLinkError ule) { + Log.w(TAG, "Could not load trace_test native library"); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java new file mode 100644 index 0000000..b3c0773 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java @@ -0,0 +1,81 @@ +/* + * 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; + +import android.content.ContentValues; + +/** + * ContentValues-like class which enables users to chain put() methods and restricts + * the other methods. + */ +/* package */ class ContentValuesBuilder { + private final ContentValues mContentValues; + + public ContentValuesBuilder(final ContentValues contentValues) { + mContentValues = contentValues; + } + + public ContentValuesBuilder put(String key, String value) { + mContentValues.put(key, value); + return this; + } + + public ContentValuesBuilder put(String key, Byte value) { + mContentValues.put(key, value); + return this; + } + + public ContentValuesBuilder put(String key, Short value) { + mContentValues.put(key, value); + return this; + } + + public ContentValuesBuilder put(String key, Integer value) { + mContentValues.put(key, value); + return this; + } + + public ContentValuesBuilder put(String key, Long value) { + mContentValues.put(key, value); + return this; + } + + public ContentValuesBuilder put(String key, Float value) { + mContentValues.put(key, value); + return this; + } + + public ContentValuesBuilder put(String key, Double value) { + mContentValues.put(key, value); + return this; + } + + public ContentValuesBuilder put(String key, Boolean value) { + mContentValues.put(key, value); + return this; + } + + public ContentValuesBuilder put(String key, byte[] value) { + mContentValues.put(key, value); + return this; + } + + public ContentValuesBuilder putNull(String key) { + mContentValues.putNull(key); + return this; + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java new file mode 100644 index 0000000..b9e9875 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java @@ -0,0 +1,101 @@ +/* + * 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; + +import android.pim.vcard.VCardConfig; +import android.pim.vcard.VCardEntry; +import android.pim.vcard.VCardEntryConstructor; +import android.pim.vcard.VCardEntryHandler; +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; + +/* package */ class ContentValuesVerifier implements VCardEntryHandler { + private AndroidTestCase mTestCase; + private List<ContentValuesVerifierElem> mContentValuesVerifierElemList = + new ArrayList<ContentValuesVerifierElem>(); + private int mIndex; + + public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) { + mTestCase = androidTestCase; + ContentValuesVerifierElem importVerifier = new ContentValuesVerifierElem(androidTestCase); + mContentValuesVerifierElemList.add(importVerifier); + return importVerifier; + } + + public void verify(int resId, int vCardType) throws IOException, VCardException { + verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType); + } + + public void verify(int resId, int vCardType, final VCardParser vCardParser) + throws IOException, VCardException { + verify(mTestCase.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 { + VCardEntryConstructor builder = + new VCardEntryConstructor(null, null, false, vCardType, null); + builder.addEntryHandler(this); + try { + vCardParser.parse(is, builder); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + } + } + } + } + + public void onStart() { + for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) { + elem.onParsingStart(); + } + } + + public void onEntryCreated(VCardEntry entry) { + mTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size()); + mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry); + mIndex++; + } + + public void onEnd() { + for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) { + elem.onParsingEnd(); + elem.verifyResolver(); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java new file mode 100644 index 0000000..2edbb36 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java @@ -0,0 +1,95 @@ +/* + * 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; + +import android.content.ContentValues; +import android.pim.vcard.VCardConfig; +import android.pim.vcard.VCardEntry; +import android.pim.vcard.VCardEntryCommitter; +import android.pim.vcard.VCardEntryConstructor; +import android.pim.vcard.VCardEntryHandler; +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.provider.ContactsContract.Data; +import android.test.AndroidTestCase; + +import java.io.IOException; +import java.io.InputStream; + +/* package */ class ContentValuesVerifierElem { + private final AndroidTestCase mTestCase; + private final ImportTestResolver mResolver; + private final VCardEntryHandler mHandler; + + public ContentValuesVerifierElem(AndroidTestCase androidTestCase) { + mTestCase = androidTestCase; + mResolver = new ImportTestResolver(androidTestCase); + mHandler = new VCardEntryCommitter(mResolver); + } + + public ContentValuesBuilder addExpected(String mimeType) { + ContentValues contentValues = new ContentValues(); + contentValues.put(Data.MIMETYPE, mimeType); + mResolver.addExpectedContentValues(contentValues); + return new ContentValuesBuilder(contentValues); + } + + public void verify(int resId, int vCardType) + throws IOException, VCardException { + verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType); + } + + 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(); + } + VCardEntryConstructor builder = + new VCardEntryConstructor(null, null, false, vCardType, null); + builder.addEntryHandler(mHandler); + try { + vCardParser.parse(is, builder); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + } + } + } + verifyResolver(); + } + + public void verifyResolver() { + mResolver.verify(); + } + + public void onParsingStart() { + mHandler.onStart(); + } + + public void onEntryCreated(VCardEntry entry) { + mHandler.onEntryCreated(entry); + } + + public void onParsingEnd() { + mHandler.onEnd(); + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java new file mode 100644 index 0000000..1b3cdcc --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java @@ -0,0 +1,213 @@ +/* + * 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; + +import android.content.ContentResolver; +import android.content.ContentValues; +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.MockCursor; + +import junit.framework.TestCase; + +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 MockEntityIterator implements EntityIterator { + List<Entity> mEntityList; + Iterator<Entity> mIterator; + + public MockEntityIterator(List<ContentValues> contentValuesList) { + mEntityList = new ArrayList<Entity>(); + Entity entity = new Entity(new ContentValues()); + for (ContentValues contentValues : contentValuesList) { + entity.addSubValue(Data.CONTENT_URI, contentValues); + } + mEntityList.add(entity); + mIterator = mEntityList.iterator(); + } + + public boolean hasNext() { + return mIterator.hasNext(); + } + + public Entity next() { + return mIterator.next(); + } + + public void reset() { + mIterator = mEntityList.iterator(); + } + + public void close() { + } +} + +/** + * Represents one contact, which should contain multiple ContentValues like + * StructuredName, Email, etc. + */ +/* package */ class ContactEntry { + private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>(); + + public ContentValuesBuilder addContentValues(String mimeType) { + ContentValues contentValues = new ContentValues(); + contentValues.put(Data.MIMETYPE, mimeType); + mContentValuesList.add(contentValues); + return new ContentValuesBuilder(contentValues); + } + + public List<ContentValues> getList() { + return mContentValuesList; + } +} + +/* package */ class ExportTestProvider extends MockContentProvider { + final private TestCase mTestCase; + final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>(); + + public ExportTestProvider(TestCase testCase) { + mTestCase = testCase; + } + + public ContactEntry buildInputEntry() { + ContactEntry contactEntry = new ContactEntry(); + mContactEntryList.add(contactEntry); + return contactEntry; + } + + /** + * <p> + * An old method which had existed but was removed from ContentResolver. + * </p> + * <p> + * 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) { + mTestCase.assertTrue(uri != null); + mTestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())); + final String authority = uri.getAuthority(); + mTestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority)); + mTestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection)); + mTestCase.assertEquals(1, selectionArgs.length); + final int id = Integer.parseInt(selectionArgs[0]); + mTestCase.assertTrue(id >= 0 && id < mContactEntryList.size()); + + return new MockEntityIterator(mContactEntryList.get(id).getList()); + } + + @Override + public Cursor query(Uri uri,String[] projection, + String selection, String[] selectionArgs, String sortOrder) { + mTestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri)); + // In this test, following arguments are not supported. + mTestCase.assertNull(selection); + mTestCase.assertNull(selectionArgs); + mTestCase.assertNull(sortOrder); + + return new MockCursor() { + int mCurrentPosition = -1; + + @Override + public int getCount() { + return mContactEntryList.size(); + } + + @Override + public boolean moveToFirst() { + mCurrentPosition = 0; + return true; + } + + @Override + public boolean moveToNext() { + if (mCurrentPosition < mContactEntryList.size()) { + mCurrentPosition++; + return true; + } else { + return false; + } + } + + @Override + public boolean isBeforeFirst() { + return mCurrentPosition < 0; + } + + @Override + public boolean isAfterLast() { + return mCurrentPosition >= mContactEntryList.size(); + } + + @Override + public int getColumnIndex(String columnName) { + mTestCase.assertEquals(Contacts._ID, columnName); + return 0; + } + + @Override + public int getInt(int columnIndex) { + mTestCase.assertEquals(0, columnIndex); + mTestCase.assertTrue(mCurrentPosition >= 0 + && mCurrentPosition < mContactEntryList.size()); + return mCurrentPosition; + } + + @Override + public String getString(int columnIndex) { + return String.valueOf(getInt(columnIndex)); + } + + @Override + public void close() { + } + }; + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java new file mode 100644 index 0000000..019b9e3 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java @@ -0,0 +1,299 @@ +/* + * 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; + +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentValues; +import android.net.Uri; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.Event; +import android.provider.ContactsContract.CommonDataKinds.GroupMembership; +import android.provider.ContactsContract.CommonDataKinds.Im; +import android.provider.ContactsContract.CommonDataKinds.Nickname; +import android.provider.ContactsContract.CommonDataKinds.Note; +import android.provider.ContactsContract.CommonDataKinds.Organization; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.CommonDataKinds.Photo; +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.text.TextUtils; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +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, + Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE, + Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE, + Im.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE, + Event.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE, + Note.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE, + Relation.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE, + GroupMembership.CONTENT_ITEM_TYPE)); + + final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues; + + private final TestCase mTestCase; + + public ImportTestProvider(TestCase testCase) { + mTestCase = testCase; + mMimeTypeToExpectedContentValues = + new HashMap<String, Collection<ContentValues>>(); + for (String acceptanbleMimeType : sKnownMimeTypeSet) { + // Do not use HashSet since the current implementation changes the content of + // ContentValues after the insertion, which make the result of hashCode() + // changes... + mMimeTypeToExpectedContentValues.put( + acceptanbleMimeType, new ArrayList<ContentValues>()); + } + } + + public void addExpectedContentValues(ContentValues expectedContentValues) { + final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE); + if (!sKnownMimeTypeSet.contains(mimeType)) { + mTestCase.fail(String.format( + "Unknow MimeType %s in the test code. Test code should be broken.", + mimeType)); + } + + final Collection<ContentValues> contentValuesCollection = + mMimeTypeToExpectedContentValues.get(mimeType); + contentValuesCollection.add(expectedContentValues); + } + + @Override + public ContentProviderResult[] applyBatch( + ArrayList<ContentProviderOperation> operations) { + if (operations == null) { + mTestCase.fail("There is no operation."); + } + + final int size = operations.size(); + ContentProviderResult[] fakeResultArray = new ContentProviderResult[size]; + for (int i = 0; i < size; i++) { + Uri uri = Uri.withAppendedPath(RawContacts.CONTENT_URI, String.valueOf(i)); + fakeResultArray[i] = new ContentProviderResult(uri); + } + + for (int i = 0; i < size; i++) { + ContentProviderOperation operation = operations.get(i); + ContentValues contentValues = operation.resolveValueBackReferences( + fakeResultArray, i); + } + for (int i = 0; i < size; i++) { + ContentProviderOperation operation = operations.get(i); + ContentValues actualContentValues = operation.resolveValueBackReferences( + fakeResultArray, i); + final Uri uri = operation.getUri(); + if (uri.equals(RawContacts.CONTENT_URI)) { + mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME)); + mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE)); + } else if (uri.equals(Data.CONTENT_URI)) { + final String mimeType = actualContentValues.getAsString(Data.MIMETYPE); + if (!sKnownMimeTypeSet.contains(mimeType)) { + mTestCase.fail(String.format( + "Unknown MimeType %s. Probably added after developing this test", + mimeType)); + } + // Remove data meaningless in this unit tests. + // Specifically, Data.DATA1 - DATA7 are set to null or empty String + // regardless of the input, but it may change depending on how + // resolver-related code handles it. + // Here, we ignore these implementation-dependent specs and + // just check whether vCard importer correctly inserts rellevent data. + Set<String> keyToBeRemoved = new HashSet<String>(); + for (Entry<String, Object> entry : actualContentValues.valueSet()) { + Object value = entry.getValue(); + if (value == null || TextUtils.isEmpty(value.toString())) { + keyToBeRemoved.add(entry.getKey()); + } + } + for (String key: keyToBeRemoved) { + actualContentValues.remove(key); + } + /* for testing + Log.d("@@@", + String.format("MimeType: %s, data: %s", + mimeType, actualContentValues.toString())); */ + // Remove RAW_CONTACT_ID entry just for safety, since we do not care + // how resolver-related code handles the entry in this unit test, + if (actualContentValues.containsKey(Data.RAW_CONTACT_ID)) { + actualContentValues.remove(Data.RAW_CONTACT_ID); + } + final Collection<ContentValues> contentValuesCollection = + mMimeTypeToExpectedContentValues.get(mimeType); + if (contentValuesCollection.isEmpty()) { + mTestCase.fail("ContentValues for MimeType " + mimeType + + " is not expected at all (" + actualContentValues + ")"); + } + boolean checked = false; + for (ContentValues expectedContentValues : contentValuesCollection) { + /*for testing + Log.d("@@@", "expected: " + + convertToEasilyReadableString(expectedContentValues)); + Log.d("@@@", "actual : " + + convertToEasilyReadableString(actualContentValues));*/ + if (equalsForContentValues(expectedContentValues, + actualContentValues)) { + mTestCase.assertTrue(contentValuesCollection.remove(expectedContentValues)); + checked = true; + break; + } + } + if (!checked) { + final StringBuilder builder = new StringBuilder(); + builder.append("Unexpected: "); + builder.append(convertToEasilyReadableString(actualContentValues)); + builder.append("\nExpected: "); + for (ContentValues expectedContentValues : contentValuesCollection) { + builder.append(convertToEasilyReadableString(expectedContentValues)); + } + mTestCase.fail(builder.toString()); + } + } else { + mTestCase.fail("Unexpected Uri has come: " + uri); + } + } // for (int i = 0; i < size; i++) { + return null; + } + + public void verify() { + StringBuilder builder = new StringBuilder(); + for (Collection<ContentValues> contentValuesCollection : + mMimeTypeToExpectedContentValues.values()) { + for (ContentValues expectedContentValues: contentValuesCollection) { + builder.append(convertToEasilyReadableString(expectedContentValues)); + builder.append("\n"); + } + } + if (builder.length() > 0) { + final String failMsg = + "There is(are) remaining expected ContentValues instance(s): \n" + + builder.toString(); + mTestCase.fail(failMsg); + } + } + + /** + * Utility method to print ContentValues whose content is printed with sorted keys. + */ + private String convertToEasilyReadableString(ContentValues contentValues) { + if (contentValues == null) { + return "null"; + } + String mimeTypeValue = ""; + SortedMap<String, String> sortedMap = new TreeMap<String, String>(); + for (Entry<String, Object> entry : contentValues.valueSet()) { + final String key = entry.getKey(); + final Object value = entry.getValue(); + final String valueString = (value != null ? value.toString() : null); + if (Data.MIMETYPE.equals(key)) { + mimeTypeValue = valueString; + } else { + mTestCase.assertNotNull(key); + sortedMap.put(key, valueString); + } + } + StringBuilder builder = new StringBuilder(); + builder.append(Data.MIMETYPE); + builder.append('='); + builder.append(mimeTypeValue); + for (Entry<String, String> entry : sortedMap.entrySet()) { + final String key = entry.getKey(); + final String value = entry.getValue(); + builder.append(' '); + builder.append(key); + builder.append("=\""); + builder.append(value); + builder.append('"'); + } + return builder.toString(); + } + + private static boolean equalsForContentValues( + ContentValues expected, ContentValues actual) { + if (expected == actual) { + return true; + } else if (expected == null || actual == null || expected.size() != actual.size()) { + return false; + } + + for (Entry<String, Object> entry : expected.valueSet()) { + final String key = entry.getKey(); + final Object value = entry.getValue(); + if (!actual.containsKey(key)) { + return false; + } + if (value instanceof byte[]) { + Object actualValue = actual.get(key); + if (!Arrays.equals((byte[])value, (byte[])actualValue)) { + return false; + } + } else if (!value.equals(actual.get(key))) { + return false; + } + } + return true; + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java b/core/tests/coretests/src/android/pim/vcard/LineVerifier.java new file mode 100644 index 0000000..cef15fd --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/LineVerifier.java @@ -0,0 +1,65 @@ +/* + * 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; + +import android.content.Context; +import android.pim.vcard.VCardComposer; + +import junit.framework.TestCase; + +import java.util.ArrayList; + +class LineVerifier implements VCardComposer.OneEntryHandler { + private final TestCase mTestCase; + private final ArrayList<LineVerifierElem> mLineVerifierElemList; + private int mVCardType; + private int index; + + public LineVerifier(TestCase testCase, int vcardType) { + mTestCase = testCase; + mLineVerifierElemList = new ArrayList<LineVerifierElem>(); + mVCardType = vcardType; + } + + public LineVerifierElem addLineVerifierElem() { + LineVerifierElem lineVerifier = new LineVerifierElem(mTestCase, mVCardType); + mLineVerifierElemList.add(lineVerifier); + return lineVerifier; + } + + public void verify(String vcard) { + if (index >= mLineVerifierElemList.size()) { + mTestCase.fail("Insufficient number of LineVerifier (" + index + ")"); + } + + LineVerifierElem lineVerifier = mLineVerifierElemList.get(index); + lineVerifier.verify(vcard); + + index++; + } + + public boolean onEntryCreated(String vcard) { + verify(vcard); + return true; + } + + public boolean onInit(Context context) { + return true; + } + + public void onTerminate() { + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java new file mode 100644 index 0000000..b23b29b --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java @@ -0,0 +1,105 @@ +/* + * 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; + +import android.pim.vcard.VCardConfig; +import android.text.TextUtils; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.List; + +class LineVerifierElem { + private final TestCase mTestCase; + private final List<String> mExpectedLineList = new ArrayList<String>(); + private final boolean mIsV30; + + public LineVerifierElem(TestCase testCase, int vcardType) { + mTestCase = testCase; + mIsV30 = VCardConfig.isV30(vcardType); + } + + public LineVerifierElem addExpected(final String line) { + if (!TextUtils.isEmpty(line)) { + mExpectedLineList.add(line); + } + return this; + } + + public void verify(final String vcard) { + final String[] lineArray = vcard.split("\\r?\\n"); + final int length = lineArray.length; + boolean beginExists = false; + boolean endExists = false; + boolean versionExists = false; + + for (int i = 0; i < length; i++) { + final String line = lineArray[i]; + if (TextUtils.isEmpty(line)) { + continue; + } + + if ("BEGIN:VCARD".equalsIgnoreCase(line)) { + if (beginExists) { + mTestCase.fail("Multiple \"BEGIN:VCARD\" line found"); + } else { + beginExists = true; + continue; + } + } else if ("END:VCARD".equalsIgnoreCase(line)) { + if (endExists) { + mTestCase.fail("Multiple \"END:VCARD\" line found"); + } else { + endExists = true; + continue; + } + } else if ((mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equalsIgnoreCase(line)) { + if (versionExists) { + mTestCase.fail("Multiple VERSION line + found"); + } else { + versionExists = true; + continue; + } + } + + if (!beginExists) { + mTestCase.fail("Property other than BEGIN came before BEGIN property: " + + line); + } else if (endExists) { + mTestCase.fail("Property other than END came after END property: " + + line); + } + + final int index = mExpectedLineList.indexOf(line); + if (index >= 0) { + mExpectedLineList.remove(index); + } else { + mTestCase.fail("Unexpected line: " + line); + } + } + + if (!mExpectedLineList.isEmpty()) { + StringBuffer buffer = new StringBuffer(); + for (String expectedLine : mExpectedLineList) { + buffer.append(expectedLine); + buffer.append("\n"); + } + + mTestCase.fail("Expected line(s) not found:" + buffer.toString()); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java b/core/tests/coretests/src/android/pim/vcard/PropertyNode.java new file mode 100644 index 0000000..2c1f6d2 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/PropertyNode.java @@ -0,0 +1,198 @@ +/* + * 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; + +import android.content.ContentValues; +import android.pim.vcard.VCardEntry; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Previously used in main vCard handling code but now exists only for testing. + * + * 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. + */ +public class PropertyNode { + public String propName; + public String propValue; + public List<String> propValue_vector; + + /** Store value as byte[],after decode. + * Used when propValue is encoded by something like BASE64, QUOTED-PRINTABLE, etc. + */ + public byte[] propValue_bytes; + + /** 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"]... + * TODO: fix this. + */ + public ContentValues paramMap; + + /** Only for TYPE=??? param store. */ + public Set<String> paramMap_TYPE; + + /** Store group values. Used only in VCard. */ + public Set<String> propGroupSet; + + public PropertyNode() { + propName = ""; + propValue = ""; + propValue_vector = new ArrayList<String>(); + paramMap = new ContentValues(); + paramMap_TYPE = new HashSet<String>(); + propGroupSet = new HashSet<String>(); + } + + public PropertyNode( + String propName, String propValue, List<String> propValue_vector, + byte[] propValue_bytes, ContentValues paramMap, Set<String> paramMap_TYPE, + Set<String> propGroupSet) { + if (propName != null) { + this.propName = propName; + } else { + this.propName = ""; + } + if (propValue != null) { + this.propValue = propValue; + } else { + this.propValue = ""; + } + if (propValue_vector != null) { + this.propValue_vector = propValue_vector; + } else { + this.propValue_vector = new ArrayList<String>(); + } + this.propValue_bytes = propValue_bytes; + if (paramMap != null) { + this.paramMap = paramMap; + } else { + this.paramMap = new ContentValues(); + } + if (paramMap_TYPE != null) { + this.paramMap_TYPE = paramMap_TYPE; + } else { + this.paramMap_TYPE = new HashSet<String>(); + } + if (propGroupSet != null) { + this.propGroupSet = propGroupSet; + } else { + this.propGroupSet = new HashSet<String>(); + } + } + + @Override + public int hashCode() { + // vCard may contain more than one same line in one entry, while HashSet or any other + // library which utilize hashCode() does not honor that, so intentionally throw an + // Exception. + throw new UnsupportedOperationException( + "PropertyNode does not provide hashCode() implementation intentionally."); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof PropertyNode)) { + return false; + } + + PropertyNode node = (PropertyNode)obj; + + if (propName == null || !propName.equals(node.propName)) { + return false; + } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) { + return false; + } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) { + return false; + } else if (!propGroupSet.equals(node.propGroupSet)) { + return false; + } + + if (propValue_bytes != null && Arrays.equals(propValue_bytes, node.propValue_bytes)) { + return true; + } else { + if (!propValue.equals(node.propValue)) { + return false; + } + + // The value in propValue_vector is not decoded even if it should be + // decoded by BASE64 or QUOTED-PRINTABLE. When the size of propValue_vector + // is 1, the encoded value is stored in propValue, so we do not have to + // check it. + return (propValue_vector.equals(node.propValue_vector) || + propValue_vector.size() == 1 || + node.propValue_vector.size() == 1); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("propName: "); + builder.append(propName); + builder.append(", paramMap: "); + builder.append(paramMap.toString()); + builder.append(", paramMap_TYPE: ["); + boolean first = true; + for (String elem : paramMap_TYPE) { + if (first) { + first = false; + } else { + builder.append(", "); + } + builder.append('"'); + builder.append(elem); + builder.append('"'); + } + builder.append("]"); + if (!propGroupSet.isEmpty()) { + builder.append(", propGroupSet: ["); + first = true; + for (String elem : propGroupSet) { + if (first) { + first = false; + } else { + builder.append(", "); + } + builder.append('"'); + builder.append(elem); + builder.append('"'); + } + builder.append("]"); + } + if (propValue_vector != null && propValue_vector.size() > 1) { + builder.append(", propValue_vector size: "); + builder.append(propValue_vector.size()); + } + if (propValue_bytes != null) { + builder.append(", propValue_bytes size: "); + builder.append(propValue_bytes.length); + } + builder.append(", propValue: \""); + builder.append(propValue); + builder.append("\""); + return builder.toString(); + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java new file mode 100644 index 0000000..cfdd074 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java @@ -0,0 +1,386 @@ +/* + * 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; + +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. + * + * This class first checks whether each propertyNode in the VNode is in the + * "ordered expected property 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 static class TypeSet extends HashSet<String> { + public TypeSet(String ... array) { + super(Arrays.asList(array)); + } + } + + public static class GroupSet extends HashSet<String> { + public GroupSet(String ... array) { + super(Arrays.asList(array)); + } + } + + private final HashMap<String, List<PropertyNode>> mOrderedNodeMap; + // Intentionally use ArrayList instead of Set, assuming there may be more than one + // exactly same objects. + private final ArrayList<PropertyNode> mUnorderedNodeList; + private final TestCase mTestCase; + + public PropertyNodesVerifierElem(TestCase testCase) { + mOrderedNodeMap = new HashMap<String, List<PropertyNode>>(); + mUnorderedNodeList = new ArrayList<PropertyNode>(); + mTestCase = testCase; + } + + // WithOrder + + public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue) { + return addExpectedNodeWithOrder(propName, propValue, null, null, null, null, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder( + String propName, String propValue, ContentValues contentValues) { + return addExpectedNodeWithOrder(propName, propValue, null, + null, contentValues, null, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder( + String propName, List<String> propValueList, ContentValues contentValues) { + return addExpectedNodeWithOrder(propName, null, propValueList, + null, contentValues, null, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder( + String propName, String propValue, List<String> propValueList) { + return addExpectedNodeWithOrder(propName, propValue, propValueList, null, + null, null, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder( + String propName, List<String> propValueList) { + final String propValue = concatinateListWithSemiColon(propValueList); + return addExpectedNodeWithOrder(propName, propValue.toString(), propValueList, + null, null, null, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue, + TypeSet paramMap_TYPE) { + return addExpectedNodeWithOrder(propName, propValue, null, + null, null, paramMap_TYPE, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, + List<String> propValueList, TypeSet paramMap_TYPE) { + return addExpectedNodeWithOrder(propName, null, propValueList, null, null, + paramMap_TYPE, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue, + ContentValues paramMap, TypeSet paramMap_TYPE) { + return addExpectedNodeWithOrder(propName, propValue, null, null, + paramMap, paramMap_TYPE, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue, + List<String> propValueList, TypeSet paramMap_TYPE) { + return addExpectedNodeWithOrder(propName, propValue, propValueList, null, null, + paramMap_TYPE, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue, + List<String> propValueList, byte[] propValue_bytes, + ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) { + if (propValue == null && propValueList != null) { + propValue = concatinateListWithSemiColon(propValueList); + } + PropertyNode propertyNode = new PropertyNode(propName, + propValue, propValueList, propValue_bytes, + paramMap, paramMap_TYPE, propGroupSet); + List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName); + if (expectedNodeList == null) { + expectedNodeList = new ArrayList<PropertyNode>(); + mOrderedNodeMap.put(propName, expectedNodeList); + } + expectedNodeList.add(propertyNode); + return this; + } + + // WithoutOrder + + public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue) { + return addExpectedNode(propName, propValue, null, null, null, null, null); + } + + public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue, + ContentValues contentValues) { + return addExpectedNode(propName, propValue, null, null, contentValues, null, null); + } + + public PropertyNodesVerifierElem addExpectedNode(String propName, + List<String> propValueList, ContentValues contentValues) { + return addExpectedNode(propName, null, + propValueList, null, contentValues, null, null); + } + + public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue, + List<String> propValueList) { + return addExpectedNode(propName, propValue, propValueList, null, null, null, null); + } + + public PropertyNodesVerifierElem addExpectedNode(String propName, + List<String> propValueList) { + return addExpectedNode(propName, null, propValueList, + null, null, null, null); + } + + public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue, + TypeSet paramMap_TYPE) { + return addExpectedNode(propName, propValue, null, null, null, paramMap_TYPE, null); + } + + public PropertyNodesVerifierElem addExpectedNode(String propName, + List<String> propValueList, TypeSet paramMap_TYPE) { + final String propValue = concatinateListWithSemiColon(propValueList); + return addExpectedNode(propName, propValue, propValueList, null, null, + paramMap_TYPE, null); + } + + public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue, + List<String> propValueList, TypeSet paramMap_TYPE) { + return addExpectedNode(propName, propValue, propValueList, null, null, + paramMap_TYPE, null); + } + + public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue, + ContentValues paramMap, TypeSet paramMap_TYPE) { + return addExpectedNode(propName, propValue, null, null, + paramMap, paramMap_TYPE, null); + } + + public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue, + List<String> propValueList, byte[] propValue_bytes, + ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) { + if (propValue == null && propValueList != null) { + propValue = concatinateListWithSemiColon(propValueList); + } + mUnorderedNodeList.add(new PropertyNode(propName, propValue, + propValueList, propValue_bytes, paramMap, paramMap_TYPE, propGroupSet)); + return this; + } + + public void verify(VNode vnode) { + for (PropertyNode actualNode : vnode.propList) { + verifyNode(actualNode.propName, actualNode); + } + if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) { + List<String> expectedProps = new ArrayList<String>(); + for (List<PropertyNode> nodes : mOrderedNodeMap.values()) { + for (PropertyNode node : nodes) { + if (!expectedProps.contains(node.propName)) { + expectedProps.add(node.propName); + } + } + } + for (PropertyNode node : mUnorderedNodeList) { + if (!expectedProps.contains(node.propName)) { + expectedProps.add(node.propName); + } + } + mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray()) + + " was not found."); + } + } + + private void verifyNode(final String propName, final PropertyNode actualNode) { + List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName); + final int size = (expectedNodeList != null ? expectedNodeList.size() : 0); + if (size > 0) { + for (int i = 0; i < size; i++) { + PropertyNode expectedNode = expectedNodeList.get(i); + List<PropertyNode> expectedButDifferentValueList = new ArrayList<PropertyNode>(); + if (expectedNode.propName.equals(propName)) { + if (expectedNode.equals(actualNode)) { + expectedNodeList.remove(i); + if (expectedNodeList.size() == 0) { + mOrderedNodeMap.remove(propName); + } + return; + } else { + expectedButDifferentValueList.add(expectedNode); + } + } + + // "actualNode" is not in ordered expected list. + // Try looking over unordered expected list. + if (tryFoundExpectedNodeFromUnorderedList(actualNode, + expectedButDifferentValueList)) { + return; + } + + if (!expectedButDifferentValueList.isEmpty()) { + // Same propName exists but with different value(s). + failWithExpectedNodeList(propName, actualNode, + expectedButDifferentValueList); + } else { + // There's no expected node with same propName. + mTestCase.fail("Unexpected property \"" + propName + "\" exists."); + } + } + } else { + List<PropertyNode> expectedButDifferentValueList = + new ArrayList<PropertyNode>(); + if (tryFoundExpectedNodeFromUnorderedList(actualNode, expectedButDifferentValueList)) { + return; + } else { + if (!expectedButDifferentValueList.isEmpty()) { + // Same propName exists but with different value(s). + failWithExpectedNodeList(propName, actualNode, + expectedButDifferentValueList); + } else { + // There's no expected node with same propName. + mTestCase.fail("Unexpected property \"" + propName + "\" exists."); + } + } + } + } + + private String concatinateListWithSemiColon(List<String> array) { + StringBuffer buffer = new StringBuffer(); + boolean first = true; + for (String propValueElem : array) { + if (first) { + first = false; + } else { + buffer.append(';'); + } + buffer.append(propValueElem); + } + + return buffer.toString(); + } + + private boolean tryFoundExpectedNodeFromUnorderedList(PropertyNode actualNode, + List<PropertyNode> expectedButDifferentValueList) { + final String propName = actualNode.propName; + int unorderedListSize = mUnorderedNodeList.size(); + for (int i = 0; i < unorderedListSize; i++) { + PropertyNode unorderedExpectedNode = mUnorderedNodeList.get(i); + if (unorderedExpectedNode.propName.equals(propName)) { + if (unorderedExpectedNode.equals(actualNode)) { + mUnorderedNodeList.remove(i); + return true; + } + expectedButDifferentValueList.add(unorderedExpectedNode); + } + } + return false; + } + + private void failWithExpectedNodeList(String propName, PropertyNode actualNode, + List<PropertyNode> expectedNodeList) { + StringBuilder builder = new StringBuilder(); + for (PropertyNode expectedNode : expectedNodeList) { + builder.append("expected: "); + builder.append(expectedNode.toString()); + builder.append("\n"); + } + mTestCase.fail("Property \"" + propName + "\" has wrong value.\n" + + builder.toString() + + " actual: " + actualNode.toString()); + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java new file mode 100644 index 0000000..004a197 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java @@ -0,0 +1,959 @@ +/* + * 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; + +import android.content.ContentValues; +import android.pim.vcard.VCardConfig; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.Event; +import android.provider.ContactsContract.CommonDataKinds.Im; +import android.provider.ContactsContract.CommonDataKinds.Nickname; +import android.provider.ContactsContract.CommonDataKinds.Note; +import android.provider.ContactsContract.CommonDataKinds.Organization; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.CommonDataKinds.Photo; +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.pim.vcard.PropertyNodesVerifierElem.TypeSet; + +import java.util.Arrays; + +/** + * Tests for the code related to vCard exporter, inculding vCard composer. + * This test class depends on vCard importer code, so if tests for vCard importer fail, + * the result of this class will not be reliable. + */ +public class VCardExporterTests extends VCardTestsBase { + private static final byte[] sPhotoByteArray = + VCardImporterTests.sPhotoByteArrayForComplicatedCase; + + public void testSimpleV21() { + mVerifier.initForExportTest(V21); + mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "Ando") + .put(StructuredName.GIVEN_NAME, "Roid"); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNode("FN", "Roid Ando") + .addExpectedNode("N", "Ando;Roid;;;", + Arrays.asList("Ando", "Roid", "", "", "")); + } + + private void testStructuredNameBasic(int vcardType) { + final boolean isV30 = VCardConfig.isV30(vcardType); + mVerifier.initForExportTest(vcardType); + mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName") + .put(StructuredName.GIVEN_NAME, "AppropriateGivenName") + .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName") + .put(StructuredName.PREFIX, "AppropriatePrefix") + .put(StructuredName.SUFFIX, "AppropriateSuffix") + .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily") + .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle"); + + PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("N", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + + "AppropriatePrefix;AppropriateSuffix", + Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", + "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")) + .addExpectedNodeWithOrder("FN", + "AppropriatePrefix AppropriateGivenName " + + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix") + .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven") + .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle") + .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily"); + + if (isV30) { + elem.addExpectedNode("SORT-STRING", + "AppropriatePhoneticGiven AppropriatePhoneticMiddle " + + "AppropriatePhoneticFamily"); + } + } + + public void testStructuredNameBasicV21() { + testStructuredNameBasic(V21); + } + + public void testStructuredNameBasicV30() { + testStructuredNameBasic(V30); + } + + /** + * Test that only "primary" StructuredName is emitted, so that our vCard file + * will not confuse the external importer, assuming there may be some importer + * which presume that there's only one property toward each of "N", "FN", etc. + * Note that more than one "N", "FN", etc. properties are acceptable in vCard spec. + */ + private void testStructuredNameUsePrimaryCommon(int vcardType) { + final boolean isV30 = (vcardType == V30); + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1") + .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1") + .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1") + .put(StructuredName.PREFIX, "DoNotEmitPrefix1") + .put(StructuredName.SUFFIX, "DoNotEmitSuffix1") + .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1") + .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1"); + + // With "IS_PRIMARY=1". This is what we should use. + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName") + .put(StructuredName.GIVEN_NAME, "AppropriateGivenName") + .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName") + .put(StructuredName.PREFIX, "AppropriatePrefix") + .put(StructuredName.SUFFIX, "AppropriateSuffix") + .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily") + .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle") + .put(StructuredName.IS_PRIMARY, 1); + + // With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first. + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2") + .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2") + .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2") + .put(StructuredName.PREFIX, "DoNotEmitPrefix2") + .put(StructuredName.SUFFIX, "DoNotEmitSuffix2") + .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2") + .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2") + .put(StructuredName.IS_PRIMARY, 1); + + PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("N", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + + "AppropriatePrefix;AppropriateSuffix", + Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", + "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")) + .addExpectedNodeWithOrder("FN", + "AppropriatePrefix AppropriateGivenName " + + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix") + .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven") + .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle") + .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily"); + + if (isV30) { + elem.addExpectedNode("SORT-STRING", + "AppropriatePhoneticGiven AppropriatePhoneticMiddle " + + "AppropriatePhoneticFamily"); + } + } + + public void testStructuredNameUsePrimaryV21() { + testStructuredNameUsePrimaryCommon(V21); + } + + public void testStructuredNameUsePrimaryV30() { + testStructuredNameUsePrimaryCommon(V30); + } + + /** + * Tests that only "super primary" StructuredName is emitted. + * See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}. + */ + private void testStructuredNameUseSuperPrimaryCommon(int vcardType) { + final boolean isV30 = (vcardType == V30); + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1") + .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1") + .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1") + .put(StructuredName.PREFIX, "DoNotEmitPrefix1") + .put(StructuredName.SUFFIX, "DoNotEmitSuffix1") + .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1") + .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1"); + + // With "IS_PRIMARY=1", but we should ignore this time. + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2") + .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2") + .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2") + .put(StructuredName.PREFIX, "DoNotEmitPrefix2") + .put(StructuredName.SUFFIX, "DoNotEmitSuffix2") + .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2") + .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2") + .put(StructuredName.IS_PRIMARY, 1); + + // With "IS_SUPER_PRIMARY=1". This is what we should use. + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName") + .put(StructuredName.GIVEN_NAME, "AppropriateGivenName") + .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName") + .put(StructuredName.PREFIX, "AppropriatePrefix") + .put(StructuredName.SUFFIX, "AppropriateSuffix") + .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily") + .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle") + .put(StructuredName.IS_SUPER_PRIMARY, 1); + + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName3") + .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName3") + .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName3") + .put(StructuredName.PREFIX, "DoNotEmitPrefix3") + .put(StructuredName.SUFFIX, "DoNotEmitSuffix3") + .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily3") + .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven3") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3") + .put(StructuredName.IS_PRIMARY, 1); + + PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("N", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + + "AppropriatePrefix;AppropriateSuffix", + Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", + "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")) + .addExpectedNodeWithOrder("FN", + "AppropriatePrefix AppropriateGivenName " + + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix") + .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven") + .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle") + .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily"); + + if (isV30) { + elem.addExpectedNode("SORT-STRING", + "AppropriatePhoneticGiven AppropriatePhoneticMiddle" + + " AppropriatePhoneticFamily"); + } + } + + public void testStructuredNameUseSuperPrimaryV21() { + testStructuredNameUseSuperPrimaryCommon(V21); + } + + public void testStructuredNameUseSuperPrimaryV30() { + testStructuredNameUseSuperPrimaryCommon(V30); + } + + public void testNickNameV30() { + mVerifier.initForExportTest(V30); + mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE) + .put(Nickname.NAME, "Nicky"); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNodeWithOrder("NICKNAME", "Nicky"); + } + + private void testPhoneBasicCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "1") + .put(Phone.TYPE, Phone.TYPE_HOME); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "1", new TypeSet("HOME")); + } + + public void testPhoneBasicV21() { + testPhoneBasicCommon(V21); + } + + public void testPhoneBasicV30() { + testPhoneBasicCommon(V30); + } + + /** + * Tests that vCard composer emits corresponding type param which we expect. + */ + private void testPhoneVariousTypeSupport(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "10") + .put(Phone.TYPE, Phone.TYPE_HOME); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "20") + .put(Phone.TYPE, Phone.TYPE_WORK); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "30") + .put(Phone.TYPE, Phone.TYPE_FAX_HOME); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "40") + .put(Phone.TYPE, Phone.TYPE_FAX_WORK); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "50") + .put(Phone.TYPE, Phone.TYPE_MOBILE); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "60") + .put(Phone.TYPE, Phone.TYPE_PAGER); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "70") + .put(Phone.TYPE, Phone.TYPE_OTHER); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "80") + .put(Phone.TYPE, Phone.TYPE_CAR); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "90") + .put(Phone.TYPE, Phone.TYPE_COMPANY_MAIN); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "100") + .put(Phone.TYPE, Phone.TYPE_ISDN); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "110") + .put(Phone.TYPE, Phone.TYPE_MAIN); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "120") + .put(Phone.TYPE, Phone.TYPE_OTHER_FAX); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "130") + .put(Phone.TYPE, Phone.TYPE_TELEX); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "140") + .put(Phone.TYPE, Phone.TYPE_WORK_MOBILE); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "150") + .put(Phone.TYPE, Phone.TYPE_WORK_PAGER); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "160") + .put(Phone.TYPE, Phone.TYPE_MMS); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "10", new TypeSet("HOME")) + .addExpectedNode("TEL", "20", new TypeSet("WORK")) + .addExpectedNode("TEL", "30", new TypeSet("HOME", "FAX")) + .addExpectedNode("TEL", "40", new TypeSet("WORK", "FAX")) + .addExpectedNode("TEL", "50", new TypeSet("CELL")) + .addExpectedNode("TEL", "60", new TypeSet("PAGER")) + .addExpectedNode("TEL", "70", new TypeSet("VOICE")) + .addExpectedNode("TEL", "80", new TypeSet("CAR")) + .addExpectedNode("TEL", "90", new TypeSet("WORK", "PREF")) + .addExpectedNode("TEL", "100", new TypeSet("ISDN")) + .addExpectedNode("TEL", "110", new TypeSet("PREF")) + .addExpectedNode("TEL", "120", new TypeSet("FAX")) + .addExpectedNode("TEL", "130", new TypeSet("TLX")) + .addExpectedNode("TEL", "140", new TypeSet("WORK", "CELL")) + .addExpectedNode("TEL", "150", new TypeSet("WORK", "PAGER")) + .addExpectedNode("TEL", "160", new TypeSet("MSG")); + } + + public void testPhoneVariousTypeSupportV21() { + testPhoneVariousTypeSupport(V21); + } + + public void testPhoneVariousTypeSupportV30() { + testPhoneVariousTypeSupport(V30); + } + + /** + * Tests that "PREF"s are emitted appropriately. + */ + private void testPhonePrefHandlingCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "1") + .put(Phone.TYPE, Phone.TYPE_HOME); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "2") + .put(Phone.TYPE, Phone.TYPE_WORK) + .put(Phone.IS_PRIMARY, 1); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "3") + .put(Phone.TYPE, Phone.TYPE_FAX_HOME) + .put(Phone.IS_PRIMARY, 1); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "4") + .put(Phone.TYPE, Phone.TYPE_FAX_WORK); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "4", new TypeSet("WORK", "FAX")) + .addExpectedNode("TEL", "3", new TypeSet("HOME", "FAX", "PREF")) + .addExpectedNode("TEL", "2", new TypeSet("WORK", "PREF")) + .addExpectedNode("TEL", "1", new TypeSet("HOME")); + } + + public void testPhonePrefHandlingV21() { + testPhonePrefHandlingCommon(V21); + } + + public void testPhonePrefHandlingV30() { + testPhonePrefHandlingCommon(V30); + } + + private void testMiscPhoneTypeHandling(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "1") + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "Modem"); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "2") + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "MSG"); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "3") + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "BBS"); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "4") + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "VIDEO"); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "5") + .put(Phone.TYPE, Phone.TYPE_CUSTOM); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "6") + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "_AUTO_CELL"); // The old indicator for the type mobile. + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "7") + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "\u643A\u5E2F"); // Mobile phone in Japanese Kanji + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "8") + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "invalid"); + PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName(); + elem.addExpectedNode("TEL", "1", new TypeSet("MODEM")) + .addExpectedNode("TEL", "2", new TypeSet("MSG")) + .addExpectedNode("TEL", "3", new TypeSet("BBS")) + .addExpectedNode("TEL", "4", new TypeSet("VIDEO")) + .addExpectedNode("TEL", "5", new TypeSet("VOICE")) + .addExpectedNode("TEL", "6", new TypeSet("CELL")) + .addExpectedNode("TEL", "7", new TypeSet("CELL")) + .addExpectedNode("TEL", "8", new TypeSet("X-invalid")); + } + + public void testPhoneTypeHandlingV21() { + testMiscPhoneTypeHandling(V21); + } + + public void testPhoneTypeHandlingV30() { + testMiscPhoneTypeHandling(V30); + } + + private void testEmailBasicCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + mVerifier.addInputEntry().addContentValues(Email.CONTENT_ITEM_TYPE) + .put(Email.DATA, "sample@example.com"); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("EMAIL", "sample@example.com"); + } + + public void testEmailBasicV21() { + testEmailBasicCommon(V21); + } + + public void testEmailBasicV30() { + testEmailBasicCommon(V30); + } + + private void testEmailVariousTypeSupportCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Email.CONTENT_ITEM_TYPE) + .put(Email.DATA, "type_home@example.com") + .put(Email.TYPE, Email.TYPE_HOME); + entry.addContentValues(Email.CONTENT_ITEM_TYPE) + .put(Email.DATA, "type_work@example.com") + .put(Email.TYPE, Email.TYPE_WORK); + entry.addContentValues(Email.CONTENT_ITEM_TYPE) + .put(Email.DATA, "type_mobile@example.com") + .put(Email.TYPE, Email.TYPE_MOBILE); + entry.addContentValues(Email.CONTENT_ITEM_TYPE) + .put(Email.DATA, "type_other@example.com") + .put(Email.TYPE, Email.TYPE_OTHER); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME")) + .addExpectedNode("EMAIL", "type_work@example.com", new TypeSet("WORK")) + .addExpectedNode("EMAIL", "type_mobile@example.com", new TypeSet("CELL")) + .addExpectedNode("EMAIL", "type_other@example.com"); + } + + public void testEmailVariousTypeSupportV21() { + testEmailVariousTypeSupportCommon(V21); + } + + public void testEmailVariousTypeSupportV30() { + testEmailVariousTypeSupportCommon(V30); + } + + private void testEmailPrefHandlingCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Email.CONTENT_ITEM_TYPE) + .put(Email.DATA, "type_home@example.com") + .put(Email.TYPE, Email.TYPE_HOME) + .put(Email.IS_PRIMARY, 1); + entry.addContentValues(Email.CONTENT_ITEM_TYPE) + .put(Email.DATA, "type_notype@example.com") + .put(Email.IS_PRIMARY, 1); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("EMAIL", "type_notype@example.com", new TypeSet("PREF")) + .addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME", "PREF")); + } + + public void testEmailPrefHandlingV21() { + testEmailPrefHandlingCommon(V21); + } + + public void testEmailPrefHandlingV30() { + testEmailPrefHandlingCommon(V30); + } + + private void testPostalAddressCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.POBOX, "Pobox") + .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood") + .put(StructuredPostal.STREET, "Street") + .put(StructuredPostal.CITY, "City") + .put(StructuredPostal.REGION, "Region") + .put(StructuredPostal.POSTCODE, "100") + .put(StructuredPostal.COUNTRY, "Country") + .put(StructuredPostal.FORMATTED_ADDRESS, "Formatted Address") + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK); + // adr-value = 0*6(text-value ";") text-value + // ; PO Box, Extended Address, Street, Locality, Region, Postal Code, + // ; Country Name + // + // The NEIGHBORHOOD field is appended after the CITY field. + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("ADR", + Arrays.asList("Pobox", "", "Street", "City Neighborhood", + "Region", "100", "Country"), new TypeSet("WORK")); + } + + public void testPostalAddressV21() { + testPostalAddressCommon(V21); + } + + public void testPostalAddressV30() { + testPostalAddressCommon(V30); + } + + private void testPostalAddressNonNeighborhood(int vcardType) { + mVerifier.initForExportTest(vcardType); + mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.CITY, "City"); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("ADR", + Arrays.asList("", "", "", "City", "", "", ""), new TypeSet("HOME")); + } + + public void testPostalAddressNonNeighborhoodV21() { + testPostalAddressNonNeighborhood(V21); + } + + public void testPostalAddressNonNeighborhoodV30() { + testPostalAddressNonNeighborhood(V30); + } + + private void testPostalAddressNonCity(int vcardType) { + mVerifier.initForExportTest(vcardType); + mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood"); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("ADR", + Arrays.asList("", "", "", "Neighborhood", "", "", ""), new TypeSet("HOME")); + } + + public void testPostalAddressNonCityV21() { + testPostalAddressNonCity(V21); + } + + public void testPostalAddressNonCityV30() { + testPostalAddressNonCity(V30); + } + + private void testPostalOnlyWithFormattedAddressCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.REGION, "") // Must be ignored. + .put(StructuredPostal.FORMATTED_ADDRESS, + "Formatted address CA 123-334 United Statue"); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNodeWithOrder("ADR", ";Formatted address CA 123-334 United Statue;;;;;", + Arrays.asList("", "Formatted address CA 123-334 United Statue", + "", "", "", "", ""), new TypeSet("HOME")); + } + + public void testPostalOnlyWithFormattedAddressV21() { + testPostalOnlyWithFormattedAddressCommon(V21); + } + + public void testPostalOnlyWithFormattedAddressV30() { + testPostalOnlyWithFormattedAddressCommon(V30); + } + + /** + * Tests that the vCard composer honors formatted data when it is available + * even when it is partial. + */ + private void testPostalWithBothStructuredAndFormattedCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.POBOX, "Pobox") + .put(StructuredPostal.COUNTRY, "Country") + .put(StructuredPostal.FORMATTED_ADDRESS, + "Formatted address CA 123-334 United Statue"); // Should be ignored + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("ADR", "Pobox;;;;;;Country", + Arrays.asList("Pobox", "", "", "", "", "", "Country"), + new TypeSet("HOME")); + } + + public void testPostalWithBothStructuredAndFormattedV21() { + testPostalWithBothStructuredAndFormattedCommon(V21); + } + + public void testPostalWithBothStructuredAndFormattedV30() { + testPostalWithBothStructuredAndFormattedCommon(V30); + } + + private void testOrganizationCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Organization.CONTENT_ITEM_TYPE) + .put(Organization.COMPANY, "CompanyX") + .put(Organization.DEPARTMENT, "DepartmentY") + .put(Organization.TITLE, "TitleZ") + .put(Organization.JOB_DESCRIPTION, "Description Rambda") // Ignored. + .put(Organization.OFFICE_LOCATION, "Mountain View") // Ignored. + .put(Organization.PHONETIC_NAME, "PhoneticName!") // Ignored + .put(Organization.SYMBOL, "(^o^)/~~"); // Ignore him (her). + entry.addContentValues(Organization.CONTENT_ITEM_TYPE) + .putNull(Organization.COMPANY) + .put(Organization.DEPARTMENT, "DepartmentXX") + .putNull(Organization.TITLE); + entry.addContentValues(Organization.CONTENT_ITEM_TYPE) + .put(Organization.COMPANY, "CompanyXYZ") + .putNull(Organization.DEPARTMENT) + .put(Organization.TITLE, "TitleXYZYX"); + // Currently we do not use group but depend on the order. + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNodeWithOrder("ORG", "CompanyX;DepartmentY", + Arrays.asList("CompanyX", "DepartmentY")) + .addExpectedNodeWithOrder("TITLE", "TitleZ") + .addExpectedNodeWithOrder("ORG", "DepartmentXX") + .addExpectedNodeWithOrder("ORG", "CompanyXYZ") + .addExpectedNodeWithOrder("TITLE", "TitleXYZYX"); + } + + public void testOrganizationV21() { + testOrganizationCommon(V21); + } + + public void testOrganizationV30() { + testOrganizationCommon(V30); + } + + private void testImVariousTypeSupportCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_AIM) + .put(Im.DATA, "aim"); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_MSN) + .put(Im.DATA, "msn"); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_YAHOO) + .put(Im.DATA, "yahoo"); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_SKYPE) + .put(Im.DATA, "skype"); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_QQ) + .put(Im.DATA, "qq"); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK) + .put(Im.DATA, "google talk"); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_ICQ) + .put(Im.DATA, "icq"); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_JABBER) + .put(Im.DATA, "jabber"); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_NETMEETING) + .put(Im.DATA, "netmeeting"); + + // No determined way to express unknown type... + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("X-JABBER", "jabber") + .addExpectedNode("X-ICQ", "icq") + .addExpectedNode("X-GOOGLE-TALK", "google talk") + .addExpectedNode("X-QQ", "qq") + .addExpectedNode("X-SKYPE-USERNAME", "skype") + .addExpectedNode("X-YAHOO", "yahoo") + .addExpectedNode("X-MSN", "msn") + .addExpectedNode("X-NETMEETING", "netmeeting") + .addExpectedNode("X-AIM", "aim"); + } + + public void testImBasiV21() { + testImVariousTypeSupportCommon(V21); + } + + public void testImBasicV30() { + testImVariousTypeSupportCommon(V30); + } + + private void testImPrefHandlingCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_AIM) + .put(Im.DATA, "aim1"); + entry.addContentValues(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_AIM) + .put(Im.DATA, "aim2") + .put(Im.TYPE, Im.TYPE_HOME) + .put(Im.IS_PRIMARY, 1); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("X-AIM", "aim1") + .addExpectedNode("X-AIM", "aim2", new TypeSet("HOME", "PREF")); + } + + public void testImPrefHandlingV21() { + testImPrefHandlingCommon(V21); + } + + public void testImPrefHandlingV30() { + testImPrefHandlingCommon(V30); + } + + private void testWebsiteCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Website.CONTENT_ITEM_TYPE) + .put(Website.URL, "http://website.example.android.com/index.html") + .put(Website.TYPE, Website.TYPE_BLOG); + entry.addContentValues(Website.CONTENT_ITEM_TYPE) + .put(Website.URL, "ftp://ftp.example.android.com/index.html") + .put(Website.TYPE, Website.TYPE_FTP); + + // We drop TYPE information since vCard (especially 3.0) does not allow us to emit it. + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("URL", "ftp://ftp.example.android.com/index.html") + .addExpectedNode("URL", "http://website.example.android.com/index.html"); + } + + public void testWebsiteV21() { + testWebsiteCommon(V21); + } + + public void testWebsiteV30() { + testWebsiteCommon(V30); + } + + private String getAndroidPropValue(final String mimeType, String value, Integer type) { + return getAndroidPropValue(mimeType, value, type, null); + } + + private String getAndroidPropValue(final String mimeType, String value, + Integer type, String label) { + return (mimeType + ";" + value + ";" + + (type != null ? type : "") + ";" + + (label != null ? label : "") + ";;;;;;;;;;;;"); + } + + private void testEventCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Event.CONTENT_ITEM_TYPE) + .put(Event.TYPE, Event.TYPE_ANNIVERSARY) + .put(Event.START_DATE, "1982-06-16"); + entry.addContentValues(Event.CONTENT_ITEM_TYPE) + .put(Event.TYPE, Event.TYPE_BIRTHDAY) + .put(Event.START_DATE, "2008-10-22"); + entry.addContentValues(Event.CONTENT_ITEM_TYPE) + .put(Event.TYPE, Event.TYPE_OTHER) + .put(Event.START_DATE, "2018-03-12"); + entry.addContentValues(Event.CONTENT_ITEM_TYPE) + .put(Event.TYPE, Event.TYPE_CUSTOM) + .put(Event.LABEL, "The last day") + .put(Event.START_DATE, "When the Tower of Hanoi with 64 rings is completed."); + entry.addContentValues(Event.CONTENT_ITEM_TYPE) + .put(Event.TYPE, Event.TYPE_BIRTHDAY) + .put(Event.START_DATE, "2009-05-19"); // Should be ignored. + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("BDAY", "2008-10-22") + .addExpectedNode("X-ANDROID-CUSTOM", + getAndroidPropValue( + Event.CONTENT_ITEM_TYPE, "1982-06-16", Event.TYPE_ANNIVERSARY)) + .addExpectedNode("X-ANDROID-CUSTOM", + getAndroidPropValue( + Event.CONTENT_ITEM_TYPE, "2018-03-12", Event.TYPE_OTHER)) + .addExpectedNode("X-ANDROID-CUSTOM", + getAndroidPropValue( + Event.CONTENT_ITEM_TYPE, + "When the Tower of Hanoi with 64 rings is completed.", + Event.TYPE_CUSTOM, "The last day")); + } + + public void testEventV21() { + testEventCommon(V21); + } + + public void testEventV30() { + testEventCommon(V30); + } + + private void testNoteCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Note.CONTENT_ITEM_TYPE) + .put(Note.NOTE, "note1"); + entry.addContentValues(Note.CONTENT_ITEM_TYPE) + .put(Note.NOTE, "note2") + .put(Note.IS_PRIMARY, 1); // Just ignored. + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNodeWithOrder("NOTE", "note1") + .addExpectedNodeWithOrder("NOTE", "note2"); + } + + public void testNoteV21() { + testNoteCommon(V21); + } + + public void testNoteV30() { + testNoteCommon(V30); + } + + private void testPhotoCommon(int vcardType) { + final boolean isV30 = vcardType == V30; + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "PhotoTest"); + entry.addContentValues(Photo.CONTENT_ITEM_TYPE) + .put(Photo.PHOTO, sPhotoByteArray); + + ContentValues contentValuesForPhoto = new ContentValues(); + contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64")); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNode("FN", "PhotoTest") + .addExpectedNode("N", "PhotoTest;;;;", + Arrays.asList("PhotoTest", "", "", "", "")) + .addExpectedNodeWithOrder("PHOTO", null, null, sPhotoByteArray, + contentValuesForPhoto, new TypeSet("JPEG"), null); + } + + public void testPhotoV21() { + testPhotoCommon(V21); + } + + public void testPhotoV30() { + testPhotoCommon(V30); + } + + private void testRelationCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + mVerifier.addInputEntry().addContentValues(Relation.CONTENT_ITEM_TYPE) + .put(Relation.TYPE, Relation.TYPE_MOTHER) + .put(Relation.NAME, "Ms. Mother"); + mVerifier.addContentValuesVerifierElem().addExpected(Relation.CONTENT_ITEM_TYPE) + .put(Relation.TYPE, Relation.TYPE_MOTHER) + .put(Relation.NAME, "Ms. Mother"); + } + + public void testRelationV21() { + testRelationCommon(V21); + } + + public void testRelationV30() { + testRelationCommon(V30); + } + + public void testV30HandleEscape() { + mVerifier.initForExportTest(V30); + mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\\") + .put(StructuredName.GIVEN_NAME, ";") + .put(StructuredName.MIDDLE_NAME, ",") + .put(StructuredName.PREFIX, "\n") + .put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]"); + // Verifies the vCard String correctly escapes each character which must be escaped. + mVerifier.addLineVerifierElem() + .addExpected("N:\\\\;\\;;\\,;\\n;") + .addExpected("FN:[<{Unescaped:Asciis}>]"); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNode("FN", "[<{Unescaped:Asciis}>]") + .addExpectedNode("N", Arrays.asList("\\", ";", ",", "\n", "")); + } + + /** + * There's no "NICKNAME" property in vCard 2.1, while there is in vCard 3.0. + * We use Android-specific "X-ANDROID-CUSTOM" property. + * This test verifies the functionality. + */ + public void testNickNameV21() { + mVerifier.initForExportTest(V21); + mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE) + .put(Nickname.NAME, "Nicky"); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("X-ANDROID-CUSTOM", + Nickname.CONTENT_ITEM_TYPE + ";Nicky;;;;;;;;;;;;;;"); + mVerifier.addContentValuesVerifierElem().addExpected(Nickname.CONTENT_ITEM_TYPE) + .put(Nickname.NAME, "Nicky"); + } + + public void testTolerateBrokenPhoneNumberEntryV21() { + mVerifier.initForExportTest(V21); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_HOME) + .put(Phone.NUMBER, "111-222-3333 (Miami)\n444-5555-666 (Tokyo);" + + "777-888-9999 (Chicago);111-222-3333 (Miami)"); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "111-222-3333", new TypeSet("HOME")) + .addExpectedNode("TEL", "444-555-5666", new TypeSet("HOME")) + .addExpectedNode("TEL", "777-888-9999", new TypeSet("HOME")); + } + + private void testPickUpNonEmptyContentValuesCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.IS_PRIMARY, 1); // Empty name. Should be ignored. + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "family1"); // Not primary. Should be ignored. + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.IS_PRIMARY, 1) + .put(StructuredName.FAMILY_NAME, "family2"); // This entry is what we want. + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.IS_PRIMARY, 1) + .put(StructuredName.FAMILY_NAME, "family3"); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "family4"); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNode("N", Arrays.asList("family2", "", "", "", "")) + .addExpectedNode("FN", "family2"); + } + + public void testPickUpNonEmptyContentValuesV21() { + testPickUpNonEmptyContentValuesCommon(V21); + } + + public void testPickUpNonEmptyContentValuesV30() { + testPickUpNonEmptyContentValuesCommon(V30); + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java new file mode 100644 index 0000000..21f2254 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java @@ -0,0 +1,1011 @@ +/* + * 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; + +import android.content.ContentValues; +import android.pim.vcard.VCardConfig; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.Event; +import android.provider.ContactsContract.CommonDataKinds.Note; +import android.provider.ContactsContract.CommonDataKinds.Organization; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.CommonDataKinds.Photo; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +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; + +public class VCardImporterTests extends VCardTestsBase { + // Push data into int array at first since values like 0x80 are + // interpreted as int by the compiler and casting all of them is + // cumbersome... + private static final int[] sPhotoIntArrayForComplicatedCase = { + 0xff, 0xd8, 0xff, 0xe1, 0x0a, 0x0f, 0x45, 0x78, 0x69, 0x66, 0x00, + 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d, + 0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0xaa, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, + 0x00, 0xba, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0xc2, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xc8, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x28, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x32, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xe6, 0x02, 0x13, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x82, + 0x98, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xfa, + 0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x84, 0xc4, 0xa5, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, + 0x01, 0x08, 0x00, 0x00, 0x04, 0x1e, 0x32, 0x30, 0x30, 0x38, 0x31, + 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31, 0x00, 0x00, + 0x44, 0x6f, 0x43, 0x6f, 0x4d, 0x6f, 0x00, 0x00, 0x44, 0x39, 0x30, + 0x35, 0x69, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x44, 0x39, 0x30, + 0x35, 0x69, 0x20, 0x56, 0x65, 0x72, 0x31, 0x2e, 0x30, 0x30, 0x00, + 0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, + 0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x49, 0x4d, 0x00, 0x30, 0x33, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x14, 0x00, + 0x14, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x34, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x11, 0x09, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x0f, 0x0b, 0x00, + 0x00, 0x27, 0x10, 0x00, 0x00, 0x05, 0x97, 0x00, 0x00, 0x27, 0x10, + 0x00, 0x00, 0x08, 0xb0, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1c, + 0x01, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x02, 0x5e, 0x00, 0x00, + 0x27, 0x10, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x27, 0x10, 0x00, + 0x00, 0x03, 0xcb, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1b, 0xe5, + 0x00, 0x00, 0x27, 0x10, 0x00, 0x28, 0x82, 0x9a, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x6a, 0x82, 0x9d, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x72, 0x88, 0x22, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x90, 0x00, + 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90, + 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0x7a, + 0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, + 0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, + 0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x03, 0xa2, 0x92, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x03, 0xaa, 0x92, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x03, 0xb2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x03, 0xba, 0x92, 0x05, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xc2, 0x92, 0x07, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x92, 0x08, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, + 0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xca, + 0x92, 0x7c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x92, 0x86, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, + 0x03, 0xd2, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, + 0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x60, 0x00, 0x00, 0xa0, 0x03, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0xa2, 0x0e, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xe8, 0xa2, 0x0f, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xf0, 0xa2, 0x10, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xa2, + 0x17, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, + 0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, + 0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, + 0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x04, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x03, 0xf8, 0xa4, 0x05, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x07, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, + 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0xa4, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, + 0x00, 0x27, 0x10, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64, + 0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, + 0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x33, 0x31, 0x00, 0x32, 0x30, + 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, 0x31, 0x33, + 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x00, 0x00, 0x29, 0x88, + 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0xb2, 0x00, 0x00, 0x00, + 0x64, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x25, 0x00, + 0x00, 0x00, 0x0a, 0x00, 0x00, 0x0e, 0x92, 0x00, 0x00, 0x03, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x30, 0x30, + 0x38, 0x31, 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31, + 0x00, 0x00, 0x20, 0x2a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x2a, + 0xe2, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x52, 0x39, 0x38, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, + 0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, + 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x04, 0x6c, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x04, 0x74, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x04, 0x7c, 0x02, 0x02, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x8b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, + 0x00, 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c, + 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, 0x2c, 0x2c, 0x30, + 0x62, 0x46, 0x4a, 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66, + 0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, 0x6e, + 0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c, + 0x9a, 0xe2, 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0x01, + 0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6, + 0x84, 0x70, 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xff, 0xc0, + 0x00, 0x11, 0x08, 0x00, 0x78, 0x00, 0xa0, 0x03, 0x01, 0x21, 0x00, + 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00, + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, + 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, + 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, + 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, + 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, + 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, + 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00, + 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, + 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, + 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, + 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, + 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, + 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, + 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, + 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, + 0x14, 0x54, 0xaa, 0x2a, 0x46, 0x48, 0xa2, 0xa4, 0x55, 0xa6, 0x04, + 0x8a, 0x29, 0xe0, 0x53, 0x10, 0xe0, 0x29, 0xc0, 0x50, 0x03, 0xb1, + 0x46, 0x29, 0x80, 0x84, 0x52, 0x11, 0x40, 0x0d, 0x22, 0x9a, 0x45, + 0x20, 0x23, 0x61, 0x51, 0x30, 0xa0, 0x08, 0xc8, 0xa8, 0xd8, 0x52, + 0x02, 0x26, 0x15, 0x0b, 0x0a, 0x00, 0xb4, 0xa2, 0xa5, 0x5a, 0x00, + 0x91, 0x45, 0x4a, 0xa2, 0x81, 0x92, 0x01, 0x4e, 0x02, 0x98, 0x87, + 0x0a, 0x70, 0xa0, 0x07, 0x62, 0x8c, 0x50, 0x21, 0x0d, 0x25, 0x00, + 0x34, 0x8a, 0x61, 0x14, 0x0c, 0x63, 0x0a, 0x89, 0x85, 0x00, 0x46, + 0xd5, 0x1b, 0x52, 0x02, 0x16, 0xa8, 0x98, 0x50, 0x05, 0x94, 0xa9, + 0x16, 0x80, 0x25, 0x5a, 0x95, 0x68, 0x18, 0xf1, 0x4f, 0x14, 0xc4, + 0x3b, 0xb5, 0x22, 0xb6, 0x38, 0x34, 0x00, 0xe3, 0x22, 0x8e, 0xf4, + 0x79, 0x8a, 0x7b, 0xd1, 0x71, 0x03, 0x30, 0xc7, 0x14, 0x83, 0xa5, + 0x00, 0x06, 0x98, 0x68, 0x01, 0x8d, 0x51, 0x35, 0x03, 0x22, 0x6a, + 0x8d, 0xa9, 0x01, 0x13, 0x54, 0x4d, 0x40, 0x13, 0xa5, 0x4a, 0x28, + 0x02, 0x45, 0x35, 0x2a, 0x9a, 0x00, 0x78, 0x34, 0xf0, 0x69, 0x80, + 0x34, 0x81, 0x45, 0x40, 0xce, 0x58, 0xe6, 0xa2, 0x4c, 0x06, 0xe4, + 0xfa, 0xd1, 0x93, 0x50, 0x21, 0xca, 0xe4, 0x55, 0x84, 0x90, 0x30, + 0xab, 0x8b, 0x18, 0xa6, 0x9a, 0x6a, 0xc4, 0x31, 0xaa, 0x26, 0xa0, + 0x64, 0x4d, 0x51, 0xb5, 0x20, 0x23, 0x6a, 0x89, 0xa8, 0x02, 0x44, + 0x35, 0x2a, 0x9a, 0x00, 0x95, 0x4d, 0x48, 0xa6, 0x80, 0x24, 0x53, + 0x4e, 0xce, 0x05, 0x30, 0x2b, 0x3b, 0xee, 0x6a, 0x91, 0x5d, 0x76, + 0x63, 0xbd, 0x65, 0x7d, 0x40, 0x66, 0x68, 0xa9, 0x02, 0x45, 0x2b, + 0xb3, 0x9e, 0xb4, 0xc5, 0x6d, 0xad, 0x9a, 0xa0, 0x2c, 0x06, 0xc8, + 0xcd, 0x04, 0xd6, 0xa2, 0x23, 0x63, 0x51, 0xb1, 0xa0, 0x64, 0x4d, + 0x51, 0x93, 0x48, 0x08, 0xda, 0xa2, 0x6a, 0x00, 0x72, 0x1a, 0x99, + 0x4d, 0x00, 0x48, 0xa6, 0xa4, 0x53, 0x4c, 0x07, 0x86, 0x03, 0xbd, + 0x2b, 0x9c, 0xa7, 0x14, 0x98, 0x10, 0x85, 0x34, 0xe0, 0xa6, 0xb3, + 0xb0, 0x0b, 0xb5, 0xa8, 0x0a, 0xd4, 0x58, 0x42, 0xed, 0x3e, 0x94, + 0xd2, 0xa6, 0x8b, 0x01, 0x34, 0x44, 0xed, 0xe6, 0x9c, 0x4d, 0x6a, + 0x80, 0x8d, 0x8d, 0x46, 0xc6, 0x80, 0x23, 0x63, 0x51, 0x9a, 0x06, + 0x46, 0xd5, 0x13, 0x52, 0x01, 0x54, 0xd4, 0xaa, 0x68, 0x02, 0x40, + 0x6a, 0x40, 0x78, 0xa0, 0x08, 0x59, 0xce, 0xee, 0xb5, 0x2a, 0x39, + 0xd9, 0x59, 0xa7, 0xa8, 0x00, 0x73, 0xeb, 0x4e, 0x0e, 0x7d, 0x69, + 0x5c, 0x05, 0xf3, 0x0f, 0xad, 0x1e, 0x61, 0xf5, 0xa7, 0x71, 0x0b, + 0xe6, 0x35, 0x21, 0x90, 0xd3, 0xb8, 0x0e, 0x32, 0x10, 0x95, 0x10, + 0x91, 0xb3, 0xd6, 0x9b, 0x60, 0x4b, 0x9c, 0x8a, 0x63, 0x1a, 0xb0, + 0x18, 0x4d, 0x46, 0xc6, 0x80, 0x22, 0x6a, 0x61, 0xa4, 0x31, 0xaa, + 0x6a, 0x55, 0x34, 0x01, 0x2a, 0x9a, 0x7e, 0x78, 0xa0, 0x08, 0x09, + 0xf9, 0xaa, 0x58, 0xcf, 0xca, 0x6b, 0x3e, 0xa0, 0x00, 0xd3, 0x81, + 0xa9, 0x01, 0x73, 0x46, 0x69, 0x80, 0xb9, 0xa4, 0xcd, 0x00, 0x2b, + 0x1f, 0x92, 0xa3, 0x07, 0x9a, 0x6f, 0x70, 0x26, 0xcf, 0x14, 0xd2, + 0x6b, 0x51, 0x0c, 0x63, 0x51, 0xb1, 0xa0, 0x08, 0xda, 0x98, 0x69, + 0x0c, 0x8d, 0x4d, 0x4a, 0xa6, 0x80, 0x24, 0x53, 0x52, 0x03, 0xc5, + 0x02, 0x21, 0x27, 0xe6, 0xa9, 0x23, 0x3f, 0x29, 0xac, 0xfa, 0x8c, + 0x01, 0xe6, 0x9c, 0x0d, 0x48, 0x0a, 0x0d, 0x2e, 0x68, 0x01, 0x73, + 0x49, 0x9a, 0x60, 0x2b, 0x1f, 0x92, 0x98, 0x3a, 0xd3, 0x7b, 0x81, + 0x36, 0x78, 0xa6, 0x93, 0x5a, 0x88, 0x8c, 0x9a, 0x63, 0x1a, 0x00, + 0x8c, 0xd3, 0x0d, 0x21, 0x91, 0x29, 0xa9, 0x14, 0xd0, 0x04, 0x8a, + 0x69, 0xe0, 0xd3, 0x11, 0x1b, 0x1e, 0x6a, 0x48, 0xcf, 0xca, 0x6b, + 0x3e, 0xa3, 0x10, 0x1a, 0x70, 0x35, 0x20, 0x38, 0x1a, 0x5c, 0xd2, + 0x01, 0x73, 0x49, 0x9a, 0x60, 0x39, 0x8f, 0xca, 0x29, 0x8b, 0xf7, + 0xaa, 0xba, 0x88, 0x96, 0x9a, 0x6b, 0x40, 0x18, 0xc6, 0xa3, 0x26, + 0x80, 0x18, 0x69, 0xa6, 0x90, 0xc8, 0x14, 0xd4, 0x8a, 0x69, 0x80, + 0xf0, 0x6a, 0x40, 0x68, 0x10, 0xbb, 0x41, 0xa7, 0xe3, 0x0b, 0xc5, + 0x2b, 0x01, 0x10, 0xa7, 0x03, 0x59, 0x0c, 0x76, 0x69, 0x73, 0x40, + 0x0b, 0x9a, 0x28, 0x11, 0x28, 0x19, 0x5e, 0x69, 0x02, 0x81, 0x5a, + 0xd8, 0x00, 0xd3, 0x4d, 0x50, 0x0c, 0x6a, 0x8c, 0xd2, 0x01, 0xa6, + 0x98, 0x69, 0x0c, 0xae, 0xa6, 0xa4, 0x06, 0x80, 0x1e, 0xa6, 0x9e, + 0x0d, 0x31, 0x12, 0x03, 0x4f, 0x06, 0x80, 0x13, 0x60, 0x34, 0xd3, + 0xc1, 0xa8, 0x92, 0x01, 0xf1, 0x8d, 0xdd, 0x69, 0xcc, 0xa1, 0x69, + 0x5b, 0x4b, 0x80, 0x83, 0x93, 0x52, 0x04, 0x14, 0xe2, 0xae, 0x03, + 0xa9, 0x0d, 0x68, 0x03, 0x4d, 0x34, 0xd0, 0x03, 0x0d, 0x30, 0xd2, + 0x01, 0x86, 0x9a, 0x68, 0x19, 0x58, 0x1a, 0x78, 0xa4, 0x04, 0x8a, + 0x69, 0xe0, 0xd3, 0x10, 0xe0, 0x69, 0xe0, 0xd0, 0x03, 0xc1, 0xa8, + 0xdb, 0xad, 0x4c, 0x81, 0x12, 0x45, 0xd6, 0x9d, 0x25, 0x1d, 0x00, + 0x6a, 0xf5, 0xa9, 0xe8, 0x80, 0x31, 0x29, 0x0d, 0x58, 0x08, 0x69, + 0x86, 0x80, 0x1a, 0x69, 0x86, 0x90, 0x0c, 0x34, 0xd3, 0x48, 0x65, + 0x51, 0x4f, 0x06, 0x98, 0x0f, 0x14, 0xf0, 0x68, 0x10, 0xf0, 0x69, + 0xe0, 0xd0, 0x03, 0x81, 0xa5, 0x2b, 0x9a, 0x1a, 0xb8, 0x87, 0xa8, + 0xdb, 0x4a, 0x46, 0x68, 0xb6, 0x80, 0x2a, 0xa8, 0x14, 0xea, 0x12, + 0xb0, 0x05, 0x21, 0xa6, 0x02, 0x1a, 0x61, 0xa0, 0x06, 0x9a, 0x61, + 0xa4, 0x31, 0x86, 0x9a, 0x69, 0x0c, 0xa8, 0x0d, 0x3c, 0x53, 0x01, + 0xe2, 0x9e, 0x28, 0x10, 0xf1, 0x4e, 0x06, 0x98, 0x0f, 0x06, 0x9e, + 0x0d, 0x02, 0x1c, 0x29, 0xc2, 0x80, 0x16, 0x96, 0x80, 0x0a, 0x4a, + 0x00, 0x43, 0x4d, 0x34, 0x0c, 0x61, 0xa6, 0x1a, 0x40, 0x34, 0xd3, + 0x4d, 0x21, 0x80, 0xff, 0xd9, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0a, + 0x07, 0x07, 0x08, 0x07, 0x06, 0x0a, 0x08, 0x08, 0x08, 0x0b, 0x0a, + 0x0a, 0x0b, 0x0e, 0x18, 0x10, 0x0e, 0x0d, 0x0d, 0x0e, 0x1d, 0x15, + 0x16, 0x11, 0x18, 0x23, 0x1f, 0x25, 0x24, 0x22, 0x1f, 0x22, 0x21, + 0x26, 0x2b, 0x37, 0x2f, 0x26, 0x29, 0x34, 0x29, 0x21, 0x22, 0x30, + 0x41, 0x31, 0x34, 0x39, 0x3b, 0x3e, 0x3e, 0x3e, 0x25, 0x2e, 0x44, + 0x49, 0x43, 0x3c, 0x48, 0x37, 0x3d, 0x3e, 0x3b, 0x01, 0x0a, 0x0b, + 0x0b, 0x0e, 0x0d, 0x0e, 0x1c, 0x10, 0x10, 0x1c, 0x3b, 0x28, 0x22, + 0x28, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0xff, 0xc0, 0x00, 0x11, + 0x08, 0x00, 0x48, 0x00, 0x60, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11, + 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01, + 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, + 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, + 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, + 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, + 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, + 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, + 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00, 0x03, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, + 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, + 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, + 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, + 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, + 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, + 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x9e, 0xd2, + 0x2e, 0x07, 0x15, 0xaf, 0x6d, 0x08, 0xe2, 0xb3, 0x45, 0x1a, 0xf6, + 0xd0, 0x00, 0x01, 0xc5, 0x68, 0x45, 0x17, 0x4a, 0xb4, 0x22, 0xe4, + 0x70, 0x8c, 0x74, 0xa9, 0x3c, 0xa1, 0x8e, 0x95, 0x48, 0x96, 0x31, + 0xe2, 0x18, 0xe9, 0x55, 0xa5, 0x8c, 0x7a, 0x50, 0x05, 0x0b, 0x88, + 0x86, 0x0f, 0x15, 0x8f, 0x75, 0x1f, 0x26, 0x93, 0x19, 0x91, 0x77, + 0x18, 0xc1, 0xac, 0x4b, 0xc8, 0xfa, 0xd6, 0x63, 0x37, 0x6d, 0x31, + 0xb4, 0x73, 0x5b, 0x36, 0xa0, 0x1c, 0x50, 0x80, 0xd7, 0x83, 0xa0, + 0xab, 0xd1, 0x62, 0xad, 0x09, 0x8f, 0x17, 0x29, 0x03, 0xb2, 0xcc, + 0xe0, 0x77, 0x14, 0xa3, 0x56, 0xb3, 0x27, 0x1e, 0x67, 0xe9, 0x52, + 0xea, 0xc6, 0x3a, 0x36, 0x48, 0xef, 0x3d, 0x27, 0x70, 0x22, 0x60, + 0x47, 0x52, 0x69, 0xb2, 0xe2, 0xad, 0x3b, 0xea, 0x80, 0xa3, 0x38, + 0xe0, 0xd6, 0x3d, 0xd8, 0x1c, 0xd0, 0xca, 0x46, 0x3d, 0xd0, 0x18, + 0x35, 0x89, 0x78, 0xa3, 0x9a, 0xcd, 0x8c, 0xd2, 0xb3, 0x93, 0x2a, + 0x2b, 0x66, 0xd5, 0xf1, 0x8a, 0x10, 0x1a, 0xd6, 0xf2, 0x03, 0x8a, + 0x9e, 0xe6, 0xf4, 0x5a, 0xdb, 0xef, 0xfe, 0x23, 0xc0, 0xa7, 0x27, + 0xcb, 0x16, 0xc4, 0xcc, 0xdd, 0xe2, 0x78, 0x9a, 0x69, 0x66, 0xcc, + 0x99, 0xe1, 0x4d, 0x47, 0xba, 0xbc, 0xd9, 0x6a, 0xee, 0x26, 0x59, + 0x59, 0x4d, 0xac, 0x69, 0x34, 0x52, 0xe5, 0x8f, 0x55, 0xad, 0x58, + 0xae, 0x85, 0xc4, 0x22, 0x41, 0xdf, 0xad, 0x76, 0x61, 0xe5, 0x6f, + 0x74, 0x45, 0x69, 0xdc, 0x00, 0x79, 0xac, 0x8b, 0xa6, 0xc9, 0x35, + 0xd4, 0x34, 0x64, 0xdc, 0x37, 0x06, 0xb1, 0xae, 0x88, 0xc1, 0xac, + 0xd8, 0xc9, 0x2c, 0xa6, 0xe0, 0x73, 0x5b, 0x36, 0xf3, 0x74, 0xe6, + 0x84, 0x05, 0xe3, 0xa9, 0x47, 0x6a, 0x14, 0xb6, 0x49, 0x3d, 0x85, + 0x3a, 0xee, 0xee, 0x2b, 0xa8, 0xe2, 0x6f, 0x30, 0x81, 0xe9, 0x8a, + 0xca, 0xa4, 0xe2, 0xd3, 0x8b, 0x01, 0xb1, 0xf9, 0x04, 0x7f, 0xaf, + 0x23, 0xf0, 0xa9, 0x54, 0x41, 0x9c, 0xfd, 0xa3, 0xf4, 0xae, 0x65, + 0x18, 0xf7, 0x25, 0x8a, 0xe2, 0x02, 0x38, 0xb8, 0xfd, 0x2a, 0x7b, + 0x5b, 0xa8, 0x6d, 0x6d, 0x5d, 0x9a, 0x5d, 0xcb, 0xbb, 0xd2, 0xb6, + 0xa6, 0xa3, 0x19, 0x5e, 0xe2, 0x03, 0x7b, 0x1d, 0xc2, 0x17, 0x8d, + 0xb8, 0xac, 0xfb, 0x89, 0x39, 0x35, 0xd6, 0x9a, 0x6a, 0xe8, 0x66, + 0x55, 0xcb, 0xf5, 0xac, 0x7b, 0x96, 0xeb, 0x50, 0xc6, 0x88, 0x6d, + 0x66, 0xe9, 0xcd, 0x6c, 0xdb, 0x4f, 0xd3, 0x9a, 0x00, 0x2f, 0xe6, + 0xf9, 0xa3, 0xe7, 0xb5, 0x4a, 0x93, 0x7f, 0xa2, 0xc6, 0x73, 0xdc, + 0xd7, 0x15, 0x55, 0xef, 0x48, 0x7d, 0x09, 0x52, 0x6e, 0x3a, 0xd4, + 0xab, 0x2f, 0xbd, 0x61, 0x16, 0x0c, 0x73, 0x49, 0xc5, 0x24, 0x92, + 0x7f, 0xa2, 0x63, 0xfd, 0xaa, 0xd6, 0x2f, 0x71, 0x0e, 0xb1, 0x93, + 0xf7, 0x2d, 0xf5, 0xa4, 0x9e, 0x4e, 0xb5, 0xdd, 0x4b, 0xf8, 0x68, + 0x4c, 0xcb, 0xb9, 0x93, 0xad, 0x65, 0xce, 0xd9, 0x26, 0xa9, 0x8d, + 0x19, 0xf6, 0xf2, 0xf4, 0xe6, 0xb5, 0xad, 0xe7, 0xc6, 0x39, 0xa0, + 0x18, 0xeb, 0xc9, 0x77, 0x6c, 0x35, 0x2a, 0x4b, 0xfe, 0x8a, 0x9c, + 0xff, 0x00, 0x11, 0xae, 0x3a, 0x8b, 0xde, 0x61, 0xd0, 0x9e, 0x39, + 0xb8, 0xeb, 0x53, 0xac, 0xb9, 0xae, 0x5b, 0x00, 0xf3, 0x27, 0x14, + 0x92, 0xc9, 0xfe, 0x8a, 0x3f, 0xde, 0x35, 0xac, 0x3a, 0x88, 0x92, + 0xcd, 0xb1, 0x6e, 0x7d, 0xcd, 0x32, 0x67, 0xeb, 0xcd, 0x7a, 0x14, + 0xfe, 0x04, 0x26, 0x66, 0xce, 0xf9, 0x26, 0xb3, 0xe6, 0x6e, 0xb4, + 0xd9, 0x48, 0xc8, 0x82, 0x4e, 0x07, 0x35, 0xa7, 0x6f, 0x2f, 0x02, + 0x9a, 0x06, 0x5f, 0x8c, 0xa4, 0x83, 0x0e, 0x32, 0x2a, 0x69, 0xe3, + 0xdd, 0x12, 0x08, 0x97, 0x85, 0xec, 0x2a, 0x2a, 0x42, 0xf1, 0x76, + 0x26, 0xe4, 0x6a, 0x59, 0x0e, 0x18, 0x10, 0x6a, 0xd2, 0x89, 0x02, + 0x6e, 0x2a, 0x71, 0xeb, 0x5c, 0x1c, 0x8c, 0xa6, 0x48, 0xbb, 0xdc, + 0x61, 0x41, 0x35, 0x72, 0x28, 0x87, 0xd9, 0xf6, 0x4a, 0xb9, 0xe7, + 0x38, 0xae, 0x8c, 0x3d, 0x36, 0xdd, 0xde, 0xc4, 0xb0, 0x21, 0x51, + 0x76, 0xa8, 0xc0, 0xaa, 0x93, 0x31, 0xe6, 0xbb, 0x2d, 0x65, 0x61, + 0x19, 0xd3, 0x1e, 0xb5, 0x46, 0x5a, 0x96, 0x5a, 0x30, 0xa0, 0x7e, + 0x05, 0x69, 0x5b, 0xc9, 0xc6, 0x28, 0x40, 0xcd, 0x08, 0x64, 0x3c, + 0x73, 0x57, 0xe1, 0x94, 0xf1, 0xcd, 0x5a, 0x21, 0x8c, 0xb9, 0x63, + 0xe7, 0x67, 0x1d, 0xab, 0x40, 0xb1, 0xfb, 0x00, 0x1d, 0xf0, 0x2b, + 0x99, 0x2d, 0x66, 0x3e, 0x88, 0x75, 0x81, 0x3f, 0x31, 0xf6, 0xab, + 0x64, 0xd6, 0xb4, 0x17, 0xee, 0xd0, 0x9e, 0xe4, 0x32, 0x1a, 0xa7, + 0x31, 0xad, 0x18, 0x14, 0x26, 0xef, 0x54, 0xa5, 0xa8, 0x65, 0xa3, + 0x9c, 0x81, 0xfa, 0x56, 0x8c, 0x2d, 0xce, 0x68, 0x40, 0xcb, 0xf1, + 0x37, 0xbd, 0x5e, 0x85, 0xea, 0xd1, 0x0c, 0xbb, 0x19, 0x56, 0x23, + 0x20, 0x1f, 0xad, 0x5c, 0x42, 0x08, 0x03, 0xb5, 0x55, 0x91, 0x04, + 0xc9, 0x80, 0x38, 0x00, 0x0a, 0x71, 0x34, 0x6c, 0x32, 0x27, 0xe9, + 0x55, 0x25, 0x15, 0x2c, 0x68, 0xa3, 0x30, 0xeb, 0x54, 0xa5, 0x15, + 0x0c, 0xd1, 0x00, 0xff, 0xd9}; + + /* package */ static final byte[] sPhotoByteArrayForComplicatedCase; + + static { + final int length = sPhotoIntArrayForComplicatedCase.length; + sPhotoByteArrayForComplicatedCase = new byte[length]; + for (int i = 0; i < length; i++) { + sPhotoByteArrayForComplicatedCase[i] = (byte)sPhotoIntArrayForComplicatedCase[i]; + } + } + + public void testV21SimpleCase1_Parsing() { + mVerifier.initForImportTest(V21, R.raw.v21_simple_1); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", "")); + } + + public void testV21SimpleCase1_Type_Generic() { + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, R.raw.v21_simple_1); + mVerifier.addContentValuesVerifierElem() + .addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "Ando") + .put(StructuredName.GIVEN_NAME, "Roid") + .put(StructuredName.DISPLAY_NAME, "Roid Ando"); + } + + public void testV21SimpleCase1_Type_Japanese() { + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_1); + mVerifier.addContentValuesVerifierElem() + .addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "Ando") + .put(StructuredName.GIVEN_NAME, "Roid") + // If name-related strings only contains printable Ascii, + // the order is remained to be US's: + // "Prefix Given Middle Family Suffix" + .put(StructuredName.DISPLAY_NAME, "Roid Ando"); + } + + public void testV21SimpleCase2() { + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_2); + mVerifier.addContentValuesVerifierElem() + .addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.DISPLAY_NAME, "Ando Roid"); + } + + public void testV21SimpleCase3() { + mVerifier.initForImportTest(V21, R.raw.v21_simple_3); + mVerifier.addContentValuesVerifierElem() + .addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "Ando") + .put(StructuredName.GIVEN_NAME, "Roid") + // "FN" field should be prefered since it should contain the original + // order intended by the author of the file. + .put(StructuredName.DISPLAY_NAME, "Ando Roid"); + } + + /** + * Tests ';' is properly handled by VCardParser implementation. + */ + public void testV21BackslashCase_Parsing() { + mVerifier.initForImportTest(V21, R.raw.v21_backslash); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "2.1") + .addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;", + Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", "")) + .addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\"); + + } + + /** + * Tests ContactStruct correctly ignores redundant fields in "N" property values and + * inserts name related data. + */ + public void testV21BackslashCase() { + mVerifier.initForImportTest(V21, R.raw.v21_backslash); + mVerifier.addContentValuesVerifierElem() + .addExpected(StructuredName.CONTENT_ITEM_TYPE) + // FAMILY_NAME is empty and removed in this test... + .put(StructuredName.GIVEN_NAME, "A;B\\") + .put(StructuredName.MIDDLE_NAME, "C\\;") + .put(StructuredName.PREFIX, "D") + .put(StructuredName.SUFFIX, ":E") + .put(StructuredName.DISPLAY_NAME, "A;B\\C\\;D:E\\\\"); + } + + public void testOrgBeforTitle() { + mVerifier.initForImportTest(V21, R.raw.v21_org_before_title); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.DISPLAY_NAME, "Normal Guy"); + elem.addExpected(Organization.CONTENT_ITEM_TYPE) + .put(Organization.COMPANY, "Company") + .put(Organization.DEPARTMENT, "Organization Devision Room Sheet No.") + .put(Organization.TITLE, "Excellent Janitor") + .put(Organization.TYPE, Organization.TYPE_WORK); + } + + public void testTitleBeforOrg() { + mVerifier.initForImportTest(V21, R.raw.v21_title_before_org); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.DISPLAY_NAME, "Nice Guy"); + elem.addExpected(Organization.CONTENT_ITEM_TYPE) + .put(Organization.COMPANY, "Marverous") + .put(Organization.DEPARTMENT, "Perfect Great Good Bad Poor") + .put(Organization.TITLE, "Cool Title") + .put(Organization.TYPE, Organization.TYPE_WORK); + } + + /** + * Verifies that vCard importer correctly interpret "PREF" attribute to IS_PRIMARY. + * The data contain three cases: one "PREF", no "PREF" and multiple "PREF", in each type. + */ + public void testV21PrefToIsPrimary() { + mVerifier.initForImportTest(V21, R.raw.v21_pref_handling); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.DISPLAY_NAME, "Smith"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "1") + .put(Phone.TYPE, Phone.TYPE_HOME); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "2") + .put(Phone.TYPE, Phone.TYPE_WORK) + .put(Phone.IS_PRIMARY, 1); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "3") + .put(Phone.TYPE, Phone.TYPE_ISDN); + elem.addExpected(Email.CONTENT_ITEM_TYPE) + .put(Email.DATA, "test@example.com") + .put(Email.TYPE, Email.TYPE_HOME) + .put(Email.IS_PRIMARY, 1); + elem.addExpected(Email.CONTENT_ITEM_TYPE) + .put(Email.DATA, "test2@examination.com") + .put(Email.TYPE, Email.TYPE_MOBILE) + .put(Email.IS_PRIMARY, 1); + elem.addExpected(Organization.CONTENT_ITEM_TYPE) + .put(Organization.COMPANY, "Company") + .put(Organization.TITLE, "Engineer") + .put(Organization.TYPE, Organization.TYPE_WORK); + elem.addExpected(Organization.CONTENT_ITEM_TYPE) + .put(Organization.COMPANY, "Mystery") + .put(Organization.TITLE, "Blogger") + .put(Organization.TYPE, Organization.TYPE_WORK); + elem.addExpected(Organization.CONTENT_ITEM_TYPE) + .put(Organization.COMPANY, "Poetry") + .put(Organization.TITLE, "Poet") + .put(Organization.TYPE, Organization.TYPE_WORK); + } + + /** + * Tests all the properties in a complicated vCard are correctly parsed by the VCardParser. + */ + public void testV21ComplicatedCase_Parsing() { + mVerifier.initForImportTest(V21, R.raw.v21_complicated); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "2.1") + .addExpectedNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao", + Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao")) + .addExpectedNodeWithOrder("FN", "Joe Due") + .addExpectedNodeWithOrder("ORG", "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper", + Arrays.asList("Gump Shrimp Co.", "Sales Dept.;Manager", "Fish keeper")) + .addExpectedNodeWithOrder("ROLE", "Fish Cake Keeper!") + .addExpectedNodeWithOrder("TITLE", "Shrimp Man") + .addExpectedNodeWithOrder("X-CLASS", "PUBLIC") + .addExpectedNodeWithOrder("TEL", "(111) 555-1212", new TypeSet("WORK", "VOICE")) + .addExpectedNodeWithOrder("TEL", "(404) 555-1212", new TypeSet("HOME", "VOICE")) + .addExpectedNodeWithOrder("TEL", "0311111111", new TypeSet("CELL")) + .addExpectedNodeWithOrder("TEL", "0322222222", new TypeSet("VIDEO")) + .addExpectedNodeWithOrder("TEL", "0333333333", new TypeSet("VOICE")) + .addExpectedNodeWithOrder("ADR", + ";;100 Waters Edge;Baytown;LA;30314;United States of America", + Arrays.asList("", "", "100 Waters Edge", "Baytown", + "LA", "30314", "United States of America"), + null, null, new TypeSet("WORK"), null) + .addExpectedNodeWithOrder("LABEL", + "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited States of America", + null, null, mContentValuesForQP, new TypeSet("WORK"), null) + .addExpectedNodeWithOrder("ADR", + ";;42 Plantation St.;Baytown;LA;30314;United States of America", + Arrays.asList("", "", "42 Plantation St.", "Baytown", + "LA", "30314", "United States of America"), null, null, + new TypeSet("HOME"), null) + .addExpectedNodeWithOrder("LABEL", + "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited States of America", + null, null, mContentValuesForQP, + new TypeSet("HOME"), null) + .addExpectedNodeWithOrder("EMAIL", "forrestgump@walladalla.com", + new TypeSet("PREF", "INTERNET")) + .addExpectedNodeWithOrder("EMAIL", "cell@example.com", new TypeSet("CELL")) + .addExpectedNodeWithOrder("NOTE", "The following note is the example from RFC 2045.") + .addExpectedNodeWithOrder("NOTE", + "Now's the time for all folk to come to the aid of their country.", + null, null, mContentValuesForQP, null, null) + .addExpectedNodeWithOrder("PHOTO", null, + null, sPhotoByteArrayForComplicatedCase, mContentValuesForBase64V21, + new TypeSet("JPEG"), null) + .addExpectedNodeWithOrder("X-ATTRIBUTE", "Some String") + .addExpectedNodeWithOrder("BDAY", "19800101") + .addExpectedNodeWithOrder("GEO", "35.6563854,139.6994233") + .addExpectedNodeWithOrder("URL", "http://www.example.com/") + .addExpectedNodeWithOrder("REV", "20080424T195243Z"); + } + + /** + * Checks ContactStruct correctly inserts values in a complicated vCard + * into ContentResolver. + */ + public void testV21ComplicatedCase() { + mVerifier.initForImportTest(V21, R.raw.v21_complicated); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "Gump") + .put(StructuredName.GIVEN_NAME, "Forrest") + .put(StructuredName.MIDDLE_NAME, "Hoge") + .put(StructuredName.PREFIX, "Pos") + .put(StructuredName.SUFFIX, "Tao") + .put(StructuredName.DISPLAY_NAME, "Joe Due"); + elem.addExpected(Organization.CONTENT_ITEM_TYPE) + .put(Organization.TYPE, Organization.TYPE_WORK) + .put(Organization.COMPANY, "Gump Shrimp Co.") + .put(Organization.DEPARTMENT, "Sales Dept.;Manager Fish keeper") + .put(Organization.TITLE, "Shrimp Man"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_WORK) + // Phone number is expected to be formated with NAMP format in default. + .put(Phone.NUMBER, "111-555-1212"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_HOME) + .put(Phone.NUMBER, "404-555-1212"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_MOBILE) + .put(Phone.NUMBER, "031-111-1111"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "VIDEO") + .put(Phone.NUMBER, "032-222-2222"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "VOICE") + .put(Phone.NUMBER, "033-333-3333"); + elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK) + .put(StructuredPostal.COUNTRY, "United States of America") + .put(StructuredPostal.POSTCODE, "30314") + .put(StructuredPostal.REGION, "LA") + .put(StructuredPostal.CITY, "Baytown") + .put(StructuredPostal.STREET, "100 Waters Edge") + .put(StructuredPostal.FORMATTED_ADDRESS, + "100 Waters Edge Baytown LA 30314 United States of America"); + elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME) + .put(StructuredPostal.COUNTRY, "United States of America") + .put(StructuredPostal.POSTCODE, "30314") + .put(StructuredPostal.REGION, "LA") + .put(StructuredPostal.CITY, "Baytown") + .put(StructuredPostal.STREET, "42 Plantation St.") + .put(StructuredPostal.FORMATTED_ADDRESS, + "42 Plantation St. Baytown LA 30314 United States of America"); + elem.addExpected(Email.CONTENT_ITEM_TYPE) + // "TYPE=INTERNET" -> TYPE_CUSTOM + the label "INTERNET" + .put(Email.TYPE, Email.TYPE_CUSTOM) + .put(Email.LABEL, "INTERNET") + .put(Email.DATA, "forrestgump@walladalla.com") + .put(Email.IS_PRIMARY, 1); + elem.addExpected(Email.CONTENT_ITEM_TYPE) + .put(Email.TYPE, Email.TYPE_MOBILE) + .put(Email.DATA, "cell@example.com"); + elem.addExpected(Note.CONTENT_ITEM_TYPE) + .put(Note.NOTE, "The following note is the example from RFC 2045."); + elem.addExpected(Note.CONTENT_ITEM_TYPE) + .put(Note.NOTE, + "Now's the time for all folk to come to the aid of their country."); + elem.addExpected(Photo.CONTENT_ITEM_TYPE) + // No information about its image format can be inserted. + .put(Photo.PHOTO, sPhotoByteArrayForComplicatedCase); + elem.addExpected(Event.CONTENT_ITEM_TYPE) + .put(Event.START_DATE, "19800101") + .put(Event.TYPE, Event.TYPE_BIRTHDAY); + elem.addExpected(Website.CONTENT_ITEM_TYPE) + .put(Website.URL, "http://www.example.com/") + .put(Website.TYPE, Website.TYPE_HOMEPAGE); + } + + public void testV30Simple_Parsing() { + mVerifier.initForImportTest(V30, R.raw.v30_simple); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "3.0") + .addExpectedNodeWithOrder("FN", "And Roid") + .addExpectedNodeWithOrder("N", "And;Roid;;;", Arrays.asList("And", "Roid", "", "", "")) + .addExpectedNodeWithOrder("ORG", "Open;Handset; Alliance", + Arrays.asList("Open", "Handset", " Alliance")) + .addExpectedNodeWithOrder("SORT-STRING", "android") + .addExpectedNodeWithOrder("TEL", "0300000000", new TypeSet("PREF", "VOICE")) + .addExpectedNodeWithOrder("CLASS", "PUBLIC") + .addExpectedNodeWithOrder("X-GNO", "0") + .addExpectedNodeWithOrder("X-GN", "group0") + .addExpectedNodeWithOrder("X-REDUCTION", "0") + .addExpectedNodeWithOrder("REV", "20081031T065854Z"); + } + + public void testV30Simple() { + mVerifier.initForImportTest(V30, R.raw.v30_simple); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "And") + .put(StructuredName.GIVEN_NAME, "Roid") + .put(StructuredName.DISPLAY_NAME, "And Roid") + .put(StructuredName.PHONETIC_GIVEN_NAME, "android"); + elem.addExpected(Organization.CONTENT_ITEM_TYPE) + .put(Organization.COMPANY, "Open") + .put(Organization.DEPARTMENT, "Handset Alliance") + .put(Organization.TYPE, Organization.TYPE_WORK); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "VOICE") + .put(Phone.NUMBER, "030-000-0000") + .put(Phone.IS_PRIMARY, 1); + } + + public void testV21Japanese1_Parsing() { + // 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.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "2.1", null, null, null, null, null) + .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;", + Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""), + null, mContentValuesForSJis, null, null) + .addExpectedNodeWithOrder("SOUND", + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;", + null, null, mContentValuesForSJis, + new TypeSet("X-IRMC-N"), null) + .addExpectedNodeWithOrder("TEL", "0300000000", null, null, null, + new TypeSet("VOICE", "PREF"), null); + } + + private void testV21Japanese1Common(int resId, int vcardType, boolean japanese) { + mVerifier.initForImportTest(vcardType, resId); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9") + .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9") + // While vCard parser does not split "SOUND" property values, + // ContactStruct care it. + .put(StructuredName.PHONETIC_GIVEN_NAME, + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + // Phone number formatting is different. + .put(Phone.NUMBER, (japanese ? "03-0000-0000" : "030-000-0000")) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "VOICE") + .put(Phone.IS_PRIMARY, 1); + } + + /** + * Verifies vCard with Japanese can be parsed correctly with + * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC_UTF8}. + */ + public void testV21Japanese1_Type_Generic_Utf8() { + testV21Japanese1Common( + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, false); + } + + /** + * Verifies vCard with Japanese can be parsed correctly with + * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_SJIS}. + */ + public void testV21Japanese1_Type_Japanese_Sjis() { + testV21Japanese1Common( + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, true); + } + + /** + * Verifies vCard with Japanese can be parsed correctly with + * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_UTF8}. + * 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); + } + + public void testV21Japanese2_Parsing() { + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, + R.raw.v21_japanese_2); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "2.1") + .addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;", + Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031", + "", "", ""), + null, mContentValuesForSJis, null, null) + .addExpectedNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031", + null, null, mContentValuesForSJis, null, null) + .addExpectedNodeWithOrder("SOUND", + "\uFF71\uFF9D\uFF84\uFF9E\uFF73;\uFF9B\uFF72\uFF84\uFF9E\u0031;;;", + null, null, mContentValuesForSJis, + new TypeSet("X-IRMC-N"), null) + .addExpectedNodeWithOrder("ADR", + ";\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" + + "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" + + "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC\u0036" + + "\u968E;;;;150-8512;", + Arrays.asList("", + "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" + + "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" + + "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" + + "\u0036\u968E", "", "", "", "150-8512", ""), + null, mContentValuesForQPAndSJis, new TypeSet("HOME"), null) + .addExpectedNodeWithOrder("NOTE", "\u30E1\u30E2", null, null, + mContentValuesForQPAndSJis, null, null); + } + + public void testV21Japanese2_Type_Generic_Utf8() { + mVerifier.initForImportTest(V21, R.raw.v21_japanese_2); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4") + .put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9\u0031") + .put(StructuredName.DISPLAY_NAME, + "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031") + // ContactStruct should correctly split "SOUND" property into several elements, + // even though VCardParser side does not care it. + .put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF71\uFF9D\uFF84\uFF9E\uFF73") + .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF9B\uFF72\uFF84\uFF9E\u0031"); + elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.POSTCODE, "150-8512") + .put(StructuredPostal.STREET, + "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" + + "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" + + "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" + + "\u0036\u968E") + .put(StructuredPostal.FORMATTED_ADDRESS, + "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" + + "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" + + "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" + + "\u0036\u968E 150-8512") + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME); + elem.addExpected(Note.CONTENT_ITEM_TYPE) + .put(Note.NOTE, "\u30E1\u30E2"); + } + + public void testV21MultipleEntryCase_Parse() { + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, + R.raw.v21_multiple_entry); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "2.1") + .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;", + Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""), + null, mContentValuesForSJis, null, null) + .addExpectedNodeWithOrder("SOUND", + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;", + null, null, mContentValuesForSJis, + new TypeSet("X-IRMC-N"), null) + .addExpectedNodeWithOrder("TEL", "9", new TypeSet("X-NEC-SECRET")) + .addExpectedNodeWithOrder("TEL", "10", new TypeSet("X-NEC-HOTEL")) + .addExpectedNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL")) + .addExpectedNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME")); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "2.1") + .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;", + Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""), + null, mContentValuesForSJis, null, null) + .addExpectedNodeWithOrder("SOUND", + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;", + null, null, mContentValuesForSJis, + new TypeSet("X-IRMC-N"), null) + .addExpectedNodeWithOrder("TEL", "13", new TypeSet("MODEM")) + .addExpectedNodeWithOrder("TEL", "14", new TypeSet("PAGER")) + .addExpectedNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY")) + .addExpectedNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL")); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "2.1") + .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;", + Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""), + null, mContentValuesForSJis, null, null) + .addExpectedNodeWithOrder("SOUND", + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;", + null, null, mContentValuesForSJis, + new TypeSet("X-IRMC-N"), null) + .addExpectedNodeWithOrder("TEL", "17", new TypeSet("X-NEC-BOY")) + .addExpectedNodeWithOrder("TEL", "18", new TypeSet("X-NEC-FRIEND")) + .addExpectedNodeWithOrder("TEL", "19", new TypeSet("X-NEC-PHS")) + .addExpectedNodeWithOrder("TEL", "20", new TypeSet("X-NEC-RESTAURANT")); + } + + public void testV21MultipleEntryCase() { + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, + 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") + .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033") + .put(StructuredName.PHONETIC_GIVEN_NAME, + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "NEC-SECRET") + .put(Phone.NUMBER, "9"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "NEC-HOTEL") + .put(Phone.NUMBER, "10"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "NEC-SCHOOL") + .put(Phone.NUMBER, "11"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_FAX_HOME) + .put(Phone.NUMBER, "12"); + + elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034") + .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034") + .put(StructuredName.PHONETIC_GIVEN_NAME, + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "MODEM") + .put(Phone.NUMBER, "13"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_PAGER) + .put(Phone.NUMBER, "14"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "NEC-FAMILY") + .put(Phone.NUMBER, "15"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "NEC-GIRL") + .put(Phone.NUMBER, "16"); + + elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035") + .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035") + .put(StructuredName.PHONETIC_GIVEN_NAME, + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "NEC-BOY") + .put(Phone.NUMBER, "17"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "NEC-FRIEND") + .put(Phone.NUMBER, "18"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "NEC-PHS") + .put(Phone.NUMBER, "19"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "NEC-RESTAURANT") + .put(Phone.NUMBER, "20"); + } + + public void testIgnoreAgentV21_Parse() { + mVerifier.initForImportTest(V21, R.raw.v21_winmo_65); + ContentValues contentValuesForValue = new ContentValues(); + contentValuesForValue.put("VALUE", "DATE"); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "2.1") + .addExpectedNodeWithOrder("N", Arrays.asList("Example", "", "", "", "")) + .addExpectedNodeWithOrder("FN", "Example") + .addExpectedNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue) + .addExpectedNodeWithOrder("AGENT", "") + .addExpectedNodeWithOrder("X-CLASS", "PUBLIC") + .addExpectedNodeWithOrder("X-REDUCTION", "") + .addExpectedNodeWithOrder("X-NO", ""); + } + + public void testIgnoreAgentV21() { + mVerifier.initForImportTest(V21, R.raw.v21_winmo_65); + ContentValuesVerifier verifier = new ContentValuesVerifier(); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "Example") + .put(StructuredName.DISPLAY_NAME, "Example"); + } + + public void testTolerateInvalidCommentLikeLineV21() { + mVerifier.initForImportTest(V21, R.raw.v21_invalid_comment_line); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.GIVEN_NAME, "Conference Call") + .put(StructuredName.DISPLAY_NAME, "Conference Call"); + elem.addExpected(Note.CONTENT_ITEM_TYPE) + .put(Note.NOTE, "This is an (sharp ->#<- sharp) example. " + + "This message must NOT be ignored."); + } + + public void testPagerV30_Parse() { + mVerifier.initForImportTest(V30, R.raw.v30_comma_separated); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "3.0") + .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", "")) + .addExpectedNodeWithOrder("TEL", "6101231234@pagersample.com", + new TypeSet("WORK", "MSG", "PAGER")); + } + + public void testPagerV30() { + mVerifier.initForImportTest(V30, R.raw.v30_comma_separated); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "F") + .put(StructuredName.MIDDLE_NAME, "M") + .put(StructuredName.GIVEN_NAME, "G") + .put(StructuredName.DISPLAY_NAME, "G M F"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_PAGER) + .put(Phone.NUMBER, "6101231234@pagersample.com"); + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java new file mode 100644 index 0000000..5b60342 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java @@ -0,0 +1,434 @@ +/* + * 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; + +import android.content.ContentValues; +import android.pim.vcard.VCardConfig; +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 { + private void testNameUtf8Common(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069") + .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B") + .put(StructuredName.MIDDLE_NAME, "B") + .put(StructuredName.PREFIX, "Dr.") + .put(StructuredName.SUFFIX, "Ph.D"); + ContentValues contentValues = + (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D", + contentValues) + .addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D", + Arrays.asList( + "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"), + null, contentValues, null, null); + } + + public void testNameUtf8V21() { + testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); + } + + public void testNameUtf8V30() { + testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8); + } + + public void testNameShiftJis() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069") + .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B") + .put(StructuredName.MIDDLE_NAME, "B") + .put(StructuredName.PREFIX, "Dr.") + .put(StructuredName.SUFFIX, "Ph.D"); + + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D", + mContentValuesForSJis) + .addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D", + Arrays.asList( + "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"), + null, mContentValuesForSJis, null, null); + } + + /** + * DoCoMo phones require all name elements should be in "family name" field. + */ + public void testNameDoCoMo() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069") + .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B") + .put(StructuredName.MIDDLE_NAME, "B") + .put(StructuredName.PREFIX, "Dr.") + .put(StructuredName.SUFFIX, "Ph.D"); + + final String fullName = "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D"; + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNode("N", fullName + ";;;;", + Arrays.asList(fullName, "", "", "", ""), + null, mContentValuesForSJis, null, null) + .addExpectedNode("FN", fullName, mContentValuesForSJis) + .addExpectedNode("SOUND", ";;;;", new TypeSet("X-IRMC-N")) + .addExpectedNode("TEL", "", new TypeSet("HOME")) + .addExpectedNode("EMAIL", "", new TypeSet("HOME")) + .addExpectedNode("ADR", "", new TypeSet("HOME")) + .addExpectedNode("X-CLASS", "PUBLIC") + .addExpectedNode("X-REDUCTION", "") + .addExpectedNode("X-NO", "") + .addExpectedNode("X-DCM-HMN-MODE", ""); + } + + private void testPhoneticNameCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0") + .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046"); + + final ContentValues contentValues = + (VCardConfig.usesShiftJis(vcardType) ? + (VCardConfig.isV30(vcardType) ? mContentValuesForSJis : + mContentValuesForQPAndSJis) : + (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8)); + PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName(); + elem.addExpectedNode("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060", + contentValues) + .addExpectedNode("X-PHONETIC-MIDDLE-NAME", + "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0", + contentValues) + .addExpectedNode("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046", + contentValues); + if (VCardConfig.isV30(vcardType)) { + elem.addExpectedNode("SORT-STRING", + "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046", + contentValues); + } + ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem() + .addExpected(StructuredName.CONTENT_ITEM_TYPE); + builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0") + .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046") + .put(StructuredName.DISPLAY_NAME, + "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 " + + "\u305F\u308D\u3046"); + } + + public void testPhoneticNameForJapaneseV21Utf8() { + testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); + } + + public void testPhoneticNameForJapaneseV21Sjis() { + testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS); + } + + public void testPhoneticNameForJapaneseV30Utf8() { + testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8); + } + + public void testPhoneticNameForJapaneseV30SJis() { + testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS); + } + + public void testPhoneticNameForMobileV21_1() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0") + .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046"); + + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNode("SOUND", + "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " + + "\uFF80\uFF9B\uFF73;;;;", + mContentValuesForSJis, new TypeSet("X-IRMC-N")); + ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem() + .addExpected(StructuredName.CONTENT_ITEM_TYPE); + builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E") + .put(StructuredName.PHONETIC_MIDDLE_NAME, + "\uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91") + .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73") + .put(StructuredName.DISPLAY_NAME, + "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " + + "\uFF80\uFF9B\uFF73"); + } + + public void testPhoneticNameForMobileV21_2() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") + .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046"); + + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNode("SOUND", "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73;;;;", + mContentValuesForSJis, new TypeSet("X-IRMC-N")); + ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem() + .addExpected(StructuredName.CONTENT_ITEM_TYPE); + builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E") + .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73") + .put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73"); + } + + private void testPostalAddressWithJapaneseCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107") + .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751") + .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02") + .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C") + .put(StructuredPostal.POSTCODE, "494-1313") + .put(StructuredPostal.COUNTRY, "\u65E5\u672C") + .put(StructuredPostal.FORMATTED_ADDRESS, + "\u3053\u3093\u306A\u3068\u3053\u308D\u3092\u898B" + + "\u308B\u306A\u3093\u3066\u6687\u4EBA\u3067\u3059\u304B\uFF1F") + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM) + .put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A"); + + ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ? + (VCardConfig.isV30(vcardType) ? mContentValuesForSJis : + mContentValuesForQPAndSJis) : + (VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 : + mContentValuesForQPAndUtf8)); + + PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName(); + // LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is + // same as that in vCard 3.0, which can be changed in the future. + elem.addExpectedNode("ADR", Arrays.asList("\u79C1\u66F8\u7BB107", + "", "\u96DB\u898B\u6CA2\u6751", "\u9E7F\u9AA8\u5E02", "\u00D7\u00D7\u770C", + "494-1313", "\u65E5\u672C"), + contentValues); + mVerifier.addContentValuesVerifierElem().addExpected(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107") + .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751") + .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02") + .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C") + .put(StructuredPostal.POSTCODE, "494-1313") + .put(StructuredPostal.COUNTRY, "\u65E5\u672C") + .put(StructuredPostal.FORMATTED_ADDRESS, + "\u65E5\u672C 494-1313 \u00D7\u00D7\u770C \u9E7F\u9AA8\u5E02 " + + "\u96DB\u898B\u6CA2\u6751 " + "\u79C1\u66F8\u7BB107") + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME); + } + public void testPostalAddresswithJapaneseV21() { + testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS); + } + + /** + * Verifies that only one address field is emitted toward DoCoMo phones. + * Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM + */ + public void testPostalAdrressForDoCoMo_1() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK) + .put(StructuredPostal.POBOX, "1"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER) + .put(StructuredPostal.POBOX, "2"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME) + .put(StructuredPostal.POBOX, "3"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM) + .put(StructuredPostal.LABEL, "custom") + .put(StructuredPostal.POBOX, "4"); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "", new TypeSet("HOME")) + .addExpectedNode("EMAIL", "", new TypeSet("HOME")) + .addExpectedNode("X-CLASS", "PUBLIC") + .addExpectedNode("X-REDUCTION", "") + .addExpectedNode("X-NO", "") + .addExpectedNode("X-DCM-HMN-MODE", "") + .addExpectedNode("ADR", + Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME")); + } + + public void testPostalAdrressForDoCoMo_2() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER) + .put(StructuredPostal.POBOX, "1"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK) + .put(StructuredPostal.POBOX, "2"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM) + .put(StructuredPostal.LABEL, "custom") + .put(StructuredPostal.POBOX, "3"); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "", new TypeSet("HOME")) + .addExpectedNode("EMAIL", "", new TypeSet("HOME")) + .addExpectedNode("X-CLASS", "PUBLIC") + .addExpectedNode("X-REDUCTION", "") + .addExpectedNode("X-NO", "") + .addExpectedNode("X-DCM-HMN-MODE", "") + .addExpectedNode("ADR", + Arrays.asList("2", "", "", "", "", "", ""), new TypeSet("WORK")); + } + + public void testPostalAdrressForDoCoMo_3() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM) + .put(StructuredPostal.LABEL, "custom1") + .put(StructuredPostal.POBOX, "1"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER) + .put(StructuredPostal.POBOX, "2"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM) + .put(StructuredPostal.LABEL, "custom2") + .put(StructuredPostal.POBOX, "3"); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "", new TypeSet("HOME")) + .addExpectedNode("EMAIL", "", new TypeSet("HOME")) + .addExpectedNode("X-CLASS", "PUBLIC") + .addExpectedNode("X-REDUCTION", "") + .addExpectedNode("X-NO", "") + .addExpectedNode("X-DCM-HMN-MODE", "") + .addExpectedNode("ADR", Arrays.asList("2", "", "", "", "", "", "")); + } + + /** + * Verifies the vCard exporter tolerates null TYPE. + */ + public void testPostalAdrressForDoCoMo_4() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.POBOX, "1"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER) + .put(StructuredPostal.POBOX, "2"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME) + .put(StructuredPostal.POBOX, "3"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK) + .put(StructuredPostal.POBOX, "4"); + entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) + .put(StructuredPostal.POBOX, "5"); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "", new TypeSet("HOME")) + .addExpectedNode("EMAIL", "", new TypeSet("HOME")) + .addExpectedNode("X-CLASS", "PUBLIC") + .addExpectedNode("X-REDUCTION", "") + .addExpectedNode("X-NO", "") + .addExpectedNode("X-DCM-HMN-MODE", "") + .addExpectedNode("ADR", + Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME")); + } + + private void testJapanesePhoneNumberCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "0312341234") + .put(Phone.TYPE, Phone.TYPE_HOME); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "09012341234") + .put(Phone.TYPE, Phone.TYPE_MOBILE); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME")) + .addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL")); + } + + public void testJapanesePhoneNumberV21_1() { + testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); + } + + public void testJapanesePhoneNumberV30() { + testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8); + } + + public void testJapanesePhoneNumberDoCoMo() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "0312341234") + .put(Phone.TYPE, Phone.TYPE_HOME); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "09012341234") + .put(Phone.TYPE, Phone.TYPE_MOBILE); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("EMAIL", "", new TypeSet("HOME")) + .addExpectedNode("X-CLASS", "PUBLIC") + .addExpectedNode("X-REDUCTION", "") + .addExpectedNode("X-NO", "") + .addExpectedNode("X-DCM-HMN-MODE", "") + .addExpectedNode("ADR", "", new TypeSet("HOME")) + .addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME")) + .addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL")); + } + + public void testNoteDoCoMo() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Note.CONTENT_ITEM_TYPE) + .put(Note.NOTE, "note1"); + entry.addContentValues(Note.CONTENT_ITEM_TYPE) + .put(Note.NOTE, "note2"); + entry.addContentValues(Note.CONTENT_ITEM_TYPE) + .put(Note.NOTE, "note3"); + + // More than one note fields must be aggregated into one note. + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "", new TypeSet("HOME")) + .addExpectedNode("EMAIL", "", new TypeSet("HOME")) + .addExpectedNode("X-CLASS", "PUBLIC") + .addExpectedNode("X-REDUCTION", "") + .addExpectedNode("X-NO", "") + .addExpectedNode("X-DCM-HMN-MODE", "") + .addExpectedNode("ADR", "", new TypeSet("HOME")) + .addExpectedNode("NOTE", "note1\nnote2\nnote3", mContentValuesForQP); + } + + public void testAndroidCustomV21() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE) + .put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC"); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("X-ANDROID-CUSTOM", + Arrays.asList(Nickname.CONTENT_ITEM_TYPE, + "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC", + "", "", "", "", "", "", "", "", "", "", "", "", "", ""), + mContentValuesForQPAndUtf8); + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java new file mode 100644 index 0000000..0857e0c --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java @@ -0,0 +1,169 @@ +/* + * 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; + +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.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; + + // Do not modify these during tests. + protected final ContentValues mContentValuesForQP; + protected final ContentValues mContentValuesForSJis; + protected final ContentValues mContentValuesForUtf8; + protected final ContentValues mContentValuesForQPAndSJis; + protected final ContentValues mContentValuesForQPAndUtf8; + protected final ContentValues mContentValuesForBase64V21; + protected final ContentValues mContentValuesForBase64V30; + + protected VCardVerifier mVerifier; + private boolean mSkipVerification; + + public VCardTestsBase() { + super(); + mContentValuesForQP = new ContentValues(); + mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE"); + mContentValuesForSJis = new ContentValues(); + mContentValuesForSJis.put("CHARSET", "SHIFT_JIS"); + mContentValuesForUtf8 = new ContentValues(); + mContentValuesForUtf8.put("CHARSET", "UTF-8"); + mContentValuesForQPAndSJis = new ContentValues(); + mContentValuesForQPAndSJis.put("ENCODING", "QUOTED-PRINTABLE"); + mContentValuesForQPAndSJis.put("CHARSET", "SHIFT_JIS"); + mContentValuesForQPAndUtf8 = new ContentValues(); + mContentValuesForQPAndUtf8.put("ENCODING", "QUOTED-PRINTABLE"); + mContentValuesForQPAndUtf8.put("CHARSET", "UTF-8"); + mContentValuesForBase64V21 = new ContentValues(); + mContentValuesForBase64V21.put("ENCODING", "BASE64"); + mContentValuesForBase64V30 = new ContentValues(); + mContentValuesForBase64V30.put("ENCODING", "b"); + } + + @Override + public void testAndroidTestCaseSetupProperly() { + super.testAndroidTestCaseSetupProperly(); + mSkipVerification = true; + } + + @Override + public void setUp() throws Exception{ + super.setUp(); + mVerifier = new VCardVerifier(this); + mSkipVerification = false; + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + if (!mSkipVerification) { + mVerifier.verify(); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java new file mode 100644 index 0000000..59299f9 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java @@ -0,0 +1,85 @@ +/* + * 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; + +import android.pim.vcard.VCardUtils; + +import junit.framework.TestCase; + +import java.util.List; + +public class VCardUtilsTests extends TestCase { + public void testContainsOnlyPrintableAscii() { + assertTrue(VCardUtils.containsOnlyPrintableAscii((String)null)); + assertTrue(VCardUtils.containsOnlyPrintableAscii((String[])null)); + assertTrue(VCardUtils.containsOnlyPrintableAscii((List<String>)null)); + assertTrue(VCardUtils.containsOnlyPrintableAscii("")); + assertTrue(VCardUtils.containsOnlyPrintableAscii("abcdefghijklmnopqrstuvwxyz")); + assertTrue(VCardUtils.containsOnlyPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); + StringBuilder builder = new StringBuilder(); + for (int i = 0x20; i < 0x7F; i++) { + builder.append((char)i); + } + assertTrue(VCardUtils.containsOnlyPrintableAscii(builder.toString())); + assertTrue(VCardUtils.containsOnlyPrintableAscii("\r\n")); + assertFalse(VCardUtils.containsOnlyPrintableAscii("\u0019")); + assertFalse(VCardUtils.containsOnlyPrintableAscii("\u007F")); + } + + public void testContainsOnlyNonCrLfPrintableAscii() { + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String)null)); + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String[])null)); + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((List<String>)null)); + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("")); + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz")); + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); + StringBuilder builder = new StringBuilder(); + for (int i = 0x20; i < 0x7F; i++) { + builder.append((char)i); + } + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(builder.toString())); + assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u0019")); + assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u007F")); + assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\r")); + assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\n")); + } + + public void testContainsOnlyAlphaDigitHyphen() { + assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String)null)); + assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String[])null)); + assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((List<String>)null)); + assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen("")); + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz")); + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("0123456789-")); + for (int i = 0; i < 0x30; i++) { + if (i == 0x2D) { // - + continue; + } + assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i))); + } + for (int i = 0x3A; i < 0x41; i++) { + assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i))); + } + for (int i = 0x5B; i < 0x61; i++) { + assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i))); + } + for (int i = 0x7B; i < 0x100; i++) { + assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i))); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java new file mode 100644 index 0000000..bfc3158 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java @@ -0,0 +1,306 @@ +/* + * 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; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.content.EntityIterator; +import android.net.Uri; +import android.pim.vcard.VCardComposer; +import android.pim.vcard.VCardConfig; +import android.pim.vcard.VCardEntryConstructor; +import android.pim.vcard.VCardInterpreter; +import android.pim.vcard.VCardInterpreterCollection; +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 android.test.mock.MockContext; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +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; + } + + @Override + public ContentResolver getContentResolver() { + return mResolver; + } +} + +/* package */ class VCardVerifier { + private class VCardVerifierInternal implements VCardComposer.OneEntryHandler { + public boolean onInit(Context context) { + return true; + } + public boolean onEntryCreated(String vcard) { + verifyOneVCard(vcard); + return true; + } + public void onTerminate() { + } + } + + private final AndroidTestCase mTestCase; + private final VCardVerifierInternal mVCardVerifierInternal; + private int mVCardType; + private boolean mIsV30; + private boolean mIsDoCoMo; + + // Only one of them must be non-empty. + private ExportTestResolver mExportTestResolver; + private InputStream mInputStream; + + // To allow duplication, use list instead of set. + // When null, we don't need to do the verification. + private PropertyNodesVerifier mPropertyNodesVerifier; + private LineVerifier mLineVerifier; + private ContentValuesVerifier mContentValuesVerifier; + private boolean mInitialized; + private boolean mVerified = false; + + public VCardVerifier(AndroidTestCase androidTestCase) { + mTestCase = androidTestCase; + mVCardVerifierInternal = new VCardVerifierInternal(); + mExportTestResolver = null; + mInputStream = null; + mInitialized = false; + mVerified = false; + } + + public void initForExportTest(int vcardType) { + if (mInitialized) { + mTestCase.fail("Already initialized"); + } + mExportTestResolver = new ExportTestResolver(mTestCase); + mVCardType = vcardType; + mIsV30 = VCardConfig.isV30(vcardType); + mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); + mInitialized = true; + } + + public void initForImportTest(int vcardType, int resId) { + if (mInitialized) { + mTestCase.fail("Already initialized"); + } + mVCardType = vcardType; + mIsV30 = VCardConfig.isV30(vcardType); + mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); + setInputResourceId(resId); + mInitialized = true; + } + + private void setInputResourceId(int resId) { + InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId); + if (inputStream == null) { + mTestCase.fail("Wrong resId: " + resId); + } + setInputStream(inputStream); + } + + private void setInputStream(InputStream inputStream) { + if (mExportTestResolver != null) { + mTestCase.fail("addInputEntry() is called."); + } else if (mInputStream != null) { + mTestCase.fail("InputStream is already set"); + } + mInputStream = inputStream; + } + + public ContactEntry addInputEntry() { + if (!mInitialized) { + mTestCase.fail("Not initialized"); + } + if (mInputStream != null) { + mTestCase.fail("setInputStream is called"); + } + return mExportTestResolver.addInputContactEntry(); + } + + public PropertyNodesVerifierElem addPropertyNodesVerifierElem() { + if (!mInitialized) { + mTestCase.fail("Not initialized"); + } + if (mPropertyNodesVerifier == null) { + mPropertyNodesVerifier = new PropertyNodesVerifier(mTestCase); + } + PropertyNodesVerifierElem elem = + mPropertyNodesVerifier.addPropertyNodesVerifierElem(); + elem.addExpectedNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1")); + + return elem; + } + + public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() { + if (!mInitialized) { + mTestCase.fail("Not initialized"); + } + PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem(); + if (mIsV30) { + elem.addExpectedNodeWithOrder("N", "").addExpectedNodeWithOrder("FN", ""); + } else if (mIsDoCoMo) { + elem.addExpectedNodeWithOrder("N", ""); + } + return elem; + } + + public LineVerifierElem addLineVerifierElem() { + if (!mInitialized) { + mTestCase.fail("Not initialized"); + } + if (mLineVerifier == null) { + mLineVerifier = new LineVerifier(mTestCase, mVCardType); + } + return mLineVerifier.addLineVerifierElem(); + } + + public ContentValuesVerifierElem addContentValuesVerifierElem() { + if (!mInitialized) { + mTestCase.fail("Not initialized"); + } + if (mContentValuesVerifier == null) { + mContentValuesVerifier = new ContentValuesVerifier(); + } + + return mContentValuesVerifier.addElem(mTestCase); + } + + private void verifyOneVCard(final String vcard) { + // Log.d("@@@", vcard); + final VCardInterpreter builder; + if (mContentValuesVerifier != null) { + final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier; + final VCardEntryConstructor vcardDataBuilder = + new VCardEntryConstructor(mVCardType); + vcardDataBuilder.addEntryHandler(mContentValuesVerifier); + if (mPropertyNodesVerifier != null) { + builder = new VCardInterpreterCollection(Arrays.asList( + mPropertyNodesVerifier, vcardDataBuilder)); + } else { + builder = vnodeBuilder; + } + } else { + if (mPropertyNodesVerifier != null) { + builder = mPropertyNodesVerifier; + } else { + return; + } + } + + 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)); + } catch (IOException e) { + mTestCase.fail("Unexpected IOException: " + e.getMessage()); + } catch (VCardException e) { + mTestCase.fail("Unexpected VCardException: " + e.getMessage()); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + } + } + } + } + + public void verify() { + if (!mInitialized) { + mTestCase.fail("Not initialized."); + } + if (mVerified) { + mTestCase.fail("verify() was called twice."); + } + if (mInputStream != null) { + try { + verifyForImportTest(); + } catch (IOException e) { + mTestCase.fail("IOException was thrown: " + e.getMessage()); + } catch (VCardException e) { + mTestCase.fail("VCardException was thrown: " + e.getMessage()); + } + } else if (mExportTestResolver != null){ + verifyForExportTest(); + } else { + mTestCase.fail("No input is determined"); + } + mVerified = true; + } + + private void verifyForImportTest() throws IOException, VCardException { + if (mLineVerifier != null) { + mTestCase.fail("Not supported now."); + } + if (mContentValuesVerifier != null) { + mContentValuesVerifier.verify(mInputStream, mVCardType); + } + } + + public static EntityIterator mockGetEntityIteratorMethod( + 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); + } + + private Method getMockGetEntityIteratorMethod() + throws SecurityException, NoSuchMethodException { + return this.getClass().getMethod("mockGetEntityIteratorMethod", + ContentResolver.class, Uri.class, String.class, String[].class, String.class); + } + + private void verifyForExportTest() { + final VCardComposer composer = + new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType); + composer.addHandler(mLineVerifier); + composer.addHandler(mVCardVerifierInternal); + if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) { + mTestCase.fail("init() failed. Reason: " + composer.getErrorReason()); + } + mTestCase.assertFalse(composer.isAfterLast()); + try { + while (!composer.isAfterLast()) { + try { + final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod(); + mTestCase.assertTrue( + composer.createOneEntry(getMockGetEntityIteratorMethod())); + } catch (Exception e) { + e.printStackTrace(); + mTestCase.fail(); + } + } + } finally { + composer.terminate(); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/VNode.java b/core/tests/coretests/src/android/pim/vcard/VNode.java new file mode 100644 index 0000000..79f10dc --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VNode.java @@ -0,0 +1,30 @@ +/* + * 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; + +import java.util.ArrayList; + +/** + * Previously used in main vCard handling code but now exists only for testing. + */ +public class VNode { + public String VName; + + public ArrayList<PropertyNode> propList = new ArrayList<PropertyNode>(); + + /** 0:parse over. 1:parsing. */ + public int parseStatus = 1; +} diff --git a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java b/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java new file mode 100644 index 0000000..0e6c325 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java @@ -0,0 +1,314 @@ +/* + * 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; + +import android.content.ContentValues; +import android.pim.vcard.VCardInterpreter; +import android.pim.vcard.VCardConfig; +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; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +/** + * Store the parse result to custom datastruct: VNode, 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. + */ +public 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. + */ + private String mTargetCharset; + + private boolean mStrictLineBreakParsing; + + public VNodeBuilder() { + this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false); + } + + public VNodeBuilder(String charset, boolean strictLineBreakParsing) { + this(null, charset, strictLineBreakParsing); + } + + /** + * @hide sourceCharset is temporal. + */ + public VNodeBuilder(String sourceCharset, String targetCharset, + boolean strictLineBreakParsing) { + if (sourceCharset != null) { + mSourceCharset = sourceCharset; + } else { + mSourceCharset = VCardConfig.DEFAULT_CHARSET; + } + if (targetCharset != null) { + mTargetCharset = targetCharset; + } else { + mTargetCharset = TARGET_CHARSET; + } + mStrictLineBreakParsing = strictLineBreakParsing; + } + + public void start() { + } + + public void end() { + } + + // Note: I guess that this code assumes the Record may nest like this: + // START:VPOS + // ... + // START:VPOS2 + // ... + // END:VPOS2 + // ... + // END:VPOS + // + // However the following code has a bug. + // When error occurs after calling startRecord(), the entry which is probably + // the cause of the error remains to be in vNodeList, while endRecord() is not called. + // + // I leave this code as is since I'm not familiar with vcalendar specification. + // But I believe we should refactor this code in the future. + // Until this, the last entry has to be removed when some error occurs. + public void startEntry() { + VNode vnode = new VNode(); + vnode.parseStatus = 1; + vnode.VName = "VCARD"; + // I feel this should be done in endRecord(), but it cannot be done because of + // the reason above. + vNodeList.add(vnode); + mNodeListPos = vNodeList.size() - 1; + mCurrentVNode = vNodeList.get(mNodeListPos); + } + + public void endEntry() { + VNode endNode = vNodeList.get(mNodeListPos); + endNode.parseStatus = 0; + while(mNodeListPos > 0){ + mNodeListPos--; + if((vNodeList.get(mNodeListPos)).parseStatus == 1) + break; + } + mCurrentVNode = vNodeList.get(mNodeListPos); + } + + public void startProperty() { + mCurrentPropNode = new PropertyNode(); + } + + public void endProperty() { + mCurrentVNode.propList.add(mCurrentPropNode); + } + + public void propertyName(String name) { + mCurrentPropNode.propName = name; + } + + // Used only in VCard. + public void propertyGroup(String group) { + mCurrentPropNode.propGroupSet.add(group); + } + + public void propertyParamType(String type) { + mCurrentParamType = type; + } + + public void propertyParamValue(String value) { + if (mCurrentParamType == null || + mCurrentParamType.equalsIgnoreCase("TYPE")) { + mCurrentPropNode.paramMap_TYPE.add(value); + } else { + mCurrentPropNode.paramMap.put(mCurrentParamType, value); + } + + mCurrentParamType = null; + } + + private String encodeString(String originalString, String targetCharset) { + if (mSourceCharset.equalsIgnoreCase(targetCharset)) { + return originalString; + } + Charset charset = Charset.forName(mSourceCharset); + 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()]; + byteBuffer.get(bytes); + try { + return new String(bytes, targetCharset); + } catch (UnsupportedEncodingException e) { + Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); + return null; + } + } + + private String handleOneValue(String value, String targetCharset, String encoding) { + if (encoding != null) { + encoding = encoding.toUpperCase(); + if (encoding.equals("BASE64") || encoding.equals("B")) { + // Assume BASE64 is used only when the number of values is 1. + mCurrentPropNode.propValue_bytes = + 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); + } + } + // Unknown encoding. Fall back to default. + } + return encodeString(value, targetCharset); + } + + public void propertyValues(List<String> values) { + if (values == null || values.size() == 0) { + mCurrentPropNode.propValue_bytes = null; + mCurrentPropNode.propValue_vector.clear(); + mCurrentPropNode.propValue_vector.add(""); + mCurrentPropNode.propValue = ""; + return; + } + + ContentValues paramMap = mCurrentPropNode.paramMap; + + String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET")); + String encoding = paramMap.getAsString("ENCODING"); + + if (targetCharset == null || targetCharset.length() == 0) { + targetCharset = mTargetCharset; + } + + for (String value : values) { + mCurrentPropNode.propValue_vector.add( + handleOneValue(value, targetCharset, encoding)); + } + + mCurrentPropNode.propValue = listToString(mCurrentPropNode.propValue_vector); + } + + private String listToString(List<String> list){ + int size = list.size(); + if (size > 1) { + StringBuilder typeListB = new StringBuilder(); + for (String type : list) { + typeListB.append(type).append(";"); + } + int len = typeListB.length(); + if (len > 0 && typeListB.charAt(len - 1) == ';') { + return typeListB.substring(0, len - 1); + } + return typeListB.toString(); + } else if (size == 1) { + return list.get(0); + } else { + return ""; + } + } + + public String getResult(){ + return null; + } +} diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java new file mode 100644 index 0000000..f82d79a --- /dev/null +++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java @@ -0,0 +1,147 @@ +/* + * 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. + */ + +package android.provider; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +/** Unit test for SettingsProvider. */ +public class SettingsProviderTest extends AndroidTestCase { + @MediumTest + public void testNameValueCache() { + ContentResolver r = getContext().getContentResolver(); + Settings.Secure.putString(r, "test_service", "Value"); + assertEquals("Value", Settings.Secure.getString(r, "test_service")); + + // Make sure the value can be overwritten. + Settings.Secure.putString(r, "test_service", "New"); + assertEquals("New", Settings.Secure.getString(r, "test_service")); + + // Also that delete works. + assertEquals(1, r.delete(Settings.Secure.getUriFor("test_service"), null, null)); + assertEquals(null, Settings.Secure.getString(r, "test_service")); + + // Try all the same things in the System table + Settings.System.putString(r, "test_setting", "Value"); + assertEquals("Value", Settings.System.getString(r, "test_setting")); + + Settings.System.putString(r, "test_setting", "New"); + assertEquals("New", Settings.System.getString(r, "test_setting")); + + assertEquals(1, r.delete(Settings.System.getUriFor("test_setting"), null, null)); + assertEquals(null, Settings.System.getString(r, "test_setting")); + } + + @MediumTest + public void testRowNameContentUri() { + ContentResolver r = getContext().getContentResolver(); + + assertEquals("content://settings/system/test_setting", + Settings.System.getUriFor("test_setting").toString()); + assertEquals("content://settings/gservices/test_service", + Settings.Secure.getUriFor("test_service").toString()); + + // These tables use the row name (not ID) as their content URI. + Uri tables[] = { Settings.System.CONTENT_URI, Settings.Secure.CONTENT_URI }; + for (Uri table : tables) { + ContentValues v = new ContentValues(); + v.put(Settings.System.NAME, "test_key"); + v.put(Settings.System.VALUE, "Test"); + Uri uri = r.insert(table, v); + assertEquals(table.toString() + "/test_key", uri.toString()); + + // Query with a specific URI and no WHERE clause succeeds. + Cursor c = r.query(uri, null, null, null, null); + try { + assertTrue(c.moveToNext()); + assertEquals("test_key", c.getString(c.getColumnIndex(Settings.System.NAME))); + assertEquals("Test", c.getString(c.getColumnIndex(Settings.System.VALUE))); + assertFalse(c.moveToNext()); + } finally { + c.close(); + } + + // Query with a specific URI and a WHERE clause fails. + try { + r.query(uri, null, "1", null, null); + fail("UnsupportedOperationException expected"); + } catch (UnsupportedOperationException e) { + if (!e.toString().contains("WHERE clause")) throw e; + } + + // Query with a tablewide URI and a WHERE clause succeeds. + c = r.query(table, null, "name='test_key'", null, null); + try { + assertTrue(c.moveToNext()); + assertEquals("test_key", c.getString(c.getColumnIndex(Settings.System.NAME))); + assertEquals("Test", c.getString(c.getColumnIndex(Settings.System.VALUE))); + assertFalse(c.moveToNext()); + } finally { + c.close(); + } + + v = new ContentValues(); + v.put(Settings.System.VALUE, "Toast"); + assertEquals(1, r.update(uri, v, null, null)); + + c = r.query(uri, null, null, null, null); + try { + assertTrue(c.moveToNext()); + assertEquals("test_key", c.getString(c.getColumnIndex(Settings.System.NAME))); + assertEquals("Toast", c.getString(c.getColumnIndex(Settings.System.VALUE))); + assertFalse(c.moveToNext()); + } finally { + c.close(); + } + + assertEquals(1, r.delete(uri, null, null)); + } + + assertEquals(null, Settings.System.getString(r, "test_key")); + assertEquals(null, Settings.Secure.getString(r, "test_key")); + } + + @MediumTest + public void testRowNumberContentUri() { + ContentResolver r = getContext().getContentResolver(); + + // The bookmarks table (and everything else) uses standard row number content URIs. + Uri uri = Settings.Bookmarks.add(r, new Intent("TEST"), + "Test Title", "Test Folder", '*', 123); + + assertTrue(ContentUris.parseId(uri) > 0); + + assertEquals("TEST", Settings.Bookmarks.getIntentForShortcut(r, '*').getAction()); + + ContentValues v = new ContentValues(); + v.put(Settings.Bookmarks.INTENT, "#Intent;action=TOAST;end"); + assertEquals(1, r.update(uri, v, null, null)); + + assertEquals("TOAST", Settings.Bookmarks.getIntentForShortcut(r, '*').getAction()); + + assertEquals(1, r.delete(uri, null, null)); + + assertEquals(null, Settings.Bookmarks.getIntentForShortcut(r, '*')); + } +} diff --git a/core/tests/coretests/src/android/provider/SmsProviderTest.java b/core/tests/coretests/src/android/provider/SmsProviderTest.java new file mode 100644 index 0000000..c8ed728 --- /dev/null +++ b/core/tests/coretests/src/android/provider/SmsProviderTest.java @@ -0,0 +1,76 @@ +/* + * 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.provider; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.provider.Telephony.Sms; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +import java.util.GregorianCalendar; + +public class SmsProviderTest extends AndroidTestCase { + + @LargeTest + public void testProvider() throws Exception { + // This test does the following + // 1. Insert 10 messages from the same number at different times. + // + // . Delete the messages and make sure that they were deleted. + + long now = System.currentTimeMillis(); + + Uri[] urls = new Uri[10]; + String[] dates = new String[]{ + Long.toString(new GregorianCalendar(1970, 1, 1, 0, 0, 0).getTimeInMillis()), + Long.toString(new GregorianCalendar(1971, 2, 13, 16, 35, 3).getTimeInMillis()), + Long.toString(new GregorianCalendar(1978, 10, 22, 0, 1, 0).getTimeInMillis()), + Long.toString(new GregorianCalendar(1980, 1, 11, 10, 22, 30).getTimeInMillis()), + Long.toString(now - (5 * 24 * 60 * 60 * 1000)), + Long.toString(now - (2 * 24 * 60 * 60 * 1000)), + Long.toString(now - (5 * 60 * 60 * 1000)), + Long.toString(now - (30 * 60 * 1000)), + Long.toString(now - (5 * 60 * 1000)), + Long.toString(now) + }; + + ContentValues map = new ContentValues(); + map.put("address", "+15045551337"); + map.put("read", 0); + + ContentResolver contentResolver = mContext.getContentResolver(); + + for (int i = 0; i < urls.length; i++) { + map.put("body", "Test " + i + " !"); + map.put("date", dates[i]); + urls[i] = contentResolver.insert(Sms.Inbox.CONTENT_URI, map); + assertNotNull(urls[i]); + } + + Cursor c = contentResolver.query(Sms.Inbox.CONTENT_URI, null, null, null, "date"); + + //DatabaseUtils.dumpCursor(c); + + for (Uri url : urls) { + int count = contentResolver.delete(url, null, null); + assertEquals(1, count); + } + } +} diff --git a/core/tests/coretests/src/android/text/SpannedTest.java b/core/tests/coretests/src/android/text/SpannedTest.java new file mode 100644 index 0000000..1c22cf9 --- /dev/null +++ b/core/tests/coretests/src/android/text/SpannedTest.java @@ -0,0 +1,187 @@ +/* + * 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. + */ + +package android.text; + +import android.graphics.Typeface; +import android.os.Parcel; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.*; +import android.text.style.*; +import android.util.Log; + +import junit.framework.TestCase; + +/** + * SpannedTest tests some features of Spanned + */ +public class SpannedTest extends TestCase { + private int mExpect; + + @SmallTest + public void testSpannableString() throws Exception { + checkPriority(new SpannableString("the quick brown fox")); + } + + @SmallTest + public void testSpannableStringBuilder() throws Exception { + checkPriority2(new SpannableStringBuilder("the quick brown fox")); + } + + @SmallTest + public void testAppend() throws Exception { + Object o = new Object(); + SpannableString ss = new SpannableString("Test"); + ss.setSpan(o, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append(ss); + assertEquals(0, ssb.getSpanStart(o)); + assertEquals(4, ssb.getSpanEnd(o)); + assertEquals(1, ssb.getSpans(0, 4, Object.class).length); + + ssb.insert(0, ss); + assertEquals(4, ssb.getSpanStart(o)); + assertEquals(8, ssb.getSpanEnd(o)); + assertEquals(0, ssb.getSpans(0, 4, Object.class).length); + assertEquals(1, ssb.getSpans(4, 8, Object.class).length); + } + + @SmallTest + public void testWrapParcel() { + SpannableString s = new SpannableString("Hello there world"); + CharacterStyle mark = new StyleSpan(Typeface.BOLD); + s.setSpan(mark, 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + s.setSpan(CharacterStyle.wrap(mark), 3, 7, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + s.setSpan(new TextAppearanceSpan("mono", 0, -1, null, null), 7, 8, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + s.setSpan(CharacterStyle.wrap(new TypefaceSpan("mono")), 8, 9, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + Parcel p = Parcel.obtain(); + TextUtils.writeToParcel(s, p, 0); + p.setDataPosition(0); + + Spanned s2 = (Spanned) TextUtils.CHAR_SEQUENCE_CREATOR. + createFromParcel(p); + StyleSpan[] style; + + style = s2.getSpans(1, 2, StyleSpan.class); + assertEquals(1, style.length); + assertEquals(1, s2.getSpanStart(style[0])); + assertEquals(2, s2.getSpanEnd(style[0])); + + style = s2.getSpans(3, 7, StyleSpan.class); + assertEquals(1, style.length); + assertEquals(3, s2.getSpanStart(style[0])); + assertEquals(7, s2.getSpanEnd(style[0])); + + TextAppearanceSpan[] appearance = s2.getSpans(7, 8, + TextAppearanceSpan.class); + assertEquals(1, appearance.length); + assertEquals(7, s2.getSpanStart(appearance[0])); + assertEquals(8, s2.getSpanEnd(appearance[0])); + + TypefaceSpan[] tf = s2.getSpans(8, 9, TypefaceSpan.class); + assertEquals(1, tf.length); + assertEquals(8, s2.getSpanStart(tf[0])); + assertEquals(9, s2.getSpanEnd(tf[0])); + } + + private void checkPriority(Spannable s) { + s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | + (5 << Spannable.SPAN_PRIORITY_SHIFT)); + s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | + (10 << Spannable.SPAN_PRIORITY_SHIFT)); + s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | + (0 << Spannable.SPAN_PRIORITY_SHIFT)); + s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | + (15 << Spannable.SPAN_PRIORITY_SHIFT)); + s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | + (3 << Spannable.SPAN_PRIORITY_SHIFT)); + s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | + (6 << Spannable.SPAN_PRIORITY_SHIFT)); + s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | + (0 << Spannable.SPAN_PRIORITY_SHIFT)); + + Object[] spans = s.getSpans(0, s.length(), Object.class); + + for (int i = 0; i < spans.length - 1; i++) { + assertEquals((s.getSpanFlags(spans[i]) & Spanned.SPAN_PRIORITY) >= + (s.getSpanFlags(spans[i + 1]) & Spanned.SPAN_PRIORITY), + true); + } + + mExpect = 0; + + s.setSpan(new Watcher(2), 0, s.length(), + Spannable.SPAN_INCLUSIVE_INCLUSIVE | + (2 << Spannable.SPAN_PRIORITY_SHIFT)); + s.setSpan(new Watcher(4), 0, s.length(), + Spannable.SPAN_INCLUSIVE_INCLUSIVE | + (4 << Spannable.SPAN_PRIORITY_SHIFT)); + s.setSpan(new Watcher(1), 0, s.length(), + Spannable.SPAN_INCLUSIVE_INCLUSIVE | + (1 << Spannable.SPAN_PRIORITY_SHIFT)); + s.setSpan(new Watcher(3), 0, s.length(), + Spannable.SPAN_INCLUSIVE_INCLUSIVE | + (3 << Spannable.SPAN_PRIORITY_SHIFT)); + + mExpect = 4; + s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + assertEquals(mExpect, 0); + } + + private void checkPriority2(SpannableStringBuilder ssb) { + checkPriority(ssb); + + mExpect = 4; + ssb.insert(3, "something"); + assertEquals(mExpect, 0); + } + + private class Watcher implements SpanWatcher, TextWatcher { + private int mSequence; + + public Watcher(int sequence) { + mSequence = sequence; + } + + public void onSpanChanged(Spannable b, Object o, int s, int e, + int st, int en) { } + public void onSpanRemoved(Spannable b, Object o, int s, int e) { } + + public void onSpanAdded(Spannable b, Object o, int s, int e) { + if (mExpect != 0) { + assertEquals(mSequence, mExpect); + mExpect = mSequence - 1; + } + } + + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { } + public void onTextChanged(CharSequence s, int start, int before, + int count) { + if (mExpect != 0) { + assertEquals(mSequence, mExpect); + mExpect = mSequence - 1; + } + } + + public void afterTextChanged(Editable s) { } + } +} diff --git a/core/tests/coretests/src/android/text/TextLayoutTest.java b/core/tests/coretests/src/android/text/TextLayoutTest.java new file mode 100644 index 0000000..6cf3000 --- /dev/null +++ b/core/tests/coretests/src/android/text/TextLayoutTest.java @@ -0,0 +1,51 @@ +/* + * 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.test.suitebuilder.annotation.SmallTest; +import android.text.DynamicLayout; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import junit.framework.TestCase; + + +public class TextLayoutTest extends TestCase { + + protected String mString; + protected TextPaint mPaint; + + protected void setUp() throws Exception { + super.setUp(); + mString = "The quick brown fox"; + mPaint = new TextPaint(); + } + + @SmallTest + public void testStaticLayout() throws Exception { + Layout l = new StaticLayout(mString, mPaint, 200, + Layout.Alignment.ALIGN_NORMAL, 1, 0, + true); + } + + @SmallTest + public void testDynamicLayoutTest() throws Exception { + Layout l = new DynamicLayout(mString, mPaint, 200, + Layout.Alignment.ALIGN_NORMAL, 1, 0, + true); + } +} diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java new file mode 100644 index 0000000..5b427be --- /dev/null +++ b/core/tests/coretests/src/android/text/TextUtilsTest.java @@ -0,0 +1,359 @@ +/* + * 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. + */ + +package android.text; + +import android.graphics.Paint; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.SpannedString; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.StyleSpan; +import android.test.MoreAsserts; + +import com.android.common.Rfc822Validator; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + +import junit.framework.TestCase; + +import java.util.List; +import java.util.Map; + +/** + * TextUtilsTest tests {@link TextUtils}. + */ +public class TextUtilsTest extends TestCase { + + @SmallTest + public void testBasic() throws Exception { + assertEquals("", TextUtils.concat()); + assertEquals("foo", TextUtils.concat("foo")); + assertEquals("foobar", TextUtils.concat("foo", "bar")); + assertEquals("foobarbaz", TextUtils.concat("foo", "bar", "baz")); + + SpannableString foo = new SpannableString("foo"); + foo.setSpan("foo", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + + SpannableString bar = new SpannableString("bar"); + bar.setSpan("bar", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + + SpannableString baz = new SpannableString("baz"); + baz.setSpan("baz", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + + assertEquals("foo", TextUtils.concat(foo).toString()); + assertEquals("foobar", TextUtils.concat(foo, bar).toString()); + assertEquals("foobarbaz", TextUtils.concat(foo, bar, baz).toString()); + + assertEquals(1, ((Spanned) TextUtils.concat(foo)).getSpanStart("foo")); + + assertEquals(1, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("foo")); + assertEquals(4, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("bar")); + + assertEquals(1, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("foo")); + assertEquals(4, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("bar")); + assertEquals(7, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("baz")); + + assertTrue(TextUtils.concat("foo", "bar") instanceof String); + assertTrue(TextUtils.concat(foo, bar) instanceof SpannedString); + } + + @SmallTest + public void testTemplateString() throws Exception { + CharSequence result; + + result = TextUtils.expandTemplate("This is a ^1 of the ^2 broadcast ^3.", + "test", "emergency", "system"); + assertEquals("This is a test of the emergency broadcast system.", + result.toString()); + + result = TextUtils.expandTemplate("^^^1^^^2^3^a^1^^b^^^c", + "one", "two", "three"); + assertEquals("^one^twothree^aone^b^^c", + result.toString()); + + result = TextUtils.expandTemplate("^"); + assertEquals("^", result.toString()); + + result = TextUtils.expandTemplate("^^"); + assertEquals("^", result.toString()); + + result = TextUtils.expandTemplate("^^^"); + assertEquals("^^", result.toString()); + + result = TextUtils.expandTemplate("shorter ^1 values ^2.", "a", ""); + assertEquals("shorter a values .", result.toString()); + + try { + TextUtils.expandTemplate("Only ^1 value given, but ^2 used.", "foo"); + fail(); + } catch (IllegalArgumentException e) { + } + + try { + TextUtils.expandTemplate("^1 value given, and ^0 used.", "foo"); + fail(); + } catch (IllegalArgumentException e) { + } + + result = TextUtils.expandTemplate("^1 value given, and ^9 used.", + "one", "two", "three", "four", "five", + "six", "seven", "eight", "nine"); + assertEquals("one value given, and nine used.", result.toString()); + + try { + TextUtils.expandTemplate("^1 value given, and ^10 used.", + "one", "two", "three", "four", "five", + "six", "seven", "eight", "nine", "ten"); + fail(); + } catch (IllegalArgumentException e) { + } + + // putting carets in the values: expansion is not recursive. + + result = TextUtils.expandTemplate("^2", "foo", "^^"); + assertEquals("^^", result.toString()); + + result = TextUtils.expandTemplate("^^2", "foo", "1"); + assertEquals("^2", result.toString()); + + result = TextUtils.expandTemplate("^1", "value with ^2 in it", "foo"); + assertEquals("value with ^2 in it", result.toString()); + } + + /** Fail unless text+spans contains a span 'spanName' with the given start and end. */ + private void checkContains(Spanned text, String[] spans, String spanName, + int start, int end) throws Exception { + for (String i: spans) { + if (i.equals(spanName)) { + assertEquals(start, text.getSpanStart(i)); + assertEquals(end, text.getSpanEnd(i)); + return; + } + } + fail(); + } + + @SmallTest + public void testTemplateSpan() throws Exception { + SpannableString template; + Spanned result; + String[] spans; + + // ordinary replacement + + template = new SpannableString("a^1b"); + template.setSpan("before", 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + template.setSpan("during", 1, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + template.setSpan("after", 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + template.setSpan("during+after", 1, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + result = (Spanned) TextUtils.expandTemplate(template, "foo"); + assertEquals(5, result.length()); + spans = result.getSpans(0, result.length(), String.class); + + // value is one character longer, so span endpoints should change. + assertEquals(4, spans.length); + checkContains(result, spans, "before", 0, 1); + checkContains(result, spans, "during", 1, 4); + checkContains(result, spans, "after", 4, 5); + checkContains(result, spans, "during+after", 1, 5); + + + // replacement with empty string + + result = (Spanned) TextUtils.expandTemplate(template, ""); + assertEquals(2, result.length()); + spans = result.getSpans(0, result.length(), String.class); + + // the "during" span should disappear. + assertEquals(3, spans.length); + checkContains(result, spans, "before", 0, 1); + checkContains(result, spans, "after", 1, 2); + checkContains(result, spans, "during+after", 1, 2); + } + + @SmallTest + public void testStringSplitterSimple() { + stringSplitterTestHelper("a,b,cde", new String[] {"a", "b", "cde"}); + } + + @SmallTest + public void testStringSplitterEmpty() { + stringSplitterTestHelper("", new String[] {}); + } + + @SmallTest + public void testStringSplitterWithLeadingEmptyString() { + stringSplitterTestHelper(",a,b,cde", new String[] {"", "a", "b", "cde"}); + } + + @SmallTest + public void testStringSplitterWithInternalEmptyString() { + stringSplitterTestHelper("a,b,,cde", new String[] {"a", "b", "", "cde"}); + } + + @SmallTest + public void testStringSplitterWithTrailingEmptyString() { + // A single trailing emtpy string should be ignored. + stringSplitterTestHelper("a,b,cde,", new String[] {"a", "b", "cde"}); + } + + private void stringSplitterTestHelper(String string, String[] expectedStrings) { + TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); + splitter.setString(string); + List<String> strings = Lists.newArrayList(); + for (String s : splitter) { + strings.add(s); + } + MoreAsserts.assertEquals(expectedStrings, strings.toArray(new String[]{})); + } + + @SmallTest + public void testTrim() { + String[] strings = { "abc", " abc", " abc", "abc ", "abc ", + " abc ", " abc ", "\nabc\n", "\nabc", "abc\n" }; + + for (String s : strings) { + assertEquals(s.trim().length(), TextUtils.getTrimmedLength(s)); + } + } + + //============================================================================================== + // Email validator + //============================================================================================== + + @SmallTest + public void testEmailValidator() { + Rfc822Validator validator = new Rfc822Validator("gmail.com"); + String[] validEmails = new String[] { + "a@b.com", "a@b.fr", "a+b@c.com", "a@b.info", + }; + + for (String email : validEmails) { + assertTrue(email + " should be a valid email address", validator.isValid(email)); + } + + String[] invalidEmails = new String[] { + "a", "a@b", "a b", "a@b.12" + }; + + for (String email : invalidEmails) { + assertFalse(email + " should not be a valid email address", validator.isValid(email)); + } + + Map<String, String> fixes = Maps.newHashMap(); + fixes.put("a", "<a@gmail.com>"); + fixes.put("a b", "<ab@gmail.com>"); + fixes.put("a@b", "<a@b>"); + + for (Map.Entry<String, String> e : fixes.entrySet()) { + assertEquals(e.getValue(), validator.fixText(e.getKey()).toString()); + } + } + + @LargeTest + public void testEllipsize() { + CharSequence s1 = "The quick brown fox jumps over \u00FEhe lazy dog."; + CharSequence s2 = new Wrapper(s1); + Spannable s3 = new SpannableString(s1); + s3.setSpan(new StyleSpan(0), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + TextPaint p = new TextPaint(); + p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG); + + for (int i = 0; i < 100; i++) { + for (int j = 0; j < 3; j++) { + TextUtils.TruncateAt kind = null; + + switch (j) { + case 0: + kind = TextUtils.TruncateAt.START; + break; + + case 1: + kind = TextUtils.TruncateAt.END; + break; + + case 2: + kind = TextUtils.TruncateAt.MIDDLE; + break; + } + + String out1 = TextUtils.ellipsize(s1, p, i, kind).toString(); + String out2 = TextUtils.ellipsize(s2, p, i, kind).toString(); + String out3 = TextUtils.ellipsize(s3, p, i, kind).toString(); + + String keep1 = TextUtils.ellipsize(s1, p, i, kind, true, null).toString(); + String keep2 = TextUtils.ellipsize(s2, p, i, kind, true, null).toString(); + String keep3 = TextUtils.ellipsize(s3, p, i, kind, true, null).toString(); + + String trim1 = keep1.replace("\uFEFF", ""); + + // Are all normal output strings identical? + assertEquals("wid " + i + " pass " + j, out1, out2); + assertEquals("wid " + i + " pass " + j, out2, out3); + + // Are preserved output strings identical? + assertEquals("wid " + i + " pass " + j, keep1, keep2); + assertEquals("wid " + i + " pass " + j, keep2, keep3); + + // Does trimming padding from preserved yield normal? + assertEquals("wid " + i + " pass " + j, out1, trim1); + + // Did preserved output strings preserve length? + assertEquals("wid " + i + " pass " + j, keep1.length(), s1.length()); + + // Does the output string actually fit in the space? + assertTrue("wid " + i + " pass " + j, p.measureText(out1) <= i); + + // Is the padded output the same width as trimmed output? + assertTrue("wid " + i + " pass " + j, p.measureText(keep1) == p.measureText(out1)); + } + } + } + + /** + * CharSequence wrapper for testing the cases where text is copied into + * a char array instead of working from a String or a Spanned. + */ + private static class Wrapper implements CharSequence { + private CharSequence mString; + + public Wrapper(CharSequence s) { + mString = s; + } + + public int length() { + return mString.length(); + } + + public char charAt(int off) { + return mString.charAt(off); + } + + public String toString() { + return mString.toString(); + } + + public CharSequence subSequence(int start, int end) { + return new Wrapper(mString.subSequence(start, end)); + } + } +} diff --git a/core/tests/coretests/src/android/text/format/TimeTest.java b/core/tests/coretests/src/android/text/format/TimeTest.java new file mode 100644 index 0000000..489f58b --- /dev/null +++ b/core/tests/coretests/src/android/text/format/TimeTest.java @@ -0,0 +1,608 @@ +/* + * 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.format; + +import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; +import android.text.format.Time; +import android.util.Log; +import android.util.TimeFormatException; + +import junit.framework.TestCase; + +public class TimeTest extends TestCase { + + @SmallTest + public void testNormalize0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + t.parse("20060432T010203"); + t.normalize(false /* use isDst */); +// System.out.println("got: " + t.year + '-' +// + t.month + '-' + t.monthDay +// + ' ' + t.hour + ':' + t.minute +// + ':' + t.second +// + "( " + t.isDst + ',' + t.gmtoff +// + ',' + t.weekDay +// + ',' + t.yearDay + ')'); + } + + private static class DateTest { + public int year1; + public int month1; + public int day1; + public int hour1; + public int minute1; + public int dst1; + + public int offset; + + public int year2; + public int month2; + public int day2; + public int hour2; + public int minute2; + public int dst2; + + public DateTest(int year1, int month1, int day1, int hour1, int minute1, int dst1, + int offset, int year2, int month2, int day2, int hour2, int minute2, + int dst2) { + this.year1 = year1; + this.month1 = month1; + this.day1 = day1; + this.hour1 = hour1; + this.minute1 = minute1; + this.dst1 = dst1; + this.offset = offset; + this.year2 = year2; + this.month2 = month2; + this.day2 = day2; + this.hour2 = hour2; + this.minute2 = minute2; + this.dst2 = dst2; + } + + public DateTest(int year1, int month1, int day1, int hour1, int minute1, + int offset, int year2, int month2, int day2, int hour2, int minute2) { + this.year1 = year1; + this.month1 = month1; + this.day1 = day1; + this.hour1 = hour1; + this.minute1 = minute1; + this.dst1 = -1; + this.offset = offset; + this.year2 = year2; + this.month2 = month2; + this.day2 = day2; + this.hour2 = hour2; + this.minute2 = minute2; + this.dst2 = -1; + } + } + + // These tests assume that DST changes on Nov 4, 2007 at 2am (to 1am). + + // The "offset" field in "dayTests" represents days. + // Use normalize(true) with these tests to change the date by 1 day. + private DateTest[] dayTests = { + // The month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11 + + // Nov 4, 12am + 0 day = Nov 4, 12am + // Nov 5, 12am + 0 day = Nov 5, 12am + new DateTest(2007, 10, 4, 0, 0, 0, 2007, 10, 4, 0, 0), + new DateTest(2007, 10, 5, 0, 0, 0, 2007, 10, 5, 0, 0), + + // Nov 3, 12am + 1 day = Nov 4, 12am + // Nov 4, 12am + 1 day = Nov 5, 12am + // Nov 5, 12am + 1 day = Nov 6, 12am + new DateTest(2007, 10, 3, 0, 0, 1, 2007, 10, 4, 0, 0), + new DateTest(2007, 10, 4, 0, 0, 1, 2007, 10, 5, 0, 0), + new DateTest(2007, 10, 5, 0, 0, 1, 2007, 10, 6, 0, 0), + + // Nov 3, 1am + 1 day = Nov 4, 1am + // Nov 4, 1am + 1 day = Nov 5, 1am + // Nov 5, 1am + 1 day = Nov 6, 1am + new DateTest(2007, 10, 3, 1, 0, 1, 2007, 10, 4, 1, 0), + new DateTest(2007, 10, 4, 1, 0, 1, 2007, 10, 5, 1, 0), + new DateTest(2007, 10, 5, 1, 0, 1, 2007, 10, 6, 1, 0), + + // Nov 3, 2am + 1 day = Nov 4, 2am + // Nov 4, 2am + 1 day = Nov 5, 2am + // Nov 5, 2am + 1 day = Nov 6, 2am + new DateTest(2007, 10, 3, 2, 0, 1, 2007, 10, 4, 2, 0), + new DateTest(2007, 10, 4, 2, 0, 1, 2007, 10, 5, 2, 0), + new DateTest(2007, 10, 5, 2, 0, 1, 2007, 10, 6, 2, 0), + }; + + // The "offset" field in "minuteTests" represents minutes. + // Use normalize(false) with these tests. + private DateTest[] minuteTests = { + // The month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11 + + // Nov 4, 12am + 0 minutes = Nov 4, 12am + // Nov 5, 12am + 0 minutes = Nov 5, 12am + new DateTest(2007, 10, 4, 0, 0, 0, 2007, 10, 4, 0, 0), + new DateTest(2007, 10, 5, 0, 0, 0, 2007, 10, 5, 0, 0), + + // Nov 3, 12am + 60 minutes = Nov 3, 1am + // Nov 4, 12am + 60 minutes = Nov 4, 1am + // Nov 5, 12am + 60 minutes = Nov 5, 1am + new DateTest(2007, 10, 3, 0, 0, 60, 2007, 10, 3, 1, 0), + new DateTest(2007, 10, 4, 0, 0, 60, 2007, 10, 4, 1, 0), + new DateTest(2007, 10, 5, 0, 0, 60, 2007, 10, 5, 1, 0), + + // Nov 3, 1am + 60 minutes = Nov 3, 2am + // Nov 4, 1am (PDT) + 30 minutes = Nov 4, 1:30am (PDT) + // Nov 4, 1am (PDT) + 60 minutes = Nov 4, 1am (PST) + new DateTest(2007, 10, 3, 1, 0, 60, 2007, 10, 3, 2, 0), + new DateTest(2007, 10, 4, 1, 0, 1, 30, 2007, 10, 4, 1, 30, 1), + new DateTest(2007, 10, 4, 1, 0, 1, 60, 2007, 10, 4, 1, 0, 0), + + // Nov 4, 1:30am (PDT) + 15 minutes = Nov 4, 1:45am (PDT) + // Nov 4, 1:30am (PDT) + 30 minutes = Nov 4, 1:00am (PST) + // Nov 4, 1:30am (PDT) + 60 minutes = Nov 4, 1:30am (PST) + new DateTest(2007, 10, 4, 1, 30, 1, 15, 2007, 10, 4, 1, 45, 1), + new DateTest(2007, 10, 4, 1, 30, 1, 30, 2007, 10, 4, 1, 0, 0), + new DateTest(2007, 10, 4, 1, 30, 1, 60, 2007, 10, 4, 1, 30, 0), + + // Nov 4, 1:30am (PST) + 15 minutes = Nov 4, 1:45am (PST) + // Nov 4, 1:30am (PST) + 30 minutes = Nov 4, 2:00am (PST) + // Nov 5, 1am + 60 minutes = Nov 5, 2am + new DateTest(2007, 10, 4, 1, 30, 0, 15, 2007, 10, 4, 1, 45, 0), + new DateTest(2007, 10, 4, 1, 30, 0, 30, 2007, 10, 4, 2, 0, 0), + new DateTest(2007, 10, 5, 1, 0, 60, 2007, 10, 5, 2, 0), + + // Nov 3, 2am + 60 minutes = Nov 3, 3am + // Nov 4, 2am + 30 minutes = Nov 4, 2:30am + // Nov 4, 2am + 60 minutes = Nov 4, 3am + // Nov 5, 2am + 60 minutes = Nov 5, 3am + new DateTest(2007, 10, 3, 2, 0, 60, 2007, 10, 3, 3, 0), + new DateTest(2007, 10, 4, 2, 0, 30, 2007, 10, 4, 2, 30), + new DateTest(2007, 10, 4, 2, 0, 60, 2007, 10, 4, 3, 0), + new DateTest(2007, 10, 5, 2, 0, 60, 2007, 10, 5, 3, 0), + }; + + @SmallTest + public void testNormalize1() throws Exception { + Time local = new Time("America/Los_Angeles"); + + int len = dayTests.length; + for (int index = 0; index < len; index++) { + DateTest test = dayTests[index]; + local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1); + // call normalize() to make sure that isDst is set + local.normalize(false /* use isDst */); + local.monthDay += test.offset; + local.normalize(true /* ignore isDst */); + if (local.year != test.year2 || local.month != test.month2 + || local.monthDay != test.day2 || local.hour != test.hour2 + || local.minute != test.minute2) { + String expectedTime = String.format("%d-%02d-%02d %02d:%02d", + test.year2, test.month2, test.day2, test.hour2, test.minute2); + String actualTime = String.format("%d-%02d-%02d %02d:%02d", + local.year, local.month, local.monthDay, local.hour, local.minute); + throw new RuntimeException( + "day test index " + index + ", normalize(): expected local " + expectedTime + + " got: " + actualTime); + } + + local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1); + // call normalize() to make sure that isDst is set + local.normalize(false /* use isDst */); + local.monthDay += test.offset; + long millis = local.toMillis(true /* ignore isDst */); + local.set(millis); + if (local.year != test.year2 || local.month != test.month2 + || local.monthDay != test.day2 || local.hour != test.hour2 + || local.minute != test.minute2) { + String expectedTime = String.format("%d-%02d-%02d %02d:%02d", + test.year2, test.month2, test.day2, test.hour2, test.minute2); + String actualTime = String.format("%d-%02d-%02d %02d:%02d", + local.year, local.month, local.monthDay, local.hour, local.minute); + throw new RuntimeException( + "day test index " + index + ", toMillis(): expected local " + expectedTime + + " got: " + actualTime); + } + } + + len = minuteTests.length; + for (int index = 0; index < len; index++) { + DateTest test = minuteTests[index]; + local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1); + local.isDst = test.dst1; + // call normalize() to make sure that isDst is set + local.normalize(false /* use isDst */); + if (test.dst2 == -1) test.dst2 = local.isDst; + local.minute += test.offset; + local.normalize(false /* use isDst */); + if (local.year != test.year2 || local.month != test.month2 + || local.monthDay != test.day2 || local.hour != test.hour2 + || local.minute != test.minute2 || local.isDst != test.dst2) { + String expectedTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d", + test.year2, test.month2, test.day2, test.hour2, test.minute2, + test.dst2); + String actualTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d", + local.year, local.month, local.monthDay, local.hour, local.minute, + local.isDst); + throw new RuntimeException( + "minute test index " + index + ", normalize(): expected local " + expectedTime + + " got: " + actualTime); + } + + local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1); + local.isDst = test.dst1; + // call normalize() to make sure that isDst is set + local.normalize(false /* use isDst */); + if (test.dst2 == -1) test.dst2 = local.isDst; + local.minute += test.offset; + long millis = local.toMillis(false /* use isDst */); + local.set(millis); + if (local.year != test.year2 || local.month != test.month2 + || local.monthDay != test.day2 || local.hour != test.hour2 + || local.minute != test.minute2 || local.isDst != test.dst2) { + String expectedTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d", + test.year2, test.month2, test.day2, test.hour2, test.minute2, + test.dst2); + String actualTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d", + local.year, local.month, local.monthDay, local.hour, local.minute, + local.isDst); + throw new RuntimeException( + "minute test index " + index + ", toMillis(): expected local " + expectedTime + + " got: " + actualTime); + } + } + } + + @SmallTest + public void testSwitchTimezone0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + t.parse("20061005T120000"); + t.switchTimezone("America/Los_Angeles"); + // System.out.println("got: " + t); + } + + @SmallTest + public void testCtor0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + assertEquals(Time.TIMEZONE_UTC, t.timezone); + } + + @SmallTest + public void testGetActualMaximum0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + int r = t.getActualMaximum(Time.SECOND); + // System.out.println("r=" + r); + } + + @SmallTest + public void testClear0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + t.clear(Time.TIMEZONE_UTC); + } + + @SmallTest + public void testCompare0() throws Exception { + Time a = new Time(Time.TIMEZONE_UTC); + Time b = new Time("America/Los_Angeles"); + int r = Time.compare(a, b); + // System.out.println("r=" + r); + } + + @SmallTest + public void testFormat0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + String r = t.format("%Y%m%dT%H%M%S"); + // System.out.println("r='" + r + "'"); + } + + @SmallTest + public void testToString0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + String r = t.toString(); + // System.out.println("r='" + r + "'"); + } + + @SmallTest + public void testGetCurrentTimezone0() throws Exception { + String r = Time.getCurrentTimezone(); + // System.out.println("r='" + r + "'"); + } + + @SmallTest + public void testSetToNow0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + t.setToNow(); + // System.out.println("t=" + t); + } + + @SmallTest + public void testMillis0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + t.set(0, 0, 0, 1, 1, 2006); + long r = t.toMillis(true /* ignore isDst */); + // System.out.println("r=" + r); + t.set(1, 0, 0, 1, 1, 2006); + r = t.toMillis(true /* ignore isDst */); + // System.out.println("r=" + r); + } + + @SmallTest + public void testMillis1() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + t.set(1, 0, 0, 1, 0, 1970); + long r = t.toMillis(true /* ignore isDst */); + // System.out.println("r=" + r); + } + + @SmallTest + public void testParse0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + t.parse("12345678T901234"); + // System.out.println("t=" + t); + } + + @SmallTest + public void testParse33390() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + + t.parse3339("1980-05-23"); + if (!t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23) { + fail("Did not parse all-day date correctly"); + } + + t.parse3339("1980-05-23T09:50:50"); + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || + t.hour != 9 || t.minute != 50 || t.second != 50 || + t.gmtoff != 0) { + fail("Did not parse timezone-offset-less date correctly"); + } + + t.parse3339("1980-05-23T09:50:50Z"); + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || + t.hour != 9 || t.minute != 50 || t.second != 50 || + t.gmtoff != 0) { + fail("Did not parse UTC date correctly"); + } + + t.parse3339("1980-05-23T09:50:50.0Z"); + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || + t.hour != 9 || t.minute != 50 || t.second != 50 || + t.gmtoff != 0) { + fail("Did not parse UTC date correctly"); + } + + t.parse3339("1980-05-23T09:50:50.12Z"); + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || + t.hour != 9 || t.minute != 50 || t.second != 50 || + t.gmtoff != 0) { + fail("Did not parse UTC date correctly"); + } + + t.parse3339("1980-05-23T09:50:50.123Z"); + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || + t.hour != 9 || t.minute != 50 || t.second != 50 || + t.gmtoff != 0) { + fail("Did not parse UTC date correctly"); + } + + // The time should be normalized to UTC + t.parse3339("1980-05-23T09:50:50-01:05"); + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || + t.hour != 10 || t.minute != 55 || t.second != 50 || + t.gmtoff != 0) { + fail("Did not parse timezone-offset date correctly"); + } + + // The time should be normalized to UTC + t.parse3339("1980-05-23T09:50:50.123-01:05"); + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || + t.hour != 10 || t.minute != 55 || t.second != 50 || + t.gmtoff != 0) { + fail("Did not parse timezone-offset date correctly"); + } + + try { + t.parse3339("1980"); + fail("Did not throw error on truncated input length"); + } catch (TimeFormatException e) { + // Successful + } + + try { + t.parse3339("1980-05-23T09:50:50.123+"); + fail("Did not throw error on truncated timezone offset"); + } catch (TimeFormatException e1) { + // Successful + } + + try { + t.parse3339("1980-05-23T09:50:50.123+05:0"); + fail("Did not throw error on truncated timezone offset"); + } catch (TimeFormatException e1) { + // Successful + } + } + + @SmallTest + public void testSet0() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + t.set(1000L); + // System.out.println("t.year=" + t.year); + // System.out.println("t=" + t); + t.set(2000L); + // System.out.println("t=" + t); + t.set(1000L * 60); + // System.out.println("t=" + t); + t.set((1000L * 60 * 60 * 24) + 1000L); + // System.out.println("t=" + t); + } + + @SmallTest + public void testSet1() throws Exception { + Time t = new Time(Time.TIMEZONE_UTC); + t.set(1, 2, 3, 4, 5, 6); + // System.out.println("t=" + t); + } + + // Timezones that cover the world. Some GMT offsets occur more than + // once in case some cities decide to change their GMT offset. + private static final String[] mTimeZones = { + "Pacific/Kiritimati", + "Pacific/Enderbury", + "Pacific/Fiji", + "Antarctica/South_Pole", + "Pacific/Norfolk", + "Pacific/Ponape", + "Asia/Magadan", + "Australia/Lord_Howe", + "Australia/Sydney", + "Australia/Adelaide", + "Asia/Tokyo", + "Asia/Seoul", + "Asia/Taipei", + "Asia/Singapore", + "Asia/Hong_Kong", + "Asia/Saigon", + "Asia/Bangkok", + "Indian/Cocos", + "Asia/Rangoon", + "Asia/Omsk", + "Antarctica/Mawson", + "Asia/Colombo", + "Asia/Calcutta", + "Asia/Oral", + "Asia/Kabul", + "Asia/Dubai", + "Asia/Tehran", + "Europe/Moscow", + "Asia/Baghdad", + "Africa/Mogadishu", + "Europe/Athens", + "Africa/Cairo", + "Europe/Rome", + "Europe/Berlin", + "Europe/Amsterdam", + "Africa/Tunis", + "Europe/London", + "Europe/Dublin", + "Atlantic/St_Helena", + "Africa/Monrovia", + "Africa/Accra", + "Atlantic/Azores", + "Atlantic/South_Georgia", + "America/Noronha", + "America/Sao_Paulo", + "America/Cayenne", + "America/St_Johns", + "America/Puerto_Rico", + "America/Aruba", + "America/New_York", + "America/Chicago", + "America/Denver", + "America/Los_Angeles", + "America/Anchorage", + "Pacific/Marquesas", + "America/Adak", + "Pacific/Honolulu", + "Pacific/Midway", + }; + + @Suppress + public void disableTestGetJulianDay() throws Exception { + Time time = new Time(); + + // For each day of the year, and for each timezone, get the Julian + // day for 12am and then check that if we change the time we get the + // same Julian day. + for (int monthDay = 1; monthDay <= 366; monthDay++) { + for (int zoneIndex = 0; zoneIndex < mTimeZones.length; zoneIndex++) { + // We leave the "month" as zero because we are changing the + // "monthDay" from 1 to 366. The call to normalize() will + // then change the "month" (but we don't really care). + time.set(0, 0, 0, monthDay, 0, 2008); + time.timezone = mTimeZones[zoneIndex]; + long millis = time.normalize(true); + if (zoneIndex == 0) { + Log.i("TimeTest", time.format("%B %d, %Y")); + } + + // This is the Julian day for 12am for this day of the year + int julianDay = Time.getJulianDay(millis, time.gmtoff); + + // Change the time during the day and check that we get the same + // Julian day. + for (int hour = 0; hour < 24; hour++) { + for (int minute = 0; minute < 60; minute += 15) { + time.set(0, minute, hour, monthDay, 0, 2008); + millis = time.normalize(true); + int day = Time.getJulianDay(millis, time.gmtoff); + if (day != julianDay) { + Log.e("TimeTest", "Julian day: " + day + " at time " + + time.hour + ":" + time.minute + + " != today's Julian day: " + julianDay + + " timezone: " + time.timezone); + } + assertEquals(day, julianDay); + } + } + } + } + } + + @Suppress + public void disableTestSetJulianDay() throws Exception { + Time time = new Time(); + + // For each day of the year in 2008, and for each timezone, + // test that we can set the Julian day correctly. + for (int monthDay = 1; monthDay <= 366; monthDay++) { + for (int zoneIndex = 0; zoneIndex < mTimeZones.length; zoneIndex++) { + // We leave the "month" as zero because we are changing the + // "monthDay" from 1 to 366. The call to normalize() will + // then change the "month" (but we don't really care). + time.set(0, 0, 0, monthDay, 0, 2008); + time.timezone = mTimeZones[zoneIndex]; + long millis = time.normalize(true); + if (zoneIndex == 0) { + Log.i("TimeTest", time.format("%B %d, %Y")); + } + int julianDay = Time.getJulianDay(millis, time.gmtoff); + + time.setJulianDay(julianDay); + + // Some places change daylight saving time at 12am and so there + // is no 12am on some days in some timezones. In those cases, + // the time is set to 1am. + // Examples: Africa/Cairo on April 25, 2008 + // America/Sao_Paulo on October 12, 2008 + // Atlantic/Azores on March 30, 2008 + assertTrue(time.hour == 0 || time.hour == 1); + assertEquals(0, time.minute); + assertEquals(0, time.second); + + millis = time.toMillis(false); + int day = Time.getJulianDay(millis, time.gmtoff); + if (day != julianDay) { + Log.i("TimeTest", "Error: gmtoff " + (time.gmtoff / 3600.0) + + " day " + julianDay + + " millis " + millis + + " " + time.format("%B %d, %Y") + " " + time.timezone); + } + assertEquals(day, julianDay); + } + } + } +} diff --git a/core/tests/coretests/src/android/util/LogTest.java b/core/tests/coretests/src/android/util/LogTest.java new file mode 100644 index 0000000..41947d7 --- /dev/null +++ b/core/tests/coretests/src/android/util/LogTest.java @@ -0,0 +1,152 @@ +package android.util; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import android.os.SystemProperties; +import android.test.PerformanceTestCase; +import android.test.suitebuilder.annotation.Suppress; +import android.util.Log; + +//This is an empty TestCase. +@Suppress +public class LogTest extends TestCase { + private static final String PROPERTY_TAG = "log.tag.LogTest"; + private static final String LOG_TAG = "LogTest"; + + + // TODO: remove this test once we uncomment out the following test. + public void testLogTestDummy() { + return; + } + + + /* TODO: This test is commented out because we will not be able to set properities. Fix the test. + public void testIsLoggable() { + // First clear any SystemProperty setting for our test key. + SystemProperties.set(PROPERTY_TAG, null); + + String value = SystemProperties.get(PROPERTY_TAG); + Assert.assertTrue(value == null || value.length() == 0); + + // Check to make sure that all levels expect for INFO, WARN, ERROR, and ASSERT are loggable. + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.INFO)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT)); + + // Set the log level to be VERBOSE for this tag. + SystemProperties.set(PROPERTY_TAG, "VERBOSE"); + + // Test to make sure all log levels >= VERBOSE are loggable. + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.VERBOSE)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.DEBUG)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.INFO)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT)); + + // Set the log level to be DEBUG for this tag. + SystemProperties.set(PROPERTY_TAG, "DEBUG"); + + // Test to make sure all log levels >= DEBUG are loggable. + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.DEBUG)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.INFO)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT)); + + // Set the log level to be INFO for this tag. + SystemProperties.set(PROPERTY_TAG, "INFO"); + + // Test to make sure all log levels >= INFO are loggable. + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.INFO)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT)); + + // Set the log level to be WARN for this tag. + SystemProperties.set(PROPERTY_TAG, "WARN"); + + // Test to make sure all log levels >= WARN are loggable. + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.INFO)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT)); + + // Set the log level to be ERROR for this tag. + SystemProperties.set(PROPERTY_TAG, "ERROR"); + + // Test to make sure all log levels >= ERROR are loggable. + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.INFO)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.WARN)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT)); + + // Set the log level to be ASSERT for this tag. + SystemProperties.set(PROPERTY_TAG, "ASSERT"); + + // Test to make sure all log levels >= ASSERT are loggable. + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.INFO)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.WARN)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.ERROR)); + Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT)); + + // Set the log level to be SUPPRESS for this tag. + SystemProperties.set(PROPERTY_TAG, "SUPPRESS"); + + // Test to make sure all log levels >= ASSERT are loggable. + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.INFO)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.WARN)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.ERROR)); + Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.ASSERT)); + } + */ + + public static class PerformanceTest extends TestCase implements PerformanceTestCase { + private static final int ITERATIONS = 1000; + + @Override + public void setUp() { + SystemProperties.set(LOG_TAG, "VERBOSE"); + } + + public boolean isPerformanceOnly() { + return true; + } + + public int startPerformance(PerformanceTestCase.Intermediates intermediates) { + intermediates.setInternalIterations(ITERATIONS * 10); + return 0; + } + + public void testIsLoggable() { + boolean canLog = false; + for (int i = ITERATIONS - 1; i >= 0; i--) { + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE); + } + } + } +} diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java new file mode 100644 index 0000000..65a6078 --- /dev/null +++ b/core/tests/coretests/src/android/util/TimeUtilsTest.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2008 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. + */ + +package android.util; + +import junit.framework.TestCase; + +import android.util.TimeUtils; + +import java.util.Calendar; +import java.util.TimeZone; + +/** + * TimeUtilsTest tests the time zone guesser. + */ +public class TimeUtilsTest extends TestCase { + public void testMainstream() throws Exception { + String[] mainstream = new String[] { + "America/New_York", // Eastern + "America/Chicago", // Central + "America/Denver", // Mountain + "America/Los_Angeles", // Pacific + "America/Anchorage", // Alaska + "Pacific/Honolulu", // Hawaii, no DST + }; + + for (String name : mainstream) { + TimeZone tz = TimeZone.getTimeZone(name); + Calendar c = Calendar.getInstance(tz); + TimeZone guess; + + c.set(2008, Calendar.OCTOBER, 20, 12, 00, 00); + guess = guess(c, "us"); + assertEquals(name, guess.getID()); + + c.set(2009, Calendar.JANUARY, 20, 12, 00, 00); + guess = guess(c, "us"); + assertEquals(name, guess.getID()); + } + } + + public void testWeird() throws Exception { + String[] weird = new String[] { + "America/Phoenix", // Mountain, no DST + "America/Adak", // Same as Hawaii, but with DST + }; + + for (String name : weird) { + TimeZone tz = TimeZone.getTimeZone(name); + Calendar c = Calendar.getInstance(tz); + TimeZone guess; + + c.set(2008, Calendar.OCTOBER, 20, 12, 00, 00); + guess = guess(c, "us"); + assertEquals(name, guess.getID()); + } + } + + public void testOld() throws Exception { + String[] old = new String[] { + "America/Indiana/Indianapolis", // Eastern, formerly no DST + }; + + for (String name : old) { + TimeZone tz = TimeZone.getTimeZone(name); + Calendar c = Calendar.getInstance(tz); + TimeZone guess; + + c.set(2005, Calendar.OCTOBER, 20, 12, 00, 00); + guess = guess(c, "us"); + assertEquals(name, guess.getID()); + } + } + + public void testWorld() throws Exception { + String[] world = new String[] { + "ad", "Europe/Andorra", + "ae", "Asia/Dubai", + "af", "Asia/Kabul", + "ag", "America/Antigua", + "ai", "America/Anguilla", + "al", "Europe/Tirane", + "am", "Asia/Yerevan", + "an", "America/Curacao", + "ao", "Africa/Luanda", + "aq", "Antarctica/McMurdo", + "aq", "Antarctica/DumontDUrville", + "aq", "Antarctica/Casey", + "aq", "Antarctica/Davis", + "aq", "Antarctica/Mawson", + "aq", "Antarctica/Syowa", + "aq", "Antarctica/Rothera", + "aq", "Antarctica/Palmer", + "ar", "America/Argentina/Buenos_Aires", + "as", "Pacific/Pago_Pago", + "at", "Europe/Vienna", + "au", "Australia/Sydney", + "au", "Australia/Adelaide", + "au", "Australia/Perth", + "au", "Australia/Eucla", + "aw", "America/Aruba", + "ax", "Europe/Mariehamn", + "az", "Asia/Baku", + "ba", "Europe/Sarajevo", + "bb", "America/Barbados", + "bd", "Asia/Dhaka", + "be", "Europe/Brussels", + "bf", "Africa/Ouagadougou", + "bg", "Europe/Sofia", + "bh", "Asia/Bahrain", + "bi", "Africa/Bujumbura", + "bj", "Africa/Porto-Novo", + "bm", "Atlantic/Bermuda", + "bn", "Asia/Brunei", + "bo", "America/La_Paz", + "br", "America/Noronha", + "br", "America/Sao_Paulo", + "br", "America/Manaus", + "bs", "America/Nassau", + "bt", "Asia/Thimphu", + "bw", "Africa/Gaborone", + "by", "Europe/Minsk", + "bz", "America/Belize", + "ca", "America/St_Johns", + "ca", "America/Halifax", + "ca", "America/Toronto", + "ca", "America/Winnipeg", + "ca", "America/Edmonton", + "ca", "America/Vancouver", + "cc", "Indian/Cocos", + "cd", "Africa/Lubumbashi", + "cd", "Africa/Kinshasa", + "cf", "Africa/Bangui", + "cg", "Africa/Brazzaville", + "ch", "Europe/Zurich", + "ci", "Africa/Abidjan", + "ck", "Pacific/Rarotonga", + "cl", "America/Santiago", + "cl", "Pacific/Easter", + "cm", "Africa/Douala", + "cn", "Asia/Shanghai", + "co", "America/Bogota", + "cr", "America/Costa_Rica", + "cu", "America/Havana", + "cv", "Atlantic/Cape_Verde", + "cx", "Indian/Christmas", + "cy", "Asia/Nicosia", + "cz", "Europe/Prague", + "de", "Europe/Berlin", + "dj", "Africa/Djibouti", + "dk", "Europe/Copenhagen", + "dm", "America/Dominica", + "do", "America/Santo_Domingo", + "dz", "Africa/Algiers", + "ec", "America/Guayaquil", + "ec", "Pacific/Galapagos", + "ee", "Europe/Tallinn", + "eg", "Africa/Cairo", + "eh", "Africa/El_Aaiun", + "er", "Africa/Asmara", + "es", "Europe/Madrid", + "es", "Atlantic/Canary", + "et", "Africa/Addis_Ababa", + "fi", "Europe/Helsinki", + "fj", "Pacific/Fiji", + "fk", "Atlantic/Stanley", + "fm", "Pacific/Ponape", + "fm", "Pacific/Truk", + "fo", "Atlantic/Faroe", + "fr", "Europe/Paris", + "ga", "Africa/Libreville", + "gb", "Europe/London", + "gd", "America/Grenada", + "ge", "Asia/Tbilisi", + "gf", "America/Cayenne", + "gg", "Europe/Guernsey", + "gh", "Africa/Accra", + "gi", "Europe/Gibraltar", + "gl", "America/Danmarkshavn", + "gl", "America/Scoresbysund", + "gl", "America/Godthab", + "gl", "America/Thule", + "gm", "Africa/Banjul", + "gn", "Africa/Conakry", + "gp", "America/Guadeloupe", + "gq", "Africa/Malabo", + "gr", "Europe/Athens", + "gs", "Atlantic/South_Georgia", + "gt", "America/Guatemala", + "gu", "Pacific/Guam", + "gw", "Africa/Bissau", + "gy", "America/Guyana", + "hk", "Asia/Hong_Kong", + "hn", "America/Tegucigalpa", + "hr", "Europe/Zagreb", + "ht", "America/Port-au-Prince", + "hu", "Europe/Budapest", + "id", "Asia/Jayapura", + "id", "Asia/Makassar", + "id", "Asia/Jakarta", + "ie", "Europe/Dublin", + "il", "Asia/Jerusalem", + "im", "Europe/Isle_of_Man", + "in", "Asia/Calcutta", + "io", "Indian/Chagos", + "iq", "Asia/Baghdad", + "ir", "Asia/Tehran", + "is", "Atlantic/Reykjavik", + "it", "Europe/Rome", + "je", "Europe/Jersey", + "jm", "America/Jamaica", + "jo", "Asia/Amman", + "jp", "Asia/Tokyo", + "ke", "Africa/Nairobi", + "kg", "Asia/Bishkek", + "kh", "Asia/Phnom_Penh", + "ki", "Pacific/Kiritimati", + "ki", "Pacific/Enderbury", + "ki", "Pacific/Tarawa", + "km", "Indian/Comoro", + "kn", "America/St_Kitts", + "kp", "Asia/Pyongyang", + "kr", "Asia/Seoul", + "kw", "Asia/Kuwait", + "ky", "America/Cayman", + "kz", "Asia/Almaty", + "kz", "Asia/Aqtau", + "la", "Asia/Vientiane", + "lb", "Asia/Beirut", + "lc", "America/St_Lucia", + "li", "Europe/Vaduz", + "lk", "Asia/Colombo", + "lr", "Africa/Monrovia", + "ls", "Africa/Maseru", + "lt", "Europe/Vilnius", + "lu", "Europe/Luxembourg", + "lv", "Europe/Riga", + "ly", "Africa/Tripoli", + "ma", "Africa/Casablanca", + "mc", "Europe/Monaco", + "md", "Europe/Chisinau", + "me", "Europe/Podgorica", + "mg", "Indian/Antananarivo", + "mh", "Pacific/Majuro", + "mk", "Europe/Skopje", + "ml", "Africa/Bamako", + "mm", "Asia/Rangoon", + "mn", "Asia/Choibalsan", + "mn", "Asia/Hovd", + "mo", "Asia/Macau", + "mp", "Pacific/Saipan", + "mq", "America/Martinique", + "mr", "Africa/Nouakchott", + "ms", "America/Montserrat", + "mt", "Europe/Malta", + "mu", "Indian/Mauritius", + "mv", "Indian/Maldives", + "mw", "Africa/Blantyre", + "mx", "America/Mexico_City", + "mx", "America/Chihuahua", + "mx", "America/Tijuana", + "my", "Asia/Kuala_Lumpur", + "mz", "Africa/Maputo", + "na", "Africa/Windhoek", + "nc", "Pacific/Noumea", + "ne", "Africa/Niamey", + "nf", "Pacific/Norfolk", + "ng", "Africa/Lagos", + "ni", "America/Managua", + "nl", "Europe/Amsterdam", + "no", "Europe/Oslo", + "np", "Asia/Katmandu", + "nr", "Pacific/Nauru", + "nu", "Pacific/Niue", + "nz", "Pacific/Auckland", + "nz", "Pacific/Chatham", + "om", "Asia/Muscat", + "pa", "America/Panama", + "pe", "America/Lima", + "pf", "Pacific/Gambier", + "pf", "Pacific/Marquesas", + "pf", "Pacific/Tahiti", + "pg", "Pacific/Port_Moresby", + "ph", "Asia/Manila", + "pk", "Asia/Karachi", + "pl", "Europe/Warsaw", + "pm", "America/Miquelon", + "pn", "Pacific/Pitcairn", + "pr", "America/Puerto_Rico", + "ps", "Asia/Gaza", + "pt", "Europe/Lisbon", + "pt", "Atlantic/Azores", + "pw", "Pacific/Palau", + "py", "America/Asuncion", + "qa", "Asia/Qatar", + "re", "Indian/Reunion", + "ro", "Europe/Bucharest", + "rs", "Europe/Belgrade", + "ru", "Asia/Kamchatka", + "ru", "Asia/Magadan", + "ru", "Asia/Vladivostok", + "ru", "Asia/Yakutsk", + "ru", "Asia/Irkutsk", + "ru", "Asia/Krasnoyarsk", + "ru", "Asia/Novosibirsk", + "ru", "Asia/Yekaterinburg", + "ru", "Europe/Samara", + "ru", "Europe/Moscow", + "ru", "Europe/Kaliningrad", + "rw", "Africa/Kigali", + "sa", "Asia/Riyadh", + "sb", "Pacific/Guadalcanal", + "sc", "Indian/Mahe", + "sd", "Africa/Khartoum", + "se", "Europe/Stockholm", + "sg", "Asia/Singapore", + "sh", "Atlantic/St_Helena", + "si", "Europe/Ljubljana", + "sj", "Arctic/Longyearbyen", + "sk", "Europe/Bratislava", + "sl", "Africa/Freetown", + "sm", "Europe/San_Marino", + "sn", "Africa/Dakar", + "so", "Africa/Mogadishu", + "sr", "America/Paramaribo", + "st", "Africa/Sao_Tome", + "sv", "America/El_Salvador", + "sy", "Asia/Damascus", + "sz", "Africa/Mbabane", + "tc", "America/Grand_Turk", + "td", "Africa/Ndjamena", + "tf", "Indian/Kerguelen", + "tg", "Africa/Lome", + "th", "Asia/Bangkok", + "tj", "Asia/Dushanbe", + "tk", "Pacific/Fakaofo", + "tl", "Asia/Dili", + "tm", "Asia/Ashgabat", + "tn", "Africa/Tunis", + "to", "Pacific/Tongatapu", + "tr", "Europe/Istanbul", + "tt", "America/Port_of_Spain", + "tv", "Pacific/Funafuti", + "tw", "Asia/Taipei", + "tz", "Africa/Dar_es_Salaam", + "ua", "Europe/Kiev", + "ug", "Africa/Kampala", + "um", "Pacific/Wake", + "um", "Pacific/Johnston", + "um", "Pacific/Midway", + "us", "America/New_York", + "us", "America/Chicago", + "us", "America/Denver", + "us", "America/Los_Angeles", + "us", "America/Anchorage", + "us", "Pacific/Honolulu", + "uy", "America/Montevideo", + "uz", "Asia/Tashkent", + "va", "Europe/Vatican", + "vc", "America/St_Vincent", + "ve", "America/Caracas", + "vg", "America/Tortola", + "vi", "America/St_Thomas", + "vn", "Asia/Saigon", + "vu", "Pacific/Efate", + "wf", "Pacific/Wallis", + "ws", "Pacific/Apia", + "ye", "Asia/Aden", + "yt", "Indian/Mayotte", + "za", "Africa/Johannesburg", + "zm", "Africa/Lusaka", + "zw", "Africa/Harare", + }; + + for (int i = 0; i < world.length; i += 2) { + String country = world[i]; + String name = world[i + 1]; + + TimeZone tz = TimeZone.getTimeZone(name); + Calendar c = Calendar.getInstance(tz); + TimeZone guess; + + c.set(2009, Calendar.JULY, 20, 12, 00, 00); + guess = guess(c, country); + assertEquals(name, guess.getID()); + + c.set(2009, Calendar.JANUARY, 20, 12, 00, 00); + guess = guess(c, country); + assertEquals(name, guess.getID()); + } + } + + public void testWorldWeird() throws Exception { + String[] world = new String[] { + // Distinguisable from Sydney only when DST not in effect + "au", "Australia/Lord_Howe", + }; + + for (int i = 0; i < world.length; i += 2) { + String country = world[i]; + String name = world[i + 1]; + + TimeZone tz = TimeZone.getTimeZone(name); + Calendar c = Calendar.getInstance(tz); + TimeZone guess; + + c.set(2009, Calendar.JULY, 20, 12, 00, 00); + guess = guess(c, country); + assertEquals(name, guess.getID()); + } + } + + private static TimeZone guess(Calendar c, String country) { + return TimeUtils.getTimeZone(c.get(c.ZONE_OFFSET) + c.get(c.DST_OFFSET), + c.get(c.DST_OFFSET) != 0, + c.getTimeInMillis(), + country); + } +} diff --git a/core/tests/coretests/src/android/view/CreateViewTest.java b/core/tests/coretests/src/android/view/CreateViewTest.java new file mode 100644 index 0000000..16656f6 --- /dev/null +++ b/core/tests/coretests/src/android/view/CreateViewTest.java @@ -0,0 +1,123 @@ +/* + * 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.view; + +import android.content.Context; +import android.test.AndroidTestCase; +import android.test.PerformanceTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.View; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class CreateViewTest extends AndroidTestCase implements PerformanceTestCase { + + public boolean isPerformanceOnly() { + return false; + } + + public int startPerformance(PerformanceTestCase.Intermediates intermediates) { + return 0; + } + + @SmallTest + public void testLayout1() throws Exception { + new CreateViewTest.ViewOne(mContext); + } + + @SmallTest + public void testLayout2() throws Exception { + LinearLayout vert = new LinearLayout(mContext); + vert.addView(new CreateViewTest.ViewOne(mContext), + new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0)); + } + + @SmallTest + public void testLayout3() throws Exception { + LinearLayout vert = new LinearLayout(mContext); + + ViewOne one = new ViewOne(mContext); + vert.addView(one, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0)); + + ViewOne two = new ViewOne(mContext); + vert.addView(two, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0)); + + ViewOne three = new ViewOne(mContext); + vert.addView(three, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0)); + + ViewOne four = new ViewOne(mContext); + vert.addView(four, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0)); + + ViewOne five = new ViewOne(mContext); + vert.addView(five, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0)); + + ViewOne six = new ViewOne(mContext); + vert.addView(six, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0)); + } + + @SmallTest + public void testLayout4() throws Exception { + TextView text = new TextView(mContext); + text.setText("S"); + } + + @SmallTest + public void testLayout5() throws Exception { + TextView text = new TextView(mContext); + text.setText("S"); + + LinearLayout vert = new LinearLayout(mContext); + vert.addView(text, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0)); + } + + @SmallTest + public void testLayout6() throws Exception { + LinearLayout vert = new LinearLayout(mContext); + + TextView one = new TextView(mContext); + one.setText("S"); + vert.addView(one, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0)); + + TextView two = new TextView(mContext); + two.setText("M"); + vert.addView(two, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0)); + + TextView three = new TextView(mContext); + three.setText("T"); + vert.addView(three, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0)); + + TextView four = new TextView(mContext); + four.setText("W"); + vert.addView(four, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0)); + + TextView five = new TextView(mContext); + five.setText("H"); + vert.addView(five, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0)); + + TextView six = new TextView(mContext); + six.setText("F"); + vert.addView(six, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0)); + } + + public static class ViewOne extends View { + public ViewOne(Context context) { + super(context); + } + } +} diff --git a/core/tests/coretests/src/android/view/InflateTest.java b/core/tests/coretests/src/android/view/InflateTest.java new file mode 100644 index 0000000..cb4f8e2 --- /dev/null +++ b/core/tests/coretests/src/android/view/InflateTest.java @@ -0,0 +1,155 @@ +/* + * 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.view; + +import android.content.Context; +import android.content.res.Resources; +import android.test.AndroidTestCase; +import android.test.PerformanceTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import com.android.frameworks.coretests.R; + +import java.util.Map; + +public class InflateTest extends AndroidTestCase implements PerformanceTestCase { + private LayoutInflater mInflater; + private Resources mResources; + private View mView; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mInflater = LayoutInflater.from(mContext); + mResources = mContext.getResources(); + + // to try to make things consistent, before doing timing + // do an initial instantiation of the layout and then clear + // out the layout cache. +// mInflater.inflate(mResId, null, null); +// mResources.flushLayoutCache(); + } + + public int startPerformance(PerformanceTestCase.Intermediates intermediates) { + return 0; + } + + public boolean isPerformanceOnly() { + return false; + } + + public void inflateTest(int resourceId) { + mView = mInflater.inflate(resourceId, null); + mResources.flushLayoutCache(); + } + + public void inflateCachedTest(int resourceId) { + // Make sure this layout is in the cache. + mInflater.inflate(resourceId, null); + + mInflater.inflate(resourceId, null); + } + + @SmallTest + public void testLayout1() throws Exception { + inflateTest(R.layout.layout_one); + } + + @SmallTest + public void testLayout2() throws Exception { + inflateTest(R.layout.layout_two); + } + + @SmallTest + public void testLayout3() throws Exception { + inflateTest(R.layout.layout_three); + } + + @SmallTest + public void testLayout4() throws Exception { + inflateTest(R.layout.layout_four); + } + + @SmallTest + public void testLayout5() throws Exception { + inflateTest(R.layout.layout_five); + } + + @SmallTest + public void testLayout6() throws Exception { + inflateTest(R.layout.layout_six); + } + + @SmallTest + public void testCachedLayout1() throws Exception { + inflateCachedTest(R.layout.layout_one); + } + + @SmallTest + public void testCachedLayout2() throws Exception { + inflateCachedTest(R.layout.layout_two); + } + + @SmallTest + public void testCachedLayout3() throws Exception { + inflateCachedTest(R.layout.layout_three); + } + + @SmallTest + public void testCachedLayout4() throws Exception { + inflateCachedTest(R.layout.layout_four); + } + + @SmallTest + public void testCachedLayout5() throws Exception { + inflateCachedTest(R.layout.layout_five); + } + + @SmallTest + public void testCachedLayout6() throws Exception { + inflateCachedTest(R.layout.layout_six); + } + +// public void testLayoutTag() throws Exception { +// public void setUp +// (Context +// context){ +// setUp(context, R.layout.layout_tag); +// } +// public void run +// () +// { +// super.run(); +// if (!"MyTag".equals(mView.getTag())) { +// throw new RuntimeException("Incorrect tag: " + mView.getTag()); +// } +// } +// } + + public static class ViewOne extends View { + public ViewOne(Context context) { + super(context); + } + + public ViewOne(Context context, AttributeSet attrs) { + super(context, attrs); + } + } +} diff --git a/core/tests/coretests/src/android/view/MenuTest.java b/core/tests/coretests/src/android/view/MenuTest.java new file mode 100644 index 0000000..e8a8438 --- /dev/null +++ b/core/tests/coretests/src/android/view/MenuTest.java @@ -0,0 +1,279 @@ +/* + * 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.view; + +import com.android.internal.view.menu.MenuBuilder; + +import junit.framework.Assert; + +import android.test.AndroidTestCase; +import android.test.MoreAsserts; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; + +import com.android.frameworks.coretests.R; + +public class MenuTest extends AndroidTestCase { + + private MenuBuilder mMenu; + + public void setUp() throws Exception { + super.setUp(); + mMenu = new MenuBuilder(super.getContext()); + } + + @SmallTest + public void testItemId() { + final int id = 512; + final MenuItem item = mMenu.add(0, id, 0, "test"); + + Assert.assertEquals(id, item.getItemId()); + Assert.assertEquals(item, mMenu.findItem(id)); + Assert.assertEquals(0, mMenu.findItemIndex(id)); + } + + @SmallTest + public void testGroupId() { + final int groupId = 541; + final int item1Index = 1; + final int item2Index = 3; + + mMenu.add(0, 0, item1Index - 1, "ignore"); + final MenuItem item = mMenu.add(groupId, 0, item1Index, "test"); + mMenu.add(0, 0, item2Index - 1, "ignore"); + final MenuItem item2 = mMenu.add(groupId, 0, item2Index, "test2"); + + Assert.assertEquals(groupId, item.getGroupId()); + Assert.assertEquals(groupId, item2.getGroupId()); + Assert.assertEquals(item1Index, mMenu.findGroupIndex(groupId)); + Assert.assertEquals(item2Index, mMenu.findGroupIndex(groupId, item1Index + 1)); + } + + @SmallTest + public void testGroup() { + // This test does the following + // 1. Create a grouped item in the menu + // 2. Check that findGroupIndex() finds the grouped item. + // 3. Check that findGroupIndex() doesn't find a non-existent group. + + final int GROUP_ONE = Menu.FIRST; + final int GROUP_TWO = Menu.FIRST + 1; + + mMenu.add(GROUP_ONE, 0, 0, "Menu text"); + Assert.assertEquals(mMenu.findGroupIndex(GROUP_ONE), 0); + Assert.assertEquals(mMenu.findGroupIndex(GROUP_TWO), -1); + //TODO: expand this test case to do multiple groups, + //adding and removing, hiding and showing, etc. + } + + @SmallTest + public void testIsShortcutWithAlpha() throws Exception { + mMenu.setQwertyMode(true); + mMenu.add(0, 0, 0, "test").setShortcut('2', 'a'); + Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_A, + makeKeyEvent(KeyEvent.KEYCODE_A, 0))); + Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_B, + makeKeyEvent(KeyEvent.KEYCODE_B, 0))); + } + + @SmallTest + public void testIsShortcutWithNumeric() throws Exception { + mMenu.setQwertyMode(false); + mMenu.add(0, 0, 0, "test").setShortcut('2', 'a'); + Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_2, + makeKeyEvent(KeyEvent.KEYCODE_2, 0))); + Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_A, + makeKeyEvent(KeyEvent.KEYCODE_A, 0))); + } + + @SmallTest + public void testIsShortcutWithAlt() throws Exception { + mMenu.setQwertyMode(true); + mMenu.add(0, 0, 0, "test").setShortcut('2', 'a'); + Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_A, + makeKeyEvent(KeyEvent.KEYCODE_A, + KeyEvent.META_ALT_ON))); + Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_A, + makeKeyEvent(KeyEvent.KEYCODE_A, + KeyEvent.META_SYM_ON))); + } + + @SmallTest + public void testIsNotShortcutWithShift() throws Exception { + mMenu.setQwertyMode(true); + mMenu.add(0, 0, 0, "test").setShortcut('2', 'a'); + Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_A, + makeKeyEvent(KeyEvent.KEYCODE_A, + KeyEvent.META_SHIFT_ON))); + } + + @SmallTest + public void testIsNotShortcutWithSym() throws Exception { + mMenu.setQwertyMode(true); + mMenu.add(0, 0, 0, "test").setShortcut('2', 'a'); + Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_A, + makeKeyEvent(KeyEvent.KEYCODE_A, + KeyEvent.META_SYM_ON))); + } + + @SmallTest + public void testIsShortcutWithUpperCaseAlpha() throws Exception { + mMenu.setQwertyMode(true); + mMenu.add(0, 0, 0, "test").setShortcut('2', 'A'); + Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_A, + makeKeyEvent(KeyEvent.KEYCODE_A, 0))); + } + + @SmallTest + public void testIsShortcutWithBackspace() throws Exception { + mMenu.setQwertyMode(true); + mMenu.add(0, 0, 0, "test").setShortcut('2', '\b'); + Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_DEL, + makeKeyEvent(KeyEvent.KEYCODE_DEL, 0))); + } + + @SmallTest + public void testIsShortcutWithNewline() throws Exception { + mMenu.setQwertyMode(true); + mMenu.add(0, 0, 0, "test").setShortcut('2', '\n'); + Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_ENTER, + makeKeyEvent(KeyEvent.KEYCODE_ENTER, 0))); + } + + @SmallTest + public void testOrder() { + final String a = "a", b = "b", c = "c"; + final int firstOrder = 7, midOrder = 8, lastOrder = 9; + + mMenu.add(0, 0, lastOrder, c); + mMenu.add(0, 0, firstOrder, a); + mMenu.add(0, 0, midOrder, b); + + Assert.assertEquals(firstOrder, mMenu.getItem(0).getOrder()); + Assert.assertEquals(a, mMenu.getItem(0).getTitle()); + Assert.assertEquals(midOrder, mMenu.getItem(1).getOrder()); + Assert.assertEquals(b, mMenu.getItem(1).getTitle()); + Assert.assertEquals(lastOrder, mMenu.getItem(2).getOrder()); + Assert.assertEquals(c, mMenu.getItem(2).getTitle()); + } + + @SmallTest + public void testTitle() { + final String title = "test"; + final MenuItem stringItem = mMenu.add(title); + final MenuItem resItem = mMenu.add(R.string.menu_test); + + Assert.assertEquals(title, stringItem.getTitle()); + Assert.assertEquals(getContext().getResources().getString(R.string.menu_test), resItem + .getTitle()); + } + + @SmallTest + public void testCheckable() { + final int groupId = 1; + final MenuItem item1 = mMenu.add(groupId, 1, 0, "item1"); + final MenuItem item2 = mMenu.add(groupId, 2, 0, "item2"); + + // Set to exclusive + mMenu.setGroupCheckable(groupId, true, true); + Assert.assertTrue("Item was not set to checkable", item1.isCheckable()); + item1.setChecked(true); + Assert.assertTrue("Item did not get checked", item1.isChecked()); + Assert.assertFalse("Item was not unchecked due to exclusive checkable", item2.isChecked()); + mMenu.findItem(2).setChecked(true); + Assert.assertTrue("Item did not get checked", item2.isChecked()); + Assert.assertFalse("Item was not unchecked due to exclusive checkable", item1.isChecked()); + + // Multiple non-exlusive checkable items + mMenu.setGroupCheckable(groupId, true, false); + Assert.assertTrue("Item was not set to checkable", item1.isCheckable()); + item1.setChecked(false); + Assert.assertFalse("Item did not get unchecked", item1.isChecked()); + item1.setChecked(true); + Assert.assertTrue("Item did not get checked", item1.isChecked()); + mMenu.findItem(2).setChecked(true); + Assert.assertTrue("Item did not get checked", item2.isChecked()); + Assert.assertTrue("Item was unchecked when it shouldnt have been", item1.isChecked()); + } + + @SmallTest + public void testVisibility() { + final MenuItem item1 = mMenu.add(0, 1, 0, "item1"); + final MenuItem item2 = mMenu.add(0, 2, 0, "item2"); + + // Should start as visible + Assert.assertTrue("Item did not start as visible", item1.isVisible()); + Assert.assertTrue("Item did not start as visible", item2.isVisible()); + + // Hide + item1.setVisible(false); + Assert.assertFalse("Item did not become invisible", item1.isVisible()); + mMenu.findItem(2).setVisible(false); + Assert.assertFalse("Item did not become invisible", item2.isVisible()); + } + + @SmallTest + public void testSubMenu() { + final SubMenu subMenu = mMenu.addSubMenu(0, 0, 0, "submenu"); + final MenuItem subMenuItem = subMenu.getItem(); + final MenuItem item1 = subMenu.add(0, 1, 0, "item1"); + final MenuItem item2 = subMenu.add(0, 2, 0, "item2"); + + // findItem should recurse into submenus + Assert.assertEquals(item1, mMenu.findItem(1)); + Assert.assertEquals(item2, mMenu.findItem(2)); + } + + @SmallTest + public void testRemove() { + final int groupId = 1; + final MenuItem item1 = mMenu.add(groupId, 1, 0, "item1"); + final MenuItem item2 = mMenu.add(groupId, 2, 0, "item2"); + final MenuItem item3 = mMenu.add(groupId, 3, 0, "item3"); + final MenuItem item4 = mMenu.add(groupId, 4, 0, "item4"); + final MenuItem item5 = mMenu.add(groupId, 5, 0, "item5"); + final MenuItem item6 = mMenu.add(0, 6, 0, "item6"); + + Assert.assertEquals(item1, mMenu.findItem(1)); + mMenu.removeItemAt(0); + Assert.assertNull(mMenu.findItem(1)); + + Assert.assertEquals(item2, mMenu.findItem(2)); + mMenu.removeItem(2); + Assert.assertNull(mMenu.findItem(2)); + + Assert.assertEquals(item3, mMenu.findItem(3)); + Assert.assertEquals(item4, mMenu.findItem(4)); + Assert.assertEquals(item5, mMenu.findItem(5)); + mMenu.removeGroup(groupId); + Assert.assertNull(mMenu.findItem(3)); + Assert.assertNull(mMenu.findItem(4)); + Assert.assertNull(mMenu.findItem(5)); + + Assert.assertEquals(item6, mMenu.findItem(6)); + mMenu.clear(); + Assert.assertNull(mMenu.findItem(6)); + } + + private KeyEvent makeKeyEvent(int keyCode, int metaState) { + return new KeyEvent(0L, 0L, KeyEvent.ACTION_DOWN, keyCode, 0, metaState); + } +} diff --git a/core/tests/coretests/src/android/webkit/WebkitTest.java b/core/tests/coretests/src/android/webkit/WebkitTest.java new file mode 100644 index 0000000..17b4088 --- /dev/null +++ b/core/tests/coretests/src/android/webkit/WebkitTest.java @@ -0,0 +1,59 @@ +/* + * 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.webkit; + +import android.test.AndroidTestCase; +import android.text.format.DateFormat; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; +import android.webkit.DateSorter; + +import java.util.Calendar; +import java.util.Date; + +public class WebkitTest extends AndroidTestCase { + + private static final String LOGTAG = WebkitTest.class.getName(); + + @MediumTest + public void testDateSorter() throws Exception { + /** + * Note: check the logging output manually to test + * nothing automated yet, besides object creation + */ + DateSorter dateSorter = new DateSorter(mContext); + Date date = new Date(); + + for (int i = 0; i < DateSorter.DAY_COUNT; i++) { + Log.i(LOGTAG, "Boundary " + i + " " + dateSorter.getBoundary(i)); + Log.i(LOGTAG, "Label " + i + " " + dateSorter.getLabel(i)); + } + + Calendar c = Calendar.getInstance(); + long time = c.getTimeInMillis(); + int index; + Log.i(LOGTAG, "now: " + dateSorter.getIndex(time)); + for (int i = 0; i < 20; i++) { + time -= 8 * 60 * 60 * 1000; // 8 hours + date.setTime(time); + c.setTime(date); + index = dateSorter.getIndex(time); + Log.i(LOGTAG, "time: " + DateFormat.format("yyyy/MM/dd kk:mm:ss", c).toString() + + " " + index + " " + dateSorter.getLabel(index)); + } + } +} diff --git a/core/tests/coretests/src/android/widget/LabelView.java b/core/tests/coretests/src/android/widget/LabelView.java new file mode 100644 index 0000000..4661c01 --- /dev/null +++ b/core/tests/coretests/src/android/widget/LabelView.java @@ -0,0 +1,191 @@ +/* + * 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; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.view.View; + +/** + * Example of how to write a custom subclass of View. LabelView + * is used to draw simple text views. Note that it does not handle + * styled text or right-to-left writing systems. + * + */ +public class LabelView extends View { + /** + * Constructor. This version is only needed if you will be instantiating + * the object manually (not from a layout XML file). + * @param context the application environment + */ + public LabelView(Context context) { + super(context); + initLabelView(); + } + + /** + * Construct object, initializing with any attributes we understand from a + * layout file. These attributes are defined in + * SDK/assets/res/any/classes.xml. + * + * @see android.view.View#View(android.content.Context, android.util.AttributeSet) + public LabelView(Context context, AttributeSet attrs) { + super(context, attrs); + initLabelView(); + + Resources.StyledAttributes a = context.obtainStyledAttributes(attrs, + R.styleable.LabelView); + + CharSequence s = a.getString(R.styleable.LabelView_text); + if (s != null) { + setText(s.toString()); + } + + ColorStateList textColor = a.getColorList(R.styleable. + LabelView_textColor); + if (textColor != null) { + setTextColor(textColor.getDefaultColor(0)); + } + + int textSize = a.getInt(R.styleable.LabelView_textSize, 0); + if (textSize > 0) { + setTextSize(textSize); + } + + a.recycle(); + } + + */ + private void initLabelView() { + mTextPaint = new Paint(); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(16); + mTextPaint.setColor(0xFF000000); + + mPaddingLeft = 3; + mPaddingTop = 3; + mPaddingRight = 3; + mPaddingBottom = 3; + } + + /** + * Sets the text to display in this label + * @param text The text to display. This will be drawn as one line. + */ + public void setText(String text) { + mText = text; + requestLayout(); + invalidate(); + } + + /** + * Sets the text size for this label + * @param size Font size + */ + public void setTextSize(int size) { + mTextPaint.setTextSize(size); + requestLayout(); + invalidate(); + } + + /** + * Sets the text color for this label + * @param color ARGB value for the text + */ + public void setTextColor(int color) { + mTextPaint.setColor(color); + invalidate(); + } + + + /** + * @see android.view.View#measure(int, int) + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measureWidth(widthMeasureSpec), + measureHeight(heightMeasureSpec)); + } + + /** + * Determines the width of this view + * @param measureSpec A measureSpec packed into an int + * @return The width of the view, honoring constraints from measureSpec + */ + private int measureWidth(int measureSpec) { + int result; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else { + // Measure the text + result = (int) mTextPaint.measureText(mText) + mPaddingLeft + + mPaddingRight; + if (specMode == MeasureSpec.AT_MOST) { + // Respect AT_MOST value if that was what is called for by measureSpec + result = Math.min(result, specSize); + } + } + + return result; + } + + /** + * Determines the height of this view + * @param measureSpec A measureSpec packed into an int + * @return The height of the view, honoring constraints from measureSpec + */ + private int measureHeight(int measureSpec) { + int result; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + mAscent = (int) mTextPaint.ascent(); + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else { + // Measure the text (beware: ascent is a negative number) + result = (int) (-mAscent + mTextPaint.descent()) + mPaddingTop + + mPaddingBottom; + if (specMode == MeasureSpec.AT_MOST) { + // Respect AT_MOST value if that was what is called for by measureSpec + result = Math.min(result, specSize); + } + } + return result; + } + + /** + * Render the text + * + * @see android.view.View#onDraw(android.graphics.Canvas) + */ + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawText(mText, mPaddingLeft, mPaddingTop - mAscent, mTextPaint); + } + + private Paint mTextPaint; + private String mText; + private int mAscent; +} diff --git a/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java b/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java new file mode 100644 index 0000000..c25df7c --- /dev/null +++ b/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java @@ -0,0 +1,132 @@ +/* + * 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.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.SpannedString; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class TextViewPerformanceTest extends AndroidTestCase { + + private String mString = "The quick brown fox"; + private Canvas mCanvas; + private PerformanceTextView mTextView; + private Paint mPaint; + private PerformanceLabelView mLabelView; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + Bitmap mBitmap = Bitmap.createBitmap(320, 240, Bitmap.Config.RGB_565); + mCanvas = new Canvas(mBitmap); + + ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(320, 240); + + mLabelView = new PerformanceLabelView(mContext); + mLabelView.setText(mString); + mLabelView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240); + mLabelView.mySetFrame(320, 240); + mLabelView.setLayoutParams(p); + mLabelView.myDraw(mCanvas); + + mPaint = new Paint(); + mCanvas.save(); + mTextView = new PerformanceTextView(mContext); + mTextView.setLayoutParams(p); + mTextView.setText(mString); + mTextView.mySetFrame(320, 240); + mTextView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240); + } + + @MediumTest + public void testDrawTextViewLine() throws Exception { + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + } + + @SmallTest + public void testSpan() throws Exception { + CharSequence charSeq = new SpannedString(mString); + mTextView.setText(charSeq); + + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + mTextView.myDraw(mCanvas); + } + + @SmallTest + public void testCanvasDrawText() throws Exception { + mCanvas.drawText(mString, 30, 30, mPaint); + } + + @SmallTest + public void testLabelViewDraw() throws Exception { + mLabelView.myDraw(mCanvas); + } + + private class PerformanceTextView extends TextView { + public PerformanceTextView(Context context) { + super(context); + } + + final void myDraw(Canvas c) { + super.onDraw(c); + } + + final void mySetFrame(int w, int h) { + super.setFrame(0, 0, w, h); + } + } + + private class PerformanceLabelView extends LabelView { + public PerformanceLabelView(Context context) { + super(context); + } + + final void myDraw(Canvas c) { + super.onDraw(c); + } + + final void mySetFrame(int w, int h) { + super.setFrame(0, 0, w, h); + } + } +} diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java new file mode 100644 index 0000000..d8d145c --- /dev/null +++ b/core/tests/coretests/src/android/widget/TextViewTest.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + +package android.widget; + +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.GetChars; +import android.widget.TextView; + +/** + * TextViewTest tests {@link TextView}. + */ +public class TextViewTest extends AndroidTestCase { + + @SmallTest + public void testArray() throws Exception { + TextView tv = new TextView(mContext); + + char[] c = new char[] { 'H', 'e', 'l', 'l', 'o', ' ', + 'W', 'o', 'r', 'l', 'd', '!' }; + + tv.setText(c, 1, 4); + CharSequence oldText = tv.getText(); + + tv.setText(c, 4, 5); + CharSequence newText = tv.getText(); + + assertTrue(newText == oldText); + + assertEquals(5, newText.length()); + assertEquals('o', newText.charAt(0)); + assertEquals("o Wor", newText.toString()); + + assertEquals(" Wo", newText.subSequence(1, 4)); + + char[] c2 = new char[7]; + ((GetChars) newText).getChars(1, 4, c2, 2); + assertEquals('\0', c2[1]); + assertEquals(' ', c2[2]); + assertEquals('W', c2[3]); + assertEquals('o', c2[4]); + assertEquals('\0', c2[5]); + } +} diff --git a/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java new file mode 100644 index 0000000..a304b68 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java @@ -0,0 +1,136 @@ +/* + * 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 com.android.internal.util; + +import com.android.internal.util.BitwiseInputStream; +import com.android.internal.util.BitwiseOutputStream; +import com.android.internal.util.HexDump; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import android.util.Log; + +import java.util.Random; + +public class BitwiseStreamsTest extends AndroidTestCase { + private final static String LOG_TAG = "BitwiseStreamsTest"; + + @SmallTest + public void testOne() throws Exception { + int offset = 3; + byte[] inBuf = HexDump.hexStringToByteArray("FFDD"); + BitwiseOutputStream outStream = new BitwiseOutputStream(30); + outStream.skip(offset); + for (int i = 0; i < inBuf.length; i++) outStream.write(8, inBuf[i]); + byte[] outBuf = outStream.toByteArray(); + BitwiseInputStream inStream = new BitwiseInputStream(outBuf); + byte[] inBufDup = new byte[inBuf.length]; + inStream.skip(offset); + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); + assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); + } + + @SmallTest + public void testTwo() throws Exception { + int offset = 3; + byte[] inBuf = HexDump.hexStringToByteArray("11d4f29c0e9ad3c36e72584e064d9b53"); + BitwiseOutputStream outStream = new BitwiseOutputStream(30); + outStream.skip(offset); + for (int i = 0; i < inBuf.length; i++) outStream.write(8, inBuf[i]); + BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); + inStream.skip(offset); + byte[] inBufDup = new byte[inBuf.length]; + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); + assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); + } + + @SmallTest + public void testThree() throws Exception { + int offset = 4; + byte[] inBuf = HexDump.hexStringToByteArray("00031040900112488ea794e0"); + BitwiseOutputStream outStream = new BitwiseOutputStream(30); + outStream.skip(offset); + for (int i = 0; i < inBuf.length; i++) outStream.write(8, inBuf[i]); + BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); + inStream.skip(offset); + byte[] inBufDup = new byte[inBuf.length]; + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); + assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); + } + + @SmallTest + public void testFour() throws Exception { + int offset = 7; + byte[] inBuf = HexDump.hexStringToByteArray("00031040900112488ea794e0"); + BitwiseOutputStream outStream = new BitwiseOutputStream(30); + outStream.skip(offset); + for (int i = 0; i < inBuf.length; i++) { + outStream.write(5, inBuf[i] >>> 3); + outStream.write(3, inBuf[i] & 0x07); + } + BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); + inStream.skip(offset); + byte[] inBufDup = new byte[inBuf.length]; + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); + assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); + } + + @SmallTest + public void testFive() throws Exception { + Random random = new Random(); + int iterations = 10000; + int[] sizeArr = new int[iterations]; + int[] valueArr = new int[iterations]; + BitwiseOutputStream outStream = new BitwiseOutputStream(iterations * 4); + for (int i = 0; i < iterations; i++) { + int x = random.nextInt(); + int size = (x & 0x07) + 1; + int value = x & (-1 >>> (32 - size)); + sizeArr[i] = size; + valueArr[i] = value; + outStream.write(size, value); + } + BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); + for (int i = 0; i < iterations; i++) { + assertEquals(valueArr[i], inStream.read(sizeArr[i])); + } + } + + @SmallTest + public void testSix() throws Exception { + int num_runs = 10; + long start = android.os.SystemClock.elapsedRealtime(); + for (int run = 0; run < num_runs; run++) { + int offset = run % 8; + byte[] inBuf = HexDump.hexStringToByteArray("00031040900112488ea794e0"); + BitwiseOutputStream outStream = new BitwiseOutputStream(30); + outStream.skip(offset); + for (int i = 0; i < inBuf.length; i++) { + outStream.write(5, inBuf[i] >>> 3); + outStream.write(3, inBuf[i] & 0x07); + } + BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); + inStream.skip(offset); + byte[] inBufDup = new byte[inBuf.length]; + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); + assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); + } + long end = android.os.SystemClock.elapsedRealtime(); + Log.d(LOG_TAG, "repeated encode-decode took " + (end - start) + " ms"); + } +} diff --git a/core/tests/coretests/src/com/android/internal/util/CharSequencesTest.java b/core/tests/coretests/src/com/android/internal/util/CharSequencesTest.java new file mode 100644 index 0000000..55d186c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/CharSequencesTest.java @@ -0,0 +1,58 @@ +/* + * 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.internal.util; + +import com.android.internal.util.CharSequences; +import static com.android.internal.util.CharSequences.forAsciiBytes; +import junit.framework.TestCase; +import android.test.suitebuilder.annotation.SmallTest; + +public class CharSequencesTest extends TestCase { + + @SmallTest + public void testCharSequences() { + String s = "Crazy Bob"; + byte[] bytes = s.getBytes(); + + String copy = toString(forAsciiBytes(bytes)); + assertTrue(s.equals(copy)); + + copy = toString(forAsciiBytes(bytes, 0, s.length())); + assertTrue(s.equals(copy)); + + String crazy = toString(forAsciiBytes(bytes, 0, 5)); + assertTrue("Crazy".equals(crazy)); + + String a = toString(forAsciiBytes(bytes, 0, 3).subSequence(2, 3)); + assertTrue("a".equals(a)); + + String empty = toString(forAsciiBytes(bytes, 0, 3).subSequence(3, 3)); + assertTrue("".equals(empty)); + + assertTrue(CharSequences.equals("bob", "bob")); + assertFalse(CharSequences.equals("b", "bob")); + assertFalse(CharSequences.equals("", "bob")); + } + + /** + * Converts a CharSequence to a string the slow way. Useful for testing + * a CharSequence implementation. + */ + static String toString(CharSequence charSequence) { + return new StringBuilder().append(charSequence).toString(); + } +} diff --git a/core/tests/coretests/src/com/google/android/net/ParentalControlTest.java b/core/tests/coretests/src/com/google/android/net/ParentalControlTest.java new file mode 100644 index 0000000..d8ffeab --- /dev/null +++ b/core/tests/coretests/src/com/google/android/net/ParentalControlTest.java @@ -0,0 +1,57 @@ +/* + * 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. + */ + +package com.google.android.net; + +import com.google.android.net.ParentalControl; +import com.google.android.net.ParentalControlState; + +import android.os.SystemClock; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +import junit.framework.Assert; + +public class ParentalControlTest extends AndroidTestCase { + + private boolean mOnResultCalled = false; + + public class Callback implements ParentalControl.Callback { + public void onResult(ParentalControlState state) { + synchronized (ParentalControlTest.class) { + mOnResultCalled = true; + ParentalControlTest.class.notifyAll(); + } + } + } + + @SmallTest + public void testParentalControlCallback() { + synchronized (ParentalControlTest.class) { + ParentalControl.getParentalControlState(new Callback(), null); + try { + long start = SystemClock.uptimeMillis(); + ParentalControlTest.class.wait(20 * 1000); + long end = SystemClock.uptimeMillis(); + Log.d("AndroidTests", "ParentalControlTest callback took " + (end-start) + " ms."); + } catch (InterruptedException ex) { + } + } + + Assert.assertTrue(mOnResultCalled); + } +} |