Messaging: Vendor chips, colorpicker, photoviewer, and vcard in-tree
Clone no longer depends on LineageOS gitlinks; the libraries used by the app live in this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -15,6 +15,7 @@
|
||||
app/build/
|
||||
modules/*/build/
|
||||
modules/*/.git/
|
||||
modules/*/sample/
|
||||
*.apk
|
||||
*.ap_
|
||||
*.dex
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Copyright (C) 2011 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT 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 := libchips
|
||||
LOCAL_STATIC_ANDROID_LIBRARIES := \
|
||||
android-support-compat \
|
||||
android-support-annotations
|
||||
LOCAL_SDK_VERSION := current
|
||||
LOCAL_SRC_FILES := \
|
||||
$(call all-java-files-under, src) \
|
||||
$(call all-logtags-files-under, src)
|
||||
LOCAL_USE_AAPT2 := true
|
||||
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
|
||||
include $(BUILD_STATIC_JAVA_LIBRARY)
|
||||
|
||||
##################################################
|
||||
# Build all sub-directories
|
||||
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.ex.chips"
|
||||
android:versionCode="1">
|
||||
|
||||
<application />
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,31 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.android.ex.chips'
|
||||
compileSdk 35
|
||||
|
||||
defaultConfig {
|
||||
minSdk 33
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.annotation:annotation:1.8.2'
|
||||
implementation 'androidx.core:core:1.15.0'
|
||||
implementation 'androidx.collection:collection:1.4.4'
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_activated="true" android:color="@color/chips_dropdown_text_activated"/>
|
||||
<item android:color="@color/chips_dropdown_text_default"/>
|
||||
|
||||
</selector>
|
||||
|
After Width: | Height: | Size: 225 B |
|
After Width: | Height: | Size: 539 B |
|
After Width: | Height: | Size: 944 B |
|
After Width: | Height: | Size: 178 B |
|
After Width: | Height: | Size: 381 B |
|
After Width: | Height: | Size: 686 B |
|
After Width: | Height: | Size: 261 B |
|
After Width: | Height: | Size: 702 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 353 B |
|
After Width: | Height: | Size: 941 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 444 B |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,6 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:angle="90"
|
||||
android:startColor="@android:color/transparent"
|
||||
android:endColor="@color/autocomplete_bottom_shadow_start" />
|
||||
</shape>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:drawable="@color/chips_dropdown_background_activated"
|
||||
android:state_activated="true"/>
|
||||
<item android:drawable="@color/chips_dropdown_background_pressed" android:state_pressed="true"/>
|
||||
<item android:drawable="@android:color/white"/>
|
||||
|
||||
</selector>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_activated="true" android:color="@android:color/white" />
|
||||
<item android:state_checked="true" android:color="@android:color/white" />
|
||||
<item android:state_selected="true" android:color="@android:color/white" />
|
||||
<item android:color="#333333"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_activated="true" android:color="@android:color/white" />
|
||||
<item android:state_checked="true" android:color="@android:color/white" />
|
||||
<item android:state_selected="true" android:color="@android:color/white" />
|
||||
<item android:color="#777777"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:id="@+id/chip_autocomplete_top_divider"
|
||||
style="@style/ChipAutocompleteDividerStyle" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chip_person_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/ChipAutocompleteWrapperStyle">
|
||||
|
||||
<LinearLayout style="@style/ChipTextViewLayoutStyle">
|
||||
|
||||
<TextView style="@style/ChipTitleStyle" />
|
||||
|
||||
<TextView style="@style/ChipSubtitleStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.android.ex.chips.CircularImageView style="@style/ChipEndIconStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chip_permission_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/ChipAutocompleteWrapperStyle">
|
||||
|
||||
<TextView style="@style/ChipPermissionStyle" />
|
||||
|
||||
<ImageView style="@style/ChipPermissionDismissIconStyle"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/chip_autocomplete_bottom_divider"
|
||||
style="@style/ChipAutocompleteDividerStyle" />
|
||||
|
||||
<View
|
||||
android:id="@+id/chip_permission_bottom_divider"
|
||||
android:visibility="gone"
|
||||
style="@style/ChipAutocompleteDividerStyle.PermissionItem" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/chips_dropdown_background"
|
||||
android:minHeight="@dimen/chip_dropdown_height"
|
||||
android:padding="12dp">
|
||||
|
||||
<com.android.ex.chips.CircularImageView style="@style/ChipStartIconStyle" />
|
||||
|
||||
<LinearLayout style="@style/ChipTextViewLayoutStyle">
|
||||
|
||||
<TextView style="@style/ChipTitleStyle" />
|
||||
|
||||
<TextView style="@style/ChipSubtitleStyle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chip_indicator_text"
|
||||
style="@style/ChipIndicatorTextStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView style="@style/ChipDeleteIconStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/text1"
|
||||
android:textColor="#aaaaaa"
|
||||
android:textSize="18sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/more_string"
|
||||
android:paddingLeft="8dip"
|
||||
android:paddingRight="8dip"
|
||||
/>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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>
|
||||
<dimen name="chip_height">26dip</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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:tools="http://schemas.android.com/tools"
|
||||
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<style name="RecipientEditTextView" parent="@style/RecipientEditTextViewBase">
|
||||
<item name="android:imeOptions">actionNext</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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="ChipAutocompleteWrapperStyle">
|
||||
<item name="android:paddingStart">@dimen/chip_wrapper_start_padding</item>
|
||||
<item name="android:paddingTop">@dimen/chip_wrapper_top_padding</item>
|
||||
<item name="android:paddingEnd">@dimen/chip_wrapper_end_padding</item>
|
||||
<item name="android:paddingBottom">@dimen/chip_wrapper_bottom_padding</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipAutocompleteDividerStyle">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">1px</item>
|
||||
<item name="android:background">@color/autocomplete_divider_color</item>
|
||||
<item name="android:layout_marginStart">@dimen/chip_wrapper_start_padding</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipAutocompleteDividerStyle.PermissionItem">
|
||||
<item name="android:background">@drawable/chip_autocomplete_divider_background</item>
|
||||
<item name="android:layout_height">3dp</item>
|
||||
<item name="android:layout_marginStart">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipStartIconStyle" parent="@style/ChipIconBaseStyle">
|
||||
<item name="android:layout_marginEnd">@dimen/chip_icon_margin_end</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipEndIconStyle" parent="@style/ChipIconBaseStyle">
|
||||
<item name="android:layout_marginStart">@dimen/chip_icon_margin_end</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipDeleteIconStyle" parent="@style/ChipDeleteIconBaseStyle">
|
||||
<item name="android:paddingStart">@dimen/chip_icon_padding</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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>
|
||||
<declare-styleable name="RecipientEditTextView">
|
||||
<attr name="avatarPosition">
|
||||
<enum name="end" value="0" />
|
||||
<enum name="start" value="1" />
|
||||
</attr>
|
||||
<attr name="chipBackground" format="reference" />
|
||||
<attr name="chipDelete" 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>
|
||||
<attr name="unselectedChipBackgroundColor" format="color" />
|
||||
<attr name="unselectedChipTextColor" format="color" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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>
|
||||
|
||||
<color name="chips_dropdown_background_activated">#4285f4</color>
|
||||
<color name="chips_dropdown_background_pressed">#ededed</color>
|
||||
|
||||
<color name="autocomplete_divider_color">#cccccc</color>
|
||||
<color name="autocomplete_bottom_shadow_start">#40000000</color>
|
||||
|
||||
<color name="chip_background">#f6f6f6</color>
|
||||
<color name="chip_background_invalid">#db4437</color>
|
||||
|
||||
<color name="chips_dropdown_text_default">#212121</color>
|
||||
<color name="chips_dropdown_text_activated">@android:color/white</color>
|
||||
<color name="chips_dropdown_text_highlighted">#757575</color>
|
||||
<color name="chips_dropdown_permission_text">#4285f4</color>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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>
|
||||
<!-- override default padding values, -1dp if it's not set to override -->
|
||||
<dimen name="chip_padding_start">-1dp</dimen>
|
||||
<dimen name="chip_padding_end">12dp</dimen>
|
||||
|
||||
<!-- chip -->
|
||||
<dimen name="chip_padding">8dp</dimen>
|
||||
<dimen name="chip_height">32dp</dimen>
|
||||
<dimen name="chip_text_size">14sp</dimen>
|
||||
<dimen name="line_spacing_extra">3dp</dimen>
|
||||
<integer name="chips_max_lines">-1</integer>
|
||||
|
||||
<!-- dropdown -->
|
||||
<dimen name="chip_icon_margin_end">12dp</dimen>
|
||||
<dimen name="chip_dropdown_height">72dp</dimen>
|
||||
<dimen name="chip_custom_divider_height">0.5dp</dimen>
|
||||
<dimen name="chip_icon_padding">8dp</dimen>
|
||||
<!-- autocomplete dropdown -->
|
||||
<dimen name="chip_wrapper_start_padding">16dp</dimen>
|
||||
<dimen name="chip_wrapper_top_padding">16dp</dimen>
|
||||
<dimen name="chip_wrapper_end_padding">16dp</dimen>
|
||||
<dimen name="chip_wrapper_bottom_padding">16dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<!-- Text displayed when the recipientedittextview is not focused. Displays the total number of recipients since the field is shrunk to just display a portion -->
|
||||
<string name="more_string">\u002B<xliff:g id="count">%1$d</xliff:g></string>
|
||||
<!-- Text displayed when the user long presses on a chip to copy the recipients email address [CHAR LIMIT=200] -->
|
||||
<string name="copy_email">Copy email address</string>
|
||||
<!-- Text displayed when the user long presses on a chip to copy the recipient's phone number [CHAR LIMIT=200] -->
|
||||
<string name="copy_number">Copy phone number</string>
|
||||
<!-- Text displayed in the enter key slot when the recipientedittextview has focus [CHAR LIMIT=12] -->
|
||||
<string name="action_label">Return</string>
|
||||
<!-- Announce the number of possible recipient entries that the user can select from for accessibility purposes [CHAR LIMIT=200] -->
|
||||
<string name="accessbility_suggestion_dropdown_opened">Contact suggestions opened</string>
|
||||
<!-- Announce that there are no possible recipient entries that the user can select from for accessibility purposes [CHAR LIMIT=200] -->
|
||||
<string name="accessbility_suggestion_dropdown_closed">No suggestions available</string>
|
||||
<!-- Description for the delete button that removes the selected contact from the recipients [CHAR LIMIT=100] -->
|
||||
<string name="dropdown_delete_button_desc">Remove <xliff:g id="contact">%s</xliff:g> from recipients</string>
|
||||
<!-- Text displayed as the primary action for a dialog to copy text. [CHAR LIMIT=25] -->
|
||||
<string name="chips_action_copy">Copy</string>
|
||||
<!-- Text displayed as the secondary action for a dialog to cancel. [CHAR LIMIT=25] -->
|
||||
<string name="chips_action_cancel">Cancel</string>
|
||||
<!-- Text displayed on the drop down item that is shown when app doesn't have Contact permission. This item will open dialog to request permission. [CHAR LIMIT=50] -->
|
||||
<string name="chips_permission_text">Allow contacts suggestions</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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:tools="http://schemas.android.com/tools">
|
||||
<style name="RecipientEditTextViewBase">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:background">@null</item>
|
||||
<item name="android:inputType">textEmailAddress|textMultiLine</item>
|
||||
<item name="android:minHeight">@dimen/chip_height</item>
|
||||
<item name="android:popupBackground">@android:color/white</item>
|
||||
<item name="android:textAlignment" tools:ignore="NewApi">viewStart</item>
|
||||
<item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
|
||||
<item name="android:textDirection" tools:ignore="NewApi">locale</item>
|
||||
</style>
|
||||
|
||||
<style name="RecipientEditTextView" parent="@style/RecipientEditTextViewBase">
|
||||
<item name="android:imeOptions">actionNext|flagNoFullscreen</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipAutocompleteWrapperStyle">
|
||||
<item name="android:paddingLeft">@dimen/chip_wrapper_start_padding</item>
|
||||
<item name="android:paddingTop">@dimen/chip_wrapper_top_padding</item>
|
||||
<item name="android:paddingRight">@dimen/chip_wrapper_end_padding</item>
|
||||
<item name="android:paddingBottom">@dimen/chip_wrapper_bottom_padding</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipAutocompleteDividerStyle">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">@dimen/chip_custom_divider_height</item>
|
||||
<item name="android:background">@color/autocomplete_divider_color</item>
|
||||
<item name="android:layout_marginLeft">@dimen/chip_wrapper_start_padding</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipAutocompleteDividerStyle.PermissionItem">
|
||||
<item name="android:background">@drawable/chip_autocomplete_divider_background</item>
|
||||
<item name="android:layout_height">3dp</item>
|
||||
<item name="android:layout_marginLeft">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipTextViewLayoutStyle">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
<item name="android:layout_weight">1</item>
|
||||
<item name="android:orientation">vertical</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipTitleStyle">
|
||||
<item name="android:id">@android:id/title</item>
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:ellipsize">middle</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:textAlignment">viewStart</item>
|
||||
<item name="android:textColor">@color/chips_dropdown_text</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipSubtitleStyle">
|
||||
<item name="android:id">@android:id/text1</item>
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:ellipsize">middle</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:textAlignment">viewStart</item>
|
||||
<item name="android:textColor">@color/chips_dropdown_text</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipIndicatorTextStyle" parent="@style/ChipSubtitleStyle">
|
||||
<item name="android:layout_marginTop">6dp</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:drawablePadding">4dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipPermissionStyle">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
<item name="android:layout_weight">1</item>
|
||||
<item name="android:textAlignment">viewStart</item>
|
||||
<item name="android:textColor">@color/chips_dropdown_permission_text</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:text">@string/chips_permission_text</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipIconBaseStyle">
|
||||
<item name="android:id">@android:id/icon</item>
|
||||
<item name="android:layout_width">40dp</item>
|
||||
<item name="android:layout_height">40dp</item>
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
<item name="android:contentDescription">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipStartIconStyle" parent="@style/ChipIconBaseStyle">
|
||||
<item name="android:layout_marginRight">@dimen/chip_icon_margin_end</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipEndIconStyle" parent="@style/ChipIconBaseStyle">
|
||||
<item name="android:layout_marginLeft">@dimen/chip_icon_margin_end</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipDeleteIconBaseStyle">
|
||||
<item name="android:id">@android:id/icon1</item>
|
||||
<item name="android:layout_width">36dp</item>
|
||||
<item name="android:layout_height">48dp</item>
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
<item name="android:paddingTop">10dp</item>
|
||||
<item name="android:paddingBottom">10dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipDeleteIconStyle" parent="@style/ChipDeleteIconBaseStyle">
|
||||
<item name="android:paddingLeft">@dimen/chip_icon_padding</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipPermissionDismissIconStyle">
|
||||
<item name="android:id">@android:id/icon2</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
<item name="android:background">?android:attr/selectableItemBackground</item>
|
||||
<item name="android:contentDescription">@android:string/cancel</item>
|
||||
<item name="android:padding">@dimen/chip_icon_padding</item>
|
||||
<item name="android:src">@drawable/chips_ic_close_24dp</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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;
|
||||
|
||||
import android.accounts.Account;
|
||||
|
||||
/**
|
||||
* The AccountSpecificAdapter interface describes an Adapter
|
||||
* that can take an account to retrieve information tied to
|
||||
* a specific account.
|
||||
*/
|
||||
public interface AccountSpecifier {
|
||||
public void setAccount(Account account);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class ChipsUtil {
|
||||
|
||||
/**
|
||||
* Listener that gets notified when we check whether we have permission.
|
||||
*/
|
||||
public interface PermissionsCheckListener {
|
||||
void onPermissionCheck(String permission, boolean granted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permissions required by Chips library.
|
||||
*/
|
||||
public static final String[] REQUIRED_PERMISSIONS =
|
||||
new String[] { Manifest.permission.READ_CONTACTS };
|
||||
|
||||
/**
|
||||
* Returns true when the caller can use Chips UI in its environment.
|
||||
*/
|
||||
public static boolean supportsChipsUi() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we are running on M or later version.
|
||||
*
|
||||
* <p>This is interesting for us because new permission model is introduced in M and we need to
|
||||
* check if we have {@link #REQUIRED_PERMISSIONS}.
|
||||
*/
|
||||
public static boolean isRunningMOrLater() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link PackageManager#PERMISSION_GRANTED} if given permission is granted, or
|
||||
* {@link PackageManager#PERMISSION_DENIED} if not.
|
||||
*/
|
||||
public static int checkPermission(Context context, String permission) {
|
||||
if (isRunningMOrLater()) {
|
||||
// TODO: Use "context.checkSelfPermission(permission)" once it's safe to move to M sdk
|
||||
return context.checkPermission(permission, Process.myPid(), Process.myUid());
|
||||
} else {
|
||||
// Assume that we have permission before M.
|
||||
return PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all permissions in {@link #REQUIRED_PERMISSIONS} are granted.
|
||||
*
|
||||
* <p>If {@link PermissionsCheckListener} is specified it will be called for every
|
||||
* {@link #checkPermission} call.
|
||||
*/
|
||||
public static boolean hasPermissions(Context context,
|
||||
@Nullable PermissionsCheckListener permissionsCheckListener) {
|
||||
for (String permission : REQUIRED_PERMISSIONS) {
|
||||
final boolean granted =
|
||||
checkPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
|
||||
if (permissionsCheckListener != null) {
|
||||
permissionsCheckListener.onPermissionCheck(permission, granted);
|
||||
}
|
||||
if (!granted) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.android.ex.chips;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* An ImageView class with a circle mask so that all images are drawn in a
|
||||
* circle instead of a square.
|
||||
*/
|
||||
public class CircularImageView extends ImageView {
|
||||
private static float circularImageBorder = 1f;
|
||||
|
||||
private final Matrix matrix;
|
||||
private final RectF source;
|
||||
private final RectF destination;
|
||||
private final Paint bitmapPaint;
|
||||
private final Paint borderPaint;
|
||||
|
||||
public CircularImageView(Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
|
||||
public CircularImageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CircularImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
matrix = new Matrix();
|
||||
source = new RectF();
|
||||
destination = new RectF();
|
||||
|
||||
bitmapPaint = new Paint();
|
||||
bitmapPaint.setAntiAlias(true);
|
||||
bitmapPaint.setFilterBitmap(true);
|
||||
bitmapPaint.setDither(true);
|
||||
|
||||
borderPaint = new Paint();
|
||||
borderPaint.setColor(Color.TRANSPARENT);
|
||||
borderPaint.setStyle(Paint.Style.STROKE);
|
||||
borderPaint.setStrokeWidth(circularImageBorder);
|
||||
borderPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
Drawable drawable = getDrawable();
|
||||
BitmapDrawable bitmapDrawable = null;
|
||||
// support state list drawable by getting the current state
|
||||
if (drawable instanceof StateListDrawable) {
|
||||
if (((StateListDrawable) drawable).getCurrent() != null) {
|
||||
bitmapDrawable = (BitmapDrawable) drawable.getCurrent();
|
||||
}
|
||||
} else {
|
||||
bitmapDrawable = (BitmapDrawable) drawable;
|
||||
}
|
||||
|
||||
if (bitmapDrawable == null) {
|
||||
return;
|
||||
}
|
||||
Bitmap bitmap = bitmapDrawable.getBitmap();
|
||||
if (bitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
source.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||
destination.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
|
||||
getHeight() - getPaddingBottom());
|
||||
|
||||
drawBitmapWithCircleOnCanvas(bitmap, canvas, source, destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the source bitmap and a canvas, draws the bitmap through a circular
|
||||
* mask. Only draws a circle with diameter equal to the destination width.
|
||||
*
|
||||
* @param bitmap The source bitmap to draw.
|
||||
* @param canvas The canvas to draw it on.
|
||||
* @param source The source bound of the bitmap.
|
||||
* @param dest The destination bound on the canvas.
|
||||
*/
|
||||
public void drawBitmapWithCircleOnCanvas(Bitmap bitmap, Canvas canvas,
|
||||
RectF source, RectF dest) {
|
||||
// Draw bitmap through shader first.
|
||||
BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,
|
||||
Shader.TileMode.CLAMP);
|
||||
matrix.reset();
|
||||
|
||||
// Fit bitmap to bounds.
|
||||
matrix.setRectToRect(source, dest, Matrix.ScaleToFit.FILL);
|
||||
|
||||
shader.setLocalMatrix(matrix);
|
||||
bitmapPaint.setShader(shader);
|
||||
canvas.drawCircle(dest.centerX(), dest.centerY(), dest.width() / 2f,
|
||||
bitmapPaint);
|
||||
|
||||
// Then draw the border.
|
||||
canvas.drawCircle(dest.centerX(), dest.centerY(),
|
||||
dest.width() / 2f - circularImageBorder / 2, borderPaint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.android.ex.chips;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Simple dialog fragment for copying the contents of a chip.
|
||||
*/
|
||||
public class CopyDialog extends DialogFragment implements DialogInterface.OnClickListener {
|
||||
|
||||
public static final String TAG = "chips-copy-dialog";
|
||||
|
||||
private static final String ARG_TEXT = "text";
|
||||
|
||||
private String mText;
|
||||
|
||||
public static CopyDialog newInstance(String text) {
|
||||
final CopyDialog fragment = new CopyDialog();
|
||||
final Bundle args = new Bundle(1);
|
||||
args.putString(ARG_TEXT, text);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Bundle args = getArguments();
|
||||
mText = args.getString(ARG_TEXT);
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setMessage(mText)
|
||||
.setPositiveButton(R.string.chips_action_copy, this)
|
||||
.setNegativeButton(R.string.chips_action_cancel, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
final ClipboardManager clipboard = (ClipboardManager)
|
||||
getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText(null, mText));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.ex.chips;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.ContactsContract;
|
||||
import androidx.collection.LruCache;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link com.android.ex.chips.PhotoManager} that
|
||||
* queries for photo bytes by using the {@link com.android.ex.chips.RecipientEntry}'s
|
||||
* photoThumbnailUri.
|
||||
*/
|
||||
public class DefaultPhotoManager implements PhotoManager {
|
||||
private static final String TAG = "DefaultPhotoManager";
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* For reading photos for directory contacts, this is the chunk size for
|
||||
* copying from the {@link InputStream} to the output stream.
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 1024*16;
|
||||
|
||||
private static class PhotoQuery {
|
||||
public static final String[] PROJECTION = {
|
||||
ContactsContract.CommonDataKinds.Photo.PHOTO
|
||||
};
|
||||
|
||||
public static final int PHOTO = 0;
|
||||
}
|
||||
|
||||
private final ContentResolver mContentResolver;
|
||||
private final LruCache<Uri, byte[]> mPhotoCacheMap;
|
||||
|
||||
public DefaultPhotoManager(ContentResolver contentResolver) {
|
||||
mContentResolver = contentResolver;
|
||||
mPhotoCacheMap = new LruCache<Uri, byte[]>(PHOTO_CACHE_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populatePhotoBytesAsync(RecipientEntry entry, PhotoManagerCallback callback) {
|
||||
final Uri photoThumbnailUri = entry.getPhotoThumbnailUri();
|
||||
if (photoThumbnailUri != null) {
|
||||
final byte[] photoBytes = mPhotoCacheMap.get(photoThumbnailUri);
|
||||
if (photoBytes != null) {
|
||||
entry.setPhotoBytes(photoBytes);
|
||||
if (callback != null) {
|
||||
callback.onPhotoBytesPopulated();
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No photo cache for " + entry.getDisplayName()
|
||||
+ ". Fetch one asynchronously");
|
||||
}
|
||||
fetchPhotoAsync(entry, photoThumbnailUri, callback);
|
||||
}
|
||||
} else if (callback != null) {
|
||||
callback.onPhotoBytesAsyncLoadFailed();
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchPhotoAsync(final RecipientEntry entry, final Uri photoThumbnailUri,
|
||||
final PhotoManagerCallback callback) {
|
||||
final AsyncTask<Void, Void, byte[]> photoLoadTask = new AsyncTask<Void, Void, byte[]>() {
|
||||
@Override
|
||||
protected byte[] doInBackground(Void... params) {
|
||||
// First try running a query. Images for local contacts are
|
||||
// loaded by sending a query to the ContactsProvider.
|
||||
final Cursor photoCursor = mContentResolver.query(
|
||||
photoThumbnailUri, PhotoQuery.PROJECTION, null, null, null);
|
||||
if (photoCursor != null) {
|
||||
try {
|
||||
if (photoCursor.moveToFirst()) {
|
||||
return photoCursor.getBlob(PhotoQuery.PHOTO);
|
||||
}
|
||||
} finally {
|
||||
photoCursor.close();
|
||||
}
|
||||
} else {
|
||||
// If the query fails, try streaming the URI directly.
|
||||
// For remote directory images, this URI resolves to the
|
||||
// directory provider and the images are loaded by sending
|
||||
// an openFile call to the provider.
|
||||
try {
|
||||
InputStream is = mContentResolver.openInputStream(
|
||||
photoThumbnailUri);
|
||||
if (is != null) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
int size;
|
||||
while ((size = is.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, size);
|
||||
}
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final byte[] photoBytes) {
|
||||
entry.setPhotoBytes(photoBytes);
|
||||
if (photoBytes != null) {
|
||||
mPhotoCacheMap.put(photoThumbnailUri, photoBytes);
|
||||
if (callback != null) {
|
||||
callback.onPhotoBytesAsynchronouslyPopulated();
|
||||
}
|
||||
} else if (callback != null) {
|
||||
callback.onPhotoBytesAsyncLoadFailed();
|
||||
}
|
||||
}
|
||||
};
|
||||
photoLoadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,569 @@
|
||||
package com.android.ex.chips;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.MarginLayoutParamsCompat;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.util.Rfc822Tokenizer;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.MarginLayoutParams;
|
||||
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
|
||||
}
|
||||
|
||||
public interface ChipDeleteListener {
|
||||
void onChipDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener that handles the dismisses of the entries of the
|
||||
* {@link RecipientEntry#ENTRY_TYPE_PERMISSION_REQUEST} type.
|
||||
*/
|
||||
public interface PermissionRequestDismissedListener {
|
||||
|
||||
/**
|
||||
* Callback that occurs when user dismisses the item that asks user to grant permissions to
|
||||
* the app.
|
||||
*/
|
||||
void onPermissionRequestDismissed();
|
||||
}
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
private final Context mContext;
|
||||
private ChipDeleteListener mDeleteListener;
|
||||
private PermissionRequestDismissedListener mPermissionRequestDismissedListener;
|
||||
private Query mQuery;
|
||||
private int mAutocompleteDividerMarginStart;
|
||||
|
||||
public DropdownChipLayouter(LayoutInflater inflater, Context context) {
|
||||
mInflater = inflater;
|
||||
mContext = context;
|
||||
mAutocompleteDividerMarginStart =
|
||||
context.getResources().getDimensionPixelOffset(R.dimen.chip_wrapper_start_padding);
|
||||
}
|
||||
|
||||
public void setQuery(Query query) {
|
||||
mQuery = query;
|
||||
}
|
||||
|
||||
public void setDeleteListener(ChipDeleteListener listener) {
|
||||
mDeleteListener = listener;
|
||||
}
|
||||
|
||||
public void setPermissionRequestDismissedListener(PermissionRequestDismissedListener listener) {
|
||||
mPermissionRequestDismissedListener = listener;
|
||||
}
|
||||
|
||||
public void setAutocompleteDividerMarginStart(int autocompleteDividerMarginStart) {
|
||||
mAutocompleteDividerMarginStart = autocompleteDividerMarginStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return bindView(convertView, parent, entry, position, type, constraint, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #bindView(View, ViewGroup, RecipientEntry, int, AdapterType, String)}
|
||||
* @param deleteDrawable a {@link android.graphics.drawable.StateListDrawable} representing
|
||||
* the delete icon. android.R.attr.state_activated should map to the delete icon, and the
|
||||
* default state can map to a drawable of your choice (or null for no drawable).
|
||||
*/
|
||||
public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
|
||||
AdapterType type, String constraint, StateListDrawable deleteDrawable) {
|
||||
// Default to show all the information
|
||||
CharSequence[] styledResults = getStyledResults(constraint, entry);
|
||||
CharSequence displayName = styledResults[0];
|
||||
CharSequence destination = styledResults[1];
|
||||
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 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;
|
||||
}
|
||||
|
||||
// For BASE_RECIPIENT set all top dividers except for the first one to be GONE.
|
||||
if (viewHolder.topDivider != null) {
|
||||
viewHolder.topDivider.setVisibility(position == 0 ? View.VISIBLE : View.GONE);
|
||||
MarginLayoutParamsCompat.setMarginStart(
|
||||
(MarginLayoutParams) viewHolder.topDivider.getLayoutParams(),
|
||||
mAutocompleteDividerMarginStart);
|
||||
}
|
||||
if (viewHolder.bottomDivider != null) {
|
||||
MarginLayoutParamsCompat.setMarginStart(
|
||||
(MarginLayoutParams) viewHolder.bottomDivider.getLayoutParams(),
|
||||
mAutocompleteDividerMarginStart);
|
||||
}
|
||||
break;
|
||||
case RECIPIENT_ALTERNATES:
|
||||
if (position != 0) {
|
||||
displayName = null;
|
||||
showImage = false;
|
||||
}
|
||||
break;
|
||||
case SINGLE_RECIPIENT:
|
||||
if (!PhoneUtil.isPhoneNumber(entry.getDestination())) {
|
||||
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);
|
||||
bindDrawableToDeleteView(deleteDrawable, entry.getDisplayName(), viewHolder.deleteView);
|
||||
bindIndicatorToView(
|
||||
entry.getIndicatorIconId(), entry.getIndicatorText(), viewHolder.indicatorView);
|
||||
bindPermissionRequestDismissView(viewHolder.permissionRequestDismissView);
|
||||
|
||||
// Hide some view groups depending on the entry type
|
||||
final int entryType = entry.getEntryType();
|
||||
if (entryType == RecipientEntry.ENTRY_TYPE_PERSON) {
|
||||
setViewVisibility(viewHolder.personViewGroup, View.VISIBLE);
|
||||
setViewVisibility(viewHolder.permissionViewGroup, View.GONE);
|
||||
setViewVisibility(viewHolder.permissionBottomDivider, View.GONE);
|
||||
} else if (entryType == RecipientEntry.ENTRY_TYPE_PERMISSION_REQUEST) {
|
||||
setViewVisibility(viewHolder.personViewGroup, View.GONE);
|
||||
setViewVisibility(viewHolder.permissionViewGroup, View.VISIBLE);
|
||||
setViewVisibility(viewHolder.permissionBottomDivider, View.VISIBLE);
|
||||
}
|
||||
|
||||
return itemView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new view with {@link #getItemLayoutResId(AdapterType)}.
|
||||
*/
|
||||
public View newView(AdapterType type) {
|
||||
return mInflater.inflate(getItemLayoutResId(type), 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(type);
|
||||
switch (type) {
|
||||
case BASE_RECIPIENT:
|
||||
case RECIPIENT_ALTERNATES:
|
||||
break;
|
||||
case SINGLE_RECIPIENT:
|
||||
itemLayout = getAlternateItemLayoutResId(type);
|
||||
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 void bindDrawableToDeleteView(final StateListDrawable drawable, String recipient,
|
||||
ImageView view) {
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
if (drawable == null) {
|
||||
view.setVisibility(View.GONE);
|
||||
} else {
|
||||
final Resources res = mContext.getResources();
|
||||
view.setImageDrawable(drawable);
|
||||
view.setContentDescription(
|
||||
res.getString(R.string.dropdown_delete_button_desc, recipient));
|
||||
if (mDeleteListener != null) {
|
||||
view.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (drawable.getCurrent() != null) {
|
||||
mDeleteListener.onChipDelete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void bindIndicatorToView(
|
||||
@DrawableRes int indicatorIconId, String indicatorText, TextView view) {
|
||||
if (view != null) {
|
||||
if (indicatorText != null || indicatorIconId != 0) {
|
||||
view.setText(indicatorText);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
final Drawable indicatorIcon;
|
||||
if (indicatorIconId != 0) {
|
||||
indicatorIcon = mContext.getDrawable(indicatorIconId).mutate();
|
||||
indicatorIcon.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
|
||||
} else {
|
||||
indicatorIcon = null;
|
||||
}
|
||||
view.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
indicatorIcon, null, null, null);
|
||||
} else {
|
||||
view.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void bindPermissionRequestDismissView(ImageView view) {
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
view.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mPermissionRequestDismissedListener != null) {
|
||||
mPermissionRequestDismissedListener.onPermissionRequestDismissed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void setViewVisibility(View view, int visibility) {
|
||||
if (view != null) {
|
||||
view.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
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 @LayoutRes int getItemLayoutResId(AdapterType type) {
|
||||
switch (type) {
|
||||
case BASE_RECIPIENT:
|
||||
return R.layout.chips_autocomplete_recipient_dropdown_item;
|
||||
case RECIPIENT_ALTERNATES:
|
||||
return R.layout.chips_recipient_dropdown_item;
|
||||
default:
|
||||
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 @LayoutRes int getAlternateItemLayoutResId(AdapterType type) {
|
||||
switch (type) {
|
||||
case BASE_RECIPIENT:
|
||||
return R.layout.chips_autocomplete_recipient_dropdown_item;
|
||||
case RECIPIENT_ALTERNATES:
|
||||
return R.layout.chips_recipient_dropdown_item;
|
||||
default:
|
||||
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 @DrawableRes int getDefaultPhotoResId() {
|
||||
return R.drawable.ic_contact_picture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an id for the ViewGroup in an item View that contains the person ui elements.
|
||||
*/
|
||||
protected @IdRes int getPersonGroupResId() {
|
||||
return R.id.chip_person_wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an id for TextView in an item View for showing a display name. By default
|
||||
* {@link android.R.id#title} is returned.
|
||||
*/
|
||||
protected @IdRes 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 @IdRes 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 @IdRes 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 @IdRes int getPhotoResId() {
|
||||
return android.R.id.icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an id for ImageView in an item View for showing the delete button. In default
|
||||
* {@link android.R.id#icon1} is returned.
|
||||
*/
|
||||
protected @IdRes int getDeleteResId() { return android.R.id.icon1; }
|
||||
|
||||
/**
|
||||
* Returns an id for the ViewGroup in an item View that contains the request permission ui
|
||||
* elements.
|
||||
*/
|
||||
protected @IdRes int getPermissionGroupResId() {
|
||||
return R.id.chip_permission_wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an id for ImageView in an item View for dismissing the permission request. In default
|
||||
* {@link android.R.id#icon2} is returned.
|
||||
*/
|
||||
protected @IdRes int getPermissionRequestDismissResId() {
|
||||
return android.R.id.icon2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a constraint and a recipient entry, tries to find the constraint in the name and
|
||||
* destination in the recipient entry. A foreground font color style will be applied to the
|
||||
* section that matches the constraint. As soon as a match has been found, no further matches
|
||||
* are attempted.
|
||||
*
|
||||
* @param constraint A string that we will attempt to find within the results.
|
||||
* @param entry The recipient entry to style results for.
|
||||
*
|
||||
* @return An array of CharSequences, the length determined by the length of results. Each
|
||||
* CharSequence will either be a styled SpannableString or just the input String.
|
||||
*/
|
||||
protected CharSequence[] getStyledResults(@Nullable String constraint, RecipientEntry entry) {
|
||||
return getStyledResults(constraint, entry.getDisplayName(), entry.getDestination());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a constraint and results, tries to find the constraint in those results, one at a time.
|
||||
* A foreground font color style will be applied to the section that matches the constraint. As
|
||||
* soon as a match has been found, no further matches are attempted.
|
||||
*
|
||||
* @param constraint A string that we will attempt to find within the results.
|
||||
* @param results Strings that may contain the constraint. The order given is the order used to
|
||||
* search for the constraint.
|
||||
*
|
||||
* @return An array of CharSequences, the length determined by the length of results. Each
|
||||
* CharSequence will either be a styled SpannableString or just the input String.
|
||||
*/
|
||||
protected CharSequence[] getStyledResults(@Nullable String constraint, String... results) {
|
||||
if (isAllWhitespace(constraint)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
CharSequence[] styledResults = new CharSequence[results.length];
|
||||
boolean foundMatch = false;
|
||||
for (int i = 0; i < results.length; i++) {
|
||||
String result = results[i];
|
||||
if (result == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!foundMatch) {
|
||||
int index = result.toLowerCase().indexOf(constraint.toLowerCase());
|
||||
if (index != -1) {
|
||||
SpannableStringBuilder styled = SpannableStringBuilder.valueOf(result);
|
||||
ForegroundColorSpan highlightSpan =
|
||||
new ForegroundColorSpan(mContext.getResources().getColor(
|
||||
R.color.chips_dropdown_text_highlighted));
|
||||
styled.setSpan(highlightSpan,
|
||||
index, index + constraint.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
styledResults[i] = styled;
|
||||
foundMatch = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
styledResults[i] = result;
|
||||
}
|
||||
return styledResults;
|
||||
}
|
||||
|
||||
private static boolean isAllWhitespace(@Nullable String string) {
|
||||
if (TextUtils.isEmpty(string)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < string.length(); ++i) {
|
||||
if (!Character.isWhitespace(string.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the
|
||||
* corresponding views.
|
||||
*/
|
||||
protected class ViewHolder {
|
||||
public final ViewGroup personViewGroup;
|
||||
public final TextView displayNameView;
|
||||
public final TextView destinationView;
|
||||
public final TextView destinationTypeView;
|
||||
public final TextView indicatorView;
|
||||
public final ImageView imageView;
|
||||
public final ImageView deleteView;
|
||||
public final View topDivider;
|
||||
public final View bottomDivider;
|
||||
public final View permissionBottomDivider;
|
||||
|
||||
public final ViewGroup permissionViewGroup;
|
||||
public final ImageView permissionRequestDismissView;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
personViewGroup = (ViewGroup) view.findViewById(getPersonGroupResId());
|
||||
displayNameView = (TextView) view.findViewById(getDisplayNameResId());
|
||||
destinationView = (TextView) view.findViewById(getDestinationResId());
|
||||
destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId());
|
||||
imageView = (ImageView) view.findViewById(getPhotoResId());
|
||||
deleteView = (ImageView) view.findViewById(getDeleteResId());
|
||||
topDivider = view.findViewById(R.id.chip_autocomplete_top_divider);
|
||||
|
||||
bottomDivider = view.findViewById(R.id.chip_autocomplete_bottom_divider);
|
||||
permissionBottomDivider = view.findViewById(R.id.chip_permission_bottom_divider);
|
||||
|
||||
indicatorView = (TextView) view.findViewById(R.id.chip_indicator_text);
|
||||
|
||||
permissionViewGroup = (ViewGroup) view.findViewById(getPermissionGroupResId());
|
||||
permissionRequestDismissView =
|
||||
(ImageView) view.findViewById(getPermissionRequestDismissResId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A utility class for chips using phone numbers.
|
||||
*/
|
||||
public class PhoneUtil {
|
||||
|
||||
// This pattern comes from android.util.Patterns. It has been tweaked to handle a "1" before
|
||||
// parens, so numbers such as "1 (425) 222-2342" match.
|
||||
private static final Pattern PHONE_PATTERN
|
||||
= Pattern.compile( // sdd = space, dot, or dash
|
||||
"(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>*
|
||||
+ "(1?[ ]*\\([0-9]+\\)[\\- \\.]*)?" // 1(<digits>)<sdd>*
|
||||
+ "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
|
||||
|
||||
/**
|
||||
* Returns true if the string is a phone number.
|
||||
*/
|
||||
public static boolean isPhoneNumber(String number) {
|
||||
// TODO: replace this function with libphonenumber's isPossibleNumber (see
|
||||
// PhoneNumberUtil). One complication is that it requires the sender's region which
|
||||
// comes from the CurrentCountryIso. For now, let's just do this simple match.
|
||||
if (TextUtils.isEmpty(number)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Matcher match = PHONE_PATTERN.matcher(number);
|
||||
return match.matches();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.ex.chips;
|
||||
|
||||
/**
|
||||
* Used by the {@link com.android.ex.chips.BaseRecipientAdapter} to handle fetching
|
||||
* photos from external sources and caching them for faster lookup later.
|
||||
*/
|
||||
public interface PhotoManager {
|
||||
|
||||
/** The number of photos cached in this Adapter. */
|
||||
public static final int PHOTO_CACHE_SIZE = 20;
|
||||
|
||||
/**
|
||||
* Sets the {@link com.android.ex.chips.RecipientEntry}'s photo bytes. If the photo bytes
|
||||
* are cached, this action happens immediately. Otherwise, the work to fetch the photo
|
||||
* bytes is performed asynchronously before setting the value on the UI thread.<p/>
|
||||
*
|
||||
* If the photo bytes were fetched asynchronously,
|
||||
* {@link PhotoManagerCallback#onPhotoBytesAsynchronouslyPopulated()} is called. This
|
||||
* method is not called if the photo bytes have been cached previously (because no
|
||||
* asynchronous work was performed). In that case,
|
||||
* {@link PhotoManagerCallback#onPhotoBytesPopulated()} is called.
|
||||
*/
|
||||
void populatePhotoBytesAsync(RecipientEntry entry, PhotoManagerCallback callback);
|
||||
|
||||
interface PhotoManagerCallback {
|
||||
void onPhotoBytesPopulated();
|
||||
void onPhotoBytesAsynchronouslyPopulated();
|
||||
void onPhotoBytesAsyncLoadFailed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Phone and Email queries for supporting Chips UI.
|
||||
*/
|
||||
/* 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.LOOKUP_KEY, // 8
|
||||
ContactsContract.CommonDataKinds.Email.MIMETYPE // 9
|
||||
}, Phone.CONTENT_FILTER_URI, Phone.CONTENT_URI) {
|
||||
|
||||
@Override
|
||||
public CharSequence getTypeLabel(Resources res, int type, CharSequence label) {
|
||||
return Phone.getTypeLabel(res, type, label);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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.LOOKUP_KEY, // 8
|
||||
ContactsContract.CommonDataKinds.Email.MIMETYPE // 9
|
||||
}, Email.CONTENT_FILTER_URI, Email.CONTENT_URI) {
|
||||
|
||||
@Override
|
||||
public CharSequence getTypeLabel(Resources res, int type, CharSequence label) {
|
||||
return Email.getTypeLabel(res, type, label);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static abstract class Query {
|
||||
private final String[] mProjection;
|
||||
private final Uri mContentFilterUri;
|
||||
private final Uri mContentUri;
|
||||
|
||||
public static final int NAME = 0; // String
|
||||
public static final int DESTINATION = 1; // String
|
||||
public static final int DESTINATION_TYPE = 2; // int
|
||||
public static final int DESTINATION_LABEL = 3; // String
|
||||
public static final int CONTACT_ID = 4; // long
|
||||
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) {
|
||||
mProjection = projection;
|
||||
mContentFilterUri = contentFilter;
|
||||
mContentUri = content;
|
||||
}
|
||||
|
||||
public String[] getProjection() {
|
||||
return mProjection;
|
||||
}
|
||||
|
||||
public Uri getContentFilterUri() {
|
||||
return mContentFilterUri;
|
||||
}
|
||||
|
||||
public Uri getContentUri() {
|
||||
return mContentUri;
|
||||
}
|
||||
|
||||
public abstract CharSequence getTypeLabel(Resources res, int type, CharSequence label);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,621 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
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.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
|
||||
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;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* RecipientAlternatesAdapter backs the RecipientEditTextView for managing contacts
|
||||
* queried by email or by phone number.
|
||||
*/
|
||||
public class RecipientAlternatesAdapter extends CursorAdapter {
|
||||
public static final int MAX_LOOKUPS = 50;
|
||||
|
||||
private final long mCurrentId;
|
||||
|
||||
private int mCheckedItemPosition = -1;
|
||||
|
||||
private OnCheckedItemChangedListener mCheckedItemChangedListener;
|
||||
|
||||
private static final String TAG = "RecipAlternates";
|
||||
|
||||
public static final int QUERY_TYPE_EMAIL = 0;
|
||||
public static final int QUERY_TYPE_PHONE = 1;
|
||||
private final Long mDirectoryId;
|
||||
private DropdownChipLayouter mDropdownChipLayouter;
|
||||
private final StateListDrawable mDeleteDrawable;
|
||||
|
||||
private static final Map<String, String> sCorrectedPhotoUris = new HashMap<String, String>();
|
||||
|
||||
public interface RecipientMatchCallback {
|
||||
public void matchesFound(Map<String, RecipientEntry> results);
|
||||
/**
|
||||
* Called with all addresses that could not be resolved to valid recipients.
|
||||
*/
|
||||
public void matchesNotFound(Set<String> unfoundAddresses);
|
||||
}
|
||||
|
||||
public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
|
||||
ArrayList<String> inAddresses, Account account, RecipientMatchCallback callback,
|
||||
ChipsUtil.PermissionsCheckListener permissionsCheckListener) {
|
||||
getMatchingRecipients(context, adapter, inAddresses, QUERY_TYPE_EMAIL, account, callback,
|
||||
permissionsCheckListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a HashMap of address to RecipientEntry that contains all contact
|
||||
* information for a contact with the provided address, if one exists. This
|
||||
* may block the UI, so run it in an async task.
|
||||
*
|
||||
* @param context Context.
|
||||
* @param inAddresses Array of addresses on which to perform the lookup.
|
||||
* @param callback RecipientMatchCallback called when a match or matches are found.
|
||||
*/
|
||||
public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
|
||||
ArrayList<String> inAddresses, int addressType, Account account,
|
||||
RecipientMatchCallback callback,
|
||||
ChipsUtil.PermissionsCheckListener permissionsCheckListener) {
|
||||
Queries.Query query;
|
||||
if (addressType == QUERY_TYPE_EMAIL) {
|
||||
query = Queries.EMAIL;
|
||||
} else {
|
||||
query = Queries.PHONE;
|
||||
}
|
||||
int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.size());
|
||||
HashSet<String> addresses = new HashSet<String>();
|
||||
StringBuilder bindString = new StringBuilder();
|
||||
// Create the "?" string and set up arguments.
|
||||
for (int i = 0; i < addressesSize; i++) {
|
||||
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
|
||||
addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
|
||||
bindString.append("?");
|
||||
if (i < addressesSize - 1) {
|
||||
bindString.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Doing reverse lookup for " + addresses.toString());
|
||||
}
|
||||
|
||||
String[] addressArray = new String[addresses.size()];
|
||||
addresses.toArray(addressArray);
|
||||
HashMap<String, RecipientEntry> recipientEntries = null;
|
||||
Cursor c = null;
|
||||
|
||||
try {
|
||||
if (ChipsUtil.hasPermissions(context, permissionsCheckListener)) {
|
||||
c = context.getContentResolver().query(
|
||||
query.getContentUri(),
|
||||
query.getProjection(),
|
||||
query.getProjection()[Queries.Query.DESTINATION] + " IN ("
|
||||
+ bindString.toString() + ")", addressArray, null);
|
||||
}
|
||||
recipientEntries = processContactEntries(c, null /* directoryId */);
|
||||
callback.matchesFound(recipientEntries);
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
final Set<String> matchesNotFound = new HashSet<String>();
|
||||
|
||||
getMatchingRecipientsFromDirectoryQueries(context, recipientEntries,
|
||||
addresses, account, matchesNotFound, query, callback, permissionsCheckListener);
|
||||
|
||||
getMatchingRecipientsFromExtensionMatcher(adapter, matchesNotFound, callback);
|
||||
}
|
||||
|
||||
public static void getMatchingRecipientsFromDirectoryQueries(Context context,
|
||||
Map<String, RecipientEntry> recipientEntries, Set<String> addresses,
|
||||
Account account, Set<String> matchesNotFound,
|
||||
RecipientMatchCallback callback,
|
||||
ChipsUtil.PermissionsCheckListener permissionsCheckListener) {
|
||||
getMatchingRecipientsFromDirectoryQueries(
|
||||
context, recipientEntries, addresses, account,
|
||||
matchesNotFound, Queries.EMAIL, callback, permissionsCheckListener);
|
||||
}
|
||||
|
||||
private static void getMatchingRecipientsFromDirectoryQueries(Context context,
|
||||
Map<String, RecipientEntry> recipientEntries, Set<String> addresses,
|
||||
Account account, Set<String> matchesNotFound, Queries.Query query,
|
||||
RecipientMatchCallback callback,
|
||||
ChipsUtil.PermissionsCheckListener permissionsCheckListener) {
|
||||
// See if any entries did not resolve; if so, we need to check other
|
||||
// directories
|
||||
|
||||
if (recipientEntries.size() < addresses.size()) {
|
||||
// Run a directory query for each unmatched recipient.
|
||||
HashSet<String> unresolvedAddresses = new HashSet<String>();
|
||||
for (String address : addresses) {
|
||||
if (!recipientEntries.containsKey(address)) {
|
||||
unresolvedAddresses.add(address);
|
||||
}
|
||||
}
|
||||
matchesNotFound.addAll(unresolvedAddresses);
|
||||
|
||||
final List<DirectorySearchParams> paramsList;
|
||||
Cursor directoryCursor = null;
|
||||
try {
|
||||
if (ChipsUtil.hasPermissions(context, permissionsCheckListener)) {
|
||||
directoryCursor = context.getContentResolver().query(
|
||||
DirectoryListQuery.URI, DirectoryListQuery.PROJECTION,
|
||||
null, null, null);
|
||||
}
|
||||
if (directoryCursor == null) {
|
||||
return;
|
||||
}
|
||||
paramsList = BaseRecipientAdapter.setupOtherDirectories(
|
||||
context, directoryCursor, account);
|
||||
} finally {
|
||||
if (directoryCursor != null) {
|
||||
directoryCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (paramsList != null) {
|
||||
Cursor directoryContactsCursor = null;
|
||||
for (String unresolvedAddress : unresolvedAddresses) {
|
||||
for (int i = 0; i < paramsList.size(); i++) {
|
||||
final long directoryId = paramsList.get(i).directoryId;
|
||||
try {
|
||||
directoryContactsCursor = doQuery(unresolvedAddress, 1 /* limit */,
|
||||
directoryId, account, context, query, permissionsCheckListener);
|
||||
if (directoryContactsCursor != null
|
||||
&& directoryContactsCursor.getCount() != 0) {
|
||||
// We found the directory with at least one contact
|
||||
final Map<String, RecipientEntry> entries =
|
||||
processContactEntries(directoryContactsCursor, directoryId);
|
||||
|
||||
for (final String address : entries.keySet()) {
|
||||
matchesNotFound.remove(address);
|
||||
}
|
||||
|
||||
callback.matchesFound(entries);
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
if (directoryContactsCursor != null) {
|
||||
directoryContactsCursor.close();
|
||||
directoryContactsCursor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void getMatchingRecipientsFromExtensionMatcher(BaseRecipientAdapter adapter,
|
||||
Set<String> matchesNotFound, RecipientMatchCallback callback) {
|
||||
// If no matches found in contact provider or the directories, try the extension
|
||||
// matcher.
|
||||
// todo (aalbert): This whole method needs to be in the adapter?
|
||||
if (adapter != null) {
|
||||
final Map<String, RecipientEntry> entries =
|
||||
adapter.getMatchingRecipients(matchesNotFound);
|
||||
if (entries != null && entries.size() > 0) {
|
||||
callback.matchesFound(entries);
|
||||
for (final String address : entries.keySet()) {
|
||||
matchesNotFound.remove(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
callback.matchesNotFound(matchesNotFound);
|
||||
}
|
||||
|
||||
private static HashMap<String, RecipientEntry> processContactEntries(Cursor c,
|
||||
Long directoryId) {
|
||||
HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
|
||||
if (c != null && c.moveToFirst()) {
|
||||
do {
|
||||
String address = c.getString(Queries.Query.DESTINATION);
|
||||
|
||||
final RecipientEntry newRecipientEntry = RecipientEntry.constructTopLevelEntry(
|
||||
c.getString(Queries.Query.NAME),
|
||||
c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
|
||||
c.getString(Queries.Query.DESTINATION),
|
||||
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,
|
||||
c.getString(Queries.Query.LOOKUP_KEY));
|
||||
|
||||
/*
|
||||
* In certain situations, we may have two results for one address, where one of the
|
||||
* results is just the email address, and the other has a name and photo, so we want
|
||||
* to use the better one.
|
||||
*/
|
||||
final RecipientEntry recipientEntry =
|
||||
getBetterRecipient(recipientEntries.get(address), newRecipientEntry);
|
||||
|
||||
recipientEntries.put(address, recipientEntry);
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Received reverse look up information for " + address
|
||||
+ " RESULTS: "
|
||||
+ " NAME : " + c.getString(Queries.Query.NAME)
|
||||
+ " CONTACT ID : " + c.getLong(Queries.Query.CONTACT_ID)
|
||||
+ " ADDRESS :" + c.getString(Queries.Query.DESTINATION));
|
||||
}
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
return recipientEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two {@link RecipientEntry}s for the same email address, this will return the one that
|
||||
* contains more complete information for display purposes. Defaults to <code>entry2</code> if
|
||||
* no significant differences are found.
|
||||
*/
|
||||
static RecipientEntry getBetterRecipient(final RecipientEntry entry1,
|
||||
final RecipientEntry entry2) {
|
||||
// If only one has passed in, use it
|
||||
if (entry2 == null) {
|
||||
return entry1;
|
||||
}
|
||||
|
||||
if (entry1 == null) {
|
||||
return entry2;
|
||||
}
|
||||
|
||||
// If only one has a display name, use it
|
||||
if (!TextUtils.isEmpty(entry1.getDisplayName())
|
||||
&& TextUtils.isEmpty(entry2.getDisplayName())) {
|
||||
return entry1;
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(entry2.getDisplayName())
|
||||
&& TextUtils.isEmpty(entry1.getDisplayName())) {
|
||||
return entry2;
|
||||
}
|
||||
|
||||
// If only one has a display name that is not the same as the destination, use it
|
||||
if (!TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())
|
||||
&& TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())) {
|
||||
return entry1;
|
||||
}
|
||||
|
||||
if (!TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())
|
||||
&& TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())) {
|
||||
return entry2;
|
||||
}
|
||||
|
||||
// If only one has a photo, use it
|
||||
if ((entry1.getPhotoThumbnailUri() != null || entry1.getPhotoBytes() != null)
|
||||
&& (entry2.getPhotoThumbnailUri() == null && entry2.getPhotoBytes() == null)) {
|
||||
return entry1;
|
||||
}
|
||||
|
||||
if ((entry2.getPhotoThumbnailUri() != null || entry2.getPhotoBytes() != null)
|
||||
&& (entry1.getPhotoThumbnailUri() == null && entry1.getPhotoBytes() == null)) {
|
||||
return entry2;
|
||||
}
|
||||
|
||||
// Go with the second option as a default
|
||||
return entry2;
|
||||
}
|
||||
|
||||
private static Cursor doQuery(CharSequence constraint, int limit, Long directoryId,
|
||||
Account account, Context context, Query query,
|
||||
ChipsUtil.PermissionsCheckListener permissionsCheckListener) {
|
||||
if (!ChipsUtil.hasPermissions(context, permissionsCheckListener)) {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Not doing query because we don't have required permissions.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
final Uri.Builder builder = query
|
||||
.getContentFilterUri()
|
||||
.buildUpon()
|
||||
.appendPath(constraint.toString())
|
||||
.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
|
||||
String.valueOf(limit + BaseRecipientAdapter.ALLOWANCE_FOR_DUPLICATES));
|
||||
if (directoryId != null) {
|
||||
builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
|
||||
String.valueOf(directoryId));
|
||||
}
|
||||
if (account != null) {
|
||||
builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_NAME, account.name);
|
||||
builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_TYPE, account.type);
|
||||
}
|
||||
return context.getContentResolver()
|
||||
.query(builder.build(), query.getProjection(), null, null, null);
|
||||
}
|
||||
|
||||
public RecipientAlternatesAdapter(Context context, long contactId, Long directoryId,
|
||||
String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
|
||||
DropdownChipLayouter dropdownChipLayouter,
|
||||
ChipsUtil.PermissionsCheckListener permissionsCheckListener) {
|
||||
this(context, contactId, directoryId, lookupKey, currentId, queryMode, listener,
|
||||
dropdownChipLayouter, null, permissionsCheckListener);
|
||||
}
|
||||
|
||||
public RecipientAlternatesAdapter(Context context, long contactId, Long directoryId,
|
||||
String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
|
||||
DropdownChipLayouter dropdownChipLayouter, StateListDrawable deleteDrawable,
|
||||
ChipsUtil.PermissionsCheckListener permissionsCheckListener) {
|
||||
super(context,
|
||||
getCursorForConstruction(context, contactId, directoryId, lookupKey, queryMode,
|
||||
permissionsCheckListener),
|
||||
0);
|
||||
mCurrentId = currentId;
|
||||
mDirectoryId = directoryId;
|
||||
mCheckedItemChangedListener = listener;
|
||||
|
||||
mDropdownChipLayouter = dropdownChipLayouter;
|
||||
mDeleteDrawable = deleteDrawable;
|
||||
}
|
||||
|
||||
private static Cursor getCursorForConstruction(Context context, long contactId,
|
||||
Long directoryId, String lookupKey, int queryType,
|
||||
ChipsUtil.PermissionsCheckListener permissionsCheckListener) {
|
||||
final Uri uri;
|
||||
final String desiredMimeType;
|
||||
final String[] projection;
|
||||
|
||||
if (queryType == QUERY_TYPE_EMAIL) {
|
||||
projection = Queries.EMAIL.getProjection();
|
||||
|
||||
if (directoryId == null || lookupKey == null) {
|
||||
uri = Queries.EMAIL.getContentUri();
|
||||
desiredMimeType = null;
|
||||
} else {
|
||||
uri = Contacts.getLookupUri(contactId, lookupKey)
|
||||
.buildUpon()
|
||||
.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
|
||||
.appendQueryParameter(
|
||||
ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId))
|
||||
.build();
|
||||
desiredMimeType = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
|
||||
}
|
||||
} else {
|
||||
projection = Queries.PHONE.getProjection();
|
||||
|
||||
if (lookupKey == null) {
|
||||
uri = Queries.PHONE.getContentUri();
|
||||
desiredMimeType = null;
|
||||
} else {
|
||||
uri = Contacts.getLookupUri(contactId, lookupKey)
|
||||
.buildUpon()
|
||||
.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
|
||||
.appendQueryParameter(
|
||||
ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId))
|
||||
.build();
|
||||
desiredMimeType = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
final String selection = new StringBuilder()
|
||||
.append(projection[Queries.Query.CONTACT_ID])
|
||||
.append(" = ?")
|
||||
.toString();
|
||||
final Cursor cursor;
|
||||
if (ChipsUtil.hasPermissions(context, permissionsCheckListener)) {
|
||||
cursor = context.getContentResolver().query(
|
||||
uri, projection, selection, new String[] {String.valueOf(contactId)}, null);
|
||||
} else {
|
||||
cursor = new MatrixCursor(projection);
|
||||
}
|
||||
|
||||
final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
|
||||
cursor.close();
|
||||
|
||||
return resultCursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new cursor based on the given cursor with all duplicate destinations removed.
|
||||
*
|
||||
* It's only intended to use for the alternate list, so...
|
||||
* - This method ignores all other fields and dedupe solely on the destination. Normally,
|
||||
* if a cursor contains multiple contacts and they have the same destination, we'd still want
|
||||
* to show both.
|
||||
* - 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
|
||||
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);
|
||||
|
||||
final Object[] row = new Object[] {
|
||||
original.getString(Query.NAME),
|
||||
original.getString(Query.DESTINATION),
|
||||
original.getInt(Query.DESTINATION_TYPE),
|
||||
original.getString(Query.DESTINATION_LABEL),
|
||||
original.getLong(Query.CONTACT_ID),
|
||||
original.getLong(Query.DATA_ID),
|
||||
original.getString(Query.PHOTO_THUMBNAIL_URI),
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
Cursor c = getCursor();
|
||||
if (c.moveToPosition(position)) {
|
||||
c.getLong(Queries.Query.DATA_ID);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public RecipientEntry getRecipientEntry(int position) {
|
||||
Cursor c = getCursor();
|
||||
c.moveToPosition(position);
|
||||
return RecipientEntry.constructTopLevelEntry(
|
||||
c.getString(Queries.Query.NAME),
|
||||
c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
|
||||
c.getString(Queries.Query.DESTINATION),
|
||||
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,
|
||||
c.getString(Queries.Query.LOOKUP_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
Cursor cursor = getCursor();
|
||||
cursor.moveToPosition(position);
|
||||
if (convertView == null) {
|
||||
convertView = mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
|
||||
}
|
||||
if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
|
||||
mCheckedItemPosition = position;
|
||||
if (mCheckedItemChangedListener != null) {
|
||||
mCheckedItemChangedListener.onCheckedItemChanged(mCheckedItemPosition);
|
||||
}
|
||||
}
|
||||
bindView(convertView, convertView.getContext(), cursor);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
int position = cursor.getPosition();
|
||||
RecipientEntry entry = getRecipientEntry(position);
|
||||
|
||||
mDropdownChipLayouter.bindView(view, null, entry, position,
|
||||
AdapterType.RECIPIENT_ALTERNATES, null, mDeleteDrawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
|
||||
}
|
||||
|
||||
/*package*/ static interface OnCheckedItemChangedListener {
|
||||
public void onCheckedItemChanged(int position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Email;
|
||||
import android.provider.ContactsContract.DisplayNameSources;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import android.text.util.Rfc822Token;
|
||||
import android.text.util.Rfc822Tokenizer;
|
||||
|
||||
/**
|
||||
* Represents one entry inside recipient auto-complete list.
|
||||
*/
|
||||
public class RecipientEntry {
|
||||
/* package */ static final int INVALID_CONTACT = -1;
|
||||
/**
|
||||
* A GENERATED_CONTACT is one that was created based entirely on
|
||||
* information passed in to the RecipientEntry from an external source
|
||||
* that is not a real contact.
|
||||
*/
|
||||
/* package */ static final int GENERATED_CONTACT = -2;
|
||||
|
||||
/** Used when {@link #mDestinationType} is invalid and thus shouldn't be used for display. */
|
||||
public static final int INVALID_DESTINATION_TYPE = -1;
|
||||
|
||||
public static final int ENTRY_TYPE_PERSON = 0;
|
||||
|
||||
/**
|
||||
* Entry of this type represents the item in auto-complete that asks user to grant permissions
|
||||
* to the app. This permission model is introduced in M platform.
|
||||
*
|
||||
* <p>Entries of this type should have {@link #mPermissions} set as well.
|
||||
*/
|
||||
public static final int ENTRY_TYPE_PERMISSION_REQUEST = 1;
|
||||
|
||||
public static final int ENTRY_TYPE_SIZE = 2;
|
||||
|
||||
private final int mEntryType;
|
||||
|
||||
/**
|
||||
* True when this entry is the first entry in a group, which should have a photo and display
|
||||
* name, while the second or later entries won't.
|
||||
*/
|
||||
private boolean mIsFirstLevel;
|
||||
private final String mDisplayName;
|
||||
|
||||
/** Destination for this contact entry. Would be an email address or a phone number. */
|
||||
private final String mDestination;
|
||||
/** Type of the destination like {@link Email#TYPE_HOME} */
|
||||
private final int mDestinationType;
|
||||
/**
|
||||
* Label of the destination which will be used when type was {@link Email#TYPE_CUSTOM}.
|
||||
* Can be null when {@link #mDestinationType} is {@link #INVALID_DESTINATION_TYPE}.
|
||||
*/
|
||||
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 Uri mPhotoThumbnailUri;
|
||||
/** Configures showing the icon in the chip */
|
||||
private final boolean mShouldDisplayIcon;
|
||||
|
||||
private boolean mIsValid;
|
||||
/**
|
||||
* This can be updated after this object being constructed, when the photo is fetched
|
||||
* from remote directories.
|
||||
*/
|
||||
private byte[] mPhotoBytes;
|
||||
|
||||
@DrawableRes private int mIndicatorIconId;
|
||||
private String mIndicatorText;
|
||||
|
||||
/** See {@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} */
|
||||
private final String mLookupKey;
|
||||
|
||||
/** Should be used when type is {@link #ENTRY_TYPE_PERMISSION_REQUEST}. */
|
||||
private final String[] mPermissions;
|
||||
|
||||
/** Whether RecipientEntry is in a replaced chip or not. */
|
||||
private boolean mInReplacedChip;
|
||||
|
||||
protected RecipientEntry(int entryType, String displayName, String destination,
|
||||
int destinationType, String destinationLabel, long contactId, Long directoryId,
|
||||
long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
|
||||
String lookupKey, String[] permissions) {
|
||||
this(entryType, displayName, destination, destinationType,
|
||||
destinationLabel, contactId, directoryId, dataId, photoThumbnailUri,
|
||||
true /* shouldDisplayIcon */, isFirstLevel, isValid, lookupKey, permissions);
|
||||
}
|
||||
|
||||
protected RecipientEntry(int entryType, String displayName, String destination,
|
||||
int destinationType, String destinationLabel, long contactId, Long directoryId,
|
||||
long dataId, Uri photoThumbnailUri, boolean shouldDisplayIcon,
|
||||
boolean isFirstLevel, boolean isValid, String lookupKey, String[] permissions) {
|
||||
mEntryType = entryType;
|
||||
mIsFirstLevel = isFirstLevel;
|
||||
mDisplayName = displayName;
|
||||
mDestination = destination;
|
||||
mDestinationType = destinationType;
|
||||
mDestinationLabel = destinationLabel;
|
||||
mContactId = contactId;
|
||||
mDirectoryId = directoryId;
|
||||
mDataId = dataId;
|
||||
mPhotoThumbnailUri = photoThumbnailUri;
|
||||
mShouldDisplayIcon = shouldDisplayIcon;
|
||||
mPhotoBytes = null;
|
||||
mIsValid = isValid;
|
||||
mLookupKey = lookupKey;
|
||||
mIndicatorIconId = 0;
|
||||
mIndicatorText = null;
|
||||
mPermissions = permissions;
|
||||
}
|
||||
|
||||
protected RecipientEntry(int entryType, String displayName, String destination,
|
||||
int destinationType, String destinationLabel, long contactId, Long directoryId,
|
||||
long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
|
||||
String lookupKey) {
|
||||
this(entryType, displayName, destination, destinationType, destinationLabel,
|
||||
contactId, directoryId, dataId, photoThumbnailUri, isFirstLevel, isValid,
|
||||
lookupKey, null);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return mIsValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this was a RecipientEntry created from recipient info or
|
||||
* an entry from contacts.
|
||||
*/
|
||||
public static boolean isCreatedRecipient(long id) {
|
||||
return id == RecipientEntry.INVALID_CONTACT || id == RecipientEntry.GENERATED_CONTACT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a RecipientEntry from just an address that has been entered.
|
||||
* This address has not been resolved to a contact and therefore does not
|
||||
* have a contact id or photo.
|
||||
*/
|
||||
public static RecipientEntry constructFakeEntry(final String address, final boolean isValid) {
|
||||
final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
|
||||
final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;
|
||||
|
||||
return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
|
||||
INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
|
||||
INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a RecipientEntry from just a phone number.
|
||||
*/
|
||||
public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
|
||||
final boolean isValid) {
|
||||
return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
|
||||
INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
|
||||
INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a RecipientEntry from just an address that has been entered
|
||||
* with both an associated display name. This address has not been resolved
|
||||
* to a contact and therefore does not have a contact id or photo.
|
||||
*/
|
||||
public static RecipientEntry constructGeneratedEntry(String display, String address,
|
||||
boolean isValid) {
|
||||
return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
|
||||
null, GENERATED_CONTACT, null /* directoryId */, GENERATED_CONTACT, null, true,
|
||||
isValid, null /* lookupKey */, null /* permissions */);
|
||||
}
|
||||
|
||||
public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
|
||||
String destination, int destinationType, String destinationLabel, long contactId,
|
||||
Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
|
||||
String lookupKey) {
|
||||
return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
|
||||
displayName, destination), destination, destinationType, destinationLabel,
|
||||
contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey,
|
||||
null /* permissions */);
|
||||
}
|
||||
|
||||
public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
|
||||
String destination, int destinationType, 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, directoryId, dataId, (thumbnailUriAsString != null
|
||||
? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey,
|
||||
null /* permissions */);
|
||||
}
|
||||
|
||||
public static RecipientEntry constructSecondLevelEntry(String displayName,
|
||||
int displayNameSource, String destination, int destinationType,
|
||||
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, directoryId, dataId, (thumbnailUriAsString != null
|
||||
? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey,
|
||||
null /* permissions */);
|
||||
}
|
||||
|
||||
public static RecipientEntry constructPermissionEntry(String[] permissions) {
|
||||
return new RecipientEntry(
|
||||
ENTRY_TYPE_PERMISSION_REQUEST,
|
||||
"" /* displayName */,
|
||||
"" /* destination */,
|
||||
Email.TYPE_CUSTOM,
|
||||
"" /* destinationLabel */,
|
||||
INVALID_CONTACT,
|
||||
null /* directoryId */,
|
||||
INVALID_CONTACT,
|
||||
null /* photoThumbnailUri */,
|
||||
true /* isFirstLevel*/,
|
||||
false /* isValid */,
|
||||
null /* lookupKey */,
|
||||
permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the display name for the entry. If the display name source is larger than
|
||||
* {@link DisplayNameSources#PHONE} we use the contact's display name, but if not,
|
||||
* i.e. the display name came from an email address or a phone number, we don't use it
|
||||
* to avoid confusion and just use the destination instead.
|
||||
*/
|
||||
private static String pickDisplayName(int displayNameSource, String displayName,
|
||||
String destination) {
|
||||
return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination;
|
||||
}
|
||||
|
||||
public int getEntryType() {
|
||||
return mEntryType;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return mDisplayName;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return mDestination;
|
||||
}
|
||||
|
||||
public int getDestinationType() {
|
||||
return mDestinationType;
|
||||
}
|
||||
|
||||
public String getDestinationLabel() {
|
||||
return mDestinationLabel;
|
||||
}
|
||||
|
||||
public long getContactId() {
|
||||
return mContactId;
|
||||
}
|
||||
|
||||
public Long getDirectoryId() {
|
||||
return mDirectoryId;
|
||||
}
|
||||
|
||||
public long getDataId() {
|
||||
return mDataId;
|
||||
}
|
||||
|
||||
public boolean isFirstLevel() {
|
||||
return mIsFirstLevel;
|
||||
}
|
||||
|
||||
public Uri getPhotoThumbnailUri() {
|
||||
return mPhotoThumbnailUri;
|
||||
}
|
||||
|
||||
/** Indicates whether the icon in the chip is displayed or not. */
|
||||
public boolean shouldDisplayIcon() {
|
||||
return mShouldDisplayIcon;
|
||||
}
|
||||
|
||||
/** This can be called outside main Looper thread. */
|
||||
public synchronized void setPhotoBytes(byte[] photoBytes) {
|
||||
mPhotoBytes = photoBytes;
|
||||
}
|
||||
|
||||
/** This can be called outside main Looper thread. */
|
||||
public synchronized byte[] getPhotoBytes() {
|
||||
return mPhotoBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used together with {@link #ENTRY_TYPE_PERMISSION_REQUEST} and indicates what permissions we
|
||||
* need to ask user to grant.
|
||||
*/
|
||||
public String[] getPermissions() {
|
||||
return mPermissions;
|
||||
}
|
||||
|
||||
public String getLookupKey() {
|
||||
return mLookupKey;
|
||||
}
|
||||
|
||||
public boolean isSelectable() {
|
||||
return mEntryType == ENTRY_TYPE_PERSON || mEntryType == ENTRY_TYPE_PERMISSION_REQUEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDisplayName + " <" + mDestination + ">, isValid=" + mIsValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if entry represents the same person as this instance. The default implementation
|
||||
* checks whether the contact ids are the same, and subclasses may opt to override this.
|
||||
*/
|
||||
public boolean isSamePerson(final RecipientEntry entry) {
|
||||
return entry != null && mContactId == entry.mContactId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource ID for the indicator icon, or 0 if no icon should be displayed.
|
||||
*/
|
||||
@DrawableRes
|
||||
public int getIndicatorIconId() {
|
||||
return mIndicatorIconId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indicator icon to the given resource ID. Set to 0 to display no icon.
|
||||
*/
|
||||
public void setIndicatorIconId(@DrawableRes int indicatorIconId) {
|
||||
mIndicatorIconId = indicatorIconId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the indicator text, or null if no text should be displayed.
|
||||
*/
|
||||
public String getIndicatorText() {
|
||||
return mIndicatorText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the indicator text. Set to null for no text to be displayed.
|
||||
*/
|
||||
public void setIndicatorText(String indicatorText) {
|
||||
mIndicatorText = indicatorText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this RecipientEntry is in a replaced chip or not. Replaced chip only occurs
|
||||
* if {@link RecipientEditTextView} uses a replacement chip for the entry.
|
||||
*/
|
||||
public boolean getInReplacedChip() {
|
||||
return mInReplacedChip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #mInReplacedChip} to {@param inReplacedChip}.
|
||||
*/
|
||||
public void setInReplacedChip(boolean inReplacedChip) {
|
||||
mInReplacedChip = inReplacedChip;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import com.android.ex.chips.DropdownChipLayouter.AdapterType;
|
||||
|
||||
class SingleRecipientArrayAdapter extends ArrayAdapter<RecipientEntry> {
|
||||
private final DropdownChipLayouter mDropdownChipLayouter;
|
||||
private final StateListDrawable mDeleteDrawable;
|
||||
|
||||
public SingleRecipientArrayAdapter(Context context, RecipientEntry entry,
|
||||
DropdownChipLayouter dropdownChipLayouter) {
|
||||
this(context, entry, dropdownChipLayouter, null);
|
||||
}
|
||||
|
||||
public SingleRecipientArrayAdapter(Context context, RecipientEntry entry,
|
||||
DropdownChipLayouter dropdownChipLayouter, StateListDrawable deleteDrawable) {
|
||||
super(context,
|
||||
dropdownChipLayouter.getAlternateItemLayoutResId(AdapterType.SINGLE_RECIPIENT),
|
||||
new RecipientEntry[] { entry });
|
||||
|
||||
mDropdownChipLayouter = dropdownChipLayouter;
|
||||
mDeleteDrawable = deleteDrawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
return mDropdownChipLayouter.bindView(convertView, parent, getItem(position), position,
|
||||
AdapterType.SINGLE_RECIPIENT, null, mDeleteDrawable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.recipientchip;
|
||||
|
||||
import com.android.ex.chips.RecipientEntry;
|
||||
|
||||
/**
|
||||
* BaseRecipientChip defines an object that contains information relevant to a
|
||||
* particular recipient.
|
||||
*/
|
||||
interface BaseRecipientChip {
|
||||
|
||||
/**
|
||||
* Set the selected state of the chip.
|
||||
*/
|
||||
void setSelected(boolean selected);
|
||||
|
||||
/**
|
||||
* Return true if the chip is selected.
|
||||
*/
|
||||
boolean isSelected();
|
||||
|
||||
/**
|
||||
* Get the text displayed in the chip.
|
||||
*/
|
||||
CharSequence getDisplay();
|
||||
|
||||
/**
|
||||
* Get the text value this chip represents.
|
||||
*/
|
||||
CharSequence getValue();
|
||||
|
||||
/**
|
||||
* Get the id of the contact associated with this chip.
|
||||
*/
|
||||
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();
|
||||
|
||||
/**
|
||||
* Get associated RecipientEntry.
|
||||
*/
|
||||
RecipientEntry getEntry();
|
||||
|
||||
/**
|
||||
* Set the text in the edittextview originally associated with this chip
|
||||
* before any reverse lookups.
|
||||
*/
|
||||
void setOriginalText(String text);
|
||||
|
||||
/**
|
||||
* Set the text in the edittextview originally associated with this chip
|
||||
* before any reverse lookups.
|
||||
*/
|
||||
CharSequence getOriginalText();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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.recipientchip;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
|
||||
/**
|
||||
* RecipientChip defines a drawable object that contains information relevant to a
|
||||
* particular recipient.
|
||||
*/
|
||||
public interface DrawableRecipientChip extends BaseRecipientChip {
|
||||
/**
|
||||
* Get the bounds of the chip; may be 0,0 if it is not visibly rendered.
|
||||
*/
|
||||
Rect getBounds();
|
||||
|
||||
/**
|
||||
* Gets the bounds of the warning icon on the chip; may be 0,0 if not visibly rendered or if
|
||||
* there is no warning icon.
|
||||
*/
|
||||
Rect getWarningIconBounds();
|
||||
|
||||
/**
|
||||
* Draw the chip.
|
||||
*/
|
||||
void draw(Canvas canvas);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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.recipientchip;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.text.style.ReplacementSpan;
|
||||
|
||||
import com.android.ex.chips.RecipientEntry;
|
||||
|
||||
/**
|
||||
* RecipientChip defines a span that contains information relevant to a
|
||||
* particular recipient.
|
||||
*/
|
||||
public class InvisibleRecipientChip extends ReplacementSpan implements DrawableRecipientChip {
|
||||
private final SimpleRecipientChip mDelegate;
|
||||
private static final Rect NULL_RECTANGLE = new Rect(0, 0, 0, 0);
|
||||
|
||||
public InvisibleRecipientChip(final RecipientEntry entry) {
|
||||
super();
|
||||
|
||||
mDelegate = new SimpleRecipientChip(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(final boolean selected) {
|
||||
mDelegate.setSelected(selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected() {
|
||||
return mDelegate.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getDisplay() {
|
||||
return mDelegate.getDisplay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getValue() {
|
||||
return mDelegate.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContactId() {
|
||||
return mDelegate.getContactId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDirectoryId() {
|
||||
return mDelegate.getDirectoryId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLookupKey() {
|
||||
return mDelegate.getLookupKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDataId() {
|
||||
return mDelegate.getDataId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipientEntry getEntry() {
|
||||
return mDelegate.getEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOriginalText(final String text) {
|
||||
mDelegate.setOriginalText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getOriginalText() {
|
||||
return mDelegate.getOriginalText();
|
||||
}
|
||||
|
||||
@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.
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(final Paint paint, final CharSequence text, final int start, final int end,
|
||||
final Paint.FontMetricsInt fm) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getBounds() {
|
||||
return NULL_RECTANGLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getWarningIconBounds() {
|
||||
return NULL_RECTANGLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(final Canvas canvas) {
|
||||
// do nothing.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.android.ex.chips.recipientchip;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.style.ReplacementSpan;
|
||||
|
||||
/**
|
||||
* ReplacementSpan that properly draws the drawable that is centered around the text
|
||||
* without changing the default text size or layout.
|
||||
*/
|
||||
public class ReplacementDrawableSpan extends ReplacementSpan {
|
||||
protected static final Paint sWorkPaint = new Paint();
|
||||
|
||||
protected Drawable mDrawable;
|
||||
private float mExtraMargin;
|
||||
|
||||
public ReplacementDrawableSpan(Drawable drawable) {
|
||||
super();
|
||||
mDrawable = drawable;
|
||||
}
|
||||
|
||||
public void setExtraMargin(float margin) {
|
||||
mExtraMargin = margin;
|
||||
}
|
||||
|
||||
private void setupFontMetrics(Paint.FontMetricsInt fm, Paint paint) {
|
||||
sWorkPaint.set(paint);
|
||||
if (fm != null) {
|
||||
sWorkPaint.getFontMetricsInt(fm);
|
||||
|
||||
final Rect bounds = getBounds();
|
||||
final int textHeight = fm.descent - fm.ascent;
|
||||
final int halfMargin = (int) mExtraMargin / 2;
|
||||
fm.ascent = Math.min(fm.top, fm.top + (textHeight - bounds.bottom) / 2) - halfMargin;
|
||||
fm.descent = Math.max(fm.bottom, fm.bottom + (bounds.bottom - textHeight) / 2)
|
||||
+ halfMargin;
|
||||
fm.top = fm.ascent;
|
||||
fm.bottom = fm.descent;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(Paint paint, CharSequence text, int i, int i2, Paint.FontMetricsInt fm) {
|
||||
setupFontMetrics(fm, paint);
|
||||
return getBounds().right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, CharSequence charSequence, int start, int end, float x, int top,
|
||||
int y, int bottom, Paint paint) {
|
||||
canvas.save();
|
||||
int transY = (bottom - mDrawable.getBounds().bottom + top) / 2;
|
||||
canvas.translate(x, transY);
|
||||
mDrawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
protected Rect getBounds() {
|
||||
return mDrawable.getBounds();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.recipientchip;
|
||||
|
||||
import com.android.ex.chips.RecipientEntry;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
class SimpleRecipientChip implements BaseRecipientChip {
|
||||
private final CharSequence mDisplay;
|
||||
|
||||
private final CharSequence mValue;
|
||||
|
||||
private final long mContactId;
|
||||
|
||||
private final Long mDirectoryId;
|
||||
|
||||
private final String mLookupKey;
|
||||
|
||||
private final long mDataId;
|
||||
|
||||
private final RecipientEntry mEntry;
|
||||
|
||||
private boolean mSelected = false;
|
||||
|
||||
private CharSequence mOriginalText;
|
||||
|
||||
public SimpleRecipientChip(final RecipientEntry entry) {
|
||||
mDisplay = entry.getDisplayName();
|
||||
mValue = entry.getDestination().trim();
|
||||
mContactId = entry.getContactId();
|
||||
mDirectoryId = entry.getDirectoryId();
|
||||
mLookupKey = entry.getLookupKey();
|
||||
mDataId = entry.getDataId();
|
||||
mEntry = entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(final boolean selected) {
|
||||
mSelected = selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected() {
|
||||
return mSelected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getDisplay() {
|
||||
return mDisplay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContactId() {
|
||||
return mContactId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDirectoryId() {
|
||||
return mDirectoryId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLookupKey() {
|
||||
return mLookupKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDataId() {
|
||||
return mDataId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipientEntry getEntry() {
|
||||
return mEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOriginalText(final String text) {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
mOriginalText = text;
|
||||
} else {
|
||||
mOriginalText = text.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getOriginalText() {
|
||||
return !TextUtils.isEmpty(mOriginalText) ? mOriginalText : mEntry.getDestination();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDisplay + " <" + mValue + ">";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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.recipientchip;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.android.ex.chips.RecipientEntry;
|
||||
|
||||
/**
|
||||
* VisibleRecipientChip defines a ReplacementSpan that contains information relevant to a
|
||||
* particular recipient and renders a background asset to go with it.
|
||||
*/
|
||||
public class VisibleRecipientChip extends ReplacementDrawableSpan implements DrawableRecipientChip {
|
||||
private final SimpleRecipientChip mDelegate;
|
||||
private Rect mWarningIconBounds = new Rect(0, 0, 0, 0);
|
||||
|
||||
public VisibleRecipientChip(final Drawable drawable, final RecipientEntry entry) {
|
||||
super(drawable);
|
||||
mDelegate = new SimpleRecipientChip(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(final boolean selected) {
|
||||
mDelegate.setSelected(selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected() {
|
||||
return mDelegate.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getDisplay() {
|
||||
return mDelegate.getDisplay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getValue() {
|
||||
return mDelegate.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContactId() {
|
||||
return mDelegate.getContactId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDirectoryId() {
|
||||
return mDelegate.getDirectoryId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLookupKey() {
|
||||
return mDelegate.getLookupKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDataId() {
|
||||
return mDelegate.getDataId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipientEntry getEntry() {
|
||||
return mDelegate.getEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOriginalText(final String text) {
|
||||
mDelegate.setOriginalText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getOriginalText() {
|
||||
return mDelegate.getOriginalText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getBounds() {
|
||||
return super.getBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(final Canvas canvas) {
|
||||
mDrawable.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDelegate.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getWarningIconBounds() {
|
||||
return mWarningIconBounds;
|
||||
}
|
||||
|
||||
public void setWarningIconBounds(Rect warningIconBounds) {
|
||||
mWarningIconBounds = warningIconBounds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# Copyright (C) 2011 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT 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_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_PACKAGE_NAME := libChipsTests
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_SDK_VERSION := current
|
||||
LOCAL_CERTIFICATE := platform
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := libchips legacy-android-test junit
|
||||
LOCAL_RESOURCE_DIR := frameworks/opt/chips/res/
|
||||
LOCAL_AAPT_FLAGS := --auto-add-overlay
|
||||
LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.ex.chips.tests"
|
||||
android:sharedUserId="com.android.uid.test">
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<!-- Run tests with "runtest android-common" -->
|
||||
<instrumentation android:name="android.test.InstrumentationTestRunner"
|
||||
android:targetPackage="com.android.ex.chips.tests"
|
||||
android:label="Chips Tests" />
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract.DisplayNameSources;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.android.ex.chips.RecipientAlternatesAdapter;
|
||||
import com.android.ex.chips.RecipientEntry;
|
||||
|
||||
public class RecipientAlternatesAdapterTest extends AndroidTestCase {
|
||||
|
||||
public void testRemoveUndesiredDestinations() {
|
||||
MatrixCursor c = new MatrixCursor(Queries.EMAIL.getProjection());
|
||||
Cursor result;
|
||||
|
||||
// Test: Empty input
|
||||
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.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.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);
|
||||
|
||||
// Test: add a third row with a non-unique destination.
|
||||
addRow(c, "ax", "1@android.com", 11, "homex", 10001, 2000, "xx", 1);
|
||||
|
||||
// Third row should be removed.
|
||||
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);
|
||||
|
||||
// Test: add a forth row with a non-unique destination again.
|
||||
addRow(c, "ax", "2@android.com", 11, "homex", 10001, 2000, "xx", 1);
|
||||
|
||||
// Forth row should also be removed.
|
||||
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);
|
||||
}
|
||||
|
||||
private static MatrixCursor addRow(MatrixCursor c,
|
||||
String displayName,
|
||||
String destination,
|
||||
int destinationType,
|
||||
String destinationLabel,
|
||||
long contactId,
|
||||
long dataId,
|
||||
String photoUri,
|
||||
int displayNameSource
|
||||
) {
|
||||
c.addRow(new Object[] {displayName, destination, destinationType, destinationLabel,
|
||||
contactId, dataId, photoUri, displayNameSource});
|
||||
return c;
|
||||
}
|
||||
|
||||
private static void assertRow(Cursor c, int position,
|
||||
String displayName,
|
||||
String destination,
|
||||
int destinationType,
|
||||
String destinationLabel,
|
||||
long contactId,
|
||||
long dataId,
|
||||
String photoUri,
|
||||
int displayNameSource
|
||||
) {
|
||||
assertTrue(c.moveToPosition(position));
|
||||
assertEquals(displayName, c.getString(0));
|
||||
assertEquals(destination, c.getString(1));
|
||||
assertEquals(destinationType, c.getInt(2));
|
||||
assertEquals(destinationLabel, c.getString(3));
|
||||
assertEquals(contactId, c.getLong(4));
|
||||
assertEquals(dataId, c.getLong(5));
|
||||
assertEquals(photoUri, c.getString(6));
|
||||
assertEquals(displayNameSource, c.getInt(7));
|
||||
}
|
||||
|
||||
public void testGetBetterRecipient() {
|
||||
// Ensure that if either (but not both) parameters are null, the other is returned
|
||||
{
|
||||
final RecipientEntry entry1 =
|
||||
RecipientEntry.constructFakeEntry("1@android.com", true);
|
||||
final RecipientEntry entry2 = null;
|
||||
|
||||
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
|
||||
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
|
||||
}
|
||||
|
||||
// Ensure that if only one has a display name, it is used
|
||||
{
|
||||
final RecipientEntry entry1 =
|
||||
RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
|
||||
"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);
|
||||
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
|
||||
}
|
||||
|
||||
// Ensure that if one has a display name different from its destination, and the other's
|
||||
// is equal to its destination, we use the unique one
|
||||
{
|
||||
final RecipientEntry entry1 =
|
||||
RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
|
||||
"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, null /* directoryId */, 0, (Uri) null,
|
||||
true, null /* lookupKey */);
|
||||
|
||||
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
|
||||
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
|
||||
}
|
||||
|
||||
// Ensure that if only one has a photo, it is used
|
||||
{
|
||||
final RecipientEntry entry1 =
|
||||
RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
|
||||
"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, null /* directoryId */,
|
||||
0, (Uri) null, true, null /* lookupKey */);
|
||||
|
||||
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
|
||||
assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
bin/
|
||||
gen/
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "colorpicker",
|
||||
sdk_version: "15",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.logtags",
|
||||
],
|
||||
resource_dirs: ["res"],
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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="com.android.colorpicker"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
</manifest>
|
||||
@@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.android.colorpicker'
|
||||
compileSdk 35
|
||||
|
||||
defaultConfig {
|
||||
minSdk 33
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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-15
|
||||
android.library=true
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,16 @@
|
||||
<?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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" />
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center" >
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center"
|
||||
android:padding="28dp" >
|
||||
|
||||
<ProgressBar
|
||||
android:id="@android:id/progress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center" />
|
||||
|
||||
<com.android.colorpicker.ColorPickerPalette
|
||||
android:id="@+id/color_picker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</ScrollView>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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.
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<ImageView
|
||||
android:id="@+id/color_picker_swatch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<ImageView
|
||||
android:id="@+id/color_picker_checkmark"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/ic_colorpicker_swatch_selected"
|
||||
android:scaleType="fitXY"
|
||||
android:visibility="gone" />
|
||||
</merge>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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>
|
||||
<dimen name="color_swatch_large">64dip</dimen>
|
||||
<dimen name="color_swatch_small">48dip</dimen>
|
||||
<dimen name="color_swatch_margins_large">8dip</dimen>
|
||||
<dimen name="color_swatch_margins_small">4dip</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
|
||||
<string name="color_picker_default_title">Select a Color</string>
|
||||
<!-- Content description for a color square. -->
|
||||
<string name="color_swatch_description">Color <xliff:g id="color_index" example="14">%1$d</xliff:g></string>
|
||||
<!-- Content description for a selected color square. -->
|
||||
<string name="color_swatch_description_selected">Color <xliff:g id="color_index" example="14">%1$d</xliff:g> selected</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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.colorpicker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
|
||||
|
||||
/**
|
||||
* A dialog which takes in as input an array of colors and creates a palette allowing the user to
|
||||
* select a specific color swatch, which invokes a listener.
|
||||
*/
|
||||
public class ColorPickerDialog extends DialogFragment implements OnColorSelectedListener {
|
||||
|
||||
public static final int SIZE_LARGE = 1;
|
||||
public static final int SIZE_SMALL = 2;
|
||||
|
||||
protected AlertDialog mAlertDialog;
|
||||
|
||||
protected static final String KEY_TITLE_ID = "title_id";
|
||||
protected static final String KEY_COLORS = "colors";
|
||||
protected static final String KEY_COLOR_CONTENT_DESCRIPTIONS = "color_content_descriptions";
|
||||
protected static final String KEY_SELECTED_COLOR = "selected_color";
|
||||
protected static final String KEY_COLUMNS = "columns";
|
||||
protected static final String KEY_SIZE = "size";
|
||||
|
||||
protected int mTitleResId = R.string.color_picker_default_title;
|
||||
protected int[] mColors = null;
|
||||
protected String[] mColorContentDescriptions = null;
|
||||
protected int mSelectedColor;
|
||||
protected int mColumns;
|
||||
protected int mSize;
|
||||
|
||||
private ColorPickerPalette mPalette;
|
||||
private ProgressBar mProgress;
|
||||
|
||||
protected OnColorSelectedListener mListener;
|
||||
|
||||
public ColorPickerDialog() {
|
||||
// Empty constructor required for dialog fragments.
|
||||
}
|
||||
|
||||
public static ColorPickerDialog newInstance(int titleResId, int[] colors, int selectedColor,
|
||||
int columns, int size) {
|
||||
ColorPickerDialog ret = new ColorPickerDialog();
|
||||
ret.initialize(titleResId, colors, selectedColor, columns, size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void initialize(int titleResId, int[] colors, int selectedColor, int columns, int size) {
|
||||
setArguments(titleResId, columns, size);
|
||||
setColors(colors, selectedColor);
|
||||
}
|
||||
|
||||
public void setArguments(int titleResId, int columns, int size) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(KEY_TITLE_ID, titleResId);
|
||||
bundle.putInt(KEY_COLUMNS, columns);
|
||||
bundle.putInt(KEY_SIZE, size);
|
||||
setArguments(bundle);
|
||||
}
|
||||
|
||||
public void setOnColorSelectedListener(OnColorSelectedListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (getArguments() != null) {
|
||||
mTitleResId = getArguments().getInt(KEY_TITLE_ID);
|
||||
mColumns = getArguments().getInt(KEY_COLUMNS);
|
||||
mSize = getArguments().getInt(KEY_SIZE);
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mColors = savedInstanceState.getIntArray(KEY_COLORS);
|
||||
mSelectedColor = (Integer) savedInstanceState.getSerializable(KEY_SELECTED_COLOR);
|
||||
mColorContentDescriptions = savedInstanceState.getStringArray(
|
||||
KEY_COLOR_CONTENT_DESCRIPTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
View view = LayoutInflater.from(getActivity()).inflate(R.layout.color_picker_dialog, null);
|
||||
mProgress = (ProgressBar) view.findViewById(android.R.id.progress);
|
||||
mPalette = (ColorPickerPalette) view.findViewById(R.id.color_picker);
|
||||
mPalette.init(mSize, mColumns, this);
|
||||
|
||||
if (mColors != null) {
|
||||
showPaletteView();
|
||||
}
|
||||
|
||||
mAlertDialog = new AlertDialog.Builder(activity)
|
||||
.setTitle(mTitleResId)
|
||||
.setView(view)
|
||||
.create();
|
||||
|
||||
return mAlertDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorSelected(int color) {
|
||||
if (mListener != null) {
|
||||
mListener.onColorSelected(color);
|
||||
}
|
||||
|
||||
if (getTargetFragment() instanceof OnColorSelectedListener) {
|
||||
final OnColorSelectedListener listener =
|
||||
(OnColorSelectedListener) getTargetFragment();
|
||||
listener.onColorSelected(color);
|
||||
}
|
||||
|
||||
if (color != mSelectedColor) {
|
||||
mSelectedColor = color;
|
||||
// Redraw palette to show checkmark on newly selected color before dismissing.
|
||||
mPalette.drawPalette(mColors, mSelectedColor);
|
||||
}
|
||||
|
||||
dismiss();
|
||||
}
|
||||
|
||||
public void showPaletteView() {
|
||||
if (mProgress != null && mPalette != null) {
|
||||
mProgress.setVisibility(View.GONE);
|
||||
refreshPalette();
|
||||
mPalette.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public void showProgressBarView() {
|
||||
if (mProgress != null && mPalette != null) {
|
||||
mProgress.setVisibility(View.VISIBLE);
|
||||
mPalette.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setColors(int[] colors, int selectedColor) {
|
||||
if (mColors != colors || mSelectedColor != selectedColor) {
|
||||
mColors = colors;
|
||||
mSelectedColor = selectedColor;
|
||||
refreshPalette();
|
||||
}
|
||||
}
|
||||
|
||||
public void setColors(int[] colors) {
|
||||
if (mColors != colors) {
|
||||
mColors = colors;
|
||||
refreshPalette();
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelectedColor(int color) {
|
||||
if (mSelectedColor != color) {
|
||||
mSelectedColor = color;
|
||||
refreshPalette();
|
||||
}
|
||||
}
|
||||
|
||||
public void setColorContentDescriptions(String[] colorContentDescriptions) {
|
||||
if (mColorContentDescriptions != colorContentDescriptions) {
|
||||
mColorContentDescriptions = colorContentDescriptions;
|
||||
refreshPalette();
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshPalette() {
|
||||
if (mPalette != null && mColors != null) {
|
||||
mPalette.drawPalette(mColors, mSelectedColor, mColorContentDescriptions);
|
||||
}
|
||||
}
|
||||
|
||||
public int[] getColors() {
|
||||
return mColors;
|
||||
}
|
||||
|
||||
public int getSelectedColor() {
|
||||
return mSelectedColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putIntArray(KEY_COLORS, mColors);
|
||||
outState.putSerializable(KEY_SELECTED_COLOR, mSelectedColor);
|
||||
outState.putStringArray(KEY_COLOR_CONTENT_DESCRIPTIONS, mColorContentDescriptions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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.colorpicker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
|
||||
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
|
||||
|
||||
/**
|
||||
* A color picker custom view which creates an grid of color squares. The number of squares per
|
||||
* row (and the padding between the squares) is determined by the user.
|
||||
*/
|
||||
public class ColorPickerPalette extends TableLayout {
|
||||
|
||||
public OnColorSelectedListener mOnColorSelectedListener;
|
||||
|
||||
private String mDescription;
|
||||
private String mDescriptionSelected;
|
||||
|
||||
private int mSwatchLength;
|
||||
private int mMarginSize;
|
||||
private int mNumColumns;
|
||||
|
||||
public ColorPickerPalette(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ColorPickerPalette(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the size, columns, and listener. Size should be a pre-defined size (SIZE_LARGE
|
||||
* or SIZE_SMALL) from ColorPickerDialogFragment.
|
||||
*/
|
||||
public void init(int size, int columns, OnColorSelectedListener listener) {
|
||||
mNumColumns = columns;
|
||||
Resources res = getResources();
|
||||
if (size == ColorPickerDialog.SIZE_LARGE) {
|
||||
mSwatchLength = res.getDimensionPixelSize(R.dimen.color_swatch_large);
|
||||
mMarginSize = res.getDimensionPixelSize(R.dimen.color_swatch_margins_large);
|
||||
} else {
|
||||
mSwatchLength = res.getDimensionPixelSize(R.dimen.color_swatch_small);
|
||||
mMarginSize = res.getDimensionPixelSize(R.dimen.color_swatch_margins_small);
|
||||
}
|
||||
mOnColorSelectedListener = listener;
|
||||
|
||||
mDescription = res.getString(R.string.color_swatch_description);
|
||||
mDescriptionSelected = res.getString(R.string.color_swatch_description_selected);
|
||||
}
|
||||
|
||||
private TableRow createTableRow() {
|
||||
TableRow row = new TableRow(getContext());
|
||||
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
row.setLayoutParams(params);
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds swatches to table in a serpentine format.
|
||||
*/
|
||||
public void drawPalette(int[] colors, int selectedColor) {
|
||||
drawPalette(colors, selectedColor, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds swatches to table in a serpentine format.
|
||||
*/
|
||||
public void drawPalette(int[] colors, int selectedColor, String[] colorContentDescriptions) {
|
||||
if (colors == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeAllViews();
|
||||
int tableElements = 0;
|
||||
int rowElements = 0;
|
||||
int rowNumber = 0;
|
||||
|
||||
// Fills the table with swatches based on the array of colors.
|
||||
TableRow row = createTableRow();
|
||||
for (int color : colors) {
|
||||
View colorSwatch = createColorSwatch(color, selectedColor);
|
||||
setSwatchDescription(rowNumber, tableElements, rowElements, color == selectedColor,
|
||||
colorSwatch, colorContentDescriptions);
|
||||
addSwatchToRow(row, colorSwatch, rowNumber);
|
||||
|
||||
tableElements++;
|
||||
rowElements++;
|
||||
if (rowElements == mNumColumns) {
|
||||
addView(row);
|
||||
row = createTableRow();
|
||||
rowElements = 0;
|
||||
rowNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create blank views to fill the row if the last row has not been filled.
|
||||
if (rowElements > 0) {
|
||||
while (rowElements != mNumColumns) {
|
||||
addSwatchToRow(row, createBlankSpace(), rowNumber);
|
||||
rowElements++;
|
||||
}
|
||||
addView(row);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a swatch to the end of the row for even-numbered rows (starting with row 0),
|
||||
* to the beginning of a row for odd-numbered rows.
|
||||
*/
|
||||
private static void addSwatchToRow(TableRow row, View swatch, int rowNumber) {
|
||||
if (rowNumber % 2 == 0) {
|
||||
row.addView(swatch);
|
||||
} else {
|
||||
row.addView(swatch, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a content description to the specified swatch view. Because the colors get added in a
|
||||
* snaking form, every other row will need to compensate for the fact that the colors are added
|
||||
* in an opposite direction from their left->right/top->bottom order, which is how the system
|
||||
* will arrange them for accessibility purposes.
|
||||
*/
|
||||
private void setSwatchDescription(int rowNumber, int index, int rowElements, boolean selected,
|
||||
View swatch, String[] contentDescriptions) {
|
||||
String description;
|
||||
if (contentDescriptions != null && contentDescriptions.length > index) {
|
||||
description = contentDescriptions[index];
|
||||
} else {
|
||||
int accessibilityIndex;
|
||||
if (rowNumber % 2 == 0) {
|
||||
// We're in a regular-ordered row
|
||||
accessibilityIndex = index + 1;
|
||||
} else {
|
||||
// We're in a backwards-ordered row.
|
||||
int rowMax = ((rowNumber + 1) * mNumColumns);
|
||||
accessibilityIndex = rowMax - rowElements;
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
description = String.format(mDescriptionSelected, accessibilityIndex);
|
||||
} else {
|
||||
description = String.format(mDescription, accessibilityIndex);
|
||||
}
|
||||
}
|
||||
swatch.setContentDescription(description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a blank space to fill the row.
|
||||
*/
|
||||
private ImageView createBlankSpace() {
|
||||
ImageView view = new ImageView(getContext());
|
||||
TableRow.LayoutParams params = new TableRow.LayoutParams(mSwatchLength, mSwatchLength);
|
||||
params.setMargins(mMarginSize, mMarginSize, mMarginSize, mMarginSize);
|
||||
view.setLayoutParams(params);
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a color swatch.
|
||||
*/
|
||||
private ColorPickerSwatch createColorSwatch(int color, int selectedColor) {
|
||||
ColorPickerSwatch view = new ColorPickerSwatch(getContext(), color,
|
||||
color == selectedColor, mOnColorSelectedListener);
|
||||
TableRow.LayoutParams params = new TableRow.LayoutParams(mSwatchLength, mSwatchLength);
|
||||
params.setMargins(mMarginSize, mMarginSize, mMarginSize, mMarginSize);
|
||||
view.setLayoutParams(params);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.colorpicker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* Creates a circular swatch of a specified color. Adds a checkmark if marked as checked.
|
||||
*/
|
||||
public class ColorPickerSwatch extends FrameLayout implements View.OnClickListener {
|
||||
private int mColor;
|
||||
private ImageView mSwatchImage;
|
||||
private ImageView mCheckmarkImage;
|
||||
private OnColorSelectedListener mOnColorSelectedListener;
|
||||
|
||||
/**
|
||||
* Interface for a callback when a color square is selected.
|
||||
*/
|
||||
public interface OnColorSelectedListener {
|
||||
|
||||
/**
|
||||
* Called when a specific color square has been selected.
|
||||
*/
|
||||
public void onColorSelected(int color);
|
||||
}
|
||||
|
||||
public ColorPickerSwatch(Context context, int color, boolean checked,
|
||||
OnColorSelectedListener listener) {
|
||||
super(context);
|
||||
mColor = color;
|
||||
mOnColorSelectedListener = listener;
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.color_picker_swatch, this);
|
||||
mSwatchImage = (ImageView) findViewById(R.id.color_picker_swatch);
|
||||
mCheckmarkImage = (ImageView) findViewById(R.id.color_picker_checkmark);
|
||||
setColor(color);
|
||||
setChecked(checked);
|
||||
setOnClickListener(this);
|
||||
}
|
||||
|
||||
protected void setColor(int color) {
|
||||
Drawable[] colorDrawable = new Drawable[]
|
||||
{getContext().getResources().getDrawable(R.drawable.color_picker_swatch)};
|
||||
mSwatchImage.setImageDrawable(new ColorStateDrawable(colorDrawable, color));
|
||||
}
|
||||
|
||||
private void setChecked(boolean checked) {
|
||||
if (checked) {
|
||||
mCheckmarkImage.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mCheckmarkImage.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mOnColorSelectedListener != null) {
|
||||
mOnColorSelectedListener.onColorSelected(mColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.colorpicker;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
|
||||
/**
|
||||
* A drawable which sets its color filter to a color specified by the user, and changes to a
|
||||
* slightly darker color when pressed or focused.
|
||||
*/
|
||||
public class ColorStateDrawable extends LayerDrawable {
|
||||
|
||||
private static final float PRESSED_STATE_MULTIPLIER = 0.70f;
|
||||
|
||||
private int mColor;
|
||||
|
||||
public ColorStateDrawable(Drawable[] layers, int color) {
|
||||
super(layers);
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] states) {
|
||||
boolean pressedOrFocused = false;
|
||||
for (int state : states) {
|
||||
if (state == android.R.attr.state_pressed || state == android.R.attr.state_focused) {
|
||||
pressedOrFocused = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pressedOrFocused) {
|
||||
super.setColorFilter(getPressedColor(mColor), PorterDuff.Mode.SRC_ATOP);
|
||||
} else {
|
||||
super.setColorFilter(mColor, PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
|
||||
return super.onStateChange(states);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a particular color, adjusts its value by a multiplier.
|
||||
*/
|
||||
private static int getPressedColor(int color) {
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
hsv[2] = hsv[2] * PRESSED_STATE_MULTIPLIER;
|
||||
return Color.HSVToColor(hsv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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.colorpicker;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* A color comparator which compares based on hue, saturation, and value.
|
||||
*/
|
||||
public class HsvColorComparator implements Comparator<Integer> {
|
||||
|
||||
@Override
|
||||
public int compare(Integer lhs, Integer rhs) {
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(lhs, hsv);
|
||||
float hue1 = hsv[0];
|
||||
float sat1 = hsv[1];
|
||||
float val1 = hsv[2];
|
||||
|
||||
float[] hsv2 = new float[3];
|
||||
Color.colorToHSV(rhs, hsv2);
|
||||
float hue2 = hsv2[0];
|
||||
float sat2 = hsv2[1];
|
||||
float val2 = hsv2[2];
|
||||
|
||||
if (hue1 < hue2) {
|
||||
return 1;
|
||||
} else if (hue1 > hue2) {
|
||||
return -1;
|
||||
} else {
|
||||
if (sat1 < sat2) {
|
||||
return 1;
|
||||
} else if (sat1 > sat2) {
|
||||
return -1;
|
||||
} else {
|
||||
if (val1 < val2) {
|
||||
return 1;
|
||||
} else if (val1 > val2) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
*~
|
||||
*.bak
|
||||
*.class
|
||||
bin/
|
||||
gen/
|
||||
.classpath
|
||||
.project
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (C) 2012 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//#################################################
|
||||
// Build appcompat library
|
||||
|
||||
package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "libphotoviewer_appcompat",
|
||||
|
||||
libs: ["androidx.annotation_annotation"],
|
||||
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
"androidx.legacy_legacy-support-core-ui",
|
||||
"androidx.legacy_legacy-support-core-utils",
|
||||
"androidx.fragment_fragment",
|
||||
"androidx.appcompat_appcompat",
|
||||
],
|
||||
|
||||
sdk_version: "current",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"appcompat/src/**/*.java",
|
||||
"src/**/*.logtags",
|
||||
],
|
||||
|
||||
resource_dirs: [
|
||||
"appcompat/res",
|
||||
"res",
|
||||
],
|
||||
}
|
||||
|
||||
//#################################################
|
||||
// Build non-appcompat library
|
||||
|
||||
android_library {
|
||||
name: "libphotoviewer",
|
||||
|
||||
libs: ["androidx.annotation_annotation"],
|
||||
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
"androidx.legacy_legacy-support-core-ui",
|
||||
"androidx.legacy_legacy-support-core-utils",
|
||||
"androidx.fragment_fragment",
|
||||
],
|
||||
|
||||
sdk_version: "current",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"activity/src/**/*.java",
|
||||
"src/**/*.logtags",
|
||||
],
|
||||
|
||||
resource_dirs: [
|
||||
"activity/res",
|
||||
"res",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012 Google Inc.
|
||||
Licensed to The Android Open Source Project.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.ex.photo"
|
||||
android:versionCode="1">
|
||||
|
||||
<application />
|
||||
</manifest>
|
||||
@@ -0,0 +1 @@
|
||||
asapperstein@google.com
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012 Google Inc.
|
||||
Licensed to The Android Open Source Project.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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="PhotoViewTheme" parent="android:Theme.Holo">
|
||||
<item name="android:colorPrimaryDark">@color/photo_status_bar_black</item>
|
||||
<item name="android:navigationBarColor">@color/photo_status_bar_black</item>
|
||||
<item name="android:windowNoTitle">false</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowActionBarOverlay">true</item>
|
||||
<item name="android:windowBackground">@color/solid_black</item>
|
||||
<item name="android:actionBarStyle">@style/Holo.ActionBar</item>
|
||||
</style>
|
||||
<style name="PhotoViewTheme.Translucent">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="PhotoViewTheme.NoActionBar">
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
<style name="Holo.ActionBar" parent="android:Widget.Holo.ActionBar">
|
||||
<item name="android:background">@drawable/actionbar_translucent</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.android.ex.photo;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
/**
|
||||
* Wrapper around {@link ActionBar}.
|
||||
*/
|
||||
public class ActionBarWrapper implements ActionBarInterface {
|
||||
|
||||
private final ActionBar mActionBar;
|
||||
|
||||
private class MenuVisiblityListenerWrapper implements ActionBar.OnMenuVisibilityListener {
|
||||
|
||||
private final ActionBarInterface.OnMenuVisibilityListener mWrapped;
|
||||
|
||||
public MenuVisiblityListenerWrapper(ActionBarInterface.OnMenuVisibilityListener wrapped) {
|
||||
mWrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMenuVisibilityChanged(boolean isVisible) {
|
||||
mWrapped.onMenuVisibilityChanged(isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
public ActionBarWrapper(ActionBar actionBar) {
|
||||
mActionBar = actionBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
|
||||
mActionBar.setDisplayHomeAsUpEnabled(showHomeAsUp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
|
||||
mActionBar.addOnMenuVisibilityListener(new MenuVisiblityListenerWrapper(listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayOptionsShowTitle() {
|
||||
mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
return mActionBar.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(CharSequence title) {
|
||||
mActionBar.setTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubtitle(CharSequence subtitle) {
|
||||
mActionBar.setSubtitle(subtitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
mActionBar.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
mActionBar.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogo(Drawable logo) {
|
||||
mActionBar.setLogo(logo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc.
|
||||
* Licensed to The Android Open Source Project.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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.photo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
/**
|
||||
* Activity to view the contents of an album.
|
||||
*/
|
||||
public class PhotoViewActivity extends FragmentActivity
|
||||
implements PhotoViewController.ActivityInterface {
|
||||
|
||||
private PhotoViewController mController;
|
||||
private ActionBarWrapper mActionBar;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mController = createController();
|
||||
mController.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
public PhotoViewController createController() {
|
||||
return new PhotoViewController(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhotoViewController getController() {
|
||||
return mController;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
mController.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mController.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
mController.onPause();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mController.onStop();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
mController.onDestroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!mController.onBackPressed()) {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
mController.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
return mController.onCreateOptionsMenu(menu) || super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
return mController.onPrepareOptionsMenu(menu) || super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return mController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
mController.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionBarInterface getActionBarInterface() {
|
||||
if (mActionBar == null) {
|
||||
mActionBar = new ActionBarWrapper(getActionBar());
|
||||
}
|
||||
return mActionBar;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012 Google Inc.
|
||||
Licensed to The Android Open Source Project.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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="PhotoViewTheme" parent="Theme.AppCompat">
|
||||
<item name="actionBarStyle">@style/AppCompat.ActionBar</item>
|
||||
<item name="colorPrimaryDark">@color/photo_status_bar_black</item>
|
||||
<item name="android:navigationBarColor">@color/photo_status_bar_black</item>
|
||||
<item name="windowActionBarOverlay">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowBackground">@color/solid_black</item>
|
||||
</style>
|
||||
<style name="PhotoViewTheme.Translucent">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="PhotoViewTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
<style name="AppCompat.ActionBar" parent="Widget.AppCompat.ActionBar">
|
||||
<item name="background">@drawable/actionbar_translucent</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.android.ex.photo;
|
||||
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
|
||||
/**
|
||||
* Wrapper around {@link ActionBar}.
|
||||
*/
|
||||
public class ActionBarWrapper implements ActionBarInterface {
|
||||
|
||||
private final ActionBar mActionBar;
|
||||
|
||||
private class MenuVisiblityListenerWrapper implements ActionBar.OnMenuVisibilityListener {
|
||||
|
||||
private final ActionBarInterface.OnMenuVisibilityListener mWrapped;
|
||||
|
||||
public MenuVisiblityListenerWrapper(ActionBarInterface.OnMenuVisibilityListener wrapped) {
|
||||
mWrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMenuVisibilityChanged(boolean isVisible) {
|
||||
mWrapped.onMenuVisibilityChanged(isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
public ActionBarWrapper(ActionBar actionBar) {
|
||||
mActionBar = actionBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
|
||||
mActionBar.setDisplayHomeAsUpEnabled(showHomeAsUp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
|
||||
mActionBar.addOnMenuVisibilityListener(new MenuVisiblityListenerWrapper(listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayOptionsShowTitle() {
|
||||
mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
return mActionBar.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(CharSequence title) {
|
||||
mActionBar.setTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubtitle(CharSequence subtitle) {
|
||||
mActionBar.setSubtitle(subtitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
mActionBar.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
mActionBar.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogo(Drawable logo) {
|
||||
mActionBar.setLogo(logo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc.
|
||||
* Licensed to The Android Open Source Project.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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.photo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
/**
|
||||
* Activity to view the contents of an album.
|
||||
*/
|
||||
public class PhotoViewActivity extends AppCompatActivity
|
||||
implements PhotoViewController.ActivityInterface {
|
||||
|
||||
private PhotoViewController mController;
|
||||
private ActionBarWrapper mActionBar;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mController = createController();
|
||||
mController.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
protected PhotoViewController createController() {
|
||||
return new PhotoViewController(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhotoViewController getController() {
|
||||
return mController;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
mController.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mController.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
mController.onPause();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mController.onStop();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
mController.onDestroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!mController.onBackPressed()) {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
mController.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
return mController.onCreateOptionsMenu(menu) || super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
return mController.onPrepareOptionsMenu(menu) || super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return mController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
mController.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionBarInterface getActionBarInterface() {
|
||||
if (mActionBar == null) {
|
||||
mActionBar = new ActionBarWrapper(getSupportActionBar());
|
||||
}
|
||||
return mActionBar;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.android.ex.photo'
|
||||
compileSdk 35
|
||||
|
||||
defaultConfig {
|
||||
minSdk 33
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src', 'activity/src']
|
||||
res.srcDirs = ['res', 'activity/res']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.annotation:annotation:1.8.2'
|
||||
implementation 'androidx.core:core:1.15.0'
|
||||
implementation 'androidx.fragment:fragment:1.8.5'
|
||||
implementation 'androidx.loader:loader:1.1.0'
|
||||
implementation 'androidx.viewpager:viewpager:1.0.0'
|
||||
}
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 152 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 140 B |
|
After Width: | Height: | Size: 889 B |
|
After Width: | Height: | Size: 161 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 250 B |
|
After Width: | Height: | Size: 2.4 KiB |