am ce2b95ae: Fix AudioEngine to allow re-initialization - DO NOT MERGE
* commit 'ce2b95ae200332495b348be07f77fe60b6c23dcc':
Fix AudioEngine to allow re-initialization - DO NOT MERGE
diff --git a/carousel/test/res/values-am/strings.xml b/carousel/test/res/values-am/strings.xml
index f1e8da2..5f4b961 100644
--- a/carousel/test/res/values-am/strings.xml
+++ b/carousel/test/res/values-am/strings.xml
@@ -21,7 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="music_demo_activity_label" msgid="4382090808250495841">"የሙዚቃ Carousel"</string>
<string name="carousel_test_activity_label" msgid="6014624482213318747">"የCarousel ፍተሻ"</string>
- <string name="carousel_test_activity_description" msgid="1632693812604375483">"የCarouselን ጥቅም የሚያሳይ መተግበሪያ"</string>
+ <string name="carousel_test_activity_description" msgid="1632693812604375483">"የCarouselን ጥቅም የሚያሳይ ትግበራ"</string>
<string name="task_switcher_activity_label" msgid="714620143340933546">"ክንውን ቀያያሪ"</string>
<string name="recent_tasks_title" msgid="1030287226205477117">"የቅርብ ጊዜ ትግበራዎች"</string>
<string name="no_recent_tasks" msgid="6884096266670555780">"ምንም የቅርብ ጊዜ ክንውን የለም"</string>
diff --git a/carousel/test/res/values-ca/strings.xml b/carousel/test/res/values-ca/strings.xml
index 9f85f5e..15d758c 100644
--- a/carousel/test/res/values-ca/strings.xml
+++ b/carousel/test/res/values-ca/strings.xml
@@ -21,7 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
<string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
- <string name="carousel_test_activity_description" msgid="1632693812604375483">"Una aplicació per mostrar com es fa servir l\'expositor giratori"</string>
+ <string name="carousel_test_activity_description" msgid="1632693812604375483">"Una aplicació per mostrar l\'ús de Carousel"</string>
<string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
<string name="recent_tasks_title" msgid="1030287226205477117">"Aplicacions usades recentment"</string>
<string name="no_recent_tasks" msgid="6884096266670555780">"No hi ha tasques recents"</string>
diff --git a/chips/Android.mk b/chips/Android.mk
index ef4b8ab..2bca597 100644
--- a/chips/Android.mk
+++ b/chips/Android.mk
@@ -17,7 +17,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE := android-common-chips
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
-LOCAL_SDK_VERSION := 14
+LOCAL_SDK_VERSION := 19
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-logtags-files-under, src)
diff --git a/chips/AndroidManifest.xml b/chips/AndroidManifest.xml
index 7487c18..02ea564 100644
--- a/chips/AndroidManifest.xml
+++ b/chips/AndroidManifest.xml
@@ -19,6 +19,6 @@
<uses-sdk
android:minSdkVersion="11"
- android:targetSdkVersion="18" />
+ android:targetSdkVersion="19" />
</manifest>
\ No newline at end of file
diff --git a/chips/project.properties b/chips/project.properties
new file mode 100644
index 0000000..91d2b02
--- /dev/null
+++ b/chips/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
+android.library=true
diff --git a/chips/res/layout/chips_recipient_dropdown_item.xml b/chips/res/layout/chips_recipient_dropdown_item.xml
index cd11d67..b02b197 100644
--- a/chips/res/layout/chips_recipient_dropdown_item.xml
+++ b/chips/res/layout/chips_recipient_dropdown_item.xml
@@ -33,25 +33,25 @@
android:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="8dip"
android:singleLine="true"
- android:ellipsize="end" />
+ android:ellipsize="end"
+ style="@style/ChipTitleStyle" />
<TextView android:id="@android:id/text1"
android:textColor="@drawable/list_item_font_secondary"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="16dip"
android:singleLine="true"
android:ellipsize="end"
- android:layout_marginTop="-4dip" />
+ android:layout_marginTop="-4dip"
+ style="@style/ChipSubtitleStyle" />
</LinearLayout>
<ImageView
android:id="@android:id/icon"
android:layout_width="48dip"
android:layout_height="48dip"
- android:layout_marginLeft="12dip"
android:src="@drawable/ic_contact_picture"
android:cropToPadding="true"
- android:scaleType="centerCrop" />
+ android:scaleType="centerCrop"
+ style="@style/ChipIconStyle" />
</LinearLayout>
diff --git a/chips/res/values-sw600dp/styles.xml b/chips/res/values-sw600dp/styles.xml
index b6014ea..00988a9 100644
--- a/chips/res/values-sw600dp/styles.xml
+++ b/chips/res/values-sw600dp/styles.xml
@@ -13,7 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources xmlns:tools="http://schemas.android.com/tools"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<style name="RecipientEditTextView" parent="@android:attr/autoCompleteTextViewStyle">
<item name="android:paddingLeft">8dip</item>
<item name="android:paddingRight">4dip</item>
@@ -25,5 +26,7 @@
<item name="android:layout_width">match_parent</item>
<item name="android:dropDownVerticalOffset">0dip</item>
<item name="android:dropDownHorizontalOffset">-4dip</item>
+ <item name="android:textAlignment" tools:ignore="NewApi">viewStart</item>
+ <item name="android:textDirection" tools:ignore="NewApi">locale</item>
</style>
</resources>
diff --git a/chips/res/values-v17/styles-v17.xml b/chips/res/values-v17/styles-v17.xml
new file mode 100644
index 0000000..d151a75
--- /dev/null
+++ b/chips/res/values-v17/styles-v17.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+ <style name="ChipTitleStyle">
+ <item name="android:paddingStart">@dimen/chip_title_padding_start</item>
+ </style>
+
+ <style name="ChipSubtitleStyle">
+ <item name="android:paddingStart">@dimen/chip_subtitle_padding_start</item>
+ </style>
+
+ <style name="ChipIconStyle">
+ <item name="android:layout_marginStart">@dimen/chip_icon_margin_start</item>
+ </style>
+</resources>
diff --git a/chips/res/values/attrs.xml b/chips/res/values/attrs.xml
index 44c2500..d3500aa 100644
--- a/chips/res/values/attrs.xml
+++ b/chips/res/values/attrs.xml
@@ -15,13 +15,21 @@
-->
<resources>
<declare-styleable name="RecipientEditTextView">
- <attr name="invalidChipBackground" format="reference" />
+ <attr name="avatarPosition">
+ <enum name="end" value="0" />
+ <enum name="start" value="1" />
+ </attr>
<attr name="chipBackground" format="reference" />
<attr name="chipBackgroundPressed" format="reference" />
<attr name="chipDelete" format="reference" />
- <attr name="chipAlternatesLayout" format="reference" />
- <attr name="chipPadding" format="reference" />
- <attr name="chipHeight" format="reference" />
<attr name="chipFontSize" format="reference" />
+ <attr name="chipHeight" format="reference" />
+ <attr name="chipPadding" format="reference" />
+ <attr name="disableDelete" format="boolean" />
+ <attr name="invalidChipBackground" format="reference" />
+ <attr name="imageSpanAlignment">
+ <enum name="bottom" value = "0"/>
+ <enum name="baseline" value = "1"/>
+ </attr>
</declare-styleable>
-</resources>
+</resources>
\ No newline at end of file
diff --git a/chips/res/values/dimen.xml b/chips/res/values/dimen.xml
index 98354d2..f989c86 100644
--- a/chips/res/values/dimen.xml
+++ b/chips/res/values/dimen.xml
@@ -20,4 +20,8 @@
<dimen name="chip_text_size">14sp</dimen>
<dimen name="line_spacing_extra">4dip</dimen>
<integer name="chips_max_lines">-1</integer>
-</resources>
\ No newline at end of file
+
+ <dimen name="chip_title_padding_start">8dip</dimen>
+ <dimen name="chip_subtitle_padding_start">16dip</dimen>
+ <dimen name="chip_icon_margin_start">12dip</dimen>
+</resources>
diff --git a/chips/res/values/styles.xml b/chips/res/values/styles.xml
index 8e2abf9..9b60cde 100644
--- a/chips/res/values/styles.xml
+++ b/chips/res/values/styles.xml
@@ -13,7 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources xmlns:tools="http://schemas.android.com/tools"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<style name="RecipientEditTextView" parent="@android:attr/autoCompleteTextViewStyle">
<item name="android:inputType">textEmailAddress|textMultiLine</item>
<item name="android:imeOptions">actionNext|flagNoFullscreen</item>
@@ -25,5 +26,19 @@
<item name="android:dropDownHorizontalOffset">-16dip</item>
<item name="android:minHeight">48dip</item>
<item name="android:lineSpacingExtra">@dimen/line_spacing_extra</item>
+ <item name="android:textAlignment" tools:ignore="NewApi">viewStart</item>
+ <item name="android:textDirection" tools:ignore="NewApi">locale</item>
+ </style>
+
+ <style name="ChipTitleStyle">
+ <item name="android:paddingLeft">@dimen/chip_title_padding_start</item>
+ </style>
+
+ <style name="ChipSubtitleStyle">
+ <item name="android:paddingLeft">@dimen/chip_subtitle_padding_start</item>
+ </style>
+
+ <style name="ChipIconStyle">
+ <item name="android:layout_marginLeft">@dimen/chip_icon_margin_start</item>
</style>
</resources>
diff --git a/chips/sample/Android.mk b/chips/sample/Android.mk
new file mode 100644
index 0000000..978aa7f
--- /dev/null
+++ b/chips/sample/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2013 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Include res dir from chips
+chips_dir := ../res
+res_dirs := res $(chips_dir)
+
+##################################################
+# Build APK
+include $(CLEAR_VARS)
+
+src_dirs := src
+LOCAL_PACKAGE_NAME := ChipsSample
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-common-chips
+
+LOCAL_SDK_VERSION := 18
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-logtags-files-under, $(src_dirs))
+LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips
+
+include $(BUILD_PACKAGE)
+
+
+##################################################
+# Build all sub-directories
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/chips/sample/AndroidManifest.xml b/chips/sample/AndroidManifest.xml
new file mode 100644
index 0000000..a490e29
--- /dev/null
+++ b/chips/sample/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.ex.chips.sample"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="18" />
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@android:style/Theme.Holo.Light" >
+ <activity
+ android:name="com.android.ex.chips.sample.MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/chips/sample/res/drawable-hdpi/ic_launcher.png b/chips/sample/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/chips/sample/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/chips/sample/res/drawable-mdpi/ic_launcher.png b/chips/sample/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/chips/sample/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/chips/sample/res/drawable-xhdpi/ic_launcher.png b/chips/sample/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/chips/sample/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/chips/sample/res/layout/activity_main.xml b/chips/sample/res/layout/activity_main.xml
new file mode 100644
index 0000000..01a9ff3
--- /dev/null
+++ b/chips/sample/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+<!-- Copyright (C) 2013 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"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".MainActivity" >
+
+ <com.android.ex.chips.RecipientEditTextView
+ android:id="@+id/email_retv"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/email_addresses"
+ android:minHeight="58dp" />
+
+ <com.android.ex.chips.RecipientEditTextView
+ android:id="@+id/phone_retv"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/phone_numbers"
+ android:minHeight="58dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/chips/sample/res/values-af/strings.xml b/chips/sample/res/values-af/strings.xml
new file mode 100644
index 0000000..e4c4945
--- /dev/null
+++ b/chips/sample/res/values-af/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips-voorbeeld"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-posadresse"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Foonnommers"</string>
+</resources>
diff --git a/chips/sample/res/values-am/strings.xml b/chips/sample/res/values-am/strings.xml
new file mode 100644
index 0000000..d19c4e8
--- /dev/null
+++ b/chips/sample/res/values-am/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"የቺፕስ ናሙና"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"የኢሜይል አድራሻዎች"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"ስልክ ቁጥሮች"</string>
+</resources>
diff --git a/chips/sample/res/values-ar/strings.xml b/chips/sample/res/values-ar/strings.xml
new file mode 100644
index 0000000..4492ec7
--- /dev/null
+++ b/chips/sample/res/values-ar/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"عينة شرائح"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"عناوين البريد الإلكتروني"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"أرقام الهواتف"</string>
+</resources>
diff --git a/chips/sample/res/values-bg/strings.xml b/chips/sample/res/values-bg/strings.xml
new file mode 100644
index 0000000..4c118c1
--- /dev/null
+++ b/chips/sample/res/values-bg/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Имейл адреси"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Телефонни номера"</string>
+</resources>
diff --git a/chips/sample/res/values-ca/strings.xml b/chips/sample/res/values-ca/strings.xml
new file mode 100644
index 0000000..847cc6f
--- /dev/null
+++ b/chips/sample/res/values-ca/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Mostra de xips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Adreces electròniques"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Números de telèfon"</string>
+</resources>
diff --git a/chips/sample/res/values-cs/strings.xml b/chips/sample/res/values-cs/strings.xml
new file mode 100644
index 0000000..3e0a928
--- /dev/null
+++ b/chips/sample/res/values-cs/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Vzorové čipy"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-mailové adresy"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefonní čísla"</string>
+</resources>
diff --git a/chips/sample/res/values-da/strings.xml b/chips/sample/res/values-da/strings.xml
new file mode 100644
index 0000000..e55fcc6
--- /dev/null
+++ b/chips/sample/res/values-da/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Eksempel på chips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-mailadresser"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefonnumre"</string>
+</resources>
diff --git a/chips/sample/res/values-de/strings.xml b/chips/sample/res/values-de/strings.xml
new file mode 100644
index 0000000..614081c
--- /dev/null
+++ b/chips/sample/res/values-de/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-Mail-Adressen"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefonnummern"</string>
+</resources>
diff --git a/chips/sample/res/values-el/strings.xml b/chips/sample/res/values-el/strings.xml
new file mode 100644
index 0000000..a90018a
--- /dev/null
+++ b/chips/sample/res/values-el/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Δείγμα τσιπ"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Διευθύνσεις ηλεκτρονικού ταχυδρομείου"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Αριθμοί τηλεφώνου"</string>
+</resources>
diff --git a/chips/sample/res/values-en-rGB/strings.xml b/chips/sample/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..aaccb10
--- /dev/null
+++ b/chips/sample/res/values-en-rGB/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Email Addresses"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Phone Numbers"</string>
+</resources>
diff --git a/chips/sample/res/values-en-rIN/strings.xml b/chips/sample/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..aaccb10
--- /dev/null
+++ b/chips/sample/res/values-en-rIN/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Email Addresses"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Phone Numbers"</string>
+</resources>
diff --git a/chips/sample/res/values-es-rUS/strings.xml b/chips/sample/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..e314778
--- /dev/null
+++ b/chips/sample/res/values-es-rUS/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Muestra de chips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Direcciones de correo electrónico"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Números de teléfono"</string>
+</resources>
diff --git a/chips/sample/res/values-es/strings.xml b/chips/sample/res/values-es/strings.xml
new file mode 100644
index 0000000..dd64514
--- /dev/null
+++ b/chips/sample/res/values-es/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Muestra de Chips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Direcciones de correo electrónico"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Números de teléfono"</string>
+</resources>
diff --git a/chips/sample/res/values-et-rEE/strings.xml b/chips/sample/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..5c7d6e5
--- /dev/null
+++ b/chips/sample/res/values-et-rEE/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-posti aadressid"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefoninumbrid"</string>
+</resources>
diff --git a/chips/sample/res/values-fa/strings.xml b/chips/sample/res/values-fa/strings.xml
new file mode 100644
index 0000000..8ee4162
--- /dev/null
+++ b/chips/sample/res/values-fa/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"نمونه تراشهها"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"آدرسهای ایمیل"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"شماره تلفنها"</string>
+</resources>
diff --git a/chips/sample/res/values-fi/strings.xml b/chips/sample/res/values-fi/strings.xml
new file mode 100644
index 0000000..c72df4d
--- /dev/null
+++ b/chips/sample/res/values-fi/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Sähköpostiosoitteet"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Puhelinnumerot"</string>
+</resources>
diff --git a/chips/sample/res/values-fr-rCA/strings.xml b/chips/sample/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..e88de2d
--- /dev/null
+++ b/chips/sample/res/values-fr-rCA/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Échantillon Chips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Adresses de courriel"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Numéros de téléphone"</string>
+</resources>
diff --git a/chips/sample/res/values-fr/strings.xml b/chips/sample/res/values-fr/strings.xml
new file mode 100644
index 0000000..2b1c18e
--- /dev/null
+++ b/chips/sample/res/values-fr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Échantillon Chips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Adresses e-mail"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Numéros de téléphone"</string>
+</resources>
diff --git a/chips/sample/res/values-hi/strings.xml b/chips/sample/res/values-hi/strings.xml
new file mode 100644
index 0000000..bae6585
--- /dev/null
+++ b/chips/sample/res/values-hi/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"चिप्स नमूने"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"ईमेल पते"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"फ़ोन नंबर"</string>
+</resources>
diff --git a/chips/sample/res/values-hr/strings.xml b/chips/sample/res/values-hr/strings.xml
new file mode 100644
index 0000000..6eb8a8e
--- /dev/null
+++ b/chips/sample/res/values-hr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-adrese"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefonski brojevi"</string>
+</resources>
diff --git a/chips/sample/res/values-hu/strings.xml b/chips/sample/res/values-hu/strings.xml
new file mode 100644
index 0000000..1d00752
--- /dev/null
+++ b/chips/sample/res/values-hu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"„Chips” minta"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-mail címek"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefonszámok"</string>
+</resources>
diff --git a/chips/sample/res/values-hy-rAM/strings.xml b/chips/sample/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..fbdcb21
--- /dev/null
+++ b/chips/sample/res/values-hy-rAM/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Չիպերի նմուշ"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Էլփոստի հասցեներ"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Հեռախոսահամարներ"</string>
+</resources>
diff --git a/chips/sample/res/values-in/strings.xml b/chips/sample/res/values-in/strings.xml
new file mode 100644
index 0000000..1ebd148
--- /dev/null
+++ b/chips/sample/res/values-in/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Contoh Chip"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Alamat Email"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Nomor Telepon"</string>
+</resources>
diff --git a/chips/sample/res/values-it/strings.xml b/chips/sample/res/values-it/strings.xml
new file mode 100644
index 0000000..aefbd01
--- /dev/null
+++ b/chips/sample/res/values-it/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Indirizzi email"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Numeri di telefono"</string>
+</resources>
diff --git a/chips/sample/res/values-iw/strings.xml b/chips/sample/res/values-iw/strings.xml
new file mode 100644
index 0000000..24c7e69
--- /dev/null
+++ b/chips/sample/res/values-iw/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"דוגמאות שבבים"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"כתובות דוא\"ל"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"מספרי טלפון"</string>
+</resources>
diff --git a/chips/sample/res/values-ja/strings.xml b/chips/sample/res/values-ja/strings.xml
new file mode 100644
index 0000000..c75120a
--- /dev/null
+++ b/chips/sample/res/values-ja/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"チップサンプル"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"メールアドレス"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"電話番号"</string>
+</resources>
diff --git a/chips/sample/res/values-ka-rGE/strings.xml b/chips/sample/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..a21dab5
--- /dev/null
+++ b/chips/sample/res/values-ka-rGE/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"ჩიპების ნიმუში"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"ელფოსტის მისამართები"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"ტელეფონის ნომრები"</string>
+</resources>
diff --git a/chips/sample/res/values-km-rKH/strings.xml b/chips/sample/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..3730a7d
--- /dev/null
+++ b/chips/sample/res/values-km-rKH/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"គំរូបន្ទះសៀគ្វី"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"អាសយដ្ឋានអ៊ីមែល"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"លេខទូរស័ព្ទ"</string>
+</resources>
diff --git a/chips/sample/res/values-ko/strings.xml b/chips/sample/res/values-ko/strings.xml
new file mode 100644
index 0000000..24d2793
--- /dev/null
+++ b/chips/sample/res/values-ko/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"칩 샘플"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"이메일 주소"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"전화번호"</string>
+</resources>
diff --git a/chips/sample/res/values-lo-rLA/strings.xml b/chips/sample/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..6357807
--- /dev/null
+++ b/chips/sample/res/values-lo-rLA/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"ທີ່ຢູ່ອີເມວ"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"ເບີໂທລະສັບ:"</string>
+</resources>
diff --git a/chips/sample/res/values-lt/strings.xml b/chips/sample/res/values-lt/strings.xml
new file mode 100644
index 0000000..b966062
--- /dev/null
+++ b/chips/sample/res/values-lt/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Lustų pavyzdžiai"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"El. pašto adresai"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefonų numeriai"</string>
+</resources>
diff --git a/chips/sample/res/values-lv/strings.xml b/chips/sample/res/values-lv/strings.xml
new file mode 100644
index 0000000..fec05b5
--- /dev/null
+++ b/chips/sample/res/values-lv/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-pasta adreses"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Tālruņa numuri"</string>
+</resources>
diff --git a/chips/sample/res/values-mn-rMN/strings.xml b/chips/sample/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..5289e5c
--- /dev/null
+++ b/chips/sample/res/values-mn-rMN/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Чипний дээж"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Имэйл хаягууд"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Утасны дугаарууд"</string>
+</resources>
diff --git a/chips/sample/res/values-ms-rMY/strings.xml b/chips/sample/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..12ab807
--- /dev/null
+++ b/chips/sample/res/values-ms-rMY/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Sampel Cip"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Alamat E-mel"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Nombor Telefon"</string>
+</resources>
diff --git a/chips/sample/res/values-nb/strings.xml b/chips/sample/res/values-nb/strings.xml
new file mode 100644
index 0000000..3bff3e2
--- /dev/null
+++ b/chips/sample/res/values-nb/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips-eksempel"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-postadresser"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefonnumre"</string>
+</resources>
diff --git a/chips/sample/res/values-nl/strings.xml b/chips/sample/res/values-nl/strings.xml
new file mode 100644
index 0000000..8951311
--- /dev/null
+++ b/chips/sample/res/values-nl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chipsvoorbeeld"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-mailadressen"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefoonnummers"</string>
+</resources>
diff --git a/chips/sample/res/values-pl/strings.xml b/chips/sample/res/values-pl/strings.xml
new file mode 100644
index 0000000..fedec0d
--- /dev/null
+++ b/chips/sample/res/values-pl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Próbka chipsów"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Adresy e-mail"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Numery telefonów"</string>
+</resources>
diff --git a/chips/sample/res/values-pt-rPT/strings.xml b/chips/sample/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..951d30a
--- /dev/null
+++ b/chips/sample/res/values-pt-rPT/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Amostra de Chips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Endereços de email"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Números de telefone"</string>
+</resources>
diff --git a/chips/sample/res/values-pt/strings.xml b/chips/sample/res/values-pt/strings.xml
new file mode 100644
index 0000000..9d2e732
--- /dev/null
+++ b/chips/sample/res/values-pt/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Amostra de chips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Endereços de e-mail"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Números de telefone"</string>
+</resources>
diff --git a/chips/sample/res/values-ro/strings.xml b/chips/sample/res/values-ro/strings.xml
new file mode 100644
index 0000000..bcffb5e
--- /dev/null
+++ b/chips/sample/res/values-ro/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Mostră Chips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Adrese de e-mail"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Numere de telefon"</string>
+</resources>
diff --git a/chips/sample/res/values-ru/strings.xml b/chips/sample/res/values-ru/strings.xml
new file mode 100644
index 0000000..10f052e
--- /dev/null
+++ b/chips/sample/res/values-ru/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Адреса эл. почты"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Номера телефонов"</string>
+</resources>
diff --git a/chips/sample/res/values-sk/strings.xml b/chips/sample/res/values-sk/strings.xml
new file mode 100644
index 0000000..1297298
--- /dev/null
+++ b/chips/sample/res/values-sk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Ukážka čipov"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-mailové adresy"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefónne čísla"</string>
+</resources>
diff --git a/chips/sample/res/values-sl/strings.xml b/chips/sample/res/values-sl/strings.xml
new file mode 100644
index 0000000..0e1c855
--- /dev/null
+++ b/chips/sample/res/values-sl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Vzorec čipov"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-poštni naslovi"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefonske številke"</string>
+</resources>
diff --git a/chips/sample/res/values-sr/strings.xml b/chips/sample/res/values-sr/strings.xml
new file mode 100644
index 0000000..dbd91a5
--- /dev/null
+++ b/chips/sample/res/values-sr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Пример чипова"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Адресе е-поште"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Бројеви телефона"</string>
+</resources>
diff --git a/chips/sample/res/values-sv/strings.xml b/chips/sample/res/values-sv/strings.xml
new file mode 100644
index 0000000..d787c85
--- /dev/null
+++ b/chips/sample/res/values-sv/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chipsprov"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-postadresser"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefonnummer"</string>
+</resources>
diff --git a/chips/sample/res/values-sw/strings.xml b/chips/sample/res/values-sw/strings.xml
new file mode 100644
index 0000000..5afd792
--- /dev/null
+++ b/chips/sample/res/values-sw/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Sampuli ya Chips"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Anwani za Barua Pepe"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Nambari za Simu"</string>
+</resources>
diff --git a/chips/sample/res/values-th/strings.xml b/chips/sample/res/values-th/strings.xml
new file mode 100644
index 0000000..80bf67d
--- /dev/null
+++ b/chips/sample/res/values-th/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"ตัวอย่างชิป"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"ที่อยู่อีเมล"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"หมายเลขโทรศัพท์"</string>
+</resources>
diff --git a/chips/sample/res/values-tl/strings.xml b/chips/sample/res/values-tl/strings.xml
new file mode 100644
index 0000000..411e0d4
--- /dev/null
+++ b/chips/sample/res/values-tl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Sample ng Mga Chip"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Mga Email Address"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Mga Numero ng Telepono"</string>
+</resources>
diff --git a/chips/sample/res/values-tr/strings.xml b/chips/sample/res/values-tr/strings.xml
new file mode 100644
index 0000000..dad01bb
--- /dev/null
+++ b/chips/sample/res/values-tr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Fiş Örneği"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"E-posta Adresleri"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Telefon Numaraları"</string>
+</resources>
diff --git a/chips/sample/res/values-uk/strings.xml b/chips/sample/res/values-uk/strings.xml
new file mode 100644
index 0000000..f09cb8c
--- /dev/null
+++ b/chips/sample/res/values-uk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Електронні адреси"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Номери телефонів"</string>
+</resources>
diff --git a/chips/sample/res/values-vi/strings.xml b/chips/sample/res/values-vi/strings.xml
new file mode 100644
index 0000000..b9bc474
--- /dev/null
+++ b/chips/sample/res/values-vi/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Mẫu chip"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Địa chỉ email"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Số điện thoại"</string>
+</resources>
diff --git a/chips/sample/res/values-zh-rCN/strings.xml b/chips/sample/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..ebee45c
--- /dev/null
+++ b/chips/sample/res/values-zh-rCN/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"电子邮件地址"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"电话号码"</string>
+</resources>
diff --git a/chips/sample/res/values-zh-rHK/strings.xml b/chips/sample/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..d2c3bb0
--- /dev/null
+++ b/chips/sample/res/values-zh-rHK/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"電郵地址"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"電話號碼"</string>
+</resources>
diff --git a/chips/sample/res/values-zh-rTW/strings.xml b/chips/sample/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..b502833
--- /dev/null
+++ b/chips/sample/res/values-zh-rTW/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"電子郵件地址"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"電話號碼"</string>
+</resources>
diff --git a/chips/sample/res/values-zu/strings.xml b/chips/sample/res/values-zu/strings.xml
new file mode 100644
index 0000000..6a106b7
--- /dev/null
+++ b/chips/sample/res/values-zu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="4076638519189386225">"Isempula yama-chip"</string>
+ <string name="email_addresses" msgid="5320415175940315400">"Amakheli we-imeyili"</string>
+ <string name="phone_numbers" msgid="7836326833170390688">"Izinombolo zefoni"</string>
+</resources>
diff --git a/chips/sample/res/values/strings.xml b/chips/sample/res/values/strings.xml
new file mode 100644
index 0000000..a40b20d
--- /dev/null
+++ b/chips/sample/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<resources>
+
+ <string name="app_name" translatable="false">Chips Sample</string>
+ <string name="email_addresses">Email Addresses</string>
+ <string name="phone_numbers">Phone Numbers</string>
+
+</resources>
diff --git a/chips/sample/src/com/android/ex/chips/sample/MainActivity.java b/chips/sample/src/com/android/ex/chips/sample/MainActivity.java
new file mode 100644
index 0000000..0622e65
--- /dev/null
+++ b/chips/sample/src/com/android/ex/chips/sample/MainActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 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.ex.chips.sample;
+
+import android.os.Bundle;
+import android.text.util.Rfc822Tokenizer;
+import android.widget.MultiAutoCompleteTextView;
+import android.app.Activity;
+
+import com.android.ex.chips.BaseRecipientAdapter;
+import com.android.ex.chips.RecipientEditTextView;
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ final RecipientEditTextView emailRetv =
+ (RecipientEditTextView) findViewById(R.id.email_retv);
+ emailRetv.setTokenizer(new Rfc822Tokenizer());
+ emailRetv.setAdapter(new BaseRecipientAdapter(this) { });
+
+ final RecipientEditTextView phoneRetv =
+ (RecipientEditTextView) findViewById(R.id.phone_retv);
+ phoneRetv.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
+ phoneRetv.setAdapter(
+ new BaseRecipientAdapter(BaseRecipientAdapter.QUERY_TYPE_PHONE, this) { });
+ }
+
+}
diff --git a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
index cdab4f3..468e168 100644
--- a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
+++ b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -43,12 +43,13 @@
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
-import android.widget.ImageView;
-import android.widget.TextView;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -59,8 +60,7 @@
/**
* Adapter for showing a recipient list.
*/
-public abstract class BaseRecipientAdapter extends BaseAdapter implements Filterable,
- AccountSpecifier {
+public class BaseRecipientAdapter extends BaseAdapter implements Filterable, AccountSpecifier {
private static final String TAG = "BaseRecipientAdapter";
private static final boolean DEBUG = false;
@@ -149,10 +149,11 @@
public final int destinationType;
public final String destinationLabel;
public final long contactId;
+ public final Long directoryId;
public final long dataId;
public final String thumbnailUriString;
public final int displayNameSource;
- public final boolean isGalContact;
+ public final String lookupKey;
public TemporaryEntry(
String displayName,
@@ -160,31 +161,34 @@
int destinationType,
String destinationLabel,
long contactId,
+ Long directoryId,
long dataId,
String thumbnailUriString,
int displayNameSource,
- boolean isGalContact) {
+ String lookupKey) {
this.displayName = displayName;
this.destination = destination;
this.destinationType = destinationType;
this.destinationLabel = destinationLabel;
this.contactId = contactId;
+ this.directoryId = directoryId;
this.dataId = dataId;
this.thumbnailUriString = thumbnailUriString;
this.displayNameSource = displayNameSource;
- this.isGalContact = isGalContact;
+ this.lookupKey = lookupKey;
}
- public TemporaryEntry(Cursor cursor, boolean isGalContact) {
+ public TemporaryEntry(Cursor cursor, Long directoryId) {
this.displayName = cursor.getString(Queries.Query.NAME);
this.destination = cursor.getString(Queries.Query.DESTINATION);
this.destinationType = cursor.getInt(Queries.Query.DESTINATION_TYPE);
this.destinationLabel = cursor.getString(Queries.Query.DESTINATION_LABEL);
this.contactId = cursor.getLong(Queries.Query.CONTACT_ID);
+ this.directoryId = directoryId;
this.dataId = cursor.getLong(Queries.Query.DATA_ID);
this.thumbnailUriString = cursor.getString(Queries.Query.PHOTO_THUMBNAIL_URI);
this.displayNameSource = cursor.getInt(Queries.Query.DISPLAY_NAME_SOURCE);
- this.isGalContact = isGalContact;
+ this.lookupKey = cursor.getString(Queries.Query.LOOKUP_KEY);
}
}
@@ -236,7 +240,8 @@
}
try {
- defaultDirectoryCursor = doQuery(constraint, mPreferredMaxResultCount, null);
+ defaultDirectoryCursor = doQuery(constraint, mPreferredMaxResultCount,
+ null /* directoryId */);
if (defaultDirectoryCursor == null) {
if (DEBUG) {
@@ -256,7 +261,7 @@
// Note: At this point each entry doesn't contain any photo
// (thus getPhotoBytes() returns null).
putOneEntry(new TemporaryEntry(defaultDirectoryCursor,
- false /* isGalContact */),
+ null /* directoryId */),
true, entryMap, nonAggregatedEntries, existingDestinations);
}
@@ -387,7 +392,7 @@
if (cursor != null) {
while (cursor.moveToNext()) {
- tempEntries.add(new TemporaryEntry(cursor, true /* isGalContact */));
+ tempEntries.add(new TemporaryEntry(cursor, mParams.directoryId));
}
}
} finally {
@@ -460,7 +465,7 @@
private final LayoutInflater mInflater;
private Account mAccount;
private final int mPreferredMaxResultCount;
- private final Handler mHandler = new Handler();
+ private DropdownChipLayouter mDropdownChipLayouter;
/**
* {@link #mEntries} is responsible for showing every result for this Adapter. To
@@ -571,6 +576,15 @@
return mQueryType;
}
+ public void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
+ mDropdownChipLayouter = dropdownChipLayouter;
+ mDropdownChipLayouter.setQuery(mQuery);
+ }
+
+ public DropdownChipLayouter getDropdownChipLayouter() {
+ return mDropdownChipLayouter;
+ }
+
/**
* Set the account when known. Causes the search to prioritize contacts from that account.
*/
@@ -688,8 +702,8 @@
entry.displayName,
entry.displayNameSource,
entry.destination, entry.destinationType, entry.destinationLabel,
- entry.contactId, entry.dataId, entry.thumbnailUriString, true,
- entry.isGalContact));
+ entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
+ true, entry.lookupKey));
} else if (entryMap.containsKey(entry.contactId)) {
// We already have a section for the person.
final List<RecipientEntry> entryList = entryMap.get(entry.contactId);
@@ -697,16 +711,16 @@
entry.displayName,
entry.displayNameSource,
entry.destination, entry.destinationType, entry.destinationLabel,
- entry.contactId, entry.dataId, entry.thumbnailUriString, true,
- entry.isGalContact));
+ entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
+ true, entry.lookupKey));
} else {
final List<RecipientEntry> entryList = new ArrayList<RecipientEntry>();
entryList.add(RecipientEntry.constructTopLevelEntry(
entry.displayName,
entry.displayNameSource,
entry.destination, entry.destinationType, entry.destinationLabel,
- entry.contactId, entry.dataId, entry.thumbnailUriString, true,
- entry.isGalContact));
+ entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
+ true, entry.lookupKey));
entryMap.put(entry.contactId, entryList);
}
}
@@ -750,7 +764,7 @@
}
- protected interface EntriesUpdatedObserver {
+ public interface EntriesUpdatedObserver {
public void onChanged(List<RecipientEntry> entries);
}
@@ -872,6 +886,39 @@
} finally {
photoCursor.close();
}
+ } else {
+ InputStream inputStream = null;
+ ByteArrayOutputStream outputStream = null;
+ try {
+ inputStream = mContentResolver.openInputStream(photoThumbnailUri);
+ final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
+
+ if (bitmap != null) {
+ outputStream = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
+ photoBytes = outputStream.toByteArray();
+
+ entry.setPhotoBytes(photoBytes);
+ mPhotoCacheMap.put(photoThumbnailUri, photoBytes);
+ }
+ } catch (final FileNotFoundException e) {
+ Log.w(TAG, "Error opening InputStream for photo", e);
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error closing photo input stream", e);
+ }
+ try {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error closing photo output stream", e);
+ }
+ }
}
}
@@ -917,7 +964,7 @@
}
@Override
- public Object getItem(int position) {
+ public RecipientEntry getItem(int position) {
return getEntries().get(position);
}
@@ -944,111 +991,12 @@
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final RecipientEntry entry = getEntries().get(position);
- String displayName = entry.getDisplayName();
- String destination = entry.getDestination();
- if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
- displayName = destination;
- // We only show the destination for secondary entries, so clear it
- // only for the first level.
- if (entry.isFirstLevel()) {
- destination = null;
- }
- }
+ final String constraint = mCurrentConstraint == null ? null :
+ mCurrentConstraint.toString();
- final View itemView = convertView != null ? convertView : mInflater.inflate(
- getItemLayout(), parent, false);
- final TextView displayNameView = (TextView) itemView.findViewById(getDisplayNameId());
- final TextView destinationView = (TextView) itemView.findViewById(getDestinationId());
- final TextView destinationTypeView = (TextView) itemView
- .findViewById(getDestinationTypeId());
- final ImageView imageView = (ImageView) itemView.findViewById(getPhotoId());
- displayNameView.setText(displayName);
- if (!TextUtils.isEmpty(destination)) {
- destinationView.setText(destination);
- } else {
- destinationView.setText(null);
- }
- if (destinationTypeView != null) {
- final CharSequence destinationType = mQuery
- .getTypeLabel(mContext.getResources(), entry.getDestinationType(),
- entry.getDestinationLabel()).toString().toUpperCase();
-
- destinationTypeView.setText(destinationType);
- }
-
- if (entry.isFirstLevel()) {
- displayNameView.setVisibility(View.VISIBLE);
- if (imageView != null) {
- imageView.setVisibility(View.VISIBLE);
- final byte[] photoBytes = entry.getPhotoBytes();
- if (photoBytes != null) {
- final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
- photoBytes.length);
- imageView.setImageBitmap(photo);
- } else {
- imageView.setImageResource(getDefaultPhotoResource());
- }
- }
- } else {
- displayNameView.setVisibility(View.GONE);
- if (imageView != null) {
- imageView.setVisibility(View.INVISIBLE);
- }
- }
- return itemView;
- }
-
- /**
- * Returns a layout id for each item inside auto-complete list.
- *
- * Each View must contain two TextViews (for display name and destination) and one ImageView
- * (for photo). Ids for those should be available via {@link #getDisplayNameId()},
- * {@link #getDestinationId()}, and {@link #getPhotoId()}.
- */
- protected int getItemLayout() {
- return R.layout.chips_recipient_dropdown_item;
- }
-
- /**
- * Returns a resource ID representing an image which should be shown when ther's no relevant
- * photo is available.
- */
- protected int getDefaultPhotoResource() {
- return R.drawable.ic_contact_picture;
- }
-
- /**
- * Returns an id for TextView in an item View for showing a display name. By default
- * {@link android.R.id#title} is returned.
- */
- protected int getDisplayNameId() {
- return android.R.id.title;
- }
-
- /**
- * Returns an id for TextView in an item View for showing a destination
- * (an email address or a phone number).
- * By default {@link android.R.id#text1} is returned.
- */
- protected int getDestinationId() {
- return android.R.id.text1;
- }
-
- /**
- * Returns an id for TextView in an item View for showing the type of the destination.
- * By default {@link android.R.id#text2} is returned.
- */
- protected int getDestinationTypeId() {
- return android.R.id.text2;
- }
-
- /**
- * Returns an id for ImageView in an item View for showing photo image for a person. In default
- * {@link android.R.id#icon} is returned.
- */
- protected int getPhotoId() {
- return android.R.id.icon;
+ return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
+ AdapterType.BASE_RECIPIENT, constraint);
}
public Account getAccount() {
diff --git a/chips/src/com/android/ex/chips/DropdownChipLayouter.java b/chips/src/com/android/ex/chips/DropdownChipLayouter.java
new file mode 100644
index 0000000..6b0e78e
--- /dev/null
+++ b/chips/src/com/android/ex/chips/DropdownChipLayouter.java
@@ -0,0 +1,274 @@
+package com.android.ex.chips;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.text.util.Rfc822Tokenizer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.ex.chips.Queries.Query;
+
+/**
+ * A class that inflates and binds the views in the dropdown list from
+ * RecipientEditTextView.
+ */
+public class DropdownChipLayouter {
+ /**
+ * The type of adapter that is requesting a chip layout.
+ */
+ public enum AdapterType {
+ BASE_RECIPIENT,
+ RECIPIENT_ALTERNATES,
+ SINGLE_RECIPIENT
+ }
+
+ private final LayoutInflater mInflater;
+ private final Context mContext;
+ private Query mQuery;
+
+ public DropdownChipLayouter(LayoutInflater inflater, Context context) {
+ mInflater = inflater;
+ mContext = context;
+ }
+
+ public void setQuery(Query query) {
+ mQuery = query;
+ }
+
+
+ /**
+ * Layouts and binds recipient information to the view. If convertView is null, inflates a new
+ * view with getItemLaytout().
+ *
+ * @param convertView The view to bind information to.
+ * @param parent The parent to bind the view to if we inflate a new view.
+ * @param entry The recipient entry to get information from.
+ * @param position The position in the list.
+ * @param type The adapter type that is requesting the bind.
+ * @param constraint The constraint typed in the auto complete view.
+ *
+ * @return A view ready to be shown in the drop down list.
+ */
+ public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
+ AdapterType type, String constraint) {
+ // Default to show all the information
+ String displayName = entry.getDisplayName();
+ String destination = entry.getDestination();
+ boolean showImage = true;
+ CharSequence destinationType = getDestinationType(entry);
+
+ final View itemView = reuseOrInflateView(convertView, parent, type);
+
+ final ViewHolder viewHolder = new ViewHolder(itemView);
+
+ // Hide some information depending on the entry type and adapter type
+ switch (type) {
+ case BASE_RECIPIENT:
+ if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
+ displayName = destination;
+
+ // We only show the destination for secondary entries, so clear it only for the
+ // first level.
+ if (entry.isFirstLevel()) {
+ destination = null;
+ }
+ }
+
+ if (!entry.isFirstLevel()) {
+ displayName = null;
+ showImage = false;
+ }
+ break;
+ case RECIPIENT_ALTERNATES:
+ if (position != 0) {
+ displayName = null;
+ showImage = false;
+ }
+ break;
+ case SINGLE_RECIPIENT:
+ destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress();
+ destinationType = null;
+ }
+
+ // Bind the information to the view
+ bindTextToView(displayName, viewHolder.displayNameView);
+ bindTextToView(destination, viewHolder.destinationView);
+ bindTextToView(destinationType, viewHolder.destinationTypeView);
+ bindIconToView(showImage, entry, viewHolder.imageView, type);
+
+ return itemView;
+ }
+
+ /**
+ * Returns a new view with {@link #getItemLayoutResId()}.
+ */
+ public View newView() {
+ return mInflater.inflate(getItemLayoutResId(), null);
+ }
+
+ /**
+ * Returns the same view, or inflates a new one if the given view was null.
+ */
+ protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) {
+ int itemLayout = getItemLayoutResId();
+ switch (type) {
+ case BASE_RECIPIENT:
+ case RECIPIENT_ALTERNATES:
+ break;
+ case SINGLE_RECIPIENT:
+ itemLayout = getAlternateItemLayoutResId();
+ break;
+ }
+ return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false);
+ }
+
+ /**
+ * Binds the text to the given text view. If the text was null, hides the text view.
+ */
+ protected void bindTextToView(CharSequence text, TextView view) {
+ if (view == null) {
+ return;
+ }
+
+ if (text != null) {
+ view.setText(text);
+ view.setVisibility(View.VISIBLE);
+ } else {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Binds the avatar icon to the image view. If we don't want to show the image, hides the
+ * image view.
+ */
+ protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
+ AdapterType type) {
+ if (view == null) {
+ return;
+ }
+
+ if (showImage) {
+ switch (type) {
+ case BASE_RECIPIENT:
+ byte[] photoBytes = entry.getPhotoBytes();
+ if (photoBytes != null && photoBytes.length > 0) {
+ final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
+ photoBytes.length);
+ view.setImageBitmap(photo);
+ } else {
+ view.setImageResource(getDefaultPhotoResId());
+ }
+ break;
+ case RECIPIENT_ALTERNATES:
+ Uri thumbnailUri = entry.getPhotoThumbnailUri();
+ if (thumbnailUri != null) {
+ // TODO: see if this needs to be done outside the main thread
+ // as it may be too slow to get immediately.
+ view.setImageURI(thumbnailUri);
+ } else {
+ view.setImageResource(getDefaultPhotoResId());
+ }
+ break;
+ case SINGLE_RECIPIENT:
+ default:
+ break;
+ }
+ view.setVisibility(View.VISIBLE);
+ } else {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ protected CharSequence getDestinationType(RecipientEntry entry) {
+ return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(),
+ entry.getDestinationLabel()).toString().toUpperCase();
+ }
+
+ /**
+ * Returns a layout id for each item inside auto-complete list.
+ *
+ * Each View must contain two TextViews (for display name and destination) and one ImageView
+ * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
+ * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
+ */
+ protected int getItemLayoutResId() {
+ return R.layout.chips_recipient_dropdown_item;
+ }
+
+ /**
+ * Returns a layout id for each item inside alternate auto-complete list.
+ *
+ * Each View must contain two TextViews (for display name and destination) and one ImageView
+ * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
+ * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
+ */
+ protected int getAlternateItemLayoutResId() {
+ return R.layout.chips_alternate_item;
+ }
+
+ /**
+ * Returns a resource ID representing an image which should be shown when ther's no relevant
+ * photo is available.
+ */
+ protected int getDefaultPhotoResId() {
+ return R.drawable.ic_contact_picture;
+ }
+
+ /**
+ * Returns an id for TextView in an item View for showing a display name. By default
+ * {@link android.R.id#title} is returned.
+ */
+ protected int getDisplayNameResId() {
+ return android.R.id.title;
+ }
+
+ /**
+ * Returns an id for TextView in an item View for showing a destination
+ * (an email address or a phone number).
+ * By default {@link android.R.id#text1} is returned.
+ */
+ protected int getDestinationResId() {
+ return android.R.id.text1;
+ }
+
+ /**
+ * Returns an id for TextView in an item View for showing the type of the destination.
+ * By default {@link android.R.id#text2} is returned.
+ */
+ protected int getDestinationTypeResId() {
+ return android.R.id.text2;
+ }
+
+ /**
+ * Returns an id for ImageView in an item View for showing photo image for a person. In default
+ * {@link android.R.id#icon} is returned.
+ */
+ protected int getPhotoResId() {
+ return android.R.id.icon;
+ }
+
+ /**
+ * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the
+ * corresponding views.
+ */
+ protected class ViewHolder {
+ public final TextView displayNameView;
+ public final TextView destinationView;
+ public final TextView destinationTypeView;
+ public final ImageView imageView;
+
+ public ViewHolder(View view) {
+ displayNameView = (TextView) view.findViewById(getDisplayNameResId());
+ destinationView = (TextView) view.findViewById(getDestinationResId());
+ destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId());
+ imageView = (ImageView) view.findViewById(getPhotoResId());
+ }
+ }
+}
diff --git a/chips/src/com/android/ex/chips/Queries.java b/chips/src/com/android/ex/chips/Queries.java
index 9d31aec..1e66b96 100644
--- a/chips/src/com/android/ex/chips/Queries.java
+++ b/chips/src/com/android/ex/chips/Queries.java
@@ -18,6 +18,7 @@
import android.content.res.Resources;
import android.net.Uri;
+import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
@@ -28,14 +29,16 @@
/* package */ class Queries {
public static final Query PHONE = new Query(new String[] {
- Contacts.DISPLAY_NAME, // 0
- Phone.NUMBER, // 1
- Phone.TYPE, // 2
- Phone.LABEL, // 3
- Phone.CONTACT_ID, // 4
- Phone._ID, // 5
- Contacts.PHOTO_THUMBNAIL_URI,// 6
- Contacts.DISPLAY_NAME_SOURCE // 7
+ Contacts.DISPLAY_NAME, // 0
+ Phone.NUMBER, // 1
+ Phone.TYPE, // 2
+ Phone.LABEL, // 3
+ Phone.CONTACT_ID, // 4
+ Phone._ID, // 5
+ Contacts.PHOTO_THUMBNAIL_URI, // 6
+ Contacts.DISPLAY_NAME_SOURCE, // 7
+ Contacts.LOOKUP_KEY, // 8
+ ContactsContract.CommonDataKinds.Email.MIMETYPE // 9
}, Phone.CONTENT_FILTER_URI, Phone.CONTENT_URI) {
@Override
@@ -46,14 +49,16 @@
};
public static final Query EMAIL = new Query(new String[]{
- Contacts.DISPLAY_NAME, // 0
- Email.DATA, // 1
- Email.TYPE, // 2
- Email.LABEL, // 3
- Email.CONTACT_ID, // 4
- Email._ID, // 5
- Contacts.PHOTO_THUMBNAIL_URI,// 6
- Contacts.DISPLAY_NAME_SOURCE // 7
+ Contacts.DISPLAY_NAME, // 0
+ Email.DATA, // 1
+ Email.TYPE, // 2
+ Email.LABEL, // 3
+ Email.CONTACT_ID, // 4
+ Email._ID, // 5
+ Contacts.PHOTO_THUMBNAIL_URI, // 6
+ Contacts.DISPLAY_NAME_SOURCE, // 7
+ Contacts.LOOKUP_KEY, // 8
+ ContactsContract.CommonDataKinds.Email.MIMETYPE // 9
}, Email.CONTENT_FILTER_URI, Email.CONTENT_URI) {
@Override
@@ -76,8 +81,10 @@
public static final int DATA_ID = 5; // long
public static final int PHOTO_THUMBNAIL_URI = 6; // String
public static final int DISPLAY_NAME_SOURCE = 7; // int
+ public static final int LOOKUP_KEY = 8; // String
+ public static final int MIME_TYPE = 9; // String
- public Query (String[] projection, Uri contentFilter, Uri content) {
+ public Query(String[] projection, Uri contentFilter, Uri content) {
mProjection = projection;
mContentFilterUri = contentFilter;
mContentUri = content;
diff --git a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index b9a7c80..f6f662d 100644
--- a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -23,19 +23,18 @@
import android.database.MatrixCursor;
import android.net.Uri;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.android.ex.chips.BaseRecipientAdapter.DirectoryListQuery;
import com.android.ex.chips.BaseRecipientAdapter.DirectorySearchParams;
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
import com.android.ex.chips.Queries.Query;
import java.util.ArrayList;
@@ -51,7 +50,6 @@
*/
public class RecipientAlternatesAdapter extends CursorAdapter {
static final int MAX_LOOKUPS = 50;
- private final LayoutInflater mLayoutInflater;
private final long mCurrentId;
@@ -63,7 +61,10 @@
public static final int QUERY_TYPE_EMAIL = 0;
public static final int QUERY_TYPE_PHONE = 1;
- private Query mQuery;
+ private final Long mDirectoryId;
+ private DropdownChipLayouter mDropdownChipLayouter;
+
+ private static final Map<String, String> sCorrectedPhotoUris = new HashMap<String, String>();
public interface RecipientMatchCallback {
public void matchesFound(Map<String, RecipientEntry> results);
@@ -125,7 +126,7 @@
query.getProjection(),
query.getProjection()[Queries.Query.DESTINATION] + " IN ("
+ bindString.toString() + ")", addressArray, null);
- recipientEntries = processContactEntries(c);
+ recipientEntries = processContactEntries(c, null /* directoryId */);
callback.matchesFound(recipientEntries);
} finally {
if (c != null) {
@@ -165,6 +166,7 @@
if (paramsList != null) {
Cursor directoryContactsCursor = null;
for (String unresolvedAddress : unresolvedAddresses) {
+ Long directoryId = null;
for (int i = 0; i < paramsList.size(); i++) {
try {
directoryContactsCursor = doQuery(unresolvedAddress, 1,
@@ -176,6 +178,7 @@
directoryContactsCursor.close();
directoryContactsCursor = null;
} else {
+ directoryId = paramsList.get(i).directoryId;
break;
}
}
@@ -183,7 +186,7 @@
if (directoryContactsCursor != null) {
try {
final Map<String, RecipientEntry> entries =
- processContactEntries(directoryContactsCursor);
+ processContactEntries(directoryContactsCursor, directoryId);
for (final String address : entries.keySet()) {
matchesNotFound.remove(address);
@@ -214,7 +217,8 @@
callback.matchesNotFound(matchesNotFound);
}
- private static HashMap<String, RecipientEntry> processContactEntries(Cursor c) {
+ private static HashMap<String, RecipientEntry> processContactEntries(Cursor c,
+ Long directoryId) {
HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
if (c != null && c.moveToFirst()) {
do {
@@ -227,10 +231,11 @@
c.getInt(Queries.Query.DESTINATION_TYPE),
c.getString(Queries.Query.DESTINATION_LABEL),
c.getLong(Queries.Query.CONTACT_ID),
+ directoryId,
c.getLong(Queries.Query.DATA_ID),
c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
true,
- false /* isGalContact TODO(skennedy) We should look these up eventually */);
+ c.getString(Queries.Query.LOOKUP_KEY));
/*
* In certain situations, we may have two results for one address, where one of the
@@ -327,46 +332,74 @@
return cursor;
}
- public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
- OnCheckedItemChangedListener listener) {
- this(context, contactId, currentId, QUERY_TYPE_EMAIL, listener);
- }
-
- public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
- int queryMode, OnCheckedItemChangedListener listener) {
- super(context, getCursorForConstruction(context, contactId, queryMode), 0);
- mLayoutInflater = LayoutInflater.from(context);
+ public RecipientAlternatesAdapter(Context context, long contactId, Long directoryId,
+ String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
+ DropdownChipLayouter dropdownChipLayouter) {
+ super(context,
+ getCursorForConstruction(context, contactId, directoryId, lookupKey, queryMode), 0);
mCurrentId = currentId;
+ mDirectoryId = directoryId;
mCheckedItemChangedListener = listener;
- if (queryMode == QUERY_TYPE_EMAIL) {
- mQuery = Queries.EMAIL;
- } else if (queryMode == QUERY_TYPE_PHONE) {
- mQuery = Queries.PHONE;
- } else {
- mQuery = Queries.EMAIL;
- Log.e(TAG, "Unsupported query type: " + queryMode);
- }
+ mDropdownChipLayouter = dropdownChipLayouter;
}
- private static Cursor getCursorForConstruction(Context context, long contactId, int queryType) {
+ private static Cursor getCursorForConstruction(Context context, long contactId,
+ Long directoryId, String lookupKey, int queryType) {
final Cursor cursor;
+ final String desiredMimeType;
if (queryType == QUERY_TYPE_EMAIL) {
+ final Uri uri;
+ final StringBuilder selection = new StringBuilder();
+ selection.append(Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID]);
+ selection.append(" = ?");
+
+ if (directoryId == null || lookupKey == null) {
+ uri = Queries.EMAIL.getContentUri();
+ desiredMimeType = null;
+ } else {
+ final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
+ builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
+ .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+ String.valueOf(directoryId));
+ uri = builder.build();
+ desiredMimeType = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
+ }
cursor = context.getContentResolver().query(
- Queries.EMAIL.getContentUri(),
+ uri,
Queries.EMAIL.getProjection(),
- Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID] + " =?", new String[] {
+ selection.toString(), new String[] {
String.valueOf(contactId)
}, null);
} else {
+ final Uri uri;
+ final StringBuilder selection = new StringBuilder();
+ selection.append(Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID]);
+ selection.append(" = ?");
+
+ if (lookupKey == null) {
+ uri = Queries.PHONE.getContentUri();
+ desiredMimeType = null;
+ } else {
+ final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
+ builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
+ .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+ String.valueOf(directoryId));
+ uri = builder.build();
+ desiredMimeType = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
+ }
cursor = context.getContentResolver().query(
- Queries.PHONE.getContentUri(),
+ uri,
Queries.PHONE.getProjection(),
- Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID] + " =?", new String[] {
+ selection.toString(), new String[] {
String.valueOf(contactId)
}, null);
}
- return removeDuplicateDestinations(cursor);
+
+ final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
+ cursor.close();
+
+ return resultCursor;
}
/**
@@ -379,22 +412,53 @@
* - This method creates a MatrixCursor, so all data will be kept in memory. We wouldn't want
* to do this if the original cursor is large, but it's okay here because the alternate list
* won't be that big.
+ *
+ * @param desiredMimeType If this is non-<code>null</code>, only entries with this mime type
+ * will be added to the cursor
+ * @param lookupKey The lookup key used for this contact if there isn't one in the cursor. This
+ * should be the same one used in the query that returned the cursor
*/
// Visible for testing
- /* package */ static Cursor removeDuplicateDestinations(Cursor original) {
+ static Cursor removeUndesiredDestinations(final Cursor original, final String desiredMimeType,
+ final String lookupKey) {
final MatrixCursor result = new MatrixCursor(
original.getColumnNames(), original.getCount());
final HashSet<String> destinationsSeen = new HashSet<String>();
+ String defaultDisplayName = null;
+ String defaultPhotoThumbnailUri = null;
+ int defaultDisplayNameSource = 0;
+
+ // Find some nice defaults in case we need them
original.moveToPosition(-1);
while (original.moveToNext()) {
+ final String mimeType = original.getString(Query.MIME_TYPE);
+
+ if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(
+ mimeType)) {
+ // Store this data
+ defaultDisplayName = original.getString(Query.NAME);
+ defaultPhotoThumbnailUri = original.getString(Query.PHOTO_THUMBNAIL_URI);
+ defaultDisplayNameSource = original.getInt(Query.DISPLAY_NAME_SOURCE);
+ break;
+ }
+ }
+
+ original.moveToPosition(-1);
+ while (original.moveToNext()) {
+ if (desiredMimeType != null) {
+ final String mimeType = original.getString(Query.MIME_TYPE);
+ if (!desiredMimeType.equals(mimeType)) {
+ continue;
+ }
+ }
final String destination = original.getString(Query.DESTINATION);
if (destinationsSeen.contains(destination)) {
continue;
}
destinationsSeen.add(destination);
- result.addRow(new Object[] {
+ final Object[] row = new Object[] {
original.getString(Query.NAME),
original.getString(Query.DESTINATION),
original.getInt(Query.DESTINATION_TYPE),
@@ -402,8 +466,48 @@
original.getLong(Query.CONTACT_ID),
original.getLong(Query.DATA_ID),
original.getString(Query.PHOTO_THUMBNAIL_URI),
- original.getInt(Query.DISPLAY_NAME_SOURCE)
- });
+ original.getInt(Query.DISPLAY_NAME_SOURCE),
+ original.getString(Query.LOOKUP_KEY),
+ original.getString(Query.MIME_TYPE)
+ };
+
+ if (row[Query.NAME] == null) {
+ row[Query.NAME] = defaultDisplayName;
+ }
+ if (row[Query.PHOTO_THUMBNAIL_URI] == null) {
+ row[Query.PHOTO_THUMBNAIL_URI] = defaultPhotoThumbnailUri;
+ }
+ if ((Integer) row[Query.DISPLAY_NAME_SOURCE] == 0) {
+ row[Query.DISPLAY_NAME_SOURCE] = defaultDisplayNameSource;
+ }
+ if (row[Query.LOOKUP_KEY] == null) {
+ row[Query.LOOKUP_KEY] = lookupKey;
+ }
+
+ // Ensure we don't have two '?' like content://.../...?account_name=...?sz=...
+ final String photoThumbnailUri = (String) row[Query.PHOTO_THUMBNAIL_URI];
+ if (photoThumbnailUri != null) {
+ if (sCorrectedPhotoUris.containsKey(photoThumbnailUri)) {
+ row[Query.PHOTO_THUMBNAIL_URI] = sCorrectedPhotoUris.get(photoThumbnailUri);
+ } else if (photoThumbnailUri.indexOf('?') != photoThumbnailUri.lastIndexOf('?')) {
+ final String[] parts = photoThumbnailUri.split("\\?");
+ final StringBuilder correctedUriBuilder = new StringBuilder();
+ for (int i = 0; i < parts.length; i++) {
+ if (i == 1) {
+ correctedUriBuilder.append("?"); // We only want one of these
+ } else if (i > 1) {
+ correctedUriBuilder.append("&"); // And we want these elsewhere
+ }
+ correctedUriBuilder.append(parts[i]);
+ }
+
+ final String correctedUri = correctedUriBuilder.toString();
+ sCorrectedPhotoUris.put(photoThumbnailUri, correctedUri);
+ row[Query.PHOTO_THUMBNAIL_URI] = correctedUri;
+ }
+ }
+
+ result.addRow(row);
}
return result;
@@ -428,10 +532,11 @@
c.getInt(Queries.Query.DESTINATION_TYPE),
c.getString(Queries.Query.DESTINATION_LABEL),
c.getLong(Queries.Query.CONTACT_ID),
+ mDirectoryId,
c.getLong(Queries.Query.DATA_ID),
c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
true,
- false /* isGalContact TODO(skennedy) We should look these up eventually */);
+ c.getString(Queries.Query.LOOKUP_KEY));
}
@Override
@@ -439,7 +544,7 @@
Cursor cursor = getCursor();
cursor.moveToPosition(position);
if (convertView == null) {
- convertView = newView();
+ convertView = mDropdownChipLayouter.newView();
}
if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
mCheckedItemPosition = position;
@@ -451,44 +556,18 @@
return convertView;
}
- // TODO: this is VERY similar to the BaseRecipientAdapter. Can we combine
- // somehow?
@Override
public void bindView(View view, Context context, Cursor cursor) {
int position = cursor.getPosition();
-
- TextView display = (TextView) view.findViewById(android.R.id.title);
- ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
RecipientEntry entry = getRecipientEntry(position);
- if (position == 0) {
- display.setText(cursor.getString(Queries.Query.NAME));
- display.setVisibility(View.VISIBLE);
- // TODO: see if this needs to be done outside the main thread
- // as it may be too slow to get immediately.
- imageView.setImageURI(entry.getPhotoThumbnailUri());
- imageView.setVisibility(View.VISIBLE);
- } else {
- display.setVisibility(View.GONE);
- imageView.setVisibility(View.GONE);
- }
- TextView destination = (TextView) view.findViewById(android.R.id.text1);
- destination.setText(cursor.getString(Queries.Query.DESTINATION));
- TextView destinationType = (TextView) view.findViewById(android.R.id.text2);
- if (destinationType != null) {
- destinationType.setText(mQuery.getTypeLabel(context.getResources(),
- cursor.getInt(Queries.Query.DESTINATION_TYPE),
- cursor.getString(Queries.Query.DESTINATION_LABEL)).toString().toUpperCase());
- }
+ mDropdownChipLayouter.bindView(view, null, entry, position,
+ AdapterType.RECIPIENT_ALTERNATES, null);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return newView();
- }
-
- private View newView() {
- return mLayoutInflater.inflate(R.layout.chips_recipient_dropdown_item, null);
+ return mDropdownChipLayouter.newView();
}
/*package*/ static interface OnCheckedItemChangedListener {
diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java
index 0a1cdff..9633587 100644
--- a/chips/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java
@@ -30,6 +30,7 @@
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
+import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -149,14 +150,35 @@
private int mChipPadding;
+ /**
+ * Enumerator for avatar position. See attr.xml for more details.
+ * 0 for end, 1 for start.
+ */
+ private int mAvatarPosition;
+
+ private static final int AVATAR_POSITION_END = 0;
+
+ private static final int AVATAR_POSITION_START = 1;
+
+ /**
+ * Enumerator for image span alignment. See attr.xml for more details.
+ * 0 for bottom, 1 for baseline.
+ */
+ private int mImageSpanAlignment;
+
+ private static final int IMAGE_SPAN_ALIGNMENT_BOTTOM = 0;
+
+ private static final int IMAGE_SPAN_ALIGNMENT_BASELINE = 1;
+
+
+ private boolean mDisableDelete;
+
private Tokenizer mTokenizer;
private Validator mValidator;
private DrawableRecipientChip mSelectedChip;
- private int mAlternatesLayout;
-
private Bitmap mDefaultContactPhoto;
private ImageSpan mMoreChip;
@@ -254,6 +276,8 @@
private boolean mAttachedToWindow;
+ private DropdownChipLayouter mDropdownChipLayouter;
+
public RecipientEditTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setChipDimensions(context, attrs);
@@ -293,6 +317,12 @@
addTextChangedListener(mTextWatcher);
mGestureDetector = new GestureDetector(context, this);
setOnEditorActionListener(this);
+
+ setDropdownChipLayouter(new DropdownChipLayouter(LayoutInflater.from(context), context));
+ }
+
+ protected void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
+ mDropdownChipLayouter = dropdownChipLayouter;
}
@Override
@@ -433,20 +463,21 @@
@Override
public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
super.setAdapter(adapter);
- ((BaseRecipientAdapter) adapter)
- .registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
- @Override
- public void onChanged(List<RecipientEntry> entries) {
- // Scroll the chips field to the top of the screen so
- // that the user can see as many results as possible.
- if (entries != null && entries.size() > 0) {
- scrollBottomIntoView();
- }
- }
- });
+ BaseRecipientAdapter baseAdapter = (BaseRecipientAdapter) adapter;
+ baseAdapter.registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
+ @Override
+ public void onChanged(List<RecipientEntry> entries) {
+ // Scroll the chips field to the top of the screen so
+ // that the user can see as many results as possible.
+ if (entries != null && entries.size() > 0) {
+ scrollBottomIntoView();
+ }
+ }
+ });
+ baseAdapter.setDropdownChipLayouter(mDropdownChipLayouter);
}
- private void scrollBottomIntoView() {
+ protected void scrollBottomIntoView() {
if (mScrollView != null && mShouldShrink) {
int[] location = new int[2];
getLocationOnScreen(location);
@@ -462,6 +493,10 @@
}
}
+ protected ScrollView getScrollView() {
+ return mScrollView;
+ }
+
@Override
public void performValidation() {
// Do nothing. Chips handles its own validation.
@@ -543,122 +578,141 @@
TextUtils.TruncateAt.END);
}
+ /**
+ * Creates a bitmap of the given contact on a selected chip.
+ *
+ * @param contact The recipient entry to pull data from.
+ * @param paint The paint to use to draw the bitmap.
+ */
private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint) {
+ paint.setColor(sSelectedTextColor);
+ Bitmap photo;
+ if (mDisableDelete) {
+ // Show the avatar instead if we don't want to delete
+ photo = getAvatarIcon(contact);
+ } else {
+ photo = ((BitmapDrawable) mChipDelete).getBitmap();
+ }
+ return createChipBitmap(contact, paint, photo, mChipBackgroundPressed);
+ }
+
+ /**
+ * Creates a bitmap of the given contact on a selected chip.
+ *
+ * @param contact The recipient entry to pull data from.
+ * @param paint The paint to use to draw the bitmap.
+ */
+ // TODO: Is leaveBlankIconSpacer obsolete now that we have left and right attributes?
+ private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint,
+ boolean leaveBlankIconSpacer) {
+ Drawable background = getChipBackground(contact);
+ Bitmap photo = getAvatarIcon(contact);
+ paint.setColor(getContext().getResources().getColor(android.R.color.black));
+ return createChipBitmap(contact, paint, photo, background);
+ }
+
+ private Bitmap createChipBitmap(RecipientEntry contact, TextPaint paint, Bitmap icon,
+ Drawable background) {
+ if (background == null) {
+ Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+ return Bitmap.createBitmap(
+ (int) mChipHeight * 2, (int) mChipHeight, Bitmap.Config.ARGB_8888);
+ }
+
+ Rect backgroundPadding = new Rect();
+ background.getPadding(backgroundPadding);
+
// Ellipsize the text so that it takes AT MOST the entire width of the
// autocomplete text entry area. Make sure to leave space for padding
// on the sides.
int height = (int) mChipHeight;
- int deleteWidth = height;
+ // Since the icon is a square, it's width is equal to the maximum height it can be inside
+ // the chip.
+ int iconWidth = height - backgroundPadding.top - backgroundPadding.bottom;
float[] widths = new float[1];
paint.getTextWidths(" ", widths);
CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
- calculateAvailableWidth() - deleteWidth - widths[0]);
+ calculateAvailableWidth() - iconWidth - widths[0] - backgroundPadding.left
+ - backgroundPadding.right);;
+ int textWidth = (int) paint.measureText(ellipsizedText, 0, ellipsizedText.length());
// Make sure there is a minimum chip width so the user can ALWAYS
// tap a chip without difficulty.
- int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
- ellipsizedText.length()))
- + (mChipPadding * 2) + deleteWidth);
+ int width = Math.max(iconWidth * 2, textWidth + (mChipPadding * 2) + iconWidth
+ + backgroundPadding.left + backgroundPadding.right);
// Create the background of the chip.
Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(tmpBitmap);
- if (mChipBackgroundPressed != null) {
- mChipBackgroundPressed.setBounds(0, 0, width, height);
- mChipBackgroundPressed.draw(canvas);
- paint.setColor(sSelectedTextColor);
- // Vertically center the text in the chip.
- canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
- getTextYOffset((String) ellipsizedText, paint, height), paint);
- // Make the delete a square.
- Rect backgroundPadding = new Rect();
- mChipBackgroundPressed.getPadding(backgroundPadding);
- mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
+
+ // Draw the background drawable
+ background.setBounds(0, 0, width, height);
+ background.draw(canvas);
+ // Draw the text vertically aligned
+ int textX = shouldPositionAvatarOnRight() ?
+ mChipPadding + backgroundPadding.left :
+ width - backgroundPadding.right - mChipPadding - textWidth;
+ canvas.drawText(ellipsizedText, 0, ellipsizedText.length(),
+ textX, getTextYOffset(ellipsizedText.toString(), paint, height), paint);
+ if (icon != null) {
+ // Draw the icon
+ int iconX = shouldPositionAvatarOnRight() ?
+ width - backgroundPadding.right - iconWidth :
+ backgroundPadding.left;
+ RectF src = new RectF(0, 0, icon.getWidth(), icon.getHeight());
+ RectF dst = new RectF(iconX,
0 + backgroundPadding.top,
- width - backgroundPadding.right,
+ iconX + iconWidth,
height - backgroundPadding.bottom);
- mChipDelete.draw(canvas);
- } else {
- Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+ drawIconOnCanvas(icon, canvas, paint, src, dst);
}
return tmpBitmap;
}
+ /**
+ * Returns true if the avatar should be positioned at the right edge of the chip.
+ * Takes into account both the set avatar position (start or end) as well as whether
+ * the layout direction is LTR or RTL.
+ */
+ private boolean shouldPositionAvatarOnRight() {
+ final boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ?
+ getLayoutDirection() == LAYOUT_DIRECTION_RTL : false;
+ final boolean assignedPosition = mAvatarPosition == AVATAR_POSITION_END;
+ // If in Rtl mode, the position should be flipped.
+ return isRtl ? !assignedPosition : assignedPosition;
+ }
- private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint,
- boolean leaveBlankIconSpacer) {
- // Ellipsize the text so that it takes AT MOST the entire width of the
- // autocomplete text entry area. Make sure to leave space for padding
- // on the sides.
- int height = (int) mChipHeight;
- int iconWidth = height;
- float[] widths = new float[1];
- paint.getTextWidths(" ", widths);
- CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
- calculateAvailableWidth() - iconWidth - widths[0]);
- // Make sure there is a minimum chip width so the user can ALWAYS
- // tap a chip without difficulty.
- int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
- ellipsizedText.length()))
- + (mChipPadding * 2) + iconWidth);
+ /**
+ * Returns the avatar icon to use for this recipient entry. Returns null if we don't want to
+ * draw an icon for this recipient.
+ */
+ private Bitmap getAvatarIcon(RecipientEntry contact) {
+ // Don't draw photos for recipients that have been typed in OR generated on the fly.
+ long contactId = contact.getContactId();
+ boolean drawPhotos = isPhoneQuery() ?
+ contactId != RecipientEntry.INVALID_CONTACT
+ : (contactId != RecipientEntry.INVALID_CONTACT
+ && (contactId != RecipientEntry.GENERATED_CONTACT &&
+ !TextUtils.isEmpty(contact.getDisplayName())));
- // Create the background of the chip.
- Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(tmpBitmap);
- Drawable background = getChipBackground(contact);
- if (background != null) {
- background.setBounds(0, 0, width, height);
- background.draw(canvas);
-
- // Don't draw photos for recipients that have been typed in OR generated on the fly.
- long contactId = contact.getContactId();
- boolean drawPhotos = isPhoneQuery() ?
- contactId != RecipientEntry.INVALID_CONTACT
- : (contactId != RecipientEntry.INVALID_CONTACT
- && (contactId != RecipientEntry.GENERATED_CONTACT &&
- !TextUtils.isEmpty(contact.getDisplayName())));
- if (drawPhotos) {
- byte[] photoBytes = contact.getPhotoBytes();
- // There may not be a photo yet if anything but the first contact address
- // was selected.
- if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
- // TODO: cache this in the recipient entry?
- ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
- .getPhotoThumbnailUri());
- photoBytes = contact.getPhotoBytes();
- }
-
- Bitmap photo;
- if (photoBytes != null) {
- photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
- } else {
- // TODO: can the scaled down default photo be cached?
- photo = mDefaultContactPhoto;
- }
- // Draw the photo on the left side.
- if (photo != null) {
- RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
- Rect backgroundPadding = new Rect();
- mChipBackground.getPadding(backgroundPadding);
- RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
- 0 + backgroundPadding.top,
- width - backgroundPadding.right,
- height - backgroundPadding.bottom);
- Matrix matrix = new Matrix();
- matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
- canvas.drawBitmap(photo, matrix, paint);
- }
- } else if (!leaveBlankIconSpacer || isPhoneQuery()) {
- iconWidth = 0;
+ if (drawPhotos) {
+ byte[] photoBytes = contact.getPhotoBytes();
+ // There may not be a photo yet if anything but the first contact address
+ // was selected.
+ if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
+ // TODO: cache this in the recipient entry?
+ getAdapter().fetchPhoto(contact, contact.getPhotoThumbnailUri());
+ photoBytes = contact.getPhotoBytes();
}
- paint.setColor(getContext().getResources().getColor(android.R.color.black));
- // Vertically center the text in the chip.
- canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
- getTextYOffset((String)ellipsizedText, paint, height), paint);
- } else {
- Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+ if (photoBytes != null) {
+ return BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
+ } else {
+ // TODO: can the scaled down default photo be cached?
+ return mDefaultContactPhoto;
+ }
}
- return tmpBitmap;
+
+ return null;
}
/**
@@ -669,13 +723,26 @@
return contact.isValid() ? mChipBackground : mInvalidChipBackground;
}
- private static float getTextYOffset(String text, TextPaint paint, int height) {
+ /**
+ * Given a height, returns a Y offset that will draw the text in the middle of the height.
+ */
+ protected float getTextYOffset(String text, TextPaint paint, int height) {
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
int textHeight = bounds.bottom - bounds.top ;
return height - ((height - textHeight) / 2) - (int)paint.descent();
}
+ /**
+ * Draws the icon onto the canvas given the source rectangle of the bitmap and the destination
+ * rectangle of the canvas.
+ */
+ protected void drawIconOnCanvas(Bitmap icon, Canvas canvas, Paint paint, RectF src, RectF dst) {
+ Matrix matrix = new Matrix();
+ matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
+ canvas.drawBitmap(icon, matrix, paint);
+ }
+
private DrawableRecipientChip constructChipSpan(RecipientEntry contact, boolean pressed,
boolean leaveIconSpace) throws NullPointerException {
if (mChipBackground == null) {
@@ -698,13 +765,25 @@
// Pass the full text, un-ellipsized, to the chip.
Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
- DrawableRecipientChip recipientChip = new VisibleRecipientChip(result, contact);
+ DrawableRecipientChip recipientChip =
+ new VisibleRecipientChip(result, contact, getImageSpanAlignment());
// Return text to the original size.
paint.setTextSize(defaultSize);
paint.setColor(defaultColor);
return recipientChip;
}
+ private int getImageSpanAlignment() {
+ switch (mImageSpanAlignment) {
+ case IMAGE_SPAN_ALIGNMENT_BASELINE:
+ return ImageSpan.ALIGN_BASELINE;
+ case IMAGE_SPAN_ALIGNMENT_BOTTOM:
+ return ImageSpan.ALIGN_BOTTOM;
+ default:
+ return ImageSpan.ALIGN_BOTTOM;
+ }
+ }
+
/**
* Calculate the bottom of the line the chip will be located on using:
* 1) which line the chip appears on
@@ -750,11 +829,6 @@
if (mChipPadding == -1) {
mChipPadding = (int) r.getDimension(R.dimen.chip_padding);
}
- mAlternatesLayout = a.getResourceId(R.styleable.RecipientEditTextView_chipAlternatesLayout,
- -1);
- if (mAlternatesLayout == -1) {
- mAlternatesLayout = R.layout.chips_alternate_item;
- }
mDefaultContactPhoto = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
@@ -773,6 +847,10 @@
if (mInvalidChipBackground == null) {
mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
}
+ mAvatarPosition = a.getInt(R.styleable.RecipientEditTextView_avatarPosition, 0);
+ mImageSpanAlignment = a.getInt(R.styleable.RecipientEditTextView_imageSpanAlignment, 0);
+ mDisableDelete = a.getBoolean(R.styleable.RecipientEditTextView_disableDelete, false);
+
mLineSpacingExtra = r.getDimension(R.dimen.line_spacing_extra);
mMaxLines = r.getInteger(R.integer.chips_max_lines);
TypedValue tv = new TypedValue();
@@ -780,6 +858,7 @@
mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
.getDisplayMetrics());
}
+
a.recycle();
}
@@ -799,6 +878,10 @@
mChipHeight = height;
}
+ public float getChipHeight() {
+ return mChipHeight;
+ }
+
/**
* Set whether to shrink the recipients field such that at most
* one line of recipients chips are shown when the field loses
@@ -845,7 +928,8 @@
Rect bounds;
for (DrawableRecipientChip chip : chips) {
bounds = chip.getBounds();
- if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
+ if (getWidth() > 0 && bounds.right - bounds.left >
+ getWidth() - getPaddingLeft() - getPaddingRight()) {
// Need to redraw that chip.
replaceChip(chip, chip.getEntry());
}
@@ -1377,9 +1461,11 @@
Spannable span = getSpannable();
DrawableRecipientChip[] chips = span.getSpans(start, end, DrawableRecipientChip.class);
if (chips != null && chips.length > 0) {
+ dismissDropDown();
return;
}
} else if (isCompletedToken) {
+ dismissDropDown();
return;
}
super.performFiltering(text, keyCode);
@@ -1511,13 +1597,14 @@
}
private ListAdapter createAlternatesAdapter(DrawableRecipientChip chip) {
- return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
- ((BaseRecipientAdapter)getAdapter()).getQueryType(), this);
+ return new RecipientAlternatesAdapter(getContext(), chip.getContactId(),
+ chip.getDirectoryId(), chip.getLookupKey(), chip.getDataId(),
+ getAdapter().getQueryType(), this, mDropdownChipLayouter);
}
private ListAdapter createSingleAddressAdapter(DrawableRecipientChip currentChip) {
- return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
- .getEntry());
+ return new SingleRecipientArrayAdapter(getContext(), currentChip.getEntry(),
+ mDropdownChipLayouter);
}
@Override
@@ -1676,8 +1763,7 @@
}
private void submitItemAtPosition(int position) {
- RecipientEntry entry = createValidatedEntry(
- (RecipientEntry)getAdapter().getItem(position));
+ RecipientEntry entry = createValidatedEntry(getAdapter().getItem(position));
if (entry == null) {
return;
}
@@ -1987,8 +2073,7 @@
return constructChipSpan(
RecipientEntry.constructFakeEntry((String) text, isValid(text.toString())),
true, false);
- } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT
- || currentChip.isGalContact()) {
+ } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
int start = getChipStart(currentChip);
int end = getChipEnd(currentChip);
getSpannable().removeSpan(currentChip);
@@ -2123,14 +2208,20 @@
// Figure out the bounds of this chip and whether or not
// the user clicked in the X portion.
// TODO: Should x and y be used, or removed?
- return chip.isSelected() && offset == getChipEnd(chip);
+ if (mDisableDelete) {
+ return false;
+ }
+
+ return chip.isSelected() &&
+ ((mAvatarPosition == AVATAR_POSITION_END && offset == getChipEnd(chip)) ||
+ (mAvatarPosition != AVATAR_POSITION_END && offset == getChipStart(chip)));
}
/**
* Remove the chip and any text associated with it from the RecipientEditTextView.
*/
// Visible for testing.
- /*pacakge*/ void removeChip(DrawableRecipientChip chip) {
+ /* package */void removeChip(DrawableRecipientChip chip) {
Spannable spannable = getSpannable();
int spanStart = spannable.getSpanStart(chip);
int spanEnd = spannable.getSpanEnd(chip);
@@ -2234,6 +2325,7 @@
if (mMoreChip != null) {
spannable.removeSpan(mMoreChip);
}
+ clearSelectedChip();
return;
}
// Get whether there are any recipients pending addition to the
@@ -2524,7 +2616,7 @@
addresses.add(createAddressText(chip.getEntry()));
}
}
- final BaseRecipientAdapter adapter = (BaseRecipientAdapter) getAdapter();
+ final BaseRecipientAdapter adapter = getAdapter();
RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
adapter.getAccount(), new RecipientMatchCallback() {
@Override
@@ -2652,9 +2744,9 @@
addresses.add(createAddressText(chip.getEntry()));
}
}
- final BaseRecipientAdapter adapter = (BaseRecipientAdapter) getAdapter();
+ final BaseRecipientAdapter adapter = getAdapter();
RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
- ((BaseRecipientAdapter) getAdapter()).getAccount(),
+ adapter.getAccount(),
new RecipientMatchCallback() {
@Override
@@ -2884,7 +2976,11 @@
protected boolean isPhoneQuery() {
return getAdapter() != null
- && ((BaseRecipientAdapter) getAdapter()).getQueryType()
- == BaseRecipientAdapter.QUERY_TYPE_PHONE;
+ && getAdapter().getQueryType() == BaseRecipientAdapter.QUERY_TYPE_PHONE;
+ }
+
+ @Override
+ public BaseRecipientAdapter getAdapter() {
+ return (BaseRecipientAdapter) super.getAdapter();
}
}
diff --git a/chips/src/com/android/ex/chips/RecipientEntry.java b/chips/src/com/android/ex/chips/RecipientEntry.java
index 30fccae..7d9b87f 100644
--- a/chips/src/com/android/ex/chips/RecipientEntry.java
+++ b/chips/src/com/android/ex/chips/RecipientEntry.java
@@ -61,6 +61,8 @@
private final String mDestinationLabel;
/** ID for the person */
private final long mContactId;
+ /** ID for the directory this contact came from, or <code>null</code> */
+ private final Long mDirectoryId;
/** ID for the destination */
private final long mDataId;
private final boolean mIsDivider;
@@ -74,11 +76,13 @@
*/
private byte[] mPhotoBytes;
- private final boolean mIsGalContact;
+ /** See {@link ContactsContract.Contacts#LOOKUP_KEY} */
+ private final String mLookupKey;
private RecipientEntry(int entryType, String displayName, String destination,
- int destinationType, String destinationLabel, long contactId, long dataId,
- Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid, boolean isGalContact) {
+ int destinationType, String destinationLabel, long contactId, Long directoryId,
+ long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
+ String lookupKey) {
mEntryType = entryType;
mIsFirstLevel = isFirstLevel;
mDisplayName = displayName;
@@ -86,12 +90,13 @@
mDestinationType = destinationType;
mDestinationLabel = destinationLabel;
mContactId = contactId;
+ mDirectoryId = directoryId;
mDataId = dataId;
mPhotoThumbnailUri = photoThumbnailUri;
mPhotoBytes = null;
mIsDivider = false;
mIsValid = isValid;
- mIsGalContact = isGalContact;
+ mLookupKey = lookupKey;
}
public boolean isValid() {
@@ -116,8 +121,8 @@
final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;
return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
- INVALID_DESTINATION_TYPE, null,
- INVALID_CONTACT, INVALID_CONTACT, null, true, isValid, false /* isGalContact */);
+ INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
+ INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
}
/**
@@ -126,8 +131,8 @@
public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
final boolean isValid) {
return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
- INVALID_DESTINATION_TYPE, null,
- INVALID_CONTACT, INVALID_CONTACT, null, true, isValid, false /* isGalContact */);
+ INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
+ INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
}
/**
@@ -149,35 +154,37 @@
public static RecipientEntry constructGeneratedEntry(String display, String address,
boolean isValid) {
return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
- null, GENERATED_CONTACT, GENERATED_CONTACT, null, true, isValid,
- false /* isGalContact */);
+ null, GENERATED_CONTACT, null /* directoryId */, GENERATED_CONTACT, null, true,
+ isValid, null /* lookupKey */);
}
public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
String destination, int destinationType, String destinationLabel, long contactId,
- long dataId, Uri photoThumbnailUri, boolean isValid, boolean isGalContact) {
+ Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
+ String lookupKey) {
return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
displayName, destination), destination, destinationType, destinationLabel,
- contactId, dataId, photoThumbnailUri, true, isValid, isGalContact);
+ contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey);
}
public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
String destination, int destinationType, String destinationLabel, long contactId,
- long dataId, String thumbnailUriAsString, boolean isValid, boolean isGalContact) {
+ Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid,
+ String lookupKey) {
return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
displayName, destination), destination, destinationType, destinationLabel,
- contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
- : null), true, isValid, isGalContact);
+ contactId, directoryId, dataId, (thumbnailUriAsString != null
+ ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey);
}
public static RecipientEntry constructSecondLevelEntry(String displayName,
int displayNameSource, String destination, int destinationType,
- String destinationLabel, long contactId, long dataId, String thumbnailUriAsString,
- boolean isValid, boolean isGalContact) {
+ String destinationLabel, long contactId, Long directoryId, long dataId,
+ String thumbnailUriAsString, boolean isValid, String lookupKey) {
return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
displayName, destination), destination, destinationType, destinationLabel,
- contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
- : null), false, isValid, isGalContact);
+ contactId, directoryId, dataId, (thumbnailUriAsString != null
+ ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey);
}
public int getEntryType() {
@@ -204,6 +211,10 @@
return mContactId;
}
+ public Long getDirectoryId() {
+ return mDirectoryId;
+ }
+
public long getDataId() {
return mDataId;
}
@@ -234,8 +245,8 @@
return mEntryType == ENTRY_TYPE_PERSON;
}
- public boolean isGalContact() {
- return mIsGalContact;
+ public String getLookupKey() {
+ return mLookupKey;
}
@Override
diff --git a/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java b/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
index 0571a4e..985953f 100644
--- a/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
+++ b/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
@@ -17,47 +17,27 @@
package com.android.ex.chips;
import android.content.Context;
-import android.text.util.Rfc822Tokenizer;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
class SingleRecipientArrayAdapter extends ArrayAdapter<RecipientEntry> {
- private int mLayoutId;
+ private final DropdownChipLayouter mDropdownChipLayouter;
- private final LayoutInflater mLayoutInflater;
-
- public SingleRecipientArrayAdapter(Context context, int resourceId, RecipientEntry entry) {
- super(context, resourceId, new RecipientEntry[] {
+ public SingleRecipientArrayAdapter(Context context, RecipientEntry entry,
+ DropdownChipLayouter dropdownChipLayouter) {
+ super(context, dropdownChipLayouter.getAlternateItemLayoutResId(), new RecipientEntry[] {
entry
});
- mLayoutInflater = LayoutInflater.from(context);
- mLayoutId = resourceId;
+
+ mDropdownChipLayouter = dropdownChipLayouter;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = newView();
- }
- bindView(convertView, getItem(position));
- return convertView;
- }
-
- private View newView() {
- return mLayoutInflater.inflate(mLayoutId, null);
- }
-
- private static void bindView(View view, RecipientEntry entry) {
- TextView display = (TextView) view.findViewById(android.R.id.title);
- ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
- display.setText(entry.getDisplayName());
- display.setVisibility(View.VISIBLE);
- imageView.setVisibility(View.VISIBLE);
- TextView destination = (TextView) view.findViewById(android.R.id.text1);
- destination.setText(Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress());
+ return mDropdownChipLayouter.bindView(convertView, parent, getItem(position), position,
+ AdapterType.SINGLE_RECIPIENT, null);
}
}
diff --git a/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
index 032d3b2..8012b5c 100644
--- a/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
+++ b/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
@@ -50,6 +50,16 @@
long getContactId();
/**
+ * Get the directory id of the contact associated with this chip.
+ */
+ Long getDirectoryId();
+
+ /**
+ * Get the directory lookup key associated with this chip, or <code>null</code>.
+ */
+ String getLookupKey();
+
+ /**
* Get the id of the data associated with this chip.
*/
long getDataId();
@@ -70,11 +80,4 @@
* before any reverse lookups.
*/
CharSequence getOriginalText();
-
- /**
- * Checks if this contact was retrieved from a GAL lookup.
- *
- * @return <code>true</code> if it came from GAL, <code>false</code> otherwise
- */
- boolean isGalContact();
}
diff --git a/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
index 11a66da..455f2cb 100644
--- a/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
+++ b/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
@@ -62,6 +62,16 @@
}
@Override
+ public Long getDirectoryId() {
+ return mDelegate.getDirectoryId();
+ }
+
+ @Override
+ public String getLookupKey() {
+ return mDelegate.getLookupKey();
+ }
+
+ @Override
public long getDataId() {
return mDelegate.getDataId();
}
@@ -82,11 +92,6 @@
}
@Override
- public boolean isGalContact() {
- return mDelegate.isGalContact();
- }
-
- @Override
public void draw(final Canvas canvas, final CharSequence text, final int start, final int end,
final float x, final int top, final int y, final int bottom, final Paint paint) {
// Do nothing.
diff --git a/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
index ac8e897..533f53f 100644
--- a/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
+++ b/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
@@ -27,6 +27,10 @@
private final long mContactId;
+ private final Long mDirectoryId;
+
+ private final String mLookupKey;
+
private final long mDataId;
private final RecipientEntry mEntry;
@@ -39,6 +43,8 @@
mDisplay = entry.getDisplayName();
mValue = entry.getDestination().trim();
mContactId = entry.getContactId();
+ mDirectoryId = entry.getDirectoryId();
+ mLookupKey = entry.getLookupKey();
mDataId = entry.getDataId();
mEntry = entry;
}
@@ -69,6 +75,16 @@
}
@Override
+ public Long getDirectoryId() {
+ return mDirectoryId;
+ }
+
+ @Override
+ public String getLookupKey() {
+ return mLookupKey;
+ }
+
+ @Override
public long getDataId() {
return mDataId;
}
@@ -93,11 +109,6 @@
}
@Override
- public boolean isGalContact() {
- return mEntry.isGalContact();
- }
-
- @Override
public String toString() {
return mDisplay + " <" + mValue + ">";
}
diff --git a/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
index 4637f69..6d3d27d 100644
--- a/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
+++ b/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
@@ -32,7 +32,12 @@
private final SimpleRecipientChip mDelegate;
public VisibleRecipientChip(final Drawable drawable, final RecipientEntry entry) {
- super(drawable, DynamicDrawableSpan.ALIGN_BOTTOM);
+ this(drawable, entry, DynamicDrawableSpan.ALIGN_BOTTOM);
+ }
+
+ public VisibleRecipientChip(final Drawable drawable, final RecipientEntry entry,
+ final int verticalAlignment) {
+ super(drawable, verticalAlignment);
mDelegate = new SimpleRecipientChip(entry);
}
@@ -63,6 +68,16 @@
}
@Override
+ public Long getDirectoryId() {
+ return mDelegate.getDirectoryId();
+ }
+
+ @Override
+ public String getLookupKey() {
+ return mDelegate.getLookupKey();
+ }
+
+ @Override
public long getDataId() {
return mDelegate.getDataId();
}
@@ -83,11 +98,6 @@
}
@Override
- public boolean isGalContact() {
- return mDelegate.isGalContact();
- }
-
- @Override
public Rect getBounds() {
return getDrawable().getBounds();
}
diff --git a/chips/tests/AndroidManifest.xml b/chips/tests/AndroidManifest.xml
index b84aecf..b2b307e 100644
--- a/chips/tests/AndroidManifest.xml
+++ b/chips/tests/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.ex.chipstests"
+ package="com.android.ex.chips.tests"
android:sharedUserId="com.android.uid.test">
<application>
@@ -24,7 +24,7 @@
<!-- Run tests with "runtest android-common" -->
<instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.ex.chipstests"
+ android:targetPackage="com.android.ex.chips.tests"
android:label="Chips Tests" />
</manifest>
diff --git a/chips/tests/src/com/android/ex/chips/ChipsTest.java b/chips/tests/src/com/android/ex/chips/ChipsTest.java
index 7963086..9116e26 100644
--- a/chips/tests/src/com/android/ex/chips/ChipsTest.java
+++ b/chips/tests/src/com/android/ex/chips/ChipsTest.java
@@ -32,7 +32,7 @@
import com.android.ex.chips.RecipientEditTextView;
import com.android.ex.chips.RecipientEntry;
import com.android.ex.chips.recipientchip.DrawableRecipientChip;
-import com.android.ex.chips.recipientchip.VisibleRecipientChip;;
+import com.android.ex.chips.recipientchip.VisibleRecipientChip;
import java.util.regex.Pattern;
diff --git a/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java b/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
index d4c0460..afb6a00 100644
--- a/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
+++ b/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
@@ -27,25 +27,28 @@
public class RecipientAlternatesAdapterTest extends AndroidTestCase {
- public void testRemoveDuplicateDestinations() {
+ public void testRemoveUndesiredDestinations() {
MatrixCursor c = new MatrixCursor(Queries.EMAIL.getProjection());
Cursor result;
// Test: Empty input
- assertEquals(0, RecipientAlternatesAdapter.removeDuplicateDestinations(c).getCount());
+ assertEquals(0, RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+ null /* desiredMimeType */, null /* lookupKey */).getCount());
// Test: One row
addRow(c, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
- result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+ result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+ null /* desiredMimeType */, null /* lookupKey */);
assertEquals(1, result.getCount());
assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
// Test: two unique rows, different destinations
addRow(c, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
- result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+ result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+ null /* desiredMimeType */, null /* lookupKey */);
assertEquals(2, result.getCount());
assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
assertRow(result, 1, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
@@ -54,7 +57,8 @@
addRow(c, "ax", "1@android.com", 11, "homex", 10001, 2000, "xx", 1);
// Third row should be removed.
- result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+ result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+ null /* desiredMimeType */, null /* lookupKey */);
assertEquals(2, result.getCount());
assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
assertRow(result, 1, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
@@ -63,7 +67,8 @@
addRow(c, "ax", "2@android.com", 11, "homex", 10001, 2000, "xx", 1);
// Forth row should also be removed.
- result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+ result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+ null /* desiredMimeType */, null /* lookupKey */);
assertEquals(2, result.getCount());
assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
assertRow(result, 1, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
@@ -120,8 +125,8 @@
{
final RecipientEntry entry1 =
RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
- "1@android.com", 0, null, 0, 0, (Uri) null, true,
- false /* isGalContact */);
+ "1@android.com", 0, null, 0, null /* directoryId */, 0, (Uri) null,
+ true, null /* lookupKey */);
final RecipientEntry entry2 = RecipientEntry.constructFakeEntry("1@android.com", true);
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
@@ -133,12 +138,12 @@
{
final RecipientEntry entry1 =
RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
- "1@android.com", 0, null, 0, 0, (Uri) null, true,
- false /* isGalContact */);
+ "1@android.com", 0, null, 0, null /* directoryId */, 0, (Uri) null,
+ true, null /* lookupKey */);
final RecipientEntry entry2 =
RecipientEntry.constructTopLevelEntry("2@android.com", DisplayNameSources.EMAIL,
- "2@android.com", 0, null, 0, 0, (Uri) null, true,
- false /* isGalContact */);
+ "2@android.com", 0, null, 0, null /* directoryId */, 0, (Uri) null,
+ true, null /* lookupKey */);
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
@@ -148,12 +153,12 @@
{
final RecipientEntry entry1 =
RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
- "1@android.com", 0, null, 0, 0, Uri.parse("http://www.android.com"),
- true, false /* isGalContact */);
+ "1@android.com", 0, null, 0, null /* directoryId */, 0,
+ Uri.parse("http://www.android.com"), true, null /* lookupKey */);
final RecipientEntry entry2 =
RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.EMAIL,
- "2@android.com", 0, null, 0, 0, (Uri) null, true,
- false /* isGalContact */);
+ "2@android.com", 0, null, 0, null /* directoryId */,
+ 0, (Uri) null, true, null /* lookupKey */);
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
diff --git a/framesequence/Android.mk b/framesequence/Android.mk
new file mode 100644
index 0000000..cc2c16c
--- /dev/null
+++ b/framesequence/Android.mk
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2014 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android-common-framesequence
+LOCAL_SDK_VERSION := 8
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/framesequence/AndroidManifest.xml b/framesequence/AndroidManifest.xml
new file mode 100644
index 0000000..f815643
--- /dev/null
+++ b/framesequence/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.support.rastermill">
+ <uses-sdk android:minSdkVersion="8"/>
+</manifest>
diff --git a/framesequence/build.xml b/framesequence/build.xml
new file mode 100644
index 0000000..b977ef7
--- /dev/null
+++ b/framesequence/build.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="rastermill" default="help">
+
+ <!-- The local.properties file is created and updated by the 'android' tool.
+ It contains the path to the SDK. It should *NOT* be checked into
+ Version Control Systems. -->
+ <property file="local.properties" />
+
+ <!-- The ant.properties file can be created by you. It is only edited by the
+ 'android' tool to add properties to it.
+ This is the place to change some Ant specific build properties.
+ Here are some properties you may want to change/update:
+
+ source.dir
+ The name of the source directory. Default is 'src'.
+ out.dir
+ The name of the output directory. Default is 'bin'.
+
+ For other overridable properties, look at the beginning of the rules
+ files in the SDK, at tools/ant/build.xml
+
+ Properties related to the SDK location or the project target should
+ be updated using the 'android' tool with the 'update' action.
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems.
+
+ -->
+ <property file="ant.properties" />
+
+ <property name="export.dir" value="exported_libs" />
+
+ <!-- if sdk.dir was not set from one of the property file, then
+ get it from the ANDROID_HOME env var.
+ This must be done before we load project.properties since
+ the proguard config can use sdk.dir -->
+ <property environment="env" />
+ <condition property="sdk.dir" value="${env.ANDROID_HOME}">
+ <isset property="env.ANDROID_HOME" />
+ </condition>
+
+ <!-- The project.properties file is created and updated by the 'android'
+ tool, as well as ADT.
+
+ This contains project specific properties such as project target, and library
+ dependencies. Lower level build properties are stored in ant.properties
+ (or in .classpath for Eclipse projects).
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems. -->
+ <loadproperties srcFile="project.properties" />
+
+ <!-- quick check on sdk.dir -->
+ <fail
+ message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
+ unless="sdk.dir"
+ />
+
+ <target name="-pre-build">
+ <exec executable="ndk-build" failonerror="true"/>
+ </target>
+
+ <target name="clean" depends="android_rules.clean">
+ <exec executable="ndk-build" failonerror="true">
+ <arg value="clean"/>
+ </exec>
+ <delete dir="${export.dir}" />
+ </target>
+
+ <!--
+ Import per project custom build rules if present at the root of the project.
+ This is the place to put custom intermediary targets such as:
+ -pre-build
+ -pre-compile
+ -post-compile (This is typically used for code obfuscation.
+ Compiled code location: ${out.classes.absolute.dir}
+ If this is not done in place, override ${out.dex.input.absolute.dir})
+ -post-package
+ -post-build
+ -pre-clean
+ -->
+ <import file="custom_rules.xml" optional="true" />
+
+ <!-- Import the actual build file.
+
+ To customize existing targets, there are two options:
+ - Customize only one target:
+ - copy/paste the target into this file, *before* the
+ <import> task.
+ - customize it to your needs.
+ - Customize the whole content of build.xml
+ - copy/paste the content of the rules files (minus the top node)
+ into this file, replacing the <import> task.
+ - customize to your needs.
+
+ ***********************
+ ****** IMPORTANT ******
+ ***********************
+ In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+ in order to avoid having your file be overridden by tools such as "android update project"
+ -->
+ <!-- version-tag: 1 -->
+ <import file="${sdk.dir}/tools/ant/build.xml" />
+
+ <target name="-post-build">
+ <delete dir="${export.dir}" />
+ <copy file="${out.library.jar.file}" tofile="${export.dir}/rastermill.jar" />
+ <copy todir="${export.dir}">
+ <fileset dir="${native.libs.absolute.dir}">
+ <include name="**/librastermill-native.so" />
+ </fileset>
+ </copy>
+ </target>
+
+</project>
diff --git a/framesequence/jni/Android.mk b/framesequence/jni/Android.mk
new file mode 100644
index 0000000..ee86fc1
--- /dev/null
+++ b/framesequence/jni/Android.mk
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2014 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+## Main library
+
+LOCAL_STATIC_LIBRARIES += libgif
+
+LOCAL_LDFLAGS := -llog -ljnigraphics
+
+LOCAL_C_INCLUDES := \
+ external/giflib
+
+LOCAL_MODULE := libframesequence
+LOCAL_SRC_FILES := \
+ BitmapDecoderJNI.cpp \
+ FrameSequence.cpp \
+ FrameSequenceJNI.cpp \
+ FrameSequence_gif.cpp \
+ JNIHelpers.cpp \
+ Registry.cpp \
+ Stream.cpp
+
+LOCAL_CFLAGS += -Wall -Wno-unused-parameter -Wno-unused-variable -Wno-overloaded-virtual
+LOCAL_CFLAGS += -fvisibility=hidden
+
+LOCAL_SDK_VERSION := 8
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/framesequence/jni/Application.mk b/framesequence/jni/Application.mk
new file mode 100644
index 0000000..eb51358
--- /dev/null
+++ b/framesequence/jni/Application.mk
@@ -0,0 +1,6 @@
+APP_PLATFORM := android-8
+APP_ABI := armeabi-v7a
+LOCAL_ARM_NEON=true
+ARCH_ARM_HAVE_NEON=true
+# TODO: Have libjpeg do this
+APP_CFLAGS := -D__ARM_HAVE_NEON=1
diff --git a/framesequence/jni/BitmapDecoderJNI.cpp b/framesequence/jni/BitmapDecoderJNI.cpp
new file mode 100644
index 0000000..5fe04b4
--- /dev/null
+++ b/framesequence/jni/BitmapDecoderJNI.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FancyDecoding"
+
+#include <android/bitmap.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "FrameSequenceJNI.h"
+#include "JNIHelpers.h"
+#include "Stream.h"
+#include "utils/log.h"
+
+void throwException(JNIEnv* env, const char* error) {
+ jclass clazz = env->FindClass("java/lang/RuntimeException");
+ env->ThrowNew(clazz, error);
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return -1;
+ }
+ if (FrameSequence_OnLoad(env)) {
+ ALOGE("Failed to load FrameSequence");
+ return -1;
+ }
+ if (JavaStream_OnLoad(env)) {
+ ALOGE("Failed to load JavaStream");
+ return -1;
+ }
+
+ return JNI_VERSION_1_6;
+}
diff --git a/framesequence/jni/Color.h b/framesequence/jni/Color.h
new file mode 100644
index 0000000..e49c64a
--- /dev/null
+++ b/framesequence/jni/Color.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RASTERMILL_COLOR_H
+#define RASTERMILL_COLOR_H
+
+#include <sys/types.h>
+
+typedef uint32_t Color8888;
+
+static const Color8888 COLOR_8888_ALPHA_MASK = 0xff000000; // TODO: handle endianness
+static const Color8888 TRANSPARENT = 0x0;
+
+// TODO: handle endianness
+#define ARGB_TO_COLOR8888(a, r, g, b) \
+ ((a) << 24 | (b) << 16 | (g) << 8 | (r))
+
+#endif // RASTERMILL_COLOR_H
diff --git a/framesequence/jni/FrameSequence.cpp b/framesequence/jni/FrameSequence.cpp
new file mode 100644
index 0000000..efcfefa
--- /dev/null
+++ b/framesequence/jni/FrameSequence.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FrameSequence.h"
+
+#include "Registry.h"
+
+FrameSequence* FrameSequence::create(Stream* stream) {
+ const RegistryEntry* entry = Registry::Find(stream);
+
+ if (!entry) return NULL;
+
+ FrameSequence* frameSequence = entry->createFrameSequence(stream);
+ if (!frameSequence->getFrameCount() ||
+ !frameSequence->getWidth() || !frameSequence->getHeight()) {
+ // invalid contents, abort
+ delete frameSequence;
+ return NULL;
+ }
+
+ return frameSequence;
+}
diff --git a/framesequence/jni/FrameSequence.h b/framesequence/jni/FrameSequence.h
new file mode 100644
index 0000000..6667cdd
--- /dev/null
+++ b/framesequence/jni/FrameSequence.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RASTERMILL_FRAME_SEQUENCE_H
+#define RASTERMILL_FRAME_SEQUENCE_H
+
+#include "Stream.h"
+#include "Color.h"
+
+class FrameSequenceState {
+public:
+ /**
+ * Produces a frame of animation in the output buffer, drawing (at minimum) the delta since
+ * previousFrameNr (the current contents of the buffer), or from scratch if previousFrameNr is
+ * negative
+ *
+ * Returns frame's delay time in milliseconds.
+ */
+ virtual long drawFrame(int frameNr,
+ Color8888* outputPtr, int outputPixelStride, int previousFrameNr) = 0;
+ virtual ~FrameSequenceState() {}
+};
+
+class FrameSequence {
+public:
+ /**
+ * Creates a FrameSequence using data from the data stream
+ *
+ * Type determined by header information in the stream
+ */
+ static FrameSequence* create(Stream* stream);
+
+ virtual ~FrameSequence() {}
+ virtual int getWidth() const = 0;
+ virtual int getHeight() const = 0;
+ virtual bool isOpaque() const = 0;
+ virtual int getFrameCount() const = 0;
+ virtual int getDefaultLoopCount() const = 0;
+
+ virtual FrameSequenceState* createState() const = 0;
+};
+
+#endif //RASTERMILL_FRAME_SEQUENCE_H
diff --git a/framesequence/jni/FrameSequenceJNI.cpp b/framesequence/jni/FrameSequenceJNI.cpp
new file mode 100644
index 0000000..efeed7e
--- /dev/null
+++ b/framesequence/jni/FrameSequenceJNI.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include "JNIHelpers.h"
+#include "utils/log.h"
+#include "FrameSequence.h"
+
+#include "FrameSequenceJNI.h"
+
+#define JNI_PACKAGE "android/support/rastermill"
+
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gFrameSequenceClassInfo;
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence
+////////////////////////////////////////////////////////////////////////////////
+
+static jobject createJavaFrameSequence(JNIEnv* env, FrameSequence* frameSequence) {
+ if (!frameSequence) {
+ return NULL;
+ }
+ return env->NewObject(gFrameSequenceClassInfo.clazz, gFrameSequenceClassInfo.ctor,
+ reinterpret_cast<jlong>(frameSequence),
+ frameSequence->getWidth(),
+ frameSequence->getHeight(),
+ frameSequence->isOpaque(),
+ frameSequence->getFrameCount(),
+ frameSequence->getDefaultLoopCount());
+}
+
+static jobject nativeDecodeByteArray(JNIEnv* env, jobject clazz,
+ jbyteArray byteArray, jint offset, jint length) {
+ jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(byteArray, NULL));
+ if (bytes == NULL) {
+ jniThrowException(env, ILLEGAL_STATE_EXEPTION,
+ "couldn't read array bytes");
+ return NULL;
+ }
+ bytes += offset;
+ MemoryStream stream(bytes, length);
+ FrameSequence* frameSequence = FrameSequence::create(&stream);
+ env->ReleasePrimitiveArrayCritical(byteArray, bytes, 0);
+ return createJavaFrameSequence(env, frameSequence);
+}
+
+static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
+ jobject istream, jbyteArray byteArray) {
+ JavaInputStream stream(env, istream, byteArray);
+ FrameSequence* frameSequence = FrameSequence::create(&stream);
+ return createJavaFrameSequence(env, frameSequence);
+}
+
+static void nativeDestroyFrameSequence(JNIEnv* env, jobject clazz,
+ jlong frameSequenceLong) {
+ FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceLong);
+ delete frameSequence;
+}
+
+static jlong nativeCreateState(JNIEnv* env, jobject clazz, jlong frameSequenceLong) {
+ FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceLong);
+ FrameSequenceState* state = frameSequence->createState();
+ return reinterpret_cast<jlong>(state);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence state
+////////////////////////////////////////////////////////////////////////////////
+
+static void nativeDestroyState(
+ JNIEnv* env, jobject clazz, jlong frameSequenceStateLong) {
+ FrameSequenceState* frameSequenceState =
+ reinterpret_cast<FrameSequenceState*>(frameSequenceStateLong);
+ delete frameSequenceState;
+}
+
+static jlong JNICALL nativeGetFrame(
+ JNIEnv* env, jobject clazz, jlong frameSequenceStateLong, jint frameNr,
+ jobject bitmap, jint previousFrameNr) {
+ FrameSequenceState* frameSequenceState =
+ reinterpret_cast<FrameSequenceState*>(frameSequenceStateLong);
+ int ret;
+ AndroidBitmapInfo info;
+ void* pixels;
+ if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
+
+ jniThrowException(env, ILLEGAL_STATE_EXEPTION,
+ "Couldn't get info from Bitmap ");
+ return 0;
+ }
+
+ if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
+ jniThrowException(env, ILLEGAL_STATE_EXEPTION,
+ "Bitmap pixels couldn't be locked");
+ return 0;
+ }
+
+ int pixelStride = info.stride >> 2;
+ jlong delayMs = frameSequenceState->drawFrame(frameNr,
+ (Color8888*) pixels, pixelStride, previousFrameNr);
+
+ AndroidBitmap_unlockPixels(env, bitmap);
+ return delayMs;
+}
+
+static JNINativeMethod gMethods[] = {
+ { "nativeDecodeByteArray",
+ "([BII)L" JNI_PACKAGE "/FrameSequence;",
+ (void*) nativeDecodeByteArray
+ },
+ { "nativeDecodeStream",
+ "(Ljava/io/InputStream;[B)L" JNI_PACKAGE "/FrameSequence;",
+ (void*) nativeDecodeStream
+ },
+ { "nativeDestroyFrameSequence",
+ "(J)V",
+ (void*) nativeDestroyFrameSequence
+ },
+ { "nativeCreateState",
+ "(J)J",
+ (void*) nativeCreateState
+ },
+ { "nativeGetFrame",
+ "(JILandroid/graphics/Bitmap;I)J",
+ (void*) nativeGetFrame
+ },
+ { "nativeDestroyState",
+ "(J)V",
+ (void*) nativeDestroyState
+ },
+};
+
+jint FrameSequence_OnLoad(JNIEnv* env) {
+ // Get jclass with env->FindClass.
+ // Register methods with env->RegisterNatives.
+ gFrameSequenceClassInfo.clazz = env->FindClass(JNI_PACKAGE "/FrameSequence");
+ if (!gFrameSequenceClassInfo.clazz) {
+ ALOGW("Failed to find " JNI_PACKAGE "/FrameSequence");
+ return -1;
+ }
+ gFrameSequenceClassInfo.clazz = (jclass)env->NewGlobalRef(gFrameSequenceClassInfo.clazz);
+
+ gFrameSequenceClassInfo.ctor = env->GetMethodID(gFrameSequenceClassInfo.clazz, "<init>", "(JIIZII)V");
+ if (!gFrameSequenceClassInfo.ctor) {
+ ALOGW("Failed to find constructor for FrameSequence - was it stripped?");
+ return -1;
+ }
+
+ return env->RegisterNatives(gFrameSequenceClassInfo.clazz, gMethods, METHOD_COUNT(gMethods));
+}
diff --git a/framesequence/jni/FrameSequenceJNI.h b/framesequence/jni/FrameSequenceJNI.h
new file mode 100644
index 0000000..a52df8a
--- /dev/null
+++ b/framesequence/jni/FrameSequenceJNI.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RASTERMILL_FRAMESEQUENCE_JNI
+#define RASTERMILL_FRAMESEQUENCE_JNI
+
+#include <jni.h>
+
+jint FrameSequence_OnLoad(JNIEnv* env);
+
+#endif // RASTERMILL_FRAMESEQUENCE_JNI
diff --git a/framesequence/jni/FrameSequence_gif.cpp b/framesequence/jni/FrameSequence_gif.cpp
new file mode 100644
index 0000000..2402439
--- /dev/null
+++ b/framesequence/jni/FrameSequence_gif.cpp
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+#include "JNIHelpers.h"
+#include "utils/log.h"
+#include "utils/math.h"
+
+#include "FrameSequence_gif.h"
+
+#define GIF_DEBUG 0
+
+// These constants are chosen to imitate common browser behavior
+// Note that 0 delay is undefined behavior in the gif standard
+static const long MIN_DELAY_MS = 20;
+static const long DEFAULT_DELAY_MS = 100;
+
+static int streamReader(GifFileType* fileType, GifByteType* out, int size) {
+ Stream* stream = (Stream*) fileType->UserData;
+ return (int) stream->read(out, size);
+}
+
+static Color8888 gifColorToColor8888(const GifColorType& color) {
+ return ARGB_TO_COLOR8888(0xff, color.Red, color.Green, color.Blue);
+}
+
+static long getDelayMs(GraphicsControlBlock& gcb) {
+ long delayMs = gcb.DelayTime * 10;
+ if (delayMs < MIN_DELAY_MS) {
+ return DEFAULT_DELAY_MS;
+ }
+ return delayMs;
+}
+
+static bool willBeCleared(const GraphicsControlBlock& gcb) {
+ return gcb.DisposalMode == DISPOSE_BACKGROUND || gcb.DisposalMode == DISPOSE_PREVIOUS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence
+////////////////////////////////////////////////////////////////////////////////
+
+FrameSequence_gif::FrameSequence_gif(Stream* stream) :
+ mLoopCount(1), mBgColor(TRANSPARENT), mPreservedFrames(NULL), mRestoringFrames(NULL) {
+ mGif = DGifOpen(stream, streamReader, NULL);
+ if (!mGif) {
+ ALOGW("Gif load failed");
+ return;
+ }
+
+ if (DGifSlurp(mGif) != GIF_OK) {
+ ALOGW("Gif slurp failed");
+ DGifCloseFile(mGif);
+ mGif = NULL;
+ return;
+ }
+
+ long durationMs = 0;
+ int lastUnclearedFrame = -1;
+ mPreservedFrames = new bool[mGif->ImageCount];
+ mRestoringFrames = new int[mGif->ImageCount];
+
+ GraphicsControlBlock gcb;
+ for (int i = 0; i < mGif->ImageCount; i++) {
+ const SavedImage& image = mGif->SavedImages[i];
+
+ // find the loop extension pair
+ for (int j = 0; (j + 1) < image.ExtensionBlockCount; j++) {
+ ExtensionBlock* eb1 = image.ExtensionBlocks + j;
+ ExtensionBlock* eb2 = image.ExtensionBlocks + j + 1;
+ if (eb1->Function == APPLICATION_EXT_FUNC_CODE &&
+ // look for "NETSCAPE2.0" app extension
+ eb1->ByteCount == 11 &&
+ !strcmp((const char*)(eb1->Bytes), "NETSCAPE2.0") &&
+ // verify extension contents and get loop count
+ eb2->Function == CONTINUE_EXT_FUNC_CODE &&
+ eb2->ByteCount == 3 &&
+ eb2->Bytes[0] == 1) {
+ mLoopCount = (int)(eb2->Bytes[2] & 0xff) + (int)(eb2->Bytes[1] & 0xff);
+ }
+ }
+
+ DGifSavedExtensionToGCB(mGif, i, &gcb);
+
+ // timing
+ durationMs += getDelayMs(gcb);
+
+ // preserve logic
+ mPreservedFrames[i] = false;
+ mRestoringFrames[i] = -1;
+ if (gcb.DisposalMode == DISPOSE_PREVIOUS && lastUnclearedFrame >= 0) {
+ mPreservedFrames[lastUnclearedFrame] = true;
+ mRestoringFrames[i] = lastUnclearedFrame;
+ }
+ if (!willBeCleared(gcb)) {
+ lastUnclearedFrame = i;
+ }
+ }
+
+#if GIF_DEBUG
+ ALOGD("FrameSequence_gif created with size %d %d, frames %d dur %ld",
+ mGif->SWidth, mGif->SHeight, mGif->ImageCount, durationMs);
+ for (int i = 0; i < mGif->ImageCount; i++) {
+ DGifSavedExtensionToGCB(mGif, i, &gcb);
+ ALOGD(" Frame %d - must preserve %d, restore point %d, trans color %d",
+ i, mPreservedFrames[i], mRestoringFrames[i], gcb.TransparentColor);
+ }
+#endif
+
+ if (mGif->SColorMap) {
+ // calculate bg color
+ GraphicsControlBlock gcb;
+ DGifSavedExtensionToGCB(mGif, 0, &gcb);
+ if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) {
+ mBgColor = gifColorToColor8888(mGif->SColorMap->Colors[mGif->SBackGroundColor]);
+ }
+ }
+}
+
+FrameSequence_gif::~FrameSequence_gif() {
+ if (mGif) {
+ DGifCloseFile(mGif);
+ }
+ delete[] mPreservedFrames;
+ delete[] mRestoringFrames;
+}
+
+FrameSequenceState* FrameSequence_gif::createState() const {
+ return new FrameSequenceState_gif(*this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// draw helpers
+////////////////////////////////////////////////////////////////////////////////
+
+// return true if area of 'target' is completely covers area of 'covered'
+static bool checkIfCover(const GifImageDesc& target, const GifImageDesc& covered) {
+ return target.Left <= covered.Left
+ && covered.Left + covered.Width <= target.Left + target.Width
+ && target.Top <= covered.Top
+ && covered.Top + covered.Height <= target.Top + target.Height;
+}
+
+static void copyLine(Color8888* dst, const unsigned char* src, const ColorMapObject* cmap,
+ int transparent, int width) {
+ for (; width > 0; width--, src++, dst++) {
+ if (*src != transparent) {
+ *dst = gifColorToColor8888(cmap->Colors[*src]);
+ }
+ }
+}
+
+static void setLineColor(Color8888* dst, Color8888 color, int width) {
+ for (; width > 0; width--, dst++) {
+ *dst = color;
+ }
+}
+
+static void getCopySize(const GifImageDesc& imageDesc, int maxWidth, int maxHeight,
+ GifWord& copyWidth, GifWord& copyHeight) {
+ copyWidth = imageDesc.Width;
+ if (imageDesc.Left + copyWidth > maxWidth) {
+ copyWidth = maxWidth - imageDesc.Left;
+ }
+ copyHeight = imageDesc.Height;
+ if (imageDesc.Top + copyHeight > maxHeight) {
+ copyHeight = maxHeight - imageDesc.Top;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence state
+////////////////////////////////////////////////////////////////////////////////
+
+FrameSequenceState_gif::FrameSequenceState_gif(const FrameSequence_gif& frameSequence) :
+ mFrameSequence(frameSequence), mPreserveBuffer(NULL), mPreserveBufferFrame(-1) {
+}
+
+FrameSequenceState_gif::~FrameSequenceState_gif() {
+ delete[] mPreserveBuffer;
+}
+
+void FrameSequenceState_gif::savePreserveBuffer(Color8888* outputPtr, int outputPixelStride, int frameNr) {
+ if (frameNr == mPreserveBufferFrame) return;
+
+ mPreserveBufferFrame = frameNr;
+ const int width = mFrameSequence.getWidth();
+ const int height = mFrameSequence.getHeight();
+ if (!mPreserveBuffer) {
+ mPreserveBuffer = new Color8888[width * height];
+ }
+ for (int y = 0; y < height; y++) {
+ memcpy(mPreserveBuffer + width * y,
+ outputPtr + outputPixelStride * y,
+ width * 4);
+ }
+}
+
+void FrameSequenceState_gif::restorePreserveBuffer(Color8888* outputPtr, int outputPixelStride) {
+ const int width = mFrameSequence.getWidth();
+ const int height = mFrameSequence.getHeight();
+ if (!mPreserveBuffer) {
+ ALOGD("preserve buffer not allocated! ah!");
+ return;
+ }
+ for (int y = 0; y < height; y++) {
+ memcpy(outputPtr + outputPixelStride * y,
+ mPreserveBuffer + width * y,
+ width * 4);
+ }
+}
+
+long FrameSequenceState_gif::drawFrame(int frameNr,
+ Color8888* outputPtr, int outputPixelStride, int previousFrameNr) {
+
+ GifFileType* gif = mFrameSequence.getGif();
+ if (!gif) {
+ ALOGD("Cannot drawFrame, mGif is NULL");
+ return -1;
+ }
+
+#if GIF_DEBUG
+ ALOGD(" drawFrame on %p nr %d on addr %p, previous frame nr %d",
+ this, frameNr, outputPtr, previousFrameNr);
+#endif
+
+ const int height = mFrameSequence.getHeight();
+ const int width = mFrameSequence.getWidth();
+
+ GraphicsControlBlock gcb;
+
+ int start = max(previousFrameNr + 1, 0);
+
+ for (int i = max(start - 1, 0); i < frameNr; i++) {
+ int neededPreservedFrame = mFrameSequence.getRestoringFrame(i);
+ if (neededPreservedFrame >= 0 && (mPreserveBufferFrame != neededPreservedFrame)) {
+#if GIF_DEBUG
+ ALOGD("frame %d needs frame %d preserved, but %d is currently, so drawing from scratch",
+ i, neededPreservedFrame, mPreserveBufferFrame);
+#endif
+ start = 0;
+ }
+ }
+
+ for (int i = start; i <= frameNr; i++) {
+ DGifSavedExtensionToGCB(gif, i, &gcb);
+ const SavedImage& frame = gif->SavedImages[i];
+
+#if GIF_DEBUG
+ bool frameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR;
+ ALOGD("producing frame %d, drawing frame %d (opaque %d, disp %d, del %d)",
+ frameNr, i, frameOpaque, gcb.DisposalMode, gcb.DelayTime);
+#endif
+ if (i == 0) {
+ //clear bitmap
+ Color8888 bgColor = mFrameSequence.getBackgroundColor();
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ outputPtr[y * outputPixelStride + x] = bgColor;
+ }
+ }
+ } else {
+ GraphicsControlBlock prevGcb;
+ DGifSavedExtensionToGCB(gif, i - 1, &prevGcb);
+ const SavedImage& prevFrame = gif->SavedImages[i - 1];
+ bool prevFrameDisposed = willBeCleared(prevGcb);
+
+ bool newFrameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR;
+ bool prevFrameCompletelyCovered = newFrameOpaque
+ && checkIfCover(frame.ImageDesc, prevFrame.ImageDesc);
+
+ if (prevFrameDisposed && !prevFrameCompletelyCovered) {
+ switch (prevGcb.DisposalMode) {
+ case DISPOSE_BACKGROUND: {
+ Color8888* dst = outputPtr + prevFrame.ImageDesc.Left +
+ prevFrame.ImageDesc.Top * outputPixelStride;
+
+ GifWord copyWidth, copyHeight;
+ getCopySize(prevFrame.ImageDesc, width, height, copyWidth, copyHeight);
+ for (; copyHeight > 0; copyHeight--) {
+ setLineColor(dst, TRANSPARENT, copyWidth);
+ dst += outputPixelStride;
+ }
+ } break;
+ case DISPOSE_PREVIOUS: {
+ restorePreserveBuffer(outputPtr, outputPixelStride);
+ } break;
+ }
+ }
+
+ if (mFrameSequence.getPreservedFrame(i - 1)) {
+ // currently drawn frame will be restored by a following DISPOSE_PREVIOUS draw, so
+ // we preserve it
+ savePreserveBuffer(outputPtr, outputPixelStride, i - 1);
+ }
+ }
+
+ bool willBeCleared = gcb.DisposalMode == DISPOSE_BACKGROUND
+ || gcb.DisposalMode == DISPOSE_PREVIOUS;
+ if (i == frameNr || !willBeCleared) {
+ const ColorMapObject* cmap = gif->SColorMap;
+ if (frame.ImageDesc.ColorMap) {
+ cmap = frame.ImageDesc.ColorMap;
+ }
+
+ if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
+ ALOGW("Warning: potentially corrupt color map");
+ }
+
+ const unsigned char* src = (unsigned char*)frame.RasterBits;
+ Color8888* dst = outputPtr + frame.ImageDesc.Left +
+ frame.ImageDesc.Top * outputPixelStride;
+ GifWord copyWidth, copyHeight;
+ getCopySize(frame.ImageDesc, width, height, copyWidth, copyHeight);
+ for (; copyHeight > 0; copyHeight--) {
+ copyLine(dst, src, cmap, gcb.TransparentColor, copyWidth);
+ src += frame.ImageDesc.Width;
+ dst += outputPixelStride;
+ }
+ }
+ }
+
+ // return last frame's delay
+ const int maxFrame = gif->ImageCount;
+ const int lastFrame = (frameNr + maxFrame - 1) % maxFrame;
+ DGifSavedExtensionToGCB(gif, lastFrame, &gcb);
+ return getDelayMs(gcb);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Registry
+////////////////////////////////////////////////////////////////////////////////
+
+#include "Registry.h"
+
+static bool isGif(void* header, int header_size) {
+ return !memcmp(GIF_STAMP, header, GIF_STAMP_LEN)
+ || !memcmp(GIF87_STAMP, header, GIF_STAMP_LEN)
+ || !memcmp(GIF89_STAMP, header, GIF_STAMP_LEN);
+}
+
+static FrameSequence* createFramesequence(Stream* stream) {
+ return new FrameSequence_gif(stream);
+}
+
+static RegistryEntry gEntry = {
+ GIF_STAMP_LEN,
+ isGif,
+ createFramesequence,
+ NULL,
+};
+static Registry gRegister(gEntry);
+
diff --git a/framesequence/jni/FrameSequence_gif.h b/framesequence/jni/FrameSequence_gif.h
new file mode 100644
index 0000000..8bf57b6
--- /dev/null
+++ b/framesequence/jni/FrameSequence_gif.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RASTERMILL_FRAMESQUENCE_GIF_H
+#define RASTERMILL_FRAMESQUENCE_GIF_H
+
+#include "config.h"
+#include "gif_lib.h"
+
+#include "Stream.h"
+#include "Color.h"
+#include "FrameSequence.h"
+
+class FrameSequence_gif : public FrameSequence {
+public:
+ FrameSequence_gif(Stream* stream);
+ virtual ~FrameSequence_gif();
+
+ virtual int getWidth() const {
+ return mGif ? mGif->SWidth : 0;
+ }
+
+ virtual int getHeight() const {
+ return mGif ? mGif->SHeight : 0;
+ }
+
+ virtual bool isOpaque() const {
+ return (mBgColor & COLOR_8888_ALPHA_MASK) == COLOR_8888_ALPHA_MASK;
+ }
+
+ virtual int getFrameCount() const {
+ return mGif ? mGif->ImageCount : 0;
+ }
+
+ virtual int getDefaultLoopCount() const {
+ return mLoopCount;
+ }
+
+ virtual FrameSequenceState* createState() const;
+
+ GifFileType* getGif() const { return mGif; }
+ Color8888 getBackgroundColor() const { return mBgColor; }
+ bool getPreservedFrame(int frameIndex) const { return mPreservedFrames[frameIndex]; }
+ int getRestoringFrame(int frameIndex) const { return mRestoringFrames[frameIndex]; }
+
+private:
+ GifFileType* mGif;
+ int mLoopCount;
+ Color8888 mBgColor;
+
+ // array of bool per frame - if true, frame data is used by a later DISPOSE_PREVIOUS frame
+ bool* mPreservedFrames;
+
+ // array of ints per frame - if >= 0, points to the index of the preserve that frame needs
+ int* mRestoringFrames;
+};
+
+class FrameSequenceState_gif : public FrameSequenceState {
+public:
+ FrameSequenceState_gif(const FrameSequence_gif& frameSequence);
+ virtual ~FrameSequenceState_gif();
+
+ // returns frame's delay time in ms
+ virtual long drawFrame(int frameNr,
+ Color8888* outputPtr, int outputPixelStride, int previousFrameNr);
+
+private:
+ void savePreserveBuffer(Color8888* outputPtr, int outputPixelStride, int frameNr);
+ void restorePreserveBuffer(Color8888* outputPtr, int outputPixelStride);
+
+ const FrameSequence_gif& mFrameSequence;
+ Color8888* mPreserveBuffer;
+ int mPreserveBufferFrame;
+};
+
+#endif //RASTERMILL_FRAMESQUENCE_GIF_H
diff --git a/framesequence/jni/JNIHelpers.cpp b/framesequence/jni/JNIHelpers.cpp
new file mode 100644
index 0000000..dd0c818
--- /dev/null
+++ b/framesequence/jni/JNIHelpers.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "JNIHelpers.h"
+#include "utils/log.h"
+
+void jniThrowException(JNIEnv* env, const char* className, const char* msg) {
+ jclass clazz = env->FindClass(className);
+ if (!clazz) {
+ ALOGE("Unable to find exception class %s", className);
+ /* ClassNotFoundException now pending */
+ return;
+ }
+
+ if (env->ThrowNew(clazz, msg) != JNI_OK) {
+ ALOGE("Failed throwing '%s' '%s'", className, msg);
+ /* an exception, most likely OOM, will now be pending */
+ }
+ env->DeleteLocalRef(clazz);
+}
diff --git a/framesequence/jni/JNIHelpers.h b/framesequence/jni/JNIHelpers.h
new file mode 100644
index 0000000..bb850d2
--- /dev/null
+++ b/framesequence/jni/JNIHelpers.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RASTERMILL_JNIHELPERS_H
+#define RASTERMILL_JNIHELPERS_H
+
+#include <jni.h>
+
+#define METHOD_COUNT(methodArray) (sizeof(methodArray) / sizeof(methodArray[0]))
+
+#define ILLEGAL_STATE_EXEPTION "java/lang/IllegalStateException"
+
+void jniThrowException(JNIEnv* env, const char* className, const char* msg);
+
+
+#endif //RASTERMILL_JNIHELPERS_H
diff --git a/framesequence/jni/Registry.cpp b/framesequence/jni/Registry.cpp
new file mode 100644
index 0000000..125ac66
--- /dev/null
+++ b/framesequence/jni/Registry.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Registry.h"
+
+#include "Stream.h"
+
+static Registry* gHead = 0;
+static int gHeaderBytesRequired = 0;
+
+Registry::Registry(const RegistryEntry& entry) {
+ mImpl = entry;
+
+ mNext = gHead;
+ gHead = this;
+
+ if (gHeaderBytesRequired < entry.requiredHeaderBytes) {
+ gHeaderBytesRequired = entry.requiredHeaderBytes;
+ }
+}
+
+const RegistryEntry* Registry::Find(Stream* stream) {
+ Registry* registry = gHead;
+ int headerSize = gHeaderBytesRequired;
+ char header[headerSize];
+ headerSize = stream->peek(header, headerSize);
+ while (registry) {
+ if (headerSize >= registry->mImpl.requiredHeaderBytes
+ && registry->mImpl.checkHeader(header, headerSize)) {
+ return &(registry->mImpl);
+ }
+ registry = registry->mNext;
+ }
+ return 0;
+}
diff --git a/framesequence/jni/Registry.h b/framesequence/jni/Registry.h
new file mode 100644
index 0000000..571c611
--- /dev/null
+++ b/framesequence/jni/Registry.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RASTERMILL_REGISTRY_H
+#define RASTERMILL_REGISTRY_H
+
+class FrameSequence;
+class Decoder;
+class Stream;
+
+struct RegistryEntry {
+ int requiredHeaderBytes;
+ bool (*checkHeader)(void* header, int header_size);
+ FrameSequence* (*createFrameSequence)(Stream* stream);
+ Decoder* (*createDecoder)(Stream* stream);
+};
+
+/**
+ * Template class for registering subclasses that can produce instances of themselves given a
+ * DataStream pointer.
+ *
+ * The super class / root constructable type only needs to define a single static construction
+ * meathod that creates an instance by iterating through all factory methods.
+ */
+class Registry {
+public:
+ Registry(const RegistryEntry& entry);
+
+ static const RegistryEntry* Find(Stream* stream);
+
+private:
+ RegistryEntry mImpl;
+ Registry* mNext;
+};
+
+#endif // RASTERMILL_REGISTRY_H
diff --git a/framesequence/jni/Stream.cpp b/framesequence/jni/Stream.cpp
new file mode 100644
index 0000000..b2e0c39
--- /dev/null
+++ b/framesequence/jni/Stream.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Stream"
+
+#include "Stream.h"
+
+#include <string.h>
+
+#include "JNIHelpers.h"
+#include "utils/log.h"
+#include "utils/math.h"
+
+static struct {
+ jmethodID read;
+ jmethodID reset;
+} gInputStreamClassInfo;
+
+Stream::Stream()
+ : mPeekBuffer(0)
+ , mPeekSize(0)
+ , mPeekOffset(0) {
+}
+
+Stream::~Stream() {
+ delete mPeekBuffer;
+}
+
+size_t Stream::peek(void* buffer, size_t size) {
+ size_t peek_remaining = mPeekSize - mPeekOffset;
+ if (size > peek_remaining) {
+ char* old_peek = mPeekBuffer;
+ mPeekBuffer = new char[size];
+ if (old_peek) {
+ memcpy(mPeekBuffer, old_peek + mPeekOffset, peek_remaining);
+ delete old_peek;
+ }
+ size_t read = doRead(mPeekBuffer + mPeekOffset, size - peek_remaining);
+ mPeekOffset = 0;
+ mPeekSize = peek_remaining + read;
+ }
+ size = min(size, mPeekSize - mPeekOffset);
+ memcpy(buffer, mPeekBuffer + mPeekOffset, size);
+ return size;
+}
+
+size_t Stream::read(void* buffer, size_t size) {
+ size_t bytes_read = 0;
+ size_t peek_remaining = mPeekSize - mPeekOffset;
+ if (peek_remaining) {
+ bytes_read = min(size, peek_remaining);
+ memcpy(buffer, mPeekBuffer + mPeekOffset, bytes_read);
+ mPeekOffset += bytes_read;
+ if (mPeekOffset == mPeekSize) {
+ delete mPeekBuffer;
+ mPeekBuffer = 0;
+ mPeekOffset = 0;
+ mPeekSize = 0;
+ }
+ size -= bytes_read;
+ buffer = ((char*) buffer) + bytes_read;
+ }
+ if (size) {
+ bytes_read += doRead(buffer, size);
+ }
+ return bytes_read;
+}
+
+size_t MemoryStream::doRead(void* buffer, size_t size) {
+ size = min(size, mRemaining);
+ memcpy(buffer, mBuffer, size);
+ mBuffer += size;
+ mRemaining -= size;
+ return size;
+}
+
+size_t FileStream::doRead(void* buffer, size_t size) {
+ return fread(buffer, 1, size, mFd);
+}
+
+size_t JavaInputStream::doRead(void* dstBuffer, size_t size) {
+ size_t totalBytesRead = 0;
+
+ do {
+ size_t requested = min(size, mByteArrayLength);
+
+ jint bytesRead = mEnv->CallIntMethod(mInputStream,
+ gInputStreamClassInfo.read, mByteArray, 0, requested);
+ if (mEnv->ExceptionCheck() || bytesRead < 0) {
+ return 0;
+ }
+
+ mEnv->GetByteArrayRegion(mByteArray, 0, bytesRead, (jbyte*)dstBuffer);
+ dstBuffer = (char*)dstBuffer + bytesRead;
+ totalBytesRead += bytesRead;
+ size -= bytesRead;
+ } while (size > 0);
+
+ return totalBytesRead;
+}
+
+jint JavaStream_OnLoad(JNIEnv* env) {
+ // Skip the verbose logging on error for these, as they won't be subject
+ // to obfuscators or similar and are thus unlikely to ever fail
+ jclass inputStreamClazz = env->FindClass("java/io/InputStream");
+ if (!inputStreamClazz) {
+ return -1;
+ }
+ gInputStreamClassInfo.read = env->GetMethodID(inputStreamClazz, "read", "([BII)I");
+ gInputStreamClassInfo.reset = env->GetMethodID(inputStreamClazz, "reset", "()V");
+ if (!gInputStreamClassInfo.read || !gInputStreamClassInfo.reset) {
+ return -1;
+ }
+ return 0;
+}
diff --git a/framesequence/jni/Stream.h b/framesequence/jni/Stream.h
new file mode 100644
index 0000000..f8f2427
--- /dev/null
+++ b/framesequence/jni/Stream.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RASTERMILL_STREAM_H
+#define RASTERMILL_STREAM_H
+
+#include <jni.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+class Stream {
+public:
+ Stream();
+ virtual ~Stream();
+
+ size_t peek(void* buffer, size_t size);
+ size_t read(void* buffer, size_t size);
+
+protected:
+ virtual size_t doRead(void* buffer, size_t size) = 0;
+
+private:
+ char* mPeekBuffer;
+ size_t mPeekSize;
+ size_t mPeekOffset;
+};
+
+class MemoryStream : public Stream {
+public:
+ MemoryStream(void* buffer, size_t size) :
+ mBuffer((char*)buffer),
+ mRemaining(size) {}
+
+protected:
+ virtual size_t doRead(void* buffer, size_t size);
+
+private:
+ char* mBuffer;
+ size_t mRemaining;
+};
+
+class FileStream : public Stream {
+public:
+ FileStream(FILE* fd) : mFd(fd) {}
+
+protected:
+ virtual size_t doRead(void* buffer, size_t size);
+
+private:
+ FILE* mFd;
+};
+
+class JavaInputStream : public Stream {
+public:
+ JavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray byteArray) :
+ mEnv(env),
+ mInputStream(inputStream),
+ mByteArray(byteArray),
+ mByteArrayLength(env->GetArrayLength(byteArray)) {}
+
+protected:
+ virtual size_t doRead(void* buffer, size_t size);
+
+private:
+ JNIEnv* mEnv;
+ const jobject mInputStream;
+ const jbyteArray mByteArray;
+ const size_t mByteArrayLength;
+};
+
+jint JavaStream_OnLoad(JNIEnv* env);
+
+#endif //RASTERMILL_STREAM_H
diff --git a/framesequence/jni/utils/log.h b/framesequence/jni/utils/log.h
new file mode 100644
index 0000000..5e15f30
--- /dev/null
+++ b/framesequence/jni/utils/log.h
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LOG_H_
+#define LOG_H_
+
+#include <android/log.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Normally we strip ALOGV (VERBOSE messages) from release builds.
+ * You can modify this (for example with "#define LOG_NDEBUG 0"
+ * at the top of your source file) to change that behavior.
+ */
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * This is the local tag used for the following simplified
+ * logging macros. You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+#ifndef LOG_TAG
+#define LOG_TAG "RasterMill"
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose log message using the current LOG_TAG.
+ */
+#ifndef ALOGV
+#if LOG_NDEBUG
+#define ALOGV(...) ((void)0)
+#else
+#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond) (__builtin_expect((cond)!=0, 0))
+
+#ifndef ALOGV_IF
+#if LOG_NDEBUG
+#define ALOGV_IF(cond, ...) ((void)0)
+#else
+#define ALOGV_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug log message using the current LOG_TAG.
+ */
+#ifndef ALOGD
+#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGD_IF
+#define ALOGD_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info log message using the current LOG_TAG.
+ */
+#ifndef ALOGI
+#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGI_IF
+#define ALOGI_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning log message using the current LOG_TAG.
+ */
+#ifndef ALOGW
+#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGW_IF
+#define ALOGW_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error log message using the current LOG_TAG.
+ */
+#ifndef ALOGE
+#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGE_IF
+#define ALOGE_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * verbose priority.
+ */
+#ifndef IF_ALOGV
+#if LOG_NDEBUG
+#define IF_ALOGV() if (false)
+#else
+#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
+#endif
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * debug priority.
+ */
+#ifndef IF_ALOGD
+#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * info priority.
+ */
+#ifndef IF_ALOGI
+#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * warn priority.
+ */
+#ifndef IF_ALOGW
+#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * error priority.
+ */
+#ifndef IF_ALOGE
+#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Log a fatal error. If the given condition fails, this stops program
+ * execution like a normal assertion, but also generating the given message.
+ * It is NOT stripped from release builds. Note that the condition test
+ * is -inverted- from the normal assert() semantics.
+ */
+#ifndef LOG_ALWAYS_FATAL_IF
+#define LOG_ALWAYS_FATAL_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)android_printAssert(#cond, LOG_TAG, ## __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+#ifndef LOG_ALWAYS_FATAL
+#define LOG_ALWAYS_FATAL(...) \
+ ( ((void)android_printAssert(NULL, LOG_TAG, ## __VA_ARGS__)) )
+#endif
+
+/*
+ * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
+ * are stripped out of release builds.
+ */
+#if LOG_NDEBUG
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) ((void)0)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) ((void)0)
+#endif
+
+#else
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ## __VA_ARGS__)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
+#endif
+
+#endif
+
+/*
+ * Assertion that generates a log message when the assertion fails.
+ * Stripped out of release builds. Uses the current LOG_TAG.
+ */
+#ifndef ALOG_ASSERT
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__)
+//#define ALOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Basic log message macro.
+ *
+ * Example:
+ * ALOG(LOG_WARN, NULL, "Failed with error %d", errno);
+ *
+ * The second argument may be NULL or "" to indicate the "global" tag.
+ */
+#ifndef ALOG
+#define ALOG(priority, tag, ...) \
+ LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef LOG_PRI
+#define LOG_PRI(priority, tag, ...) \
+ __android_log_print(priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to pass in a varargs ("args" is a va_list).
+ */
+#ifndef LOG_PRI_VA
+#define LOG_PRI_VA(priority, tag, fmt, args) \
+ __android_log_vprint(priority, NULL, tag, fmt, args)
+#endif
+
+/*
+ * Conditional given a desired logging priority and tag.
+ */
+#ifndef IF_ALOG
+#define IF_ALOG(priority, tag) \
+ if (__android_log_assert(ANDROID_##priority, tag))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LOG_H_ */
diff --git a/framesequence/jni/utils/math.h b/framesequence/jni/utils/math.h
new file mode 100644
index 0000000..87f100b
--- /dev/null
+++ b/framesequence/jni/utils/math.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MATH_H_
+#define MATH_H_
+
+#define max(a,b) \
+ ({ __typeof__ (a) _a = (a); \
+ __typeof__ (b) _b = (b); \
+ _a > _b ? _a : _b; })
+
+#define min(a,b) \
+ ({ __typeof__ (a) _a = (a); \
+ __typeof__ (b) _b = (b); \
+ _a < _b ? _a : _b; })
+
+#endif /* MATH_H_ */
diff --git a/framesequence/project.properties b/framesequence/project.properties
new file mode 100644
index 0000000..db721fd
--- /dev/null
+++ b/framesequence/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-8
+android.library=true
diff --git a/framesequence/samples/RastermillSamples/Android.mk b/framesequence/samples/RastermillSamples/Android.mk
new file mode 100644
index 0000000..bb8920f
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/Android.mk
@@ -0,0 +1,40 @@
+# Copyright (C) 2014 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := FrameSequenceSample
+
+# java dependency
+LOCAL_STATIC_JAVA_LIBRARIES += android-common-framesequence
+
+# native dependency
+ifneq (,$(TARGET_BUILD_APPS))
+ LOCAL_JNI_SHARED_LIBRARIES := libframesequence
+else
+ LOCAL_REQUIRED_MODULES := libframesequence
+endif
+
+# proguard for framesequence library code
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+LOCAL_SDK_VERSION := 19
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, res)
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+LOCAL_AAPT_FLAGS += --extra-packages com.android.rastermill.samples
+
+include $(BUILD_PACKAGE)
diff --git a/framesequence/samples/RastermillSamples/AndroidManifest.xml b/framesequence/samples/RastermillSamples/AndroidManifest.xml
new file mode 100644
index 0000000..b554021
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.rastermill.samples"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="15"
+ android:targetSdkVersion="18" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".SamplesList"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".AnimatedGifTest" />
+ </application>
+
+</manifest>
diff --git a/framesequence/samples/RastermillSamples/build.xml b/framesequence/samples/RastermillSamples/build.xml
new file mode 100644
index 0000000..5e55b4e
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/build.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="RastermillSamples" default="help">
+
+ <!-- The local.properties file is created and updated by the 'android' tool.
+ It contains the path to the SDK. It should *NOT* be checked into
+ Version Control Systems. -->
+ <property file="local.properties" />
+
+ <!-- The ant.properties file can be created by you. It is only edited by the
+ 'android' tool to add properties to it.
+ This is the place to change some Ant specific build properties.
+ Here are some properties you may want to change/update:
+
+ source.dir
+ The name of the source directory. Default is 'src'.
+ out.dir
+ The name of the output directory. Default is 'bin'.
+
+ For other overridable properties, look at the beginning of the rules
+ files in the SDK, at tools/ant/build.xml
+
+ Properties related to the SDK location or the project target should
+ be updated using the 'android' tool with the 'update' action.
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems.
+
+ -->
+ <property file="ant.properties" />
+
+ <!-- if sdk.dir was not set from one of the property file, then
+ get it from the ANDROID_HOME env var.
+ This must be done before we load project.properties since
+ the proguard config can use sdk.dir -->
+ <property environment="env" />
+ <condition property="sdk.dir" value="${env.ANDROID_HOME}">
+ <isset property="env.ANDROID_HOME" />
+ </condition>
+
+ <!-- The project.properties file is created and updated by the 'android'
+ tool, as well as ADT.
+
+ This contains project specific properties such as project target, and library
+ dependencies. Lower level build properties are stored in ant.properties
+ (or in .classpath for Eclipse projects).
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems. -->
+ <loadproperties srcFile="project.properties" />
+
+ <!-- quick check on sdk.dir -->
+ <fail
+ message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
+ unless="sdk.dir"
+ />
+
+ <target name="-pre-build">
+ <ant dir="../../" target="release" inheritAll="false" />
+ <copy todir="libs">
+ <fileset dir="../../exported_libs" />
+ </copy>
+ </target>
+
+ <!--
+ Import per project custom build rules if present at the root of the project.
+ This is the place to put custom intermediary targets such as:
+ -pre-build
+ -pre-compile
+ -post-compile (This is typically used for code obfuscation.
+ Compiled code location: ${out.classes.absolute.dir}
+ If this is not done in place, override ${out.dex.input.absolute.dir})
+ -post-package
+ -post-build
+ -pre-clean
+ -->
+ <import file="custom_rules.xml" optional="true" />
+
+ <!-- Import the actual build file.
+
+ To customize existing targets, there are two options:
+ - Customize only one target:
+ - copy/paste the target into this file, *before* the
+ <import> task.
+ - customize it to your needs.
+ - Customize the whole content of build.xml
+ - copy/paste the content of the rules files (minus the top node)
+ into this file, replacing the <import> task.
+ - customize to your needs.
+
+ ***********************
+ ****** IMPORTANT ******
+ ***********************
+ In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+ in order to avoid having your file be overridden by tools such as "android update project"
+ -->
+ <!-- version-tag: 1 -->
+ <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>
diff --git a/framesequence/samples/RastermillSamples/proguard.flags b/framesequence/samples/RastermillSamples/proguard.flags
new file mode 100644
index 0000000..4acde2d
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/proguard.flags
@@ -0,0 +1,3 @@
+-keep class android.support.rastermill.** {
+ *;
+}
diff --git a/framesequence/samples/RastermillSamples/project.properties b/framesequence/samples/RastermillSamples/project.properties
new file mode 100644
index 0000000..ce39f2d
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-18
diff --git a/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png b/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png b/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png b/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml b/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml
new file mode 100644
index 0000000..0b9a2df
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView
+ android:id="@+id/imageview"
+ android:layout_width="match_parent"
+ android:layout_height="300dp" />
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <Button
+ android:id="@+id/start"
+ android:text="@string/start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:id="@+id/stop"
+ android:text="@string/stop"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:id="@+id/vis"
+ android:text="@string/vis"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:id="@+id/invis"
+ android:text="@string/invis"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/framesequence/samples/RastermillSamples/res/raw/animated.gif b/framesequence/samples/RastermillSamples/res/raw/animated.gif
new file mode 100644
index 0000000..51baf15
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/raw/animated.gif
Binary files differ
diff --git a/framesequence/samples/RastermillSamples/res/values/strings.xml b/framesequence/samples/RastermillSamples/res/values/strings.xml
new file mode 100644
index 0000000..811c979
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/values/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- NOTE: all strings should be marked as translatable=false,
+ since this sample app is for testing, and won't be shipped -->
+
+ <string name="app_name" translatable="false">Rastermill Samples</string>
+ <string name="action_settings" translatable="false">Settings</string>
+
+ <string name="start" translatable="false">start</string>
+ <string name="stop" translatable="false">stop</string>
+ <string name="vis" translatable="false">vis</string>
+ <string name="invis" translatable="false">invis</string>
+
+</resources>
diff --git a/framesequence/samples/RastermillSamples/res/values/styles.xml b/framesequence/samples/RastermillSamples/res/values/styles.xml
new file mode 100644
index 0000000..737bdc3
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/values/styles.xml
@@ -0,0 +1,7 @@
+<resources>
+
+ <!-- Application theme. -->
+ <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ </style>
+
+</resources>
diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java
new file mode 100644
index 0000000..ea593dc
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 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.rastermill.samples;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.rastermill.FrameSequence;
+import android.support.rastermill.FrameSequenceDrawable;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import java.io.InputStream;
+
+public class AnimatedGifTest extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.basic_test_activity);
+ ImageView imageView = (ImageView) findViewById(R.id.imageview);
+
+ InputStream is = getResources().openRawResource(R.raw.animated);
+
+ FrameSequence fs = FrameSequence.decodeStream(is);
+ final FrameSequenceDrawable drawable = new FrameSequenceDrawable(fs);
+ drawable.setOnFinishedListener(new FrameSequenceDrawable.OnFinishedListener() {
+ @Override
+ public void onFinished(FrameSequenceDrawable drawable) {
+ Toast.makeText(getApplicationContext(),
+ "THE ANIMATION HAS FINISHED", Toast.LENGTH_SHORT).show();
+ }
+ });
+ imageView.setImageDrawable(drawable);
+
+ findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ drawable.start();
+ }
+ });
+ findViewById(R.id.stop).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ drawable.stop();
+ }
+ });
+ findViewById(R.id.vis).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ drawable.setVisible(true, true);
+ }
+ });
+ findViewById(R.id.invis).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ drawable.setVisible(false, true);
+ }
+ });
+ }
+}
diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java
new file mode 100644
index 0000000..0447537
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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.rastermill.samples;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SamplesList extends ListActivity {
+
+ static final String KEY_NAME = "name";
+ static final String KEY_CLASS = "clazz";
+
+ static Map<String,?> makeSample(String name, Class<?> activity) {
+ Map<String,Object> ret = new HashMap<String,Object>();
+ ret.put(KEY_NAME, name);
+ ret.put(KEY_CLASS, activity);
+ return ret;
+ }
+
+ @SuppressWarnings("serial")
+ static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{
+ add(makeSample("Animation Test", AnimatedGifTest.class));
+ }};
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setListAdapter(new SimpleAdapter(this, SAMPLES,
+ android.R.layout.simple_list_item_1, new String[] { KEY_NAME },
+ new int[] { android.R.id.text1 }));
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Class<?> clazz = (Class<?>) SAMPLES.get(position).get(KEY_CLASS);
+ startActivity(new Intent(this, clazz));
+ }
+
+}
diff --git a/framesequence/src/android/support/rastermill/FrameSequence.java b/framesequence/src/android/support/rastermill/FrameSequence.java
new file mode 100644
index 0000000..d6bde0f
--- /dev/null
+++ b/framesequence/src/android/support/rastermill/FrameSequence.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2013 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.support.rastermill;
+
+import android.graphics.Bitmap;
+
+import java.io.InputStream;
+
+public class FrameSequence {
+ static {
+ System.loadLibrary("framesequence");
+ }
+
+ private final long mNativeFrameSequence;
+ private final int mWidth;
+ private final int mHeight;
+ private final boolean mOpaque;
+ private final int mFrameCount;
+ private final int mDefaultLoopCount;
+
+ public int getWidth() { return mWidth; }
+ public int getHeight() { return mHeight; }
+ public boolean isOpaque() { return mOpaque; }
+ public int getFrameCount() { return mFrameCount; }
+ public int getDefaultLoopCount() { return mDefaultLoopCount; }
+
+ private static native FrameSequence nativeDecodeByteArray(byte[] data, int offset, int length);
+ private static native FrameSequence nativeDecodeStream(InputStream is, byte[] tempStorage);
+ private static native void nativeDestroyFrameSequence(long nativeFrameSequence);
+ private static native long nativeCreateState(long nativeFrameSequence);
+ private static native void nativeDestroyState(long nativeState);
+ private static native long nativeGetFrame(long nativeState, int frameNr,
+ Bitmap output, int previousFrameNr);
+
+ @SuppressWarnings("unused") // called by native
+ private FrameSequence(long nativeFrameSequence, int width, int height,
+ boolean opaque, int frameCount, int defaultLoopCount) {
+ mNativeFrameSequence = nativeFrameSequence;
+ mWidth = width;
+ mHeight = height;
+ mOpaque = opaque;
+ mFrameCount = frameCount;
+ mDefaultLoopCount = defaultLoopCount;
+ }
+
+ public static FrameSequence decodeByteArray(byte[] data) {
+ return decodeByteArray(data, 0, data.length);
+ }
+
+ public static FrameSequence decodeByteArray(byte[] data, int offset, int length) {
+ if (data == null) throw new IllegalArgumentException();
+ if (offset < 0 || length < 0 || (offset + length > data.length)) {
+ throw new IllegalArgumentException("invalid offset/length parameters");
+ }
+ return nativeDecodeByteArray(data, offset, length);
+ }
+
+ public static FrameSequence decodeStream(InputStream stream) {
+ if (stream == null) throw new IllegalArgumentException();
+ byte[] tempStorage = new byte[16 * 1024]; // TODO: use buffer pool
+ return nativeDecodeStream(stream, tempStorage);
+ }
+
+ State createState() {
+ if (mNativeFrameSequence == 0) {
+ throw new IllegalStateException("attempted to use incorrectly built FrameSequence");
+ }
+
+ long nativeState = nativeCreateState(mNativeFrameSequence);
+ if (nativeState == 0) {
+ return null;
+ }
+ return new State(nativeState);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mNativeFrameSequence != 0) nativeDestroyFrameSequence(mNativeFrameSequence);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Playback state used when moving frames forward in a frame sequence.
+ *
+ * Note that this doesn't require contiguous frames to be rendered, it just stores
+ * information (in the case of gif, a recall buffer) that will be used to construct
+ * frames based upon data recorded before previousFrameNr.
+ *
+ * Note: {@link #recycle()} *must* be called before the object is GC'd to free native resources
+ *
+ * Note: State holds a native ref to its FrameSequence instance, so its FrameSequence should
+ * remain ref'd while it is in use
+ */
+ static class State {
+ private long mNativeState;
+
+ public State(long nativeState) {
+ mNativeState = nativeState;
+ }
+
+ public void recycle() {
+ if (mNativeState != 0) {
+ nativeDestroyState(mNativeState);
+ mNativeState = 0;
+ }
+ }
+
+ // TODO: consider adding alternate API for drawing into a SurfaceTexture
+ public long getFrame(int frameNr, Bitmap output, int previousFrameNr) {
+ if (output == null || output.getConfig() != Bitmap.Config.ARGB_8888) {
+ throw new IllegalArgumentException("Bitmap passed must be non-null and ARGB_8888");
+ }
+ if (mNativeState == 0) {
+ throw new IllegalStateException("attempted to draw recycled FrameSequenceState");
+ }
+ return nativeGetFrame(mNativeState, frameNr, output, previousFrameNr);
+ }
+ }
+
+ // TODO: add recycle() cleanup method
+}
diff --git a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
new file mode 100644
index 0000000..f5f1f47
--- /dev/null
+++ b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2013 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.support.rastermill;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+
+public class FrameSequenceDrawable extends Drawable implements Animatable, Runnable {
+ private static final Object sLock = new Object();
+ private static HandlerThread sDecodingThread;
+ private static Handler sDecodingThreadHandler;
+ private static void initializeDecodingThread() {
+ synchronized (sLock) {
+ if (sDecodingThread != null) return;
+
+ sDecodingThread = new HandlerThread("FrameSequence decoding thread");
+ sDecodingThread.start();
+ sDecodingThreadHandler = new Handler(sDecodingThread.getLooper());
+ }
+ }
+
+ public static interface OnFinishedListener {
+ /**
+ * Called when a FrameSequenceDrawable has finished looping.
+ *
+ * Note that this is will not be called if the drawable is explicitly
+ * stopped, or marked invisible.
+ */
+ public abstract void onFinished(FrameSequenceDrawable drawable);
+ }
+
+ /**
+ * Register a callback to be invoked when a FrameSequenceDrawable finishes looping.
+ *
+ * @see setLoopBehavior()
+ */
+ public void setOnFinishedListener(OnFinishedListener onFinishedListener) {
+ mOnFinishedListener = onFinishedListener;
+ }
+
+ /**
+ * Loop only once.
+ */
+ public static final int LOOP_ONCE = 1;
+
+ /**
+ * Loop continuously. The OnFinishedListener will never be called.
+ */
+ public static final int LOOP_INF = 2;
+
+ /**
+ * Use loop count stored in source data, or LOOP_ONCE if not present.
+ */
+ public static final int LOOP_DEFAULT = 3;
+
+ /**
+ * Define looping behavior of frame sequence.
+ *
+ * Must be one of LOOP_ONCE, LOOP_INF, or LOOP_DEFAULT
+ */
+ public void setLoopBehavior(int loopBehavior) {
+ mLoopBehavior = loopBehavior;
+ }
+
+ private final FrameSequence mFrameSequence;
+ private final FrameSequence.State mFrameSequenceState;
+
+ private final Paint mPaint;
+ private final Rect mSrcRect;
+
+ //Protects the fields below
+ private final Object mLock = new Object();
+
+ private Bitmap mFrontBitmap;
+ private Bitmap mBackBitmap;
+
+ private static final int STATE_SCHEDULED = 1;
+ private static final int STATE_DECODING = 2;
+ private static final int STATE_WAITING_TO_SWAP = 3;
+ private static final int STATE_READY_TO_SWAP = 4;
+
+ private int mState;
+ private int mCurrentLoop;
+ private int mLoopBehavior = LOOP_DEFAULT;
+
+ private long mLastSwap;
+ private int mNextFrameToDecode;
+ private OnFinishedListener mOnFinishedListener;
+
+ /**
+ * Runs on decoding thread, only modifies mBackBitmap's pixels
+ */
+ private Runnable mDecodeRunnable = new Runnable() {
+ @Override
+ public void run() {
+ int nextFrame;
+ Bitmap bitmap;
+ synchronized (mLock) {
+ nextFrame = mNextFrameToDecode;
+ if (nextFrame < 0) {
+ return;
+ }
+ bitmap = mBackBitmap;
+ mState = STATE_DECODING;
+ }
+ int lastFrame = nextFrame - 2;
+ long invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame);
+
+ synchronized (mLock) {
+ if (mNextFrameToDecode < 0 || mState != STATE_DECODING) return;
+ invalidateTimeMs += mLastSwap;
+
+ mState = STATE_WAITING_TO_SWAP;
+ }
+ scheduleSelf(FrameSequenceDrawable.this, invalidateTimeMs);
+ }
+ };
+
+ private Runnable mCallbackRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mOnFinishedListener != null) {
+ mOnFinishedListener.onFinished(FrameSequenceDrawable.this);
+ }
+ }
+ };
+
+ public FrameSequenceDrawable(FrameSequence frameSequence) {
+ if (frameSequence == null) throw new IllegalArgumentException();
+
+ mFrameSequence = frameSequence;
+ mFrameSequenceState = frameSequence.createState();
+ // TODO: add callback for requesting bitmaps, to allow for reuse
+ final int width = frameSequence.getWidth();
+ final int height = frameSequence.getHeight();
+
+ mFrontBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mBackBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mSrcRect = new Rect(0, 0, width, height);
+ mPaint = new Paint();
+ mPaint.setFilterBitmap(true);
+
+ mLastSwap = 0;
+
+ mNextFrameToDecode = -1;
+ mFrameSequenceState.getFrame(0, mFrontBitmap, -1);
+ initializeDecodingThread();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mFrontBitmap.recycle();
+ mBackBitmap.recycle();
+ mFrameSequenceState.recycle();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ synchronized (mLock) {
+ if (isRunning() && mState == STATE_READY_TO_SWAP) {
+ // Because draw has occurred, the view system is guaranteed to no longer hold a
+ // reference to the old mFrontBitmap, so we now use it to produce the next frame
+ Bitmap tmp = mBackBitmap;
+ mBackBitmap = mFrontBitmap;
+ mFrontBitmap = tmp;
+
+ mLastSwap = SystemClock.uptimeMillis();
+
+ boolean continueLooping = true;
+ if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) {
+ mCurrentLoop++;
+ if ((mLoopBehavior == LOOP_ONCE && mCurrentLoop == 1) ||
+ (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) {
+ continueLooping = false;
+ }
+ }
+
+ if (continueLooping) {
+ scheduleDecodeLocked();
+ } else {
+ scheduleSelf(mCallbackRunnable, 0);
+ }
+ }
+ }
+
+ canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint);
+ }
+
+ private void scheduleDecodeLocked() {
+ mState = STATE_SCHEDULED;
+ mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount();
+ sDecodingThreadHandler.post(mDecodeRunnable);
+ }
+
+ @Override
+ public void run() {
+ // set ready to swap
+ synchronized (mLock) {
+ if (mState != STATE_WAITING_TO_SWAP || mNextFrameToDecode < 0) return;
+ mState = STATE_READY_TO_SWAP;
+ }
+ invalidateSelf();
+ }
+
+ @Override
+ public void start() {
+ if (!isRunning()) {
+ synchronized (mLock) {
+ if (mState == STATE_SCHEDULED) return; // already scheduled
+ mCurrentLoop = 0;
+ scheduleDecodeLocked();
+ }
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (isRunning()) {
+ unscheduleSelf(this);
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ synchronized (mLock) {
+ return mNextFrameToDecode > -1;
+ }
+ }
+
+ @Override
+ public void scheduleSelf(Runnable what, long when) {
+ super.scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleSelf(Runnable what) {
+ synchronized (mLock) {
+ mNextFrameToDecode = -1;
+ }
+ super.unscheduleSelf(what);
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ boolean changed = super.setVisible(visible, restart);
+
+ if (!visible) {
+ stop();
+ } else if (restart || changed) {
+ stop();
+ start();
+ }
+
+ return changed;
+ }
+
+ // drawing properties
+
+ @Override
+ public void setFilterBitmap(boolean filter) {
+ mPaint.setFilterBitmap(filter);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mFrameSequence.getWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mFrameSequence.getHeight();
+ }
+
+ @Override
+ public int getOpacity() {
+ return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT;
+ }
+}