Merge QQ3A.200605.002 into master
Bug: 158095402
Merged-In: I26f7303c27fd5d14a8668137d32bb554ab4b47e7
Change-Id: I655d550990b2e32a11682ee26145465b310d159b
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 481589c..99cf388 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,6 +16,7 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.car.dialer">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion='24'/>
@@ -38,7 +39,8 @@
android:name=".DialerApplication"
android:directBootAware="true"
android:label="@string/phone_app_name"
- android:icon="@drawable/ic_app_icon">
+ android:icon="@drawable/ic_app_icon"
+ android:supportsRtl="true">
<activity android:name=".ui.TelecomActivity"
android:launchMode="singleTask"
@@ -109,12 +111,6 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
- <!-- BroadcastReceiver for receiving Intents from Notification mechanism. -->
- <receiver
- android:directBootAware="true"
- android:exported="false"
- android:name="com.android.car.dialer.notification.NotificationReceiver"/>
-
<receiver
android:directBootAware="true"
android:name="com.android.car.dialer.notification.MissedCallReceiver">
@@ -130,5 +126,14 @@
<action android:name="android.bluetooth.device.action.BOND_STATE_CHANGED"/>
</intent-filter>
</receiver>
+
+ <!-- Workaround for b/113294940, remove when converting to Android.bp -->
+ <provider
+ android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
+ tools:replace="android:authorities"
+ android:authorities="${applicationId}"
+ android:exported="false"
+ android:multiprocess="true" />
+
</application>
</manifest>
diff --git a/res/color/contact_details_icon_tint.xml b/res/color/contact_details_icon_tint.xml
new file mode 100644
index 0000000..3d694c3
--- /dev/null
+++ b/res/color/contact_details_icon_tint.xml
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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:color="@color/primary_icon_color"
+ android:state_enabled="true"
+ android:state_activated="false"/>
+ <item android:color="#FFFFFF"
+ android:state_enabled="true"
+ android:state_activated="true"/>
+ <item android:color="#80FFFFFF"
+ android:state_enabled="false"/>
+</selector>
diff --git a/res/color/icon_accent_activatable.xml b/res/color/icon_accent_activatable.xml
index cf4b9e7..c652f07 100644
--- a/res/color/icon_accent_activatable.xml
+++ b/res/color/icon_accent_activatable.xml
@@ -16,6 +16,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/primary_icon_color"
android:state_activated="false"/>
- <item android:color="?android:colorAccent"
+ <item android:color="@color/audio_output_accent"
android:state_activated="true"/>
</selector>
diff --git a/res/color/text_accent_activatable.xml b/res/color/text_accent_activatable.xml
deleted file mode 100644
index 4f1dc0f..0000000
--- a/res/color/text_accent_activatable.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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:color="@color/primary_text_color"
- android:state_activated="false"/>
- <item android:color="?android:colorAccent"
- android:state_activated="true"/>
-</selector>
diff --git a/res/drawable/ic_arrow_right.xml b/res/drawable/ic_arrow_right.xml
index 051b6a0..877c510 100644
--- a/res/drawable/ic_arrow_right.xml
+++ b/res/drawable/ic_arrow_right.xml
@@ -18,7 +18,8 @@
android:viewportHeight="48"
android:width="@dimen/primary_icon_size"
android:height="@dimen/primary_icon_size"
- android:tint="@color/icon_tint_state_list">
+ android:tint="@color/icon_tint_state_list"
+ android:autoMirrored="true">
<path android:pathData="M0-.25h48v48H0z"/>
<path
diff --git a/res/drawable/ic_favorite_activatable.xml b/res/drawable/ic_favorite_activatable.xml
index 7285513..d40add4 100644
--- a/res/drawable/ic_favorite_activatable.xml
+++ b/res/drawable/ic_favorite_activatable.xml
@@ -15,7 +15,11 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_favorite_empty"
- android:state_activated="false"/>
+ android:state_activated="false" android:state_enabled="true"/>
<item android:drawable="@drawable/ic_favorite"
- android:state_activated="true"/>
+ android:state_activated="true" android:state_enabled="true"/>
+ <item android:drawable="@drawable/ic_favorite"
+ android:state_activated="true" android:state_enabled="false"/>
+ <item android:drawable="@drawable/ic_imported_favorite"
+ android:state_activated="false" android:state_enabled="false"/>
</selector>
diff --git a/res/drawable/ic_imported_favorite.xml b/res/drawable/ic_imported_favorite.xml
new file mode 100644
index 0000000..c6858bb
--- /dev/null
+++ b/res/drawable/ic_imported_favorite.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/primary_icon_size"
+ android:height="@dimen/primary_icon_size"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path android:pathData="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z"
+ android:fillColor="#ff000000"/>
+</vector>
diff --git a/res/layout-port/dialpad_fragment.xml b/res/layout-port/dialpad_fragment.xml
index fffbf70..fd046c1 100644
--- a/res/layout-port/dialpad_fragment.xml
+++ b/res/layout-port/dialpad_fragment.xml
@@ -16,16 +16,22 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/type_down_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="@dimen/config_type_down_guideline_percent"/>
+
<fragment
android:id="@+id/dialpad_fragment"
android:name="com.android.car.dialer.ui.dialpad.KeypadFragment"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/keypad_margin"
- app:layout_constraintTop_toBottomOf="@+id/title"
- app:layout_constraintBottom_toTopOf="@+id/call_button"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
+ android:layout_marginStart="@dimen/dialpad_info_margin_start"
+ app:layout_constraintTop_toBottomOf="@id/type_down_guideline"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/title"
@@ -37,9 +43,8 @@
android:autoSizeMinTextSize="@dimen/dialpad_info_title_text_size_min"
android:autoSizeMaxTextSize="@dimen/dialpad_info_title_text_size_max"
app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/dialpad_fragment"
- app:layout_constraintStart_toStartOf="@id/dialpad_fragment"
+ app:layout_constraintTop_toTopOf="@id/dialpad_fragment"
+ app:layout_constraintStart_toEndOf="@id/dialpad_fragment"
app:layout_constraintEnd_toStartOf="@+id/delete_button"
app:layout_goneMarginEnd="@dimen/dialpad_info_edge_padding_size"/>
@@ -48,19 +53,10 @@
style="@style/DialpadSecondaryButton"
android:src="@drawable/ic_backspace"
android:layout_marginStart="@dimen/dialpad_info_title_padding_size"
- android:layout_marginEnd="@dimen/dialpad_info_edge_padding_size"
+ android:layout_marginEnd="@dimen/dialpad_info_margin_end"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintStart_toEndOf="@id/title"
- app:layout_constraintEnd_toEndOf="@id/dialpad_fragment"/>
-
- <include
- layout="@layout/dialpad_user_profile"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/title"
- app:layout_constraintBottom_toTopOf="@id/dialpad_fragment"
- app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
@@ -70,7 +66,32 @@
android:layout_height="@dimen/call_button_height"
android:src="@drawable/icon_call_button"
android:layout_marginBottom="@dimen/call_button_bottom_margin"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/dialpad_fragment"
+ app:layout_constraintStart_toEndOf="@id/dialpad_fragment"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dialpad_line_divider_height"
+ android:background="@color/divider_color"
+ android:layout_marginStart="@dimen/horizontal_divider_inset"
+ android:layout_marginEnd="@dimen/horizontal_divider_inset"
+ app:layout_constraintTop_toBottomOf="@id/type_down_guideline"/>
+
+ <include
+ layout="@layout/dialpad_user_profile"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_constraintStart_toEndOf="@id/dialpad_fragment"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <include
+ layout="@layout/list_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/type_down_guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
diff --git a/res/layout-w1280dp/type_down_list_item.xml b/res/layout-w1280dp/type_down_list_item.xml
new file mode 100644
index 0000000..fec8a53
--- /dev/null
+++ b/res/layout-w1280dp/type_down_list_item.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/contact_result"
+ android:foreground="?android:attr/selectableItemBackground"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/type_down_list_item_height">
+
+ <ImageView
+ android:id="@+id/contact_picture"
+ android:layout_width="@dimen/avatar_icon_size"
+ android:layout_height="@dimen/avatar_icon_size"
+ android:layout_marginStart="@dimen/contact_result_avatar_margin"
+ android:scaleType="centerCrop"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <TextView
+ android:id="@+id/contact_name"
+ android:layout_marginStart="@dimen/contact_result_name_margin"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.ContactResultTitle"
+ android:duplicateParentState="true"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/phone_number"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ android:id="@+id/phone_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ app:layout_constraintTop_toBottomOf="@id/contact_name"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="@id/contact_name"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/add_favorite_number_list_item.xml b/res/layout/add_favorite_number_list_item.xml
deleted file mode 100644
index ffe5445..0000000
--- a/res/layout/add_favorite_number_list_item.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<androidx.constraintlayout.widget.ConstraintLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="@dimen/add_favorite_number_list_height"
- android:background="?android:attr/selectableItemBackground"
- android:paddingStart="@dimen/add_favorite_number_list_padding"
- android:paddingEnd="@dimen/add_favorite_number_list_padding">
-
- <TextView
- android:id="@+id/phone_number"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.AddFavoriteNumberTitle"
- android:duplicateParentState="true"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/phone_number_description"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/phone_number_checkbox"/>
-
- <TextView
- android:id="@+id/phone_number_description"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.AddFavoriteNumberSubtitle"
- android:duplicateParentState="true"
- app:layout_constraintTop_toBottomOf="@id/phone_number"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/phone_number_checkbox"/>
-
- <ImageView
- android:id="@+id/phone_number_checkbox"
- android:src="@drawable/ic_favorite_activatable"
- android:layout_width="@dimen/primary_icon_size"
- android:layout_height="@dimen/primary_icon_size"
- android:tint="@color/primary_icon_selector"
- android:duplicateParentState="true"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/audio_route_list_item.xml b/res/layout/audio_route_list_item.xml
deleted file mode 100644
index c1510ef..0000000
--- a/res/layout/audio_route_list_item.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-<androidx.constraintlayout.widget.ConstraintLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="@dimen/audio_route_height"
- android:background="?android:attr/selectableItemBackground"
- android:elevation="@dimen/dialer_card_elevation">
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/text_start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- app:layout_constraintGuide_begin="@dimen/audio_route_constraint_guide_begin"/>
- <ImageView
- android:id="@+id/icon"
- android:layout_width="@dimen/audio_route_icon_size"
- android:layout_height="@dimen/audio_route_icon_size"
- android:scaleType="fitCenter"
- android:layout_marginStart="@dimen/audio_route_content_padding"
- android:tint="@color/icon_accent_activatable"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"/>
- <TextView
- android:id="@+id/body"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textColor="@color/text_accent_activatable"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="@+id/text_start"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"/>
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/audio_route_switch_dialog.xml b/res/layout/audio_route_switch_dialog.xml
deleted file mode 100644
index 7a5013c..0000000
--- a/res/layout/audio_route_switch_dialog.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<androidx.recyclerview.widget.RecyclerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"/>
diff --git a/res/layout/contact_result.xml b/res/layout/contact_result.xml
index f6188f7..1e85899 100644
--- a/res/layout/contact_result.xml
+++ b/res/layout/contact_result.xml
@@ -39,8 +39,9 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.ContactResultTitle"
+ android:duplicateParentState="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
-</androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/layout/dialpad_fragment.xml b/res/layout/dialpad_fragment.xml
index 6627e70..a265518 100644
--- a/res/layout/dialpad_fragment.xml
+++ b/res/layout/dialpad_fragment.xml
@@ -39,9 +39,8 @@
<FrameLayout
android:layout_height="0dp"
android:layout_width="0dp"
- app:layout_constraintDimensionRatio="W, 1:1"
- app:layout_constraintTop_toTopOf="@id/dialpad_fragment"
- app:layout_constraintBottom_toBottomOf="@id/dialpad_fragment"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/divider"
app:layout_constraintEnd_toEndOf="parent">
<include layout="@layout/dialpad_info"/>
diff --git a/res/layout/dialpad_info.xml b/res/layout/dialpad_info.xml
index f182721..4219ba9 100644
--- a/res/layout/dialpad_info.xml
+++ b/res/layout/dialpad_info.xml
@@ -38,17 +38,27 @@
android:autoSizeMaxTextSize="@dimen/dialpad_info_title_text_size_max"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="@id/dialpad_info_guideline"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/delete_button"/>
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@+id/delete_button"/>
<ImageButton
android:id="@+id/delete_button"
style="@style/DialpadSecondaryButton"
android:src="@drawable/ic_backspace"
- android:layout_marginStart="@dimen/dialpad_info_title_padding_size"
+ android:layout_marginLeft="@dimen/dialpad_info_title_padding_size"
+ android:layout_marginRight="@dimen/dialpad_info_margin_end"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/title"
- app:layout_constraintStart_toEndOf="@id/title"
+ app:layout_constraintLeft_toRightOf="@id/title"
+ app:layout_constraintRight_toRightOf="parent"/>
+
+ <ImageView
+ android:id="@+id/call_button"
+ style="@style/DialpadPrimaryButton"
+ android:src="@drawable/icon_call_button"
+ android:layout_marginBottom="@dimen/dialpad_info_edge_padding_size"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<include
@@ -57,15 +67,17 @@
android:layout_height="0dp"
android:layout_marginTop="@dimen/dialpad_user_profile_padding"
app:layout_constraintTop_toBottomOf="@id/title"
- app:layout_constraintBottom_toTopOf="@+id/call_button"
+ app:layout_constraintBottom_toTopOf="@id/call_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
- <ImageView
- android:id="@+id/call_button"
- style="@style/DialpadPrimaryButton"
- android:src="@drawable/icon_call_button"
- app:layout_constraintBottom_toBottomOf="parent"
+ <include
+ layout="@layout/list_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_marginBottom="@dimen/dialpad_user_profile_padding"
+ app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_constraintBottom_toTopOf="@id/call_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
diff --git a/res/layout/favorite_contact_list_item.xml b/res/layout/favorite_contact_list_item.xml
index 82002fe..032a153 100644
--- a/res/layout/favorite_contact_list_item.xml
+++ b/res/layout/favorite_contact_list_item.xml
@@ -13,13 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
+<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/text_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="center_horizontal"
android:background="?android:attr/selectableItemBackground">
<ImageView
@@ -27,19 +26,32 @@
android:layout_width="@dimen/large_avatar_icon_size"
android:layout_height="@dimen/large_avatar_icon_size"
android:layout_marginBottom="@dimen/favorites_avatar_margin_bottom"
- android:scaleType="centerCrop"/>
+ android:scaleType="centerCrop"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/title"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/title"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:singleLine="true"/>
+ android:singleLine="true"
+ app:layout_constraintTop_toBottomOf="@id/icon"
+ app:layout_constraintBottom_toTopOf="@+id/text"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/text"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:singleLine="true"/>
-</LinearLayout>
+ android:singleLine="true"
+ app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_constraintBottom_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/keypad.xml b/res/layout/keypad.xml
index a63b5ae..7d8d47c 100644
--- a/res/layout/keypad.xml
+++ b/res/layout/keypad.xml
@@ -30,8 +30,8 @@
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/four"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/two"/>
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@+id/two"/>
<com.android.car.dialer.ui.dialpad.KeypadButton
android:id="@+id/two"
@@ -41,8 +41,8 @@
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/five"
- app:layout_constraintStart_toEndOf="@id/one"
- app:layout_constraintEnd_toStartOf="@+id/three"/>
+ app:layout_constraintLeft_toRightOf="@id/one"
+ app:layout_constraintRight_toLeftOf="@+id/three"/>
<com.android.car.dialer.ui.dialpad.KeypadButton
android:id="@+id/three"
@@ -52,8 +52,8 @@
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/six"
- app:layout_constraintStart_toEndOf="@id/two"
- app:layout_constraintEnd_toEndOf="parent"/>
+ app:layout_constraintLeft_toRightOf="@id/two"
+ app:layout_constraintRight_toRightOf="parent"/>
<!-- Row 2 -->
<com.android.car.dialer.ui.dialpad.KeypadButton
@@ -64,8 +64,8 @@
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/one"
app:layout_constraintBottom_toTopOf="@+id/seven"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/five"/>
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@+id/five"/>
<com.android.car.dialer.ui.dialpad.KeypadButton
android:id="@+id/five"
@@ -74,8 +74,8 @@
style="@style/KeypadButtonStyle"
app:layout_constraintTop_toBottomOf="@id/two"
app:layout_constraintBottom_toTopOf="@+id/eight"
- app:layout_constraintStart_toEndOf="@id/four"
- app:layout_constraintEnd_toStartOf="@+id/six"/>
+ app:layout_constraintLeft_toRightOf="@id/four"
+ app:layout_constraintRight_toLeftOf="@+id/six"/>
<com.android.car.dialer.ui.dialpad.KeypadButton
android:id="@+id/six"
@@ -84,8 +84,8 @@
style="@style/KeypadButtonStyle"
app:layout_constraintTop_toBottomOf="@id/three"
app:layout_constraintBottom_toTopOf="@+id/nine"
- app:layout_constraintStart_toEndOf="@id/five"
- app:layout_constraintEnd_toEndOf="parent"/>
+ app:layout_constraintLeft_toRightOf="@id/five"
+ app:layout_constraintRight_toRightOf="parent"/>
<!-- Row 3 -->
@@ -97,8 +97,8 @@
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/four"
app:layout_constraintBottom_toTopOf="@+id/star"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/eight"/>
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@+id/eight"/>
<com.android.car.dialer.ui.dialpad.KeypadButton
android:id="@+id/eight"
@@ -107,8 +107,8 @@
style="@style/KeypadButtonStyle"
app:layout_constraintTop_toBottomOf="@id/five"
app:layout_constraintBottom_toTopOf="@+id/zero"
- app:layout_constraintStart_toEndOf="@id/seven"
- app:layout_constraintEnd_toStartOf="@+id/nine"/>
+ app:layout_constraintLeft_toRightOf="@id/seven"
+ app:layout_constraintRight_toLeftOf="@+id/nine"/>
<com.android.car.dialer.ui.dialpad.KeypadButton
android:id="@+id/nine"
@@ -117,8 +117,8 @@
style="@style/KeypadButtonStyle"
app:layout_constraintTop_toBottomOf="@id/six"
app:layout_constraintBottom_toTopOf="@+id/pound"
- app:layout_constraintStart_toEndOf="@id/eight"
- app:layout_constraintEnd_toEndOf="parent"/>
+ app:layout_constraintLeft_toRightOf="@id/eight"
+ app:layout_constraintRight_toRightOf="parent"/>
<!-- Row 4 -->
<com.android.car.dialer.ui.dialpad.KeypadButton
@@ -129,8 +129,8 @@
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/seven"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/zero"/>
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@+id/zero"/>
<com.android.car.dialer.ui.dialpad.KeypadButton
android:id="@+id/zero"
@@ -139,8 +139,8 @@
style="@style/KeypadButtonStyle"
app:layout_constraintTop_toBottomOf="@id/eight"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/star"
- app:layout_constraintEnd_toStartOf="@+id/pound"/>
+ app:layout_constraintLeft_toRightOf="@id/star"
+ app:layout_constraintRight_toLeftOf="@+id/pound"/>
<com.android.car.dialer.ui.dialpad.KeypadButton
android:id="@+id/pound"
@@ -149,8 +149,8 @@
style="@style/KeypadButtonStyle"
app:layout_constraintTop_toBottomOf="@id/nine"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/zero"
- app:layout_constraintEnd_toEndOf="parent"/>
+ app:layout_constraintLeft_toRightOf="@id/zero"
+ app:layout_constraintRight_toRightOf="parent"/>
<include layout="@layout/keypad_dividers"/>
diff --git a/res/layout/phone_number_list_item.xml b/res/layout/phone_number_list_item.xml
deleted file mode 100644
index 04e1b61..0000000
--- a/res/layout/phone_number_list_item.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.car.dialer.widget.CheckableRelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:listPreferredItemPaddingStart"
- android:paddingEnd="?android:listPreferredItemPaddingEnd">
-
- <RadioButton
- android:id="@+id/phone_number_checkbox"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_marginEnd="@dimen/phone_number_radio_list_padding"/>
- <TextView
- android:id="@+id/phone_number"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_toEndOf="@id/phone_number_checkbox"
- android:textAppearance="?android:attr/textAppearanceMedium"/>
- <TextView
- android:id="@+id/phone_number_description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/phone_number"
- android:layout_toEndOf="@id/phone_number_checkbox"
- android:textAppearance="?android:attr/textAppearanceSmall"/>
-
-</com.android.car.dialer.widget.CheckableRelativeLayout>
diff --git a/res/layout/type_down_list_item.xml b/res/layout/type_down_list_item.xml
new file mode 100644
index 0000000..a0476f6
--- /dev/null
+++ b/res/layout/type_down_list_item.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/contact_result"
+ android:foreground="?android:attr/selectableItemBackground"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/type_down_list_item_height">
+
+ <TextView
+ android:id="@+id/contact_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.ContactResultTitle"
+ android:duplicateParentState="true"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/phone_number"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ android:id="@+id/phone_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ app:layout_constraintTop_toBottomOf="@id/contact_name"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="@id/contact_name"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/values-h610dp/dimens.xml b/res/values-h610dp/dimens.xml
index 23a9548..7e70374 100644
--- a/res/values-h610dp/dimens.xml
+++ b/res/values-h610dp/dimens.xml
@@ -24,6 +24,8 @@
<!-- Control bar bottom padding -->
<dimen name="in_call_controller_bar_margin_bottom">@*android:dimen/car_padding_2</dimen>
+ <dimen name="type_down_list_item_height">@dimen/list_item_height</dimen>
+
<dimen name="fab_outline_size">104dp</dimen>
<dimen name="fab_ripple_radius">52dp</dimen>
diff --git a/res/values-port/dimens.xml b/res/values-port/dimens.xml
index cdb70cf..5318d56 100644
--- a/res/values-port/dimens.xml
+++ b/res/values-port/dimens.xml
@@ -22,4 +22,5 @@
<dimen name="keypad_margin">@*android:dimen/car_padding_4</dimen>
<dimen name="dialpad_info_title_text_size_max">36sp</dimen>
+ <dimen name="dialpad_info_margin_start">154dp</dimen>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f1aa7b0..49d2e52 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -18,13 +18,11 @@
<color name="phone_call">@*android:color/car_green_700</color>
<color name="phone_end_call">@*android:color/car_red_500a</color>
<color name="onhold_call_background">@*android:color/car_grey_868</color>
+ <color name="audio_output_accent">@*android:color/car_accent</color>
<!-- Dialpad page -->
<color name="call_button_outline">@*android:color/car_green_500</color>
- <!--Contact details-->
- <color name="contact_details_icon_tint">@color/primary_icon_color</color>
-
<color name="car_key2">@color/car_key2_dark</color>
<color name="car_key2_light">@*android:color/car_grey_400</color>
<color name="car_key2_dark">@*android:color/car_grey_700</color>
diff --git a/res/values/configs.xml b/res/values/configs.xml
index 03bb96e..03e7a89 100644
--- a/res/values/configs.xml
+++ b/res/values/configs.xml
@@ -24,8 +24,6 @@
<item name="config_background_image_alpha" format="float" type="dimen">1.0</item>
<item name="config_background_image_error_alpha" format="float" type="dimen">1.0</item>
- <bool name="config_show_detailed_user_profile_on_dialpad">false</bool>
-
<!-- A config determines whether to show divider and callog_action_button for callog items
that are not stored as contacts. "true" will show divider and button as disable state and
"false" will make divider and button invisible for non contacts. -->
@@ -48,4 +46,12 @@
<!-- Config gate the full screen incall ui -->
<bool name="config_show_fullscreen_incall_ui">true</bool>
+
+ <!-- A config determines whether to show type down list on Dialpad -->
+ <bool name="config_show_type_down_list_on_dialpad">true</bool>
+ <!-- A config determines whether to show user profile for input number only when type down is
+ not showing. This config works only when config_show_type_down_list_on_dialpad is false. -->
+ <bool name="config_show_detailed_user_profile_on_dialpad">false</bool>
+ <!-- A percent determines the size of the area for type down list on Dialpad in portrait mode. -->
+ <item name="config_type_down_guideline_percent" format="float" type="dimen">0.3</item>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 010fca4..16d2ce1 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -17,13 +17,10 @@
<!-- Dialer -->
<dimen name="dialer_card_elevation">2dp</dimen>
- <!-- Audio route dimentions -->
- <dimen name="audio_route_height">@dimen/control_bar_height</dimen>
- <dimen name="audio_route_content_padding">@dimen/list_item_padding</dimen>
- <dimen name="audio_route_icon_size">@dimen/primary_icon_size</dimen>
+ <!-- TODO: UNUSED! REMOVE THIS. b/149125789-->
<dimen name="audio_route_constraint_guide_begin">@dimen/list_item_guideline</dimen>
- <!-- Call list dimentions -->
+ <!-- Call list dimensions -->
<dimen name="call_history_item_height">@dimen/list_item_height</dimen>
<dimen name="call_history_item_padding">@dimen/list_item_padding</dimen>
<dimen name="call_history_guideline_begin">@dimen/list_item_guideline_begin</dimen>
@@ -88,6 +85,9 @@
<dimen name="dialpad_info_title_text_size_min">24sp</dimen>
<dimen name="dialpad_contact_avatar_size">64dp</dimen>
<dimen name="dialpad_contact_label_margin">12dp</dimen>
+ <dimen name="dialpad_info_margin_end">112dp</dimen>
+ <dimen name="dialpad_info_margin_start">154dp</dimen>
+ <dimen name="type_down_list_item_height">@dimen/control_bar_height</dimen>
<!-- Keypad dimensions -->
<dimen name="keypad_minimum_size">@dimen/touch_target_size</dimen>
@@ -99,10 +99,6 @@
<dimen name="favorite_card_space_vertical">@*android:dimen/car_padding_2</dimen>
<dimen name="favorites_avatar_margin_bottom">@*android:dimen/car_padding_3</dimen>
- <!-- Add faovirte flow dimensions -->
- <dimen name="add_favorite_number_list_height">@dimen/list_item_height</dimen>
- <dimen name="add_favorite_number_list_padding">@dimen/list_item_padding</dimen>
-
<dimen name="call_fab_elevation">8dp</dimen>
<dimen name="bksp_button_width">@dimen/touch_target_size</dimen>
@@ -134,6 +130,7 @@
<dimen name="list_item_padding">@*android:dimen/car_keyline_1</dimen>
<dimen name="list_divider_inset">@*android:dimen/car_keyline_1</dimen>
<dimen name="list_divider_height">@*android:dimen/car_list_divider_height</dimen>
+ <dimen name="horizontal_divider_inset">@*android:dimen/car_padding_2</dimen>
<dimen name="vertical_divider_inset">@*android:dimen/car_padding_2</dimen>
<dimen name="vertical_divider_width">2dp</dimen>
<dimen name="primary_icon_size">@*android:dimen/car_primary_icon_size</dimen>
@@ -145,7 +142,6 @@
<dimen name="touch_target_width">156dp</dimen>
<dimen name="subheader_list_height">76dp</dimen>
<dimen name="control_bar_height">96dp</dimen>
- <dimen name="phone_number_radio_list_padding">@*android:dimen/car_padding_2</dimen>
<!-- Loading status view dimensions -->
<dimen name="loading_info_icon_size">56dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0418d13..a890cd1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -141,14 +141,27 @@
<!-- Description for the default phone number of the contact [CHAR LIMIT=30] -->
<string name="primary_number_description">
<xliff:g id="label" example="Mobile">%1$s</xliff:g>
- " - Default"
+ ", default"
</string>
- <!-- Description for the favorite phone number in add to favorite flow [CHAR LIMIT=30] -->
+ <!-- Description for the phone favorite number in the add-to-favorite flow [CHAR LIMIT=30] -->
<string name="favorite_number_description">
+ "Favorite - "
<xliff:g id="label" example="Mobile">%1$s</xliff:g>
- " - Favorite"
</string>
+ <!-- Description for the local favorite number in the add-to-favorite flow and the contact details page [CHAR LIMIT=30] -->
+ <string name="local_favorite_number_description">
+ "Local favorite - "
+ <xliff:g id="label" example="Mobile">%1$s</xliff:g>
+ </string>
+
+ <!-- Header for phone favorite numbers [CHAR LIMIT=30] -->
+ <string name="phone_favorites">Favorites</string>
+ <!-- Header for local favorite numbers [CHAR LIMIT=30] -->
+ <string name="local_favorites">Local favorites</string>
+
+ <!-- Message in the add-to-favorite flow that no phone number available for this contact [CHAR_LIMIT=50] -->
+ <string name="no_phone_numbers">No phone numbers</string>
<!-- Heads Up Notification -->
<!-- Name of incoming call notification channel in app info [CHAR LIMIT=50] -->
@@ -192,4 +205,8 @@
<string name="given_name_first_title">First name</string>
<!-- Title of family name [CHAR LIMIT=40]-->
<string name="family_name_first_title">Last name</string>
+
+ <!-- Audio route selection dialog. Placeholder resources for overriding -->
+ <string name="audio_route_dialog_title">Output call audio to:</string>
+ <string name="audio_route_dialog_subtitle"></string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index af01627..a7c24a3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -106,7 +106,9 @@
</style>
<!-- Contact results -->
- <style name="TextAppearance.ContactResultTitle" parent="TextAppearance.Body1"/>
+ <style name="TextAppearance.ContactResultTitle" parent="TextAppearance.Body1">
+ <item name="android:textColor">@color/primary_text_selector</item>
+ </style>
<!-- Add to favorite flow dialog -->
<style name="TextAppearance.AddFavoriteNumberTitle" parent="TextAppearance.Body1">
diff --git a/res/values/themes.xml b/res/values/themes.xml
index bae1bc3..11152f4 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -18,6 +18,7 @@
<style name="Theme.Dialer" parent="Theme.CarUi">
<item name="android:listDivider">@drawable/list_divider</item>
<item name="android:buttonStyle">@style/Widget.Dialer.Button</item>
+ <item name="android:textDirection">locale</item>
</style>
<style name="Theme.Dialer.Telecom">
diff --git a/src/com/android/car/dialer/Constants.java b/src/com/android/car/dialer/Constants.java
index fc86af8..1011f28 100644
--- a/src/com/android/car/dialer/Constants.java
+++ b/src/com/android/car/dialer/Constants.java
@@ -34,6 +34,8 @@
"com.android.car.dialer.EXTRA_ACTION_READ_MISSED";
/** Intent extra flag to show incoming call. */
public static final String EXTRA_SHOW_INCOMING_CALL = "show_incoming_call";
+
+ public static final String EXTRA_SHOW_DIALPAD = "show_dialpad";
}
/** Constants used by {@link androidx.core.app.JobIntentService}s. */
@@ -41,6 +43,6 @@
public static final int NOTIFICATION_SERVICE = 2019;
}
- /**See {@link android.content.res.Resources#getIdentifier(String, String, String)}*/
+ /** See {@link android.content.res.Resources#getIdentifier(String, String, String)} */
public static int INVALID_RES_ID = 0;
}
diff --git a/src/com/android/car/dialer/notification/InCallNotificationController.java b/src/com/android/car/dialer/notification/InCallNotificationController.java
index e68b35b..624f104 100644
--- a/src/com/android/car/dialer/notification/InCallNotificationController.java
+++ b/src/com/android/car/dialer/notification/InCallNotificationController.java
@@ -171,13 +171,13 @@
private PendingIntent getFullscreenIntent(Call call) {
Intent intent = getIntent(NotificationService.ACTION_SHOW_FULLSCREEN_UI, call);
- return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private Notification.Action getAction(Call call, @StringRes int actionText,
String intentAction) {
CharSequence text = mContext.getString(actionText);
- PendingIntent intent = PendingIntent.getBroadcast(
+ PendingIntent intent = PendingIntent.getService(
mContext,
0,
getIntent(intentAction, call),
@@ -186,7 +186,7 @@
}
private Intent getIntent(String action, Call call) {
- Intent intent = new Intent(action, null, mContext, NotificationReceiver.class);
+ Intent intent = new Intent(action, null, mContext, NotificationService.class);
intent.putExtra(NotificationService.EXTRA_CALL_ID, call.getDetails().getTelecomCallId());
return intent;
}
diff --git a/src/com/android/car/dialer/notification/MissedCallNotificationController.java b/src/com/android/car/dialer/notification/MissedCallNotificationController.java
index 5b0fd86..3de1e60 100644
--- a/src/com/android/car/dialer/notification/MissedCallNotificationController.java
+++ b/src/com/android/car/dialer/notification/MissedCallNotificationController.java
@@ -195,8 +195,8 @@
private PendingIntent getDeleteIntent() {
Intent intent = new Intent(NotificationService.ACTION_READ_ALL_MISSED, null, mContext,
- NotificationReceiver.class);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ NotificationService.class);
+ PendingIntent pendingIntent = PendingIntent.getService(
mContext,
0,
intent,
@@ -207,7 +207,7 @@
private Notification.Action getAction(String phoneNumberString, @StringRes int actionText,
String intentAction) {
CharSequence text = mContext.getString(actionText);
- PendingIntent intent = PendingIntent.getBroadcast(
+ PendingIntent intent = PendingIntent.getService(
mContext,
0,
getIntent(intentAction, phoneNumberString),
@@ -216,7 +216,7 @@
}
private Intent getIntent(String action, String phoneNumberString) {
- Intent intent = new Intent(action, null, mContext, NotificationReceiver.class);
+ Intent intent = new Intent(action, null, mContext, NotificationService.class);
intent.putExtra(NotificationService.EXTRA_CALL_ID, phoneNumberString);
return intent;
}
diff --git a/src/com/android/car/dialer/notification/NotificationReceiver.java b/src/com/android/car/dialer/notification/NotificationReceiver.java
deleted file mode 100644
index 8c7d8d7..0000000
--- a/src/com/android/car/dialer/notification/NotificationReceiver.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.car.dialer.notification;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/** This class is responsible for offloading notification broadcasts to the notification service. */
-public class NotificationReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- NotificationService.enqueueWork(context, intent);
- }
-}
diff --git a/src/com/android/car/dialer/notification/NotificationService.java b/src/com/android/car/dialer/notification/NotificationService.java
index f7b8a01..54abe15 100644
--- a/src/com/android/car/dialer/notification/NotificationService.java
+++ b/src/com/android/car/dialer/notification/NotificationService.java
@@ -16,13 +16,13 @@
package com.android.car.dialer.notification;
+import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.os.IBinder;
import android.telecom.Call;
import android.text.TextUtils;
-import androidx.core.app.JobIntentService;
-
import com.android.car.dialer.Constants;
import com.android.car.dialer.telecom.UiCallManager;
import com.android.car.dialer.ui.activecall.InCallActivity;
@@ -31,11 +31,11 @@
import java.util.List;
/**
- * A {@link JobIntentService} that is used to handle actions from notifications to:
+ * A {@link Service} that is used to handle actions from notifications to:
* <ul><li>answer or inject an incoming call.
* <li>call back or message to a missed call.
*/
-public class NotificationService extends JobIntentService {
+public class NotificationService extends Service {
static final String ACTION_ANSWER_CALL = "CD.ACTION_ANSWER_CALL";
static final String ACTION_DECLINE_CALL = "CD.ACTION_DECLINE_CALL";
static final String ACTION_SHOW_FULLSCREEN_UI = "CD.ACTION_SHOW_FULLSCREEN_UI";
@@ -44,21 +44,20 @@
static final String ACTION_READ_ALL_MISSED = "CD.ACTION_READ_ALL_MISSED";
static final String EXTRA_CALL_ID = "CD.EXTRA_CALL_ID";
- /** Create an intent to handle reading all missed call action and schedule for executing. */
- public static void readAllMissedCall(Context context) {
- Intent readAllMissedCallIntent = new Intent(context, NotificationReceiver.class);
- readAllMissedCallIntent.setAction(ACTION_READ_ALL_MISSED);
- enqueueWork(context, readAllMissedCallIntent);
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
}
- /** Enqueue the intent. */
- static void enqueueWork(Context context, Intent intent) {
- enqueueWork(
- context, NotificationService.class, Constants.JobIds.NOTIFICATION_SERVICE, intent);
+ /** Create an intent to handle reading all missed call action and schedule for executing. */
+ public static void readAllMissedCall(Context context) {
+ Intent readAllMissedCallIntent = new Intent(context, NotificationService.class);
+ readAllMissedCallIntent.setAction(ACTION_READ_ALL_MISSED);
+ context.startService(readAllMissedCallIntent);
}
@Override
- protected void onHandleWork(Intent intent) {
+ public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getAction();
String callId = intent.getStringExtra(EXTRA_CALL_ID);
switch (action) {
@@ -92,6 +91,8 @@
default:
break;
}
+
+ return START_NOT_STICKY;
}
private void answerCall(String callId) {
diff --git a/src/com/android/car/dialer/telecom/InCallRouter.java b/src/com/android/car/dialer/telecom/InCallRouter.java
index 5e0376d..4ba91e4 100644
--- a/src/com/android/car/dialer/telecom/InCallRouter.java
+++ b/src/com/android/car/dialer/telecom/InCallRouter.java
@@ -22,6 +22,7 @@
import androidx.annotation.MainThread;
+import com.android.car.dialer.Constants;
import com.android.car.dialer.R;
import com.android.car.dialer.log.L;
import com.android.car.dialer.notification.InCallNotificationController;
@@ -83,8 +84,10 @@
int state = call.getState();
if (state == Call.STATE_RINGING) {
routeToNotification(call);
- } else {
- routeToInCallPage(call);
+ } else if (state != Call.STATE_DISCONNECTED) {
+ // Don't launch the in call page if state is disconnected.
+ // Otherwise, the InCallActivity finishes right after onCreate() and flashes.
+ routeToInCallPage(false);
}
}
@@ -134,7 +137,11 @@
@Override
public void onStateChanged(Call call, int state) {
L.d(TAG, "Ringing call state changed to %d", state);
- routeToInCallPage(call);
+ if (call.getState() != Call.STATE_DISCONNECTED) {
+ // Don't launch the in call page if state is disconnected. Otherwise, the
+ // InCallActivity finishes right after onCreate() and flashes.
+ routeToInCallPage(false);
+ }
mInCallNotificationController.cancelInCallNotification(call);
call.unregisterCallback(this);
}
@@ -144,16 +151,14 @@
/**
* Launches {@link InCallActivity} and presents the on going call in the in call page.
*/
- private void routeToInCallPage(Call call) {
+ void routeToInCallPage(boolean showDialpad) {
// It has been configured not to show the fullscreen incall ui.
if (!mShowFullscreenIncallUi) {
return;
}
- // Don't launch the in call page if state is disconnected. Otherwise, the InCallActivity
- // finishes right after onCreate() and flashes.
- if (call.getState() != Call.STATE_DISCONNECTED) {
- Intent launchIntent = new Intent(mContext, InCallActivity.class);
- mContext.startActivity(launchIntent);
- }
+
+ Intent launchIntent = new Intent(mContext, InCallActivity.class);
+ launchIntent.putExtra(Constants.Intents.EXTRA_SHOW_INCOMING_CALL, showDialpad);
+ mContext.startActivity(launchIntent);
}
}
diff --git a/src/com/android/car/dialer/telecom/InCallServiceImpl.java b/src/com/android/car/dialer/telecom/InCallServiceImpl.java
index a5faaf6..d60c9d1 100644
--- a/src/com/android/car/dialer/telecom/InCallServiceImpl.java
+++ b/src/com/android/car/dialer/telecom/InCallServiceImpl.java
@@ -37,7 +37,8 @@
/** An action which indicates a bind is from local component. */
public static final String ACTION_LOCAL_BIND = "local_bind";
- private CopyOnWriteArrayList<Callback> mCallbacks = new CopyOnWriteArrayList<>();
+ private CopyOnWriteArrayList<CallAudioStateCallback> mCallAudioStateCallbacks =
+ new CopyOnWriteArrayList<>();
private InCallRouter mInCallRouter;
@@ -59,6 +60,14 @@
boolean onTelecomCallRemoved(Call telecomCall);
}
+ /** Listens to call audio state changes. Callbacks will be called on the main thread. */
+ public interface CallAudioStateCallback {
+ /**
+ * Called when the call audio state has changed.
+ */
+ void onCallAudioStateChanged(CallAudioState callAudioState);
+ }
+
@Override
public void onCreate() {
super.onCreate();
@@ -76,21 +85,12 @@
@Override
public void onCallAdded(Call telecomCall) {
L.d(TAG, "onCallAdded: %s", telecomCall);
-
- for (Callback callback : mCallbacks) {
- callback.onTelecomCallAdded(telecomCall);
- }
-
mInCallRouter.onCallAdded(telecomCall);
}
@Override
public void onCallRemoved(Call telecomCall) {
L.d(TAG, "onCallRemoved: %s", telecomCall);
- for (Callback callback : mCallbacks) {
- callback.onTelecomCallRemoved(telecomCall);
- }
-
mInCallRouter.onCallRemoved(telecomCall);
}
@@ -113,17 +113,23 @@
@Override
public void onCallAudioStateChanged(CallAudioState audioState) {
- for (Callback callback : mCallbacks) {
+ for (CallAudioStateCallback callback : mCallAudioStateCallbacks) {
callback.onCallAudioStateChanged(audioState);
}
}
- public void registerCallback(Callback callback) {
- mCallbacks.add(callback);
+ @Override
+ public void onBringToForeground(boolean showDialpad) {
+ L.d(TAG, "onBringToForeground: %s", showDialpad);
+ mInCallRouter.routeToInCallPage(showDialpad);
}
- public void unregisterCallback(Callback callback) {
- mCallbacks.remove(callback);
+ public void addCallAudioStateChangedCallback(CallAudioStateCallback callback) {
+ mCallAudioStateCallbacks.add(callback);
+ }
+
+ public void removeCallAudioStateChangedCallback(CallAudioStateCallback callback) {
+ mCallAudioStateCallbacks.remove(callback);
}
public void addActiveCallListChangedCallback(ActiveCallListChangedCallback callback) {
@@ -134,15 +140,6 @@
mInCallRouter.unregisterActiveCallHandler(callback);
}
- @Deprecated
- interface Callback {
- void onTelecomCallAdded(Call telecomCall);
-
- void onTelecomCallRemoved(Call telecomCall);
-
- void onCallAudioStateChanged(CallAudioState audioState);
- }
-
/**
* Local binder only available for Car Dialer package.
*/
diff --git a/src/com/android/car/dialer/telecom/UiCallManager.java b/src/com/android/car/dialer/telecom/UiCallManager.java
index 956121c..6822c2d 100644
--- a/src/com/android/car/dialer/telecom/UiCallManager.java
+++ b/src/com/android/car/dialer/telecom/UiCallManager.java
@@ -42,11 +42,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.ITelephony;
-import com.google.i18n.phonenumbers.PhoneNumberUtil;
-import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
-import com.google.i18n.phonenumbers.PhoneNumberUtil.ValidationResult;
-import com.google.i18n.phonenumbers.Phonenumber;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -307,24 +302,13 @@
}
/**
- * Runs basic validation check of a phone number, to verify it is the correct length
- * in an internationalized way. Further validation on whether the number actually exists
- * is left for the phone carrier.
+ * Runs basic validation check of a phone number, to verify it is not empty.
*/
private boolean isValidNumber(String number) {
- Phonenumber.PhoneNumber phoneNumber = TelecomUtils.createI18nPhoneNumber(mContext,
- number);
- if (phoneNumber != null) {
- for (PhoneNumberType type : PhoneNumberType.values()) {
- ValidationResult result =
- PhoneNumberUtil.getInstance().isPossibleNumberForTypeWithReason(phoneNumber,
- type);
- if (result != ValidationResult.TOO_SHORT && result != ValidationResult.TOO_LONG) {
- return true;
- }
- }
+ if (TextUtils.isEmpty(number)) {
+ return false;
}
- return false;
+ return true;
}
public void callVoicemail() {
diff --git a/src/com/android/car/dialer/ui/TelecomActivity.java b/src/com/android/car/dialer/ui/TelecomActivity.java
index 121aeb2..5092450 100644
--- a/src/com/android/car/dialer/ui/TelecomActivity.java
+++ b/src/com/android/car/dialer/ui/TelecomActivity.java
@@ -17,6 +17,7 @@
package com.android.car.dialer.ui;
import android.app.SearchManager;
+import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
@@ -30,7 +31,6 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProviders;
@@ -69,8 +69,7 @@
* <p>Based on call and connectivity status, it will choose the right page to display.
*/
public class TelecomActivity extends FragmentActivity implements
- DialerBaseFragment.DialerFragmentParent, FragmentManager.OnBackStackChangedListener,
- Toolbar.OnHeightChangedListener {
+ DialerBaseFragment.DialerFragmentParent {
private static final String TAG = "CD.TelecomActivity";
private LiveData<String> mBluetoothErrorMsgLiveData;
private LiveData<Integer> mDialerAppStateLiveData;
@@ -78,6 +77,7 @@
// View objects for this activity.
private TelecomPageTab.Factory mTabFactory;
private Toolbar mCarUiToolbar;
+ private BluetoothDevice mBluetoothDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -87,7 +87,6 @@
setContentView(R.layout.telecom_activity);
mCarUiToolbar = findViewById(R.id.car_ui_toolbar);
- mCarUiToolbar.registerToolbarHeightChangeListener(this);
setupTabLayout();
@@ -99,6 +98,7 @@
dialerAppState -> updateCurrentFragment(dialerAppState));
MutableLiveData<Integer> toolbarTitleMode = viewModel.getToolbarTitleMode();
toolbarTitleMode.setValue(Themes.getAttrInteger(this, R.attr.toolbarTitleMode));
+ viewModel.getRefreshTabsLiveData().observe(this, this::refreshTabs);
InCallViewModel inCallViewModel = ViewModelProviders.of(this).get(InCallViewModel.class);
mOngoingCallListLiveData = inCallViewModel.getOngoingCallList();
@@ -108,25 +108,11 @@
handleIntent();
}
- @Override
- public void onStart() {
- getSupportFragmentManager().addOnBackStackChangedListener(this);
- onBackStackChanged();
- super.onStart();
- L.d(TAG, "onStart");
- }
-
- @Override
- public void onStop() {
- super.onStop();
- L.d(TAG, "onStop");
- getSupportFragmentManager().removeOnBackStackChangedListener(this);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mCarUiToolbar.unregisterToolbarHeightChangeListener(this);
+ private void refreshTabs(boolean refreshTabs) {
+ L.v(TAG, "hfp connected device list Changes.");
+ if (refreshTabs) {
+ setupTabLayout();
+ }
}
@Override
@@ -269,6 +255,7 @@
private void setupTabLayout() {
boolean wasContentFragmentRestored = false;
mTabFactory = new TelecomPageTab.Factory(this, getSupportFragmentManager());
+ mCarUiToolbar.clearAllTabs();
for (int i = 0; i < mTabFactory.getTabCount(); i++) {
TelecomPageTab tab = mTabFactory.createTab(getBaseContext(), i);
mCarUiToolbar.addTab(tab);
@@ -356,25 +343,6 @@
}
@Override
- public void onBackStackChanged() {
- L.d(TAG, "onBackStackChanged");
- Fragment topFragment = getSupportFragmentManager().findFragmentById(
- R.id.content_fragment_container);
- if (topFragment instanceof DialerBaseFragment) {
- ((DialerBaseFragment) topFragment).setupToolbar(mCarUiToolbar);
- }
- }
-
- @Override
- public void onHeightChanged(int height) {
- Fragment topFragment = getSupportFragmentManager().findFragmentById(
- R.id.content_fragment_container);
- if (topFragment instanceof DialerBaseFragment) {
- ((DialerBaseFragment) topFragment).setToolbarHeight(height);
- }
- }
-
- @Override
public boolean onNavigateUp() {
if (isBackNavigationAvailable()) {
onBackPressed();
diff --git a/src/com/android/car/dialer/ui/TelecomActivityViewModel.java b/src/com/android/car/dialer/ui/TelecomActivityViewModel.java
index e28c4ce..cd16402 100644
--- a/src/com/android/car/dialer/ui/TelecomActivityViewModel.java
+++ b/src/com/android/car/dialer/ui/TelecomActivityViewModel.java
@@ -27,11 +27,13 @@
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Transformations;
import com.android.car.dialer.R;
import com.android.car.dialer.livedata.BluetoothHfpStateLiveData;
import com.android.car.dialer.livedata.BluetoothPairListLiveData;
import com.android.car.dialer.livedata.BluetoothStateLiveData;
+import com.android.car.dialer.livedata.HfpDeviceListLiveData;
import com.android.car.dialer.log.L;
import com.android.car.dialer.telecom.UiBluetoothMonitor;
@@ -52,10 +54,13 @@
private final Context mApplicationContext;
private final LiveData<String> mErrorStringLiveData;
private final MutableLiveData<Integer> mDialerAppStateLiveData;
+ private final LiveData<Boolean> mRefreshTabsLiveData;
private final ToolbarTitleLiveData mToolbarTitleLiveData;
private final MutableLiveData<Integer> mToolbarTitleMode;
+ private BluetoothDevice mBluetoothDevice;
+
/**
* App state indicates if bluetooth is connected or it should just show the content fragments.
*/
@@ -90,6 +95,22 @@
}
mDialerAppStateLiveData = new DialerAppStateLiveData(mErrorStringLiveData);
+
+ HfpDeviceListLiveData hfpDeviceListLiveData = new HfpDeviceListLiveData(getApplication());
+ mRefreshTabsLiveData = Transformations.map(hfpDeviceListLiveData, (hfpDeviceList) -> {
+ if (hfpDeviceList != null && !hfpDeviceList.isEmpty()) {
+ if (!hfpDeviceList.contains(mBluetoothDevice)) {
+ mBluetoothDevice = hfpDeviceList.get(0);
+ return true;
+ }
+ } else {
+ if (mBluetoothDevice != null) {
+ mBluetoothDevice = null;
+ return true;
+ }
+ }
+ return false;
+ });
}
/**
@@ -108,6 +129,9 @@
return mToolbarTitleMode;
}
+ /**
+ * Returns the state of Dialer App.
+ */
public MutableLiveData<Integer> getDialerAppState() {
return mDialerAppStateLiveData;
}
@@ -120,6 +144,13 @@
return mErrorStringLiveData;
}
+ /**
+ * Returns the live data which monitors whether to refresh Dialer.
+ */
+ public LiveData<Boolean> getRefreshTabsLiveData() {
+ return mRefreshTabsLiveData;
+ }
+
private static class DialerAppStateLiveData extends MediatorLiveData<Integer> {
private final LiveData<String> mErrorStringLiveData;
@@ -203,16 +234,16 @@
isBluetoothEnabled,
hasPairedDevices,
isHfpConnected);
- if (!isBluetoothEnabled) {
- setValue(mContext.getString(R.string.bluetooth_disabled));
- } else if (!hasPairedDevices) {
- setValue(mContext.getString(R.string.bluetooth_unpaired));
- } else if (!isHfpConnected) {
- setValue(mContext.getString(R.string.no_hfp));
- } else {
+ if (isHfpConnected) {
if (!NO_BT_ERROR.equals(getValue())) {
setValue(NO_BT_ERROR);
}
+ } else if (!isBluetoothEnabled) {
+ setValue(mContext.getString(R.string.bluetooth_disabled));
+ } else if (!hasPairedDevices) {
+ setValue(mContext.getString(R.string.bluetooth_unpaired));
+ } else {
+ setValue(mContext.getString(R.string.no_hfp));
}
}
diff --git a/src/com/android/car/dialer/ui/activecall/InCallActivity.java b/src/com/android/car/dialer/ui/activecall/InCallActivity.java
index 015dc08..af438a1 100644
--- a/src/com/android/car/dialer/ui/activecall/InCallActivity.java
+++ b/src/com/android/car/dialer/ui/activecall/InCallActivity.java
@@ -41,6 +41,7 @@
private Fragment mOngoingCallFragment;
private Fragment mIncomingCallFragment;
+ private InCallViewModel mInCallViewModel;
private MutableLiveData<Boolean> mShowIncomingCall;
private LiveData<Call> mIncomingCallLiveData;
@@ -56,12 +57,12 @@
mIncomingCallFragment = getSupportFragmentManager().findFragmentById(
R.id.incoming_call_fragment);
- mShowIncomingCall = new MutableLiveData();
- InCallViewModel inCallViewModel = ViewModelProviders.of(this).get(InCallViewModel.class);
+ mShowIncomingCall = new MutableLiveData<>();
+ mInCallViewModel = ViewModelProviders.of(this).get(InCallViewModel.class);
mIncomingCallLiveData = LiveDataFunctions.iff(mShowIncomingCall,
- inCallViewModel.getIncomingCall());
- LiveDataFunctions.pair(inCallViewModel.getOngoingCallList(), mIncomingCallLiveData).observe(
- this, this::updateVisibility);
+ mInCallViewModel.getIncomingCall());
+ LiveDataFunctions.pair(mInCallViewModel.getOngoingCallList(),
+ mIncomingCallLiveData).observe(this, this::updateVisibility);
handleIntent();
}
@@ -97,9 +98,11 @@
private void handleIntent() {
Intent intent = getIntent();
- if (intent != null && getIntent().getBooleanExtra(
- Constants.Intents.EXTRA_SHOW_INCOMING_CALL, false)) {
- mShowIncomingCall.setValue(true);
+ if (intent != null) {
+ mShowIncomingCall.setValue(
+ getIntent().getBooleanExtra(Constants.Intents.EXTRA_SHOW_INCOMING_CALL, false));
+ mInCallViewModel.getDialpadOpenState().setValue(
+ getIntent().getBooleanExtra(Constants.Intents.EXTRA_SHOW_DIALPAD, false));
} else {
mShowIncomingCall.setValue(false);
}
diff --git a/src/com/android/car/dialer/ui/activecall/InCallViewModel.java b/src/com/android/car/dialer/ui/activecall/InCallViewModel.java
index 328695b..39210fa 100644
--- a/src/com/android/car/dialer/ui/activecall/InCallViewModel.java
+++ b/src/com/android/car/dialer/ui/activecall/InCallViewModel.java
@@ -23,6 +23,7 @@
import android.content.ServiceConnection;
import android.os.IBinder;
import android.telecom.Call;
+import android.telecom.CallAudioState;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
@@ -53,7 +54,7 @@
* in call page should use a different ViewModel.
*/
public class InCallViewModel extends AndroidViewModel implements
- InCallServiceImpl.ActiveCallListChangedCallback {
+ InCallServiceImpl.ActiveCallListChangedCallback, InCallServiceImpl.CallAudioStateCallback {
private static final String TAG = "CD.InCallViewModel";
private final MutableLiveData<List<Call>> mCallListLiveData;
@@ -68,6 +69,7 @@
private final LiveData<Call> mSecondaryCallLiveData;
private final LiveData<CallDetail> mSecondaryCallDetailLiveData;
private final LiveData<Integer> mAudioRouteLiveData;
+ private MutableLiveData<CallAudioState> mCallAudioStateLiveData;
private final MutableLiveData<Boolean> mDialpadIsOpen;
private final ShowOnholdCallLiveData mShowOnholdCall;
private LiveData<Long> mCallConnectTimeLiveData;
@@ -86,6 +88,7 @@
}
updateCallList();
mInCallService.addActiveCallListChangedCallback(InCallViewModel.this);
+ mInCallService.addCallAudioStateChangedCallback(InCallViewModel.this);
}
@Override
@@ -114,6 +117,7 @@
mIncomingCallLiveData = new MutableLiveData<>();
mOngoingCallListLiveData = new MutableLiveData<>();
+ mCallAudioStateLiveData = new MutableLiveData<>();
mCallComparator = new CallComparator();
mCallListLiveData = new MutableLiveData<List<Call>>() {
@Override
@@ -231,6 +235,13 @@
return mAudioRouteLiveData;
}
+ /**
+ * Returns current call audio state.
+ */
+ public MutableLiveData<CallAudioState> getCallAudioState() {
+ return mCallAudioStateLiveData;
+ }
+
/** Return the {@link MutableLiveData} for dialpad open state. */
public MutableLiveData<Boolean> getDialpadOpenState() {
return mDialpadIsOpen;
@@ -257,6 +268,12 @@
return false;
}
+ @Override
+ public void onCallAudioStateChanged(CallAudioState callAudioState) {
+ L.i(TAG, "onCallAudioStateChanged %s %s", callAudioState, this);
+ mCallAudioStateLiveData.setValue(callAudioState);
+ }
+
private void updateCallList() {
List<Call> callList = new ArrayList<>();
callList.addAll(mInCallService.getCalls());
@@ -271,6 +288,7 @@
call.unregisterCallback(mCallStateChangedCallback);
}
mInCallService.removeActiveCallListChangedCallback(this);
+ mInCallService.removeCallAudioStateChangedCallback(this);
}
mInCallService = null;
}
diff --git a/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragment.java b/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragment.java
index 8a822db..cda13a4 100644
--- a/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragment.java
+++ b/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragment.java
@@ -17,11 +17,13 @@
package com.android.car.dialer.ui.activecall;
import android.app.AlertDialog;
-import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.telecom.Call;
import android.telecom.CallAudioState;
import android.telecom.CallAudioState.CallAudioRoute;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -36,16 +38,19 @@
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProviders;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
import com.android.car.apps.common.util.ViewUtils;
import com.android.car.dialer.R;
import com.android.car.dialer.log.L;
import com.android.car.dialer.telecom.UiCallManager;
+import com.android.car.ui.AlertDialogBuilder;
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiListItem;
+import com.android.car.ui.recyclerview.CarUiListItemAdapter;
import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
import java.util.List;
/** A Fragment of the bar which controls on going call. */
@@ -73,7 +78,9 @@
.build();
private AlertDialog mAudioRouteSelectionDialog;
- private AudioRouteListAdapter mAudioRouteAdapter;
+ private List<CarUiListItem> mAudioRouteListItems;
+ private List<Integer> mAvailableRoutes;
+ private CarUiListItemAdapter mAudioRouteAdapter;
private View mMuteButton;
private View mAudioRouteView;
private ImageView mAudioRouteButton;
@@ -83,24 +90,51 @@
private MutableLiveData<Boolean> mDialpadState;
private LiveData<List<Call>> mCallListLiveData;
private int mPrimaryCallState;
+ private int mActiveRoute;
+ private MutableLiveData<CallAudioState> mCallAudioState;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- View dialogView = LayoutInflater.from(getContext()).inflate(
- R.layout.audio_route_switch_dialog, null, false);
- RecyclerView list = dialogView.findViewById(R.id.list);
- list.setLayoutManager(new LinearLayoutManager(getContext()));
+ mAvailableRoutes = UiCallManager.get().getSupportedAudioRoute();
+ mActiveRoute = UiCallManager.get().getAudioRoute();
- mAudioRouteSelectionDialog = new AlertDialog.Builder(getContext())
- .setView(dialogView)
- .create();
+ if (mAvailableRoutes.contains(CallAudioState.ROUTE_EARPIECE)
+ && mAvailableRoutes.contains(CallAudioState.ROUTE_WIRED_HEADSET)) {
+ // Keep either ROUTE_EARPIECE or ROUTE_WIRED_HEADSET, but not both of them.
+ mAvailableRoutes.remove(CallAudioState.ROUTE_WIRED_HEADSET);
+ }
- List<Integer> availableRoutes = UiCallManager.get().getSupportedAudioRoute();
- int activeRoute = UiCallManager.get().getAudioRoute();
- mAudioRouteAdapter = new AudioRouteListAdapter(getContext(), availableRoutes, activeRoute);
- list.setAdapter(mAudioRouteAdapter);
+ mAudioRouteListItems = new ArrayList<>();
+ mAudioRouteAdapter = new CarUiListItemAdapter(mAudioRouteListItems);
+
+ for (Integer audioRoute : mAvailableRoutes) {
+ CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+ AudioRouteInfo routeInfo = getAudioRouteInfo(audioRoute);
+ Drawable drawable = getResources().getDrawable(routeInfo.mIcon, null);
+ drawable.setTintList(
+ getResources().getColorStateList(R.color.icon_accent_activatable, null));
+ item.setIcon(drawable);
+ item.setOnItemClickedListener((i) -> {
+ onSetAudioRoute(audioRoute);
+ });
+ String routeTitle = getString(routeInfo.mLabel);
+ item.setTitle(mActiveRoute == audioRoute ? withAccentColor(routeTitle) : routeTitle);
+ item.setActivated(mActiveRoute == audioRoute);
+ mAudioRouteListItems.add(item);
+ }
+
+ AlertDialogBuilder audioRouteSelectionDialogBuilder = new AlertDialogBuilder(getContext())
+ .setAdapter(mAudioRouteAdapter)
+ .setTitle(getString(R.string.audio_route_dialog_title));
+
+ String subtitle = getString(R.string.audio_route_dialog_subtitle);
+ if (!subtitle.isEmpty()) {
+ audioRouteSelectionDialogBuilder.setSubtitle(subtitle);
+ }
+
+ mAudioRouteSelectionDialog = audioRouteSelectionDialogBuilder.create();
InCallViewModel inCallViewModel = ViewModelProviders.of(getActivity()).get(
InCallViewModel.class);
@@ -110,6 +144,7 @@
inCallViewModel.getAudioRoute().observe(this, this::updateViewBasedOnAudioRoute);
mDialpadState = inCallViewModel.getDialpadOpenState();
+ mCallAudioState = inCallViewModel.getCallAudioState();
mCallListLiveData = inCallViewModel.getAllCallList();
mCallListLiveData.observe(this, v -> updatePauseButtonEnabledState());
@@ -133,6 +168,8 @@
}
});
+ mCallAudioState.observe(this, state -> mMuteButton.setActivated(state.isMuted()));
+
View dialPadButton = fragmentView.findViewById(R.id.toggle_dialpad_button);
dialPadButton.setOnClickListener(v -> mDialpadState.setValue(!mDialpadState.getValue()));
mDialpadState.observe(this, activated -> dialPadButton.setActivated(activated));
@@ -147,7 +184,6 @@
if (audioRoutes.size() > 1) {
mAudioRouteView.setOnClickListener((v) -> {
mAudioRouteView.setActivated(true);
- mAudioRouteAdapter.setActiveAudioRoute(UiCallManager.get().getAudioRoute());
mAudioRouteSelectionDialog.show();
});
}
@@ -179,6 +215,26 @@
}
}
+ private void updateAudioRouteListItems() {
+ for (int i = 0; i < mAvailableRoutes.size(); i++) {
+ int audioRoute = mAvailableRoutes.get(i);
+ CarUiContentListItem item = (CarUiContentListItem) mAudioRouteListItems.get(i);
+ boolean isActiveRoute = audioRoute == mActiveRoute;
+ String routeTitle = item.getTitle().toString();
+ item.setActivated(isActiveRoute);
+ item.setTitle(isActiveRoute ? withAccentColor(routeTitle) : routeTitle);
+ }
+ mAudioRouteAdapter.notifyDataSetChanged();
+ }
+
+ private CharSequence withAccentColor(String routeTitle) {
+ ForegroundColorSpan activeRouteSpan = new ForegroundColorSpan(
+ getResources().getColor(R.color.audio_output_accent, null));
+ SpannableString spannableTitle = new SpannableString(routeTitle);
+ spannableTitle.setSpan(activeRouteSpan, 0, routeTitle.length(), 0);
+ return spannableTitle;
+ }
+
/** Set the call state and change the view for the pause button accordingly */
private void setCallState(int callState) {
L.d(TAG, "Call State: %s", callState);
@@ -224,6 +280,8 @@
private void onSetAudioRoute(@CallAudioRoute int audioRoute) {
UiCallManager.get().setAudioRoute(audioRoute);
+ mActiveRoute = audioRoute;
+ updateAudioRouteListItems();
mAudioRouteSelectionDialog.dismiss();
}
@@ -233,6 +291,8 @@
}
L.i(TAG, "Audio Route State: " + audioRoute);
+ mActiveRoute = audioRoute;
+ updateAudioRouteListItems();
AudioRouteInfo audioRouteInfo = getAudioRouteInfo(audioRoute);
if (mAudioRouteButton != null) {
mAudioRouteButton.setImageResource(audioRouteInfo.mIconActivatable);
@@ -274,64 +334,4 @@
mIconActivatable = iconActivatable;
}
}
-
- private class AudioRouteListAdapter extends
- RecyclerView.Adapter<AudioRouteItemViewHolder> {
- private List<Integer> mSupportedRoutes;
- private Context mContext;
- private int mActiveAudioRoute;
-
- AudioRouteListAdapter(Context context,
- List<Integer> supportedRoutes,
- int activeAudioRoute) {
- mSupportedRoutes = supportedRoutes;
- mActiveAudioRoute = activeAudioRoute;
- mContext = context;
- if (mSupportedRoutes.contains(CallAudioState.ROUTE_EARPIECE)
- && mSupportedRoutes.contains(CallAudioState.ROUTE_WIRED_HEADSET)) {
- // Keep either ROUTE_EARPIECE or ROUTE_WIRED_HEADSET, but not both of them.
- mSupportedRoutes.remove(CallAudioState.ROUTE_WIRED_HEADSET);
- }
- }
-
- public void setActiveAudioRoute(int route) {
- if (mActiveAudioRoute != route) {
- mActiveAudioRoute = route;
- notifyDataSetChanged();
- }
- }
-
- @Override
- public AudioRouteItemViewHolder onCreateViewHolder(ViewGroup container, int position) {
- View listItemView = LayoutInflater.from(mContext).inflate(
- R.layout.audio_route_list_item, container, false);
- return new AudioRouteItemViewHolder(listItemView);
- }
-
- @Override
- public void onBindViewHolder(AudioRouteItemViewHolder viewHolder, int position) {
- int audioRoute = mSupportedRoutes.get(position);
- AudioRouteInfo routeInfo = getAudioRouteInfo(audioRoute);
- viewHolder.mBody.setText(routeInfo.mLabel);
- viewHolder.mIcon.setImageResource(routeInfo.mIcon);
- viewHolder.itemView.setActivated(audioRoute == mActiveAudioRoute);
- viewHolder.itemView.setOnClickListener((v) -> onSetAudioRoute(audioRoute));
- }
-
- @Override
- public int getItemCount() {
- return mSupportedRoutes.size();
- }
- }
-
- private static class AudioRouteItemViewHolder extends RecyclerView.ViewHolder {
- public final ImageView mIcon;
- public final TextView mBody;
-
- public AudioRouteItemViewHolder(View itemView) {
- super(itemView);
- mIcon = itemView.findViewById(R.id.icon);
- mBody = itemView.findViewById(R.id.body);
- }
- }
}
diff --git a/src/com/android/car/dialer/ui/calllog/CallHistoryFragment.java b/src/com/android/car/dialer/ui/calllog/CallHistoryFragment.java
index 2658885..6595a06 100644
--- a/src/com/android/car/dialer/ui/calllog/CallHistoryFragment.java
+++ b/src/com/android/car/dialer/ui/calllog/CallHistoryFragment.java
@@ -42,6 +42,7 @@
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
// Don't recreate the adapter if we already have one, so that the list items
// will display immediately upon the view being recreated. If they're not displayed
// immediately, we won't remember our scroll position.
diff --git a/src/com/android/car/dialer/ui/calllog/CallLogViewHolder.java b/src/com/android/car/dialer/ui/calllog/CallLogViewHolder.java
index bef477b..9ffde69 100644
--- a/src/com/android/car/dialer/ui/calllog/CallLogViewHolder.java
+++ b/src/com/android/car/dialer/ui/calllog/CallLogViewHolder.java
@@ -31,7 +31,6 @@
import com.android.car.dialer.ui.view.ContactAvatarOutputlineProvider;
import com.android.car.dialer.widget.CallTypeIconsView;
import com.android.car.telephony.common.Contact;
-import com.android.car.telephony.common.InMemoryPhoneBook;
import com.android.car.telephony.common.PhoneCallLog;
import com.android.car.telephony.common.TelecomUtils;
@@ -70,12 +69,13 @@
* Binds the view holder with relevant data.
*/
public void bind(UiCallLog uiCallLog) {
+ Contact contact = uiCallLog.getContact();
+
TelecomUtils.setContactBitmapAsync(
mAvatarView.getContext(),
mAvatarView,
- uiCallLog.getAvatarUri(),
- uiCallLog.getInitials(),
- uiCallLog.getTitle());
+ contact,
+ uiCallLog.getNumber());
mTitleView.setText(uiCallLog.getTitle());
for (PhoneCallLog.Record record : uiCallLog.getCallRecords()) {
@@ -99,7 +99,7 @@
ViewUtils.setOnClickListener(mPlaceCallView,
view -> UiCallManager.get().placeCall(uiCallLog.getNumber()));
- setUpActionButton(uiCallLog);
+ setUpActionButton(contact);
}
/**
@@ -110,12 +110,11 @@
ViewUtils.setOnClickListener(mPlaceCallView, null);
}
- private void setUpActionButton(UiCallLog uiCallLog) {
+ private void setUpActionButton(Contact contact) {
if (mActionButton == null) {
return;
}
- Contact contact = InMemoryPhoneBook.get().lookupContactEntry(uiCallLog.getNumber());
boolean forceShowActionButton = itemView.getResources().getBoolean(
R.bool.config_show_calllog_action_button_for_non_contact);
diff --git a/src/com/android/car/dialer/ui/common/ContactResultsLiveData.java b/src/com/android/car/dialer/ui/common/ContactResultsLiveData.java
new file mode 100644
index 0000000..4f82d7f
--- /dev/null
+++ b/src/com/android/car/dialer/ui/common/ContactResultsLiveData.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.car.dialer.ui.common;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
+
+import com.android.car.dialer.livedata.SharedPreferencesLiveData;
+import com.android.car.dialer.ui.common.entity.ContactSortingInfo;
+import com.android.car.telephony.common.Contact;
+import com.android.car.telephony.common.InMemoryPhoneBook;
+import com.android.car.telephony.common.ObservableAsyncQuery;
+import com.android.car.telephony.common.QueryParam;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Represents a list of {@link Contact} based on the search query
+ */
+public class ContactResultsLiveData extends
+ MediatorLiveData<List<ContactResultsLiveData.ContactResultListItem>> {
+ private static final String[] CONTACT_DETAILS_PROJECTION = {
+ ContactsContract.CommonDataKinds.Phone._ID,
+ ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY,
+ ContactsContract.CommonDataKinds.Phone.NUMBER,
+ };
+ private final Context mContext;
+ private final SearchQueryParamProvider mSearchQueryParamProvider;
+ private final ObservableAsyncQuery mObservableAsyncQuery;
+ private final LiveData<String> mSearchQueryLiveData;
+ private final LiveData<List<Contact>> mContactListLiveData;
+ private final SharedPreferencesLiveData mSortOrderPreferenceLiveData;
+ private boolean mShowOnlyOneEntry;
+
+ /**
+ * @param searchQueryLiveData represents a list of strings that are used to query the data
+ * @param sortOrderPreferenceLiveData has the information on how to order the acquired contacts.
+ * @param showOnlyOneEntry determines whether to show only entry per contact.
+ */
+ public ContactResultsLiveData(Context context,
+ LiveData<String> searchQueryLiveData,
+ SharedPreferencesLiveData sortOrderPreferenceLiveData,
+ boolean showOnlyOneEntry) {
+ mContext = context;
+ mShowOnlyOneEntry = showOnlyOneEntry;
+ mSearchQueryParamProvider = new SearchQueryParamProvider(searchQueryLiveData);
+ mObservableAsyncQuery = new ObservableAsyncQuery(mSearchQueryParamProvider,
+ context.getContentResolver(), this::onQueryFinished);
+
+ mContactListLiveData = InMemoryPhoneBook.get().getContactsLiveData();
+ addSource(mContactListLiveData, this::onContactsChange);
+ mSearchQueryLiveData = searchQueryLiveData;
+ addSource(mSearchQueryLiveData, this::onSearchQueryChanged);
+
+ mSortOrderPreferenceLiveData = sortOrderPreferenceLiveData;
+ addSource(mSortOrderPreferenceLiveData, this::onSortOrderChanged);
+ }
+
+ /**
+ * This constructor only allows one entry per contact.
+ *
+ * @param searchQueryLiveData represents a list of strings that are used to query the data
+ * @param sortOrderPreferenceLiveData has the information on how to order the acquired contacts.
+ */
+ public ContactResultsLiveData(Context context,
+ LiveData<String> searchQueryLiveData,
+ SharedPreferencesLiveData sortOrderPreferenceLiveData) {
+ this(context, searchQueryLiveData, sortOrderPreferenceLiveData, true);
+ }
+
+ private void onContactsChange(List<Contact> contactList) {
+ if (contactList == null || contactList.isEmpty()) {
+ mObservableAsyncQuery.stopQuery();
+ setValue(Collections.emptyList());
+ } else {
+ onSearchQueryChanged(mSearchQueryLiveData.getValue());
+ }
+ }
+
+ private void onSearchQueryChanged(String searchQuery) {
+ if (TextUtils.isEmpty(searchQuery)) {
+ mObservableAsyncQuery.stopQuery();
+ setValue(Collections.emptyList());
+ } else {
+ mObservableAsyncQuery.startQuery();
+ }
+ }
+
+ private void onSortOrderChanged(SharedPreferences unusedSharedPreferences) {
+ setValue(getValue());
+ }
+
+ private void onQueryFinished(@Nullable Cursor cursor) {
+ if (cursor == null) {
+ setValue(Collections.emptyList());
+ return;
+ }
+
+ List<ContactResultListItem> contactResults = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ int lookupKeyColIdx = cursor.getColumnIndex(
+ ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY);
+ int numberIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
+ String lookupKey = cursor.getString(lookupKeyColIdx);
+ String number = cursor.getString(numberIdx);
+ List<Contact> lookupResults = InMemoryPhoneBook.get().lookupContactByKey(lookupKey);
+ for (Contact contact : lookupResults) {
+ contactResults.add(new ContactResultListItem(contact, number));
+ }
+ }
+
+ if (mShowOnlyOneEntry) {
+ Set<Contact> set = new HashSet<>();
+ contactResults = contactResults.stream()
+ .filter(o -> set.add(o.getContact()))
+ .collect(Collectors.toList());
+ }
+
+ setValue(contactResults);
+ cursor.close();
+ }
+
+ /**
+ * Sort and replace null list with empty list.
+ */
+ @Override
+ public void setValue(List<ContactResultListItem> contactResults) {
+ if (contactResults != null && !contactResults.isEmpty()) {
+ Collections.sort(contactResults, (o1, o2) -> ContactSortingInfo.getSortingInfo(
+ mContext, mSortOrderPreferenceLiveData).first.compare(o1.mContact,
+ o2.mContact));
+ }
+ super.setValue(contactResults == null ? Collections.EMPTY_LIST : contactResults);
+ }
+
+ private static class SearchQueryParamProvider implements QueryParam.Provider {
+ private final LiveData<String> mSearchQueryLiveData;
+
+ private SearchQueryParamProvider(LiveData<String> searchQueryLiveData) {
+ mSearchQueryLiveData = searchQueryLiveData;
+ }
+
+ @Nullable
+ @Override
+ public QueryParam getQueryParam() {
+ Uri lookupUri = Uri.withAppendedPath(
+ ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI,
+ Uri.encode(mSearchQueryLiveData.getValue()));
+ return new QueryParam(lookupUri, CONTACT_DETAILS_PROJECTION, null,
+ /* selectionArgs= */null, /* orderBy= */null);
+ }
+ }
+
+ /**
+ * Represent a contact search result.
+ */
+ public static class ContactResultListItem {
+ private final Contact mContact;
+ private final String mNumber;
+
+ public ContactResultListItem(Contact contact, String number) {
+ mContact = contact;
+ mNumber = number;
+ }
+
+ /**
+ * Returns the contact.
+ */
+ public Contact getContact() {
+ return mContact;
+ }
+
+ /**
+ * Returns the number. It is a string read from column
+ * {@link ContactsContract.CommonDataKinds.Phone#NUMBER}.
+ */
+ public String getNumber() {
+ return mNumber;
+ }
+ }
+}
diff --git a/src/com/android/car/dialer/ui/common/DialerBaseFragment.java b/src/com/android/car/dialer/ui/common/DialerBaseFragment.java
index 9e7aa7a..04afb88 100644
--- a/src/com/android/car/dialer/ui/common/DialerBaseFragment.java
+++ b/src/com/android/car/dialer/ui/common/DialerBaseFragment.java
@@ -18,7 +18,9 @@
import android.app.Activity;
import android.os.Bundle;
+import android.view.View;
+import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
@@ -55,6 +57,17 @@
mToolbarHeight = new MutableLiveData<>();
}
+ @CallSuper
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ Toolbar toolbar = getActivity().findViewById(R.id.car_ui_toolbar);
+ // Null check for unit tests to pass
+ if (toolbar != null) {
+ setupToolbar(toolbar);
+ }
+ }
+
@Override
public void onStart() {
super.onStart();
@@ -64,7 +77,7 @@
/**
* Customizes the tool bar. Can be overridden in subclasses.
*/
- public void setupToolbar(@NonNull Toolbar toolbar) {
+ protected void setupToolbar(@NonNull Toolbar toolbar) {
TelecomActivityViewModel viewModel = ViewModelProviders.of(getActivity()).get(
TelecomActivityViewModel.class);
LiveData<String> toolbarTitleLiveData = viewModel.getToolbarTitle();
@@ -79,7 +92,7 @@
setShowToolbarBackground(true);
- setToolbarHeight(toolbar.getHeight());
+ setToolbarHeight(toolbar);
}
/**
@@ -106,7 +119,13 @@
/**
* Sets the toolbar height.
*/
- public final void setToolbarHeight(int toolbarHeight) {
+ protected final void setToolbarHeight(Toolbar toolbar) {
+ int toolbarFirstRowHeight = getResources().getDimensionPixelSize(
+ R.dimen.car_ui_toolbar_first_row_height);
+ int toolbarHeight = toolbar.isTabsInSecondRow() && getToolbarState() == Toolbar.State.HOME
+ ? toolbarFirstRowHeight + getResources().getDimensionPixelSize(
+ R.dimen.car_ui_toolbar_second_row_height)
+ : toolbarFirstRowHeight;
mToolbarHeight.setValue(toolbarHeight);
}
diff --git a/src/com/android/car/dialer/ui/common/DialerUtils.java b/src/com/android/car/dialer/ui/common/DialerUtils.java
index 3bcca38..5826e42 100644
--- a/src/com/android/car/dialer/ui/common/DialerUtils.java
+++ b/src/com/android/car/dialer/ui/common/DialerUtils.java
@@ -17,6 +17,10 @@
package com.android.car.dialer.ui.common;
import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.car.dialer.R;
import com.android.car.dialer.log.L;
@@ -24,6 +28,8 @@
import com.android.car.telephony.common.PhoneNumber;
import com.android.car.telephony.common.TelecomUtils;
import com.android.car.ui.AlertDialogBuilder;
+import com.android.car.ui.recyclerview.CarUiRadioButtonListItem;
+import com.android.car.ui.recyclerview.CarUiRadioButtonListItemAdapter;
import java.util.ArrayList;
import java.util.List;
@@ -41,8 +47,9 @@
public interface PhoneNumberSelectionCallback {
/**
* Called when a phone number is chosen.
+ *
* @param phoneNumber The phone number
- * @param always Whether the user pressed "aways" or "just once"
+ * @param always Whether the user pressed "aways" or "just once"
*/
void onPhoneNumberSelected(PhoneNumber phoneNumber, boolean always);
}
@@ -54,16 +61,28 @@
public static void showPhoneNumberSelector(Context context,
List<PhoneNumber> numbers,
PhoneNumberSelectionCallback callback) {
+
final List<PhoneNumber> selectedPhoneNumber = new ArrayList<>();
+ List<CarUiRadioButtonListItem> items = new ArrayList<>();
+ CarUiRadioButtonListItemAdapter adapter = new CarUiRadioButtonListItemAdapter(items);
+
+ for (PhoneNumber number : numbers) {
+ CharSequence readableLabel = number.getReadableLabel(context.getResources());
+ CarUiRadioButtonListItem item = new CarUiRadioButtonListItem();
+ item.setTitle(number.isPrimary()
+ ? context.getString(R.string.primary_number_description, readableLabel)
+ : readableLabel);
+ item.setBody(number.getNumber());
+ item.setOnCheckedChangeListener((i, isChecked) -> {
+ selectedPhoneNumber.clear();
+ selectedPhoneNumber.add(number);
+ });
+ items.add(item);
+ }
+
new AlertDialogBuilder(context)
.setTitle(R.string.select_number_dialog_title)
- .setSingleChoiceItems(
- new PhoneNumberListAdapter(context, numbers),
- -1,
- ((dialog, which) -> {
- selectedPhoneNumber.clear();
- selectedPhoneNumber.add(numbers.get(which));
- }))
+ .setSingleChoiceItems(adapter, null)
.setNeutralButton(R.string.select_number_dialog_just_once_button,
(dialog, which) -> {
if (!selectedPhoneNumber.isEmpty()) {
@@ -106,4 +125,23 @@
L.w(TAG, "contact %s doesn't have any phone number", contact.getDisplayName());
}
}
+
+ /**
+ * Returns true if the contact has postal address and show postal address config is true.
+ */
+ private static boolean hasPostalAddress(Resources resources, @NonNull Contact contact) {
+ boolean showPostalAddress = resources.getBoolean(
+ R.bool.config_show_postal_address);
+ return showPostalAddress && (!contact.getPostalAddresses().isEmpty());
+ }
+
+ /**
+ * Returns true if the contact has either phone number or postal address to show.
+ */
+ public static boolean hasContactDetail(Resources resources, @Nullable Contact contact) {
+ boolean hasContactDetail = contact != null
+ && (!contact.getNumbers().isEmpty() || DialerUtils.hasPostalAddress(
+ resources, contact));
+ return hasContactDetail;
+ }
}
diff --git a/src/com/android/car/dialer/ui/common/FavoritePhoneNumberListAdapter.java b/src/com/android/car/dialer/ui/common/FavoritePhoneNumberListAdapter.java
deleted file mode 100644
index 4f93fd0..0000000
--- a/src/com/android/car/dialer/ui/common/FavoritePhoneNumberListAdapter.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.car.dialer.ui.common;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-
-import com.android.car.dialer.R;
-import com.android.car.telephony.common.Contact;
-import com.android.car.telephony.common.PhoneNumber;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * {@link BaseAdapter} that presents the {@link PhoneNumber} and its type as two line list
- * item with stars to indicate favorite state or user selection to add to favorite. Currently
- * favorite phone number is set to disabled so user can not take any action for an existing favorite
- * phone number.
- */
-public class FavoritePhoneNumberListAdapter extends BaseAdapter {
- private final Context mContext;
- private final FavoritePhoneNumberPresenter mFavoritePhoneNumberPresenter;
- private final List<PhoneNumber> mPhoneNumbers;
- private Contact mContact;
-
- /**
- * A presenter that presents the favorite state for phone number and provides the click
- * listener.
- */
- public interface FavoritePhoneNumberPresenter {
- /**
- * Provides the click listener for the given phone number and its present view.
- */
- void onItemClicked(PhoneNumber phoneNumber, View itemView);
- }
-
- public FavoritePhoneNumberListAdapter(Context context,
- FavoritePhoneNumberPresenter favoritePhoneNumberPresenter) {
- mContext = context;
- mFavoritePhoneNumberPresenter = favoritePhoneNumberPresenter;
- mPhoneNumbers = new ArrayList<>();
- }
-
- /**
- * Sets the phone numbers to display
- */
- public void setPhoneNumbers(Contact contact, List<PhoneNumber> phoneNumbers) {
- mPhoneNumbers.clear();
- mContact = contact;
- mPhoneNumbers.addAll(phoneNumbers);
-
- notifyDataSetChanged();
- }
-
- public Contact getContact() {
- return mContact;
- }
-
- @Override
- public int getCount() {
- return mPhoneNumbers.size();
- }
-
- @Override
- public PhoneNumber getItem(int position) {
- return mPhoneNumbers.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View itemView;
- if (convertView == null) {
- itemView = LayoutInflater.from(mContext).inflate(
- R.layout.add_favorite_number_list_item, parent, false);
- } else {
- itemView = convertView;
- }
- PhoneNumber phoneNumber = getItem(position);
- bind(phoneNumber, itemView);
- return itemView;
- }
-
- void bind(PhoneNumber phoneNumber, View itemView) {
- TextView phoneNumberView = itemView.findViewById(R.id.phone_number);
- TextView phoneNumberDescriptionView = itemView.findViewById(R.id.phone_number_description);
- phoneNumberView.setText(phoneNumber.getRawNumber());
- CharSequence readableLabel = phoneNumber.getReadableLabel(itemView.getResources());
-
- if (phoneNumber.isFavorite()) {
- phoneNumberDescriptionView.setText(
- itemView.getResources().getString(R.string.favorite_number_description,
- readableLabel));
- itemView.setActivated(true);
- itemView.setEnabled(false);
- } else {
- phoneNumberDescriptionView.setText(readableLabel);
- itemView.setActivated(false);
- itemView.setEnabled(true);
- itemView.setOnClickListener(
- view -> mFavoritePhoneNumberPresenter.onItemClicked(phoneNumber, itemView));
- }
- }
-}
diff --git a/src/com/android/car/dialer/ui/common/PhoneNumberListAdapter.java b/src/com/android/car/dialer/ui/common/PhoneNumberListAdapter.java
deleted file mode 100644
index 923fb51..0000000
--- a/src/com/android/car/dialer/ui/common/PhoneNumberListAdapter.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.car.dialer.ui.common;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.car.dialer.R;
-import com.android.car.telephony.common.PhoneNumber;
-
-import java.util.List;
-
-/**
- * {@link ArrayAdapter} that simply presents the {@link PhoneNumber} and its type as two line list
- * item.
- */
-public class PhoneNumberListAdapter extends ArrayAdapter<PhoneNumber> {
- private final Context mContext;
-
- public PhoneNumberListAdapter(Context context, List<PhoneNumber> phoneNumbers) {
- super(context, R.layout.phone_number_list_item, R.id.phone_number, phoneNumbers);
- mContext = context;
- }
-
- @Override
- public View getView(int position, @Nullable View convertView,
- @NonNull ViewGroup parent) {
- View view = super.getView(position, convertView, parent);
- PhoneNumber phoneNumber = getItem(position);
- if (phoneNumber == null) {
- return view;
- }
- TextView phoneNumberView = view.findViewById(R.id.phone_number);
- phoneNumberView.setText(phoneNumber.getRawNumber());
- TextView phoneNumberDescriptionView = view.findViewById(R.id.phone_number_description);
- CharSequence readableLabel = phoneNumber.getReadableLabel(mContext.getResources());
- if (phoneNumber.isPrimary()) {
- phoneNumberDescriptionView.setText(
- mContext.getString(R.string.primary_number_description, readableLabel));
- } else {
- phoneNumberDescriptionView.setText(readableLabel);
- }
- return view;
- }
-}
diff --git a/src/com/android/car/dialer/ui/common/UiCallLogLiveData.java b/src/com/android/car/dialer/ui/common/UiCallLogLiveData.java
index 5b6d06a..1c48f1a 100644
--- a/src/com/android/car/dialer/ui/common/UiCallLogLiveData.java
+++ b/src/com/android/car/dialer/ui/common/UiCallLogLiveData.java
@@ -16,7 +16,12 @@
package com.android.car.dialer.ui.common;
+import android.content.ContentResolver;
import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.PhoneLookup;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -43,16 +48,21 @@
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
/**
- * Represents a list of {@link UiCallLog}s and label {@link String}s for UI representation.
- * This live data gets data source from both call log and contact list. It also refresh
- * itself on the relative time in the body text.
+ * Represents a list of {@link UiCallLog}s and label {@link String}s for UI representation. This
+ * live data gets data source from both call log and contact list. It also refresh itself on the
+ * relative time in the body text.
*/
public class UiCallLogLiveData extends MediatorLiveData<List<Object>> {
private static final String TAG = "CD.UiCallLogLiveData";
private static final String TYPE_AND_RELATIVE_TIME_JOINER = ", ";
+ private final ExecutorService mExecutorService;
+ private Future<?> mRunnableFuture;
private Context mContext;
public UiCallLogLiveData(Context context,
@@ -60,6 +70,8 @@
CallHistoryLiveData callHistoryLiveData,
LiveData<List<Contact>> contactListLiveData) {
mContext = context;
+ mExecutorService = Executors.newSingleThreadExecutor();
+
addSource(callHistoryLiveData, this::onCallHistoryChanged);
addSource(contactListLiveData,
(contacts) -> onCallHistoryChanged(callHistoryLiveData.getValue()));
@@ -71,7 +83,14 @@
if (callLogs == null && getValue() == null) {
return;
}
- setValue(convert(callLogs));
+
+ if (mRunnableFuture != null) {
+ mRunnableFuture.cancel(true);
+ }
+ Runnable runnable = () -> {
+ postValue(convert(callLogs));
+ };
+ mRunnableFuture = mExecutorService.submit(runnable);
}
private void updateRelativeTime() {
@@ -133,14 +152,51 @@
String relativeTime = getRelativeTime(phoneCallLog.getLastCallEndTimestamp());
if (TelecomUtils.isVoicemailNumber(mContext, number)) {
String title = mContext.getString(R.string.voicemail);
- UiCallLog uiCallLog = new UiCallLog(title, relativeTime, number, null, null,
+ UiCallLog uiCallLog = new UiCallLog(title, relativeTime, number, null,
phoneCallLog.getAllCallRecords());
uiCallLogs.add(uiCallLog);
continue;
}
- Contact contact = inMemoryPhoneBook.lookupContactEntry(number);
String title;
+ CharSequence typeLabel = "";
+ Contact contact = null;
+
+ // If InMemoryPhoneBook hasn't finished loading, there is still a chance that this
+ // number can be found there later. So query will not be proceeded now.
+ // TODO: will move to utils later.
+ if (inMemoryPhoneBook.isLoaded()) {
+ contact = inMemoryPhoneBook.lookupContactEntry(number);
+ if (contact == null && !TextUtils.isEmpty(number)) {
+ ContentResolver cr = mContext.getContentResolver();
+ try (Cursor cursor = cr.query(
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(number)),
+ new String[]{
+ PhoneLookup.LOOKUP_KEY,
+ PhoneLookup.TYPE,
+ PhoneLookup.LABEL,
+ },
+ null, null, null)) {
+
+ if (cursor != null && cursor.moveToFirst()) {
+ int lookupKeyColIdx = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
+ int typeColumn = cursor.getColumnIndex(PhoneLookup.TYPE);
+ int labelColumn = cursor.getColumnIndex(PhoneLookup.LABEL);
+
+ List<Contact> lookupResults =
+ InMemoryPhoneBook.get().lookupContactByKey(
+ cursor.getString(lookupKeyColIdx));
+ contact = lookupResults.size() > 0 ? lookupResults.get(0) : null;
+ int type = cursor.getInt(typeColumn);
+ String label = cursor.getString(labelColumn);
+ typeLabel = ContactsContract.CommonDataKinds.Phone.getTypeLabel(
+ mContext.getResources(), type, label);
+ }
+ }
+ }
+ }
+
if (contact != null && contact.getDisplayName() != null) {
title = contact.getDisplayName();
} else if (!TextUtils.isEmpty(number)) {
@@ -153,10 +209,11 @@
UiCallLog uiCallLog = new UiCallLog(
title,
- getSecondaryText(getType(phoneNumber), relativeTime),
+ getSecondaryText(
+ TextUtils.isEmpty(typeLabel) ? getType(phoneNumber) : typeLabel,
+ relativeTime),
number,
- contact != null ? contact.getInitials() : null,
- contact != null ? contact.getAvatarUri() : null,
+ contact,
phoneCallLog.getAllCallRecords());
uiCallLogs.add(uiCallLog);
diff --git a/src/com/android/car/dialer/ui/common/entity/ActionButton.java b/src/com/android/car/dialer/ui/common/entity/ActionButton.java
new file mode 100644
index 0000000..46dafe9
--- /dev/null
+++ b/src/com/android/car/dialer/ui/common/entity/ActionButton.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.car.dialer.ui.common.entity;
+
+/**Objects stand for buttons.*/
+public final class ActionButton {
+}
diff --git a/src/com/android/car/dialer/ui/common/entity/Header.java b/src/com/android/car/dialer/ui/common/entity/Header.java
new file mode 100644
index 0000000..c184dfe
--- /dev/null
+++ b/src/com/android/car/dialer/ui/common/entity/Header.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.car.dialer.ui.common.entity;
+
+/**
+ * Objects stand for headers.
+ */
+public final class Header {
+ private final String mHeader;
+
+ public Header(String header) {
+ mHeader = header;
+ }
+
+ /**
+ * Getter to return the header string.
+ */
+ public String getHeader() {
+ return mHeader;
+ }
+}
diff --git a/src/com/android/car/dialer/ui/common/entity/UiCallLog.java b/src/com/android/car/dialer/ui/common/entity/UiCallLog.java
index 9de9730..35ae239 100644
--- a/src/com/android/car/dialer/ui/common/entity/UiCallLog.java
+++ b/src/com/android/car/dialer/ui/common/entity/UiCallLog.java
@@ -16,11 +16,10 @@
package com.android.car.dialer.ui.common.entity;
-import android.net.Uri;
-
import androidx.annotation.Nullable;
import com.android.car.dialer.livedata.CallHistoryLiveData;
+import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.PhoneCallLog;
import java.util.ArrayList;
@@ -32,29 +31,26 @@
*/
public class UiCallLog {
private final String mTitle;
+ private String mText;
private final String mNumber;
@Nullable
- private final String mInitials;
- private final Uri mAvatarUri;
+ private final Contact mContact;
private final List<PhoneCallLog.Record> mCallRecords;
- private String mText;
- public UiCallLog(String title, String text, String number, @Nullable String initials,
- Uri avatarUri, List<PhoneCallLog.Record> callRecords) {
+ public UiCallLog(String title, String text, String number, @Nullable Contact contact,
+ List<PhoneCallLog.Record> callRecords) {
mTitle = title;
mText = text;
mNumber = number;
- mInitials = initials;
- mAvatarUri = avatarUri;
+ mContact = contact;
mCallRecords = new ArrayList<>(callRecords);
}
/**
- * Returns the initials of a call log item. Returns null if this item is not a contact.
+ * Updates the body text.
*/
- @Nullable
- public String getInitials() {
- return mInitials;
+ public void setText(String text) {
+ mText = text;
}
/**
@@ -72,23 +68,18 @@
}
/**
- * Updates the body text.
- */
- public void setText(String text) {
- mText = text;
- }
-
- /**
* Returns the number of this call log.
*/
public String getNumber() {
return mNumber;
}
- /** Returns the avatar of the contact associated with the number. */
+ /**
+ * Returns the related contact of a call log item. Returns null if this item is not a contact.
+ */
@Nullable
- public Uri getAvatarUri() {
- return mAvatarUri;
+ public Contact getContact() {
+ return mContact;
}
/**
diff --git a/src/com/android/car/dialer/ui/contact/ContactDetailsFragment.java b/src/com/android/car/dialer/ui/contact/ContactDetailsFragment.java
index a301393..7f33f46 100644
--- a/src/com/android/car/dialer/ui/contact/ContactDetailsFragment.java
+++ b/src/com/android/car/dialer/ui/contact/ContactDetailsFragment.java
@@ -94,6 +94,7 @@
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
ContactDetailsAdapter contactDetailsAdapter = new ContactDetailsAdapter(getContext(),
mContact, this);
getRecyclerView().setAdapter(contactDetailsAdapter);
@@ -111,6 +112,10 @@
private void onContactChanged(Contact contact) {
getArguments().clear();
Toolbar toolbar = getActivity().findViewById(R.id.car_ui_toolbar);
+ // Null check to have unit tests to pass.
+ if (toolbar == null) {
+ return;
+ }
toolbar.setTitle(null);
toolbar.setLogo(null);
if (mShowActionBarView) {
@@ -148,7 +153,7 @@
}
@Override
- public void setupToolbar(@NonNull Toolbar toolbar) {
+ protected void setupToolbar(@NonNull Toolbar toolbar) {
toolbar.setState(getToolbarState());
toolbar.setMenuItems(null);
@@ -156,6 +161,8 @@
if (!mShowActionBarView) {
setShowToolbarBackground(false);
}
+
+ setToolbarHeight(toolbar);
}
@Override
diff --git a/src/com/android/car/dialer/ui/contact/ContactDetailsViewHolder.java b/src/com/android/car/dialer/ui/contact/ContactDetailsViewHolder.java
index 0ffe3f9..eec3013 100644
--- a/src/com/android/car/dialer/ui/contact/ContactDetailsViewHolder.java
+++ b/src/com/android/car/dialer/ui/contact/ContactDetailsViewHolder.java
@@ -67,7 +67,7 @@
@Nullable
private final View mCallActionView;
@Nullable
- private final View mFavoriteActionView;
+ private final ImageView mFavoriteActionView;
// Applies to address items
@Nullable
@@ -152,26 +152,42 @@
}
public void bind(Context context, Contact contact, PhoneNumber phoneNumber) {
-
mTitle.setText(phoneNumber.getRawNumber());
- // Present the phone number type.
+ // Present the phone number type and local favorite state.
CharSequence readableLabel = phoneNumber.getReadableLabel(context.getResources());
+ CharSequence favoriteLabel = phoneNumber.isFavorite() ? context.getString(
+ R.string.local_favorite_number_description, readableLabel) : readableLabel;
if (phoneNumber.isPrimary()) {
- mText.setText(context.getString(R.string.primary_number_description, readableLabel));
+ mText.setText(context.getString(R.string.primary_number_description, favoriteLabel));
mText.setTextAppearance(R.style.TextAppearance_DefaultNumberLabel);
} else {
- mText.setText(readableLabel);
+ mText.setText(favoriteLabel);
mText.setTextAppearance(R.style.TextAppearance_ContactDetailsListSubtitle);
}
mCallActionView.setOnClickListener(
v -> UiCallManager.get().placeCall(phoneNumber.getRawNumber()));
- mFavoriteActionView.setActivated(phoneNumber.isFavorite());
- mFavoriteActionView.setOnClickListener(v -> {
- mPhoneNumberPresenter.onClick(contact, phoneNumber);
- mFavoriteActionView.setActivated(!mFavoriteActionView.isActivated());
- });
+
+ if (phoneNumber.isFavorite() || !contact.isStarred()) {
+ // If the phone number is marked as favorite locally, enable the action button to
+ // allow users to remove from local favorites.
+ // If the phone number is not a local favorite or a favorite from phone, allow users
+ // to mark it as favorite locally.
+ ViewUtils.setActivated(mFavoriteActionView, phoneNumber.isFavorite());
+ ViewUtils.setEnabled(mFavoriteActionView, true);
+ ViewUtils.setOnClickListener(mFavoriteActionView, v -> {
+ mPhoneNumberPresenter.onClick(contact, phoneNumber);
+ mFavoriteActionView.setActivated(!mFavoriteActionView.isActivated());
+ });
+ } else {
+ // The contact is favorite contact downloaded from phone. Disable the favorite action
+ // button so the phone numbers can not be marked as favorite locally as it is not
+ // necessary.
+ ViewUtils.setActivated(mFavoriteActionView, false);
+ ViewUtils.setEnabled(mFavoriteActionView, false);
+ ViewUtils.setOnClickListener(mFavoriteActionView, null);
+ }
}
public void bind(Context context, @NonNull PostalAddress postalAddress) {
diff --git a/src/com/android/car/dialer/ui/contact/ContactListFragment.java b/src/com/android/car/dialer/ui/contact/ContactListFragment.java
index d638b6c..954806a 100644
--- a/src/com/android/car/dialer/ui/contact/ContactListFragment.java
+++ b/src/com/android/car/dialer/ui/contact/ContactListFragment.java
@@ -42,6 +42,7 @@
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
// Don't recreate the adapter if we already have one, so that the list items
// will display immediately upon the view being recreated. If they're not displayed
// immediately, we won't remember our scroll position.
diff --git a/src/com/android/car/dialer/ui/contact/ContactListViewHolder.java b/src/com/android/car/dialer/ui/contact/ContactListViewHolder.java
index 8a35f87..9619151 100644
--- a/src/com/android/car/dialer/ui/contact/ContactListViewHolder.java
+++ b/src/com/android/car/dialer/ui/contact/ContactListViewHolder.java
@@ -126,8 +126,7 @@
return;
}
- boolean hasContactDetail = contact != null
- && (!contact.getNumbers().isEmpty() || hasPostalAddress(contact));
+ boolean hasContactDetail = DialerUtils.hasContactDetail(itemView.getResources(), contact);
ViewUtils.setEnabled(mShowContactDetailView, hasContactDetail);
ViewUtils.setVisible(mShowContactDetailView, hasContactDetail || forceShowButton);
@@ -140,12 +139,6 @@
}
}
- private boolean hasPostalAddress(@NonNull Contact contact) {
- boolean showPostalAddress = itemView.getResources().getBoolean(
- R.bool.config_show_postal_address);
- return showPostalAddress && (!contact.getPostalAddresses().isEmpty());
- }
-
/**
* Recycles views.
*/
diff --git a/src/com/android/car/dialer/ui/dialpad/AbstractDialpadFragment.java b/src/com/android/car/dialer/ui/dialpad/AbstractDialpadFragment.java
index 2333414..93cc34b 100644
--- a/src/com/android/car/dialer/ui/dialpad/AbstractDialpadFragment.java
+++ b/src/com/android/car/dialer/ui/dialpad/AbstractDialpadFragment.java
@@ -89,6 +89,7 @@
@CallSuper
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
mTitleView = view.findViewById(R.id.title);
if (mTitleView != null && getResources().getBoolean(R.bool.config_enable_dial_motion)) {
mInputMotionAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(getContext(),
diff --git a/src/com/android/car/dialer/ui/dialpad/DialpadFragment.java b/src/com/android/car/dialer/ui/dialpad/DialpadFragment.java
index 4438980..3368a81 100644
--- a/src/com/android/car/dialer/ui/dialpad/DialpadFragment.java
+++ b/src/com/android/car/dialer/ui/dialpad/DialpadFragment.java
@@ -32,6 +32,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProviders;
import com.android.car.apps.common.util.ViewUtils;
import com.android.car.dialer.R;
@@ -42,6 +43,7 @@
import com.android.car.telephony.common.InMemoryPhoneBook;
import com.android.car.telephony.common.PhoneNumber;
import com.android.car.telephony.common.TelecomUtils;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
import com.android.car.ui.toolbar.Toolbar;
import com.google.common.annotations.VisibleForTesting;
@@ -77,11 +79,15 @@
.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S)
.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P)
.build();
+ private final TypeDownResultsAdapter mAdapter = new TypeDownResultsAdapter();
+ private TypeDownResultsViewModel mTypeDownResultsViewModel;
private TextView mTitleView;
@Nullable
private TextView mDisplayName;
@Nullable
+ private CarUiRecyclerView mRecyclerView;
+ @Nullable
private TextView mLabel;
@Nullable
private ImageView mAvatar;
@@ -120,6 +126,11 @@
mMode = getArguments().getInt(DIALPAD_MODE_KEY);
L.d(TAG, "onCreate mode: %s", mMode);
mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, TONE_RELATIVE_VOLUME);
+
+ mTypeDownResultsViewModel = ViewModelProviders.of(this).get(
+ TypeDownResultsViewModel.class);
+ mTypeDownResultsViewModel.getContactSearchResults().observe(this,
+ contactResults -> mAdapter.setData(contactResults));
}
@Override
@@ -132,6 +143,8 @@
mMode == MODE_EMERGENCY ? R.style.TextAppearance_EmergencyDialNumber
: R.style.TextAppearance_DialNumber);
mDisplayName = rootView.findViewById(R.id.display_name);
+ mRecyclerView = rootView.findViewById(R.id.list_view);
+ mRecyclerView.setAdapter(mAdapter);
mLabel = rootView.findViewById(R.id.label);
mAvatar = rootView.findViewById(R.id.dialpad_contact_avatar);
if (mAvatar != null) {
@@ -174,7 +187,7 @@
}
@Override
- public void setupToolbar(Toolbar toolbar) {
+ protected void setupToolbar(Toolbar toolbar) {
// Only setup the actionbar if we're in dial mode.
// In all the other modes, there will be another fragment in the activity
// at the same time, and we don't want to mess up it's action bar.
@@ -238,7 +251,13 @@
ViewUtils.setVisible(mDeleteButton, true);
}
- presentContactInfo(number.toString());
+ if (getResources().getBoolean(R.bool.config_show_type_down_list_on_dialpad)) {
+ resetContactInfo();
+ ViewUtils.setVisible(mRecyclerView, true);
+ mTypeDownResultsViewModel.setSearchQuery(number.toString());
+ } else {
+ presentContactInfo(number.toString());
+ }
}
@Override
diff --git a/src/com/android/car/dialer/ui/dialpad/InCallDialpadFragment.java b/src/com/android/car/dialer/ui/dialpad/InCallDialpadFragment.java
index 22079c9..8d8e5aa 100644
--- a/src/com/android/car/dialer/ui/dialpad/InCallDialpadFragment.java
+++ b/src/com/android/car/dialer/ui/dialpad/InCallDialpadFragment.java
@@ -114,7 +114,7 @@
}
@Override
- public void setupToolbar(Toolbar toolbar) {
+ protected void setupToolbar(Toolbar toolbar) {
// No-op
}
diff --git a/src/com/android/car/dialer/ui/dialpad/TypeDownResultsAdapter.java b/src/com/android/car/dialer/ui/dialpad/TypeDownResultsAdapter.java
new file mode 100644
index 0000000..30ac4f3
--- /dev/null
+++ b/src/com/android/car/dialer/ui/dialpad/TypeDownResultsAdapter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.car.dialer.ui.dialpad;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.car.dialer.R;
+import com.android.car.dialer.ui.search.ContactResultViewHolder;
+import com.android.car.dialer.ui.search.ContactResultsAdapter;
+
+/**
+ * An adapter used for type down functionality.
+ */
+public class TypeDownResultsAdapter extends ContactResultsAdapter {
+
+ public TypeDownResultsAdapter() {
+ super(null);
+ }
+
+ @Override
+ public ContactResultViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.type_down_list_item, parent, false);
+ return new ContactResultViewHolder(view, null);
+ }
+
+ @Override
+ public void onBindViewHolder(ContactResultViewHolder holder, int position) {
+ holder.bindTypeDownResult(getContactResults().get(position));
+ }
+}
diff --git a/src/com/android/car/dialer/ui/dialpad/TypeDownResultsViewModel.java b/src/com/android/car/dialer/ui/dialpad/TypeDownResultsViewModel.java
new file mode 100644
index 0000000..8696fab
--- /dev/null
+++ b/src/com/android/car/dialer/ui/dialpad/TypeDownResultsViewModel.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.car.dialer.ui.dialpad;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+
+import com.android.car.dialer.ui.common.ContactResultsLiveData;
+import com.android.car.dialer.ui.search.ContactResultsViewModel;
+
+import java.util.List;
+
+/**
+ * {link AndroidViewModel} used for type down functionality.
+ */
+public class TypeDownResultsViewModel extends ContactResultsViewModel {
+
+ private final ContactResultsLiveData mContactSearchResultsLiveData;
+
+ public TypeDownResultsViewModel(@NonNull Application application) {
+ super(application);
+ mContactSearchResultsLiveData = new ContactResultsLiveData(application,
+ getSearchQueryLiveData(), getSharedPreferencesLiveData(),
+ /* showOnlyOneEntry */ false);
+ }
+
+ @Override
+ public LiveData<List<ContactResultsLiveData.ContactResultListItem>> getContactSearchResults() {
+ return mContactSearchResultsLiveData;
+ }
+}
diff --git a/src/com/android/car/dialer/ui/favorite/AddFavoriteFragment.java b/src/com/android/car/dialer/ui/favorite/AddFavoriteFragment.java
index 574cee0..a45d5bc 100644
--- a/src/com/android/car/dialer/ui/favorite/AddFavoriteFragment.java
+++ b/src/com/android/car/dialer/ui/favorite/AddFavoriteFragment.java
@@ -17,18 +17,24 @@
package com.android.car.dialer.ui.favorite;
import android.app.AlertDialog;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.widget.Toast;
import androidx.lifecycle.ViewModelProviders;
import com.android.car.dialer.R;
-import com.android.car.dialer.ui.common.FavoritePhoneNumberListAdapter;
import com.android.car.dialer.ui.search.ContactResultsFragment;
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.PhoneNumber;
import com.android.car.ui.AlertDialogBuilder;
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiListItem;
+import com.android.car.ui.recyclerview.CarUiListItemAdapter;
+import java.util.ArrayList;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -44,7 +50,12 @@
}
private AlertDialog mCurrentDialog;
- private FavoritePhoneNumberListAdapter mDialogAdapter;
+ private CarUiListItemAdapter mDialogAdapter;
+ private List<CarUiListItem> mFavoritePhoneNumberList;
+ private Set<PhoneNumber> mSelectedNumbers;
+ private Contact mSelectedContact;
+ private Drawable mFavoriteIcon;
+ private Drawable mFavoriteIconEmpty;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -52,36 +63,77 @@
FavoriteViewModel favoriteViewModel = ViewModelProviders.of(getActivity()).get(
FavoriteViewModel.class);
- Set<PhoneNumber> selectedNumbers = new HashSet<>();
+ mSelectedNumbers = new HashSet<>();
- mDialogAdapter = new FavoritePhoneNumberListAdapter(getContext(),
- (phoneNumber, itemView) -> {
- boolean isActivated = itemView.isActivated();
- itemView.setActivated(!isActivated);
- if (isActivated) {
- selectedNumbers.remove(phoneNumber);
- } else {
- selectedNumbers.add(phoneNumber);
- }
- }
- );
+ mFavoriteIcon = getResources().getDrawable(R.drawable.ic_favorite_activatable, null);
+ mFavoriteIcon.setTintList(
+ getResources().getColorStateList(R.color.primary_icon_selector, null));
+
+ mFavoritePhoneNumberList = new ArrayList<>();
+ mDialogAdapter = new CarUiListItemAdapter(mFavoritePhoneNumberList);
mCurrentDialog = new AlertDialogBuilder(getContext())
.setTitle(R.string.select_number_dialog_title)
- .setAdapter(mDialogAdapter, null)
+ .setAdapter(mDialogAdapter)
.setNegativeButton(R.string.cancel_add_favorites_dialog, null)
.setPositiveButton(R.string.confirm_add_favorites_dialog,
(d, which) -> {
- for (PhoneNumber number : selectedNumbers) {
- favoriteViewModel.addToFavorite(mDialogAdapter.getContact(),
+ for (PhoneNumber number : mSelectedNumbers) {
+ favoriteViewModel.addToFavorite(mSelectedContact,
number);
}
- selectedNumbers.clear();
+ mSelectedNumbers.clear();
getFragmentManager().popBackStackImmediate();
})
.create();
}
+ private void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
+ mFavoritePhoneNumberList.clear();
+ for (PhoneNumber number : phoneNumbers) {
+ CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.ICON);
+ item.setTitle(number.getNumber());
+ item.setSupplementalIcon(mFavoriteIcon.getConstantState().newDrawable());
+ setFavoriteItemState(item, number);
+
+ item.setOnItemClickedListener((listItem) -> {
+ if (mSelectedNumbers.contains(number)) {
+ mSelectedNumbers.remove(number);
+ listItem.setActivated(false);
+ } else {
+ mSelectedNumbers.add(number);
+ listItem.setActivated(true);
+ }
+ mDialogAdapter.notifyItemChanged(mFavoritePhoneNumberList.indexOf(listItem));
+ });
+ mFavoritePhoneNumberList.add(item);
+ }
+ mDialogAdapter.notifyDataSetChanged();
+ }
+
+ private void setFavoriteItemState(CarUiContentListItem item, PhoneNumber number) {
+ CharSequence readableLabel = number.getReadableLabel(getResources());
+
+ if (number.isFavorite()) {
+ // This phone number is marked as favorite locally. Disable the favorite action button.
+ item.setEnabled(false);
+ item.setActivated(true);
+ item.setBody(getResources().getString(R.string.favorite_number_description,
+ readableLabel));
+ } else if (mSelectedContact.isStarred()) {
+ // This contact is downloaded from phone, all phone numbers under this contact will show
+ // under the favorite tab. Disable the favorite action button.
+ item.setActivated(false);
+ item.setEnabled(false);
+ item.setBody(getResources().getString(R.string.favorite_number_description,
+ readableLabel));
+ } else {
+ item.setEnabled(true);
+ item.setActivated(false);
+ item.setBody(readableLabel);
+ }
+ }
+
@Override
public void onShowContactDetail(Contact contact) {
if (contact == null) {
@@ -89,7 +141,13 @@
return;
}
- mDialogAdapter.setPhoneNumbers(contact, contact.getNumbers());
- mCurrentDialog.show();
+ mSelectedContact = contact;
+ if (contact.getNumbers().isEmpty()) {
+ Toast.makeText(getContext(), R.string.no_phone_numbers, Toast.LENGTH_SHORT).show();
+ mCurrentDialog.dismiss();
+ } else {
+ setPhoneNumbers(contact.getNumbers());
+ mCurrentDialog.show();
+ }
}
}
diff --git a/src/com/android/car/dialer/ui/favorite/BluetoothFavoriteContactsLiveData.java b/src/com/android/car/dialer/ui/favorite/BluetoothFavoriteContactsLiveData.java
new file mode 100644
index 0000000..a030b3b
--- /dev/null
+++ b/src/com/android/car/dialer/ui/favorite/BluetoothFavoriteContactsLiveData.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.car.dialer.ui.favorite;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.telephony.common.AsyncQueryLiveData;
+import com.android.car.telephony.common.Contact;
+import com.android.car.telephony.common.QueryParam;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Presents the favorite contacts downloaded from phone. It reads the contacts provider. */
+class BluetoothFavoriteContactsLiveData extends AsyncQueryLiveData<List<Contact>> {
+ private final Context mContext;
+
+ BluetoothFavoriteContactsLiveData(Context context) {
+ super(context, QueryParam.of(new FavoriteQueryParam()));
+ mContext = context;
+ }
+
+ @Override
+ protected List<Contact> convertToEntity(@NonNull Cursor cursor) {
+ List<Contact> resultList = new ArrayList<>();
+
+ while (cursor.moveToNext()) {
+ Contact favoriteEntry = Contact.fromCursor(mContext, cursor);
+ // If there is already a contact with the same phone number, don't add duplicate
+ // entries.
+ boolean alreadyExists = false;
+ for (Contact contact : resultList) {
+ if (favoriteEntry.equals(contact) && favoriteEntry.getNumbers().containsAll(
+ contact.getNumbers())) {
+ alreadyExists = true;
+ break;
+ }
+ }
+ if (!alreadyExists) {
+ resultList.add(favoriteEntry);
+ }
+ }
+ return resultList;
+ }
+
+ private static class FavoriteQueryParam extends QueryParam {
+ FavoriteQueryParam() {
+ super(ContactsContract.Data.CONTENT_URI,
+ null,
+ ContactsContract.Data.MIMETYPE + " = ? AND "
+ + ContactsContract.CommonDataKinds.Phone.STARRED + " = ? ",
+ new String[]{
+ ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
+ String.valueOf(1)},
+ ContactsContract.Contacts.DISPLAY_NAME + " ASC ");
+ }
+ }
+}
diff --git a/src/com/android/car/dialer/ui/favorite/FavoriteAdapter.java b/src/com/android/car/dialer/ui/favorite/FavoriteAdapter.java
index da7ebe5..a140c0d 100644
--- a/src/com/android/car/dialer/ui/favorite/FavoriteAdapter.java
+++ b/src/com/android/car/dialer/ui/favorite/FavoriteAdapter.java
@@ -25,6 +25,7 @@
import com.android.car.dialer.R;
import com.android.car.dialer.log.L;
import com.android.car.dialer.ui.common.OnItemClickedListener;
+import com.android.car.dialer.ui.common.entity.Header;
import com.android.car.telephony.common.Contact;
import java.util.Collections;
@@ -35,21 +36,28 @@
*/
public class FavoriteAdapter extends RecyclerView.Adapter<FavoriteContactViewHolder> {
private static final String TAG = "CD.FavoriteAdapter";
- private static final int TYPE_CONTACT = 0;
- private static final int TYPE_ADD_FAVORITE = 1;
+ static final int TYPE_CONTACT = 0;
+ static final int TYPE_HEADER = 1;
+ static final int TYPE_ADD_FAVORITE = 2;
- /** Listener interface for when the add favorite button is clicked */
+ /**
+ * Listener interface for when the add favorite button is clicked
+ */
public interface OnAddFavoriteClickedListener {
- /** Called when the add favorite button is clicked */
+ /**
+ * Called when the add favorite button is clicked
+ */
void onAddFavoriteClicked();
}
- private List<Contact> mFavoriteContacts = Collections.emptyList();
+ private List<Object> mFavoriteContacts = Collections.emptyList();
private OnItemClickedListener<Contact> mListener;
private OnAddFavoriteClickedListener mAddFavoriteListener;
- /** Sets the favorite contact list. */
- public void setFavoriteContacts(List<Contact> favoriteContacts) {
+ /**
+ * Sets the favorite contact list.
+ */
+ public void setFavoriteContacts(List<Object> favoriteContacts) {
L.d(TAG, "setFavoriteContacts %s", favoriteContacts);
mFavoriteContacts = (favoriteContacts != null) ? favoriteContacts : Collections.emptyList();
notifyDataSetChanged();
@@ -57,42 +65,57 @@
@Override
public int getItemCount() {
- return mFavoriteContacts.size() + 1; // +1 for the "Add a favorite" button
+ return mFavoriteContacts.size();
}
@Override
public int getItemViewType(int position) {
- return position < mFavoriteContacts.size()
- ? TYPE_CONTACT
- : TYPE_ADD_FAVORITE;
+ Object item = mFavoriteContacts.get(position);
+ if (item instanceof Contact) {
+ return TYPE_CONTACT;
+ } else if (item instanceof Header) {
+ return TYPE_HEADER;
+ } else {
+ return TYPE_ADD_FAVORITE;
+ }
}
@Override
public FavoriteContactViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view;
+ int layoutId;
if (viewType == TYPE_CONTACT) {
- view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.favorite_contact_list_item, parent, false);
+ layoutId = R.layout.favorite_contact_list_item;
+ } else if (viewType == TYPE_HEADER) {
+ layoutId = R.layout.header_item;
} else {
- view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.add_favorite_list_item, parent, false);
+ layoutId = R.layout.add_favorite_list_item;
}
+ View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return new FavoriteContactViewHolder(view);
}
@Override
public void onBindViewHolder(FavoriteContactViewHolder viewHolder, int position) {
- if (getItemViewType(position) == TYPE_CONTACT) {
- Contact contact = mFavoriteContacts.get(position);
- viewHolder.onBind(contact);
- viewHolder.itemView.setOnClickListener((v) -> onItemViewClicked(contact));
- } else {
- viewHolder.itemView.setOnClickListener((v) -> {
- if (mAddFavoriteListener != null) {
- mAddFavoriteListener.onAddFavoriteClicked();
- }
- });
+ int itemViewType = getItemViewType(position);
+ switch (itemViewType) {
+ case TYPE_CONTACT:
+ Contact contact = (Contact) mFavoriteContacts.get(position);
+ viewHolder.onBind(contact);
+ viewHolder.itemView.setOnClickListener(v -> onItemViewClicked(contact));
+ break;
+ case TYPE_HEADER:
+ Header header = (Header) mFavoriteContacts.get(position);
+ viewHolder.onBind(header);
+ viewHolder.itemView.setOnClickListener(null);
+ break;
+ case TYPE_ADD_FAVORITE:
+ viewHolder.itemView.setOnClickListener(v -> {
+ if (mAddFavoriteListener != null) {
+ mAddFavoriteListener.onAddFavoriteClicked();
+ }
+ });
+ break;
}
}
@@ -110,8 +133,8 @@
}
/**
- * Sets a {@link OnAddFavoriteClickedListener listener} which will be called when the
- * "Add favorite" button is clicked.
+ * Sets a {@link OnAddFavoriteClickedListener listener} which will be called when the "Add
+ * favorite" button is clicked.
*/
public void setOnAddFavoriteClickedListener(OnAddFavoriteClickedListener listener) {
mAddFavoriteListener = listener;
diff --git a/src/com/android/car/dialer/ui/favorite/FavoriteContactViewHolder.java b/src/com/android/car/dialer/ui/favorite/FavoriteContactViewHolder.java
index 137a150..f9c17e2 100644
--- a/src/com/android/car/dialer/ui/favorite/FavoriteContactViewHolder.java
+++ b/src/com/android/car/dialer/ui/favorite/FavoriteContactViewHolder.java
@@ -21,10 +21,13 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.car.apps.common.util.ViewUtils;
import com.android.car.dialer.R;
import com.android.car.dialer.log.L;
+import com.android.car.dialer.ui.common.entity.Header;
import com.android.car.dialer.ui.view.ContactAvatarOutputlineProvider;
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.PhoneNumber;
@@ -32,8 +35,6 @@
import java.util.List;
-import javax.annotation.Nonnull;
-
/**
* A {@link RecyclerView.ViewHolder ViewHolder} that will hold layouts for favorite contacts list
* items.
@@ -58,7 +59,7 @@
/**
* Binds view with favorite contact.
*/
- public void onBind(@Nonnull Contact contact) {
+ public void onBind(@NonNull Contact contact) {
Context context = itemView.getContext();
String displayName = contact.getDisplayName();
mTitle.setText(displayName);
@@ -85,4 +86,8 @@
TelecomUtils.setContactBitmapAsync(context, mIcon, contact);
}
+
+ public void onBind(@NonNull Header header) {
+ ViewUtils.setText(mTitle, header.getHeader());
+ }
}
diff --git a/src/com/android/car/dialer/ui/favorite/FavoriteFragment.java b/src/com/android/car/dialer/ui/favorite/FavoriteFragment.java
index 7e76f32..c870dcf 100644
--- a/src/com/android/car/dialer/ui/favorite/FavoriteFragment.java
+++ b/src/com/android/car/dialer/ui/favorite/FavoriteFragment.java
@@ -16,6 +16,7 @@
package com.android.car.dialer.ui.favorite;
+import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Bundle;
@@ -38,6 +39,7 @@
* Contains a list of favorite contacts.
*/
public class FavoriteFragment extends DialerListBaseFragment {
+ private FavoriteAdapter mFavoriteAdapter;
/**
* Constructs a new {@link FavoriteFragment}
@@ -48,15 +50,16 @@
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
getRecyclerView().addItemDecoration(new ItemSpacingDecoration());
getRecyclerView().setItemAnimator(null);
- FavoriteAdapter adapter = new FavoriteAdapter();
- adapter.setOnAddFavoriteClickedListener(this::onAddFavoriteClicked);
+ mFavoriteAdapter = new FavoriteAdapter();
+ mFavoriteAdapter.setOnAddFavoriteClickedListener(this::onAddFavoriteClicked);
FavoriteViewModel favoriteViewModel = ViewModelProviders.of(getActivity()).get(
FavoriteViewModel.class);
- adapter.setOnListItemClickedListener(this::onItemClicked);
+ mFavoriteAdapter.setOnListItemClickedListener(this::onItemClicked);
favoriteViewModel.getFavoriteContacts().observe(this, contacts -> {
if (contacts.isLoading()) {
showLoading();
@@ -65,20 +68,18 @@
R.string.no_favorites_added, R.string.add_favorite_button,
v -> onAddFavoriteClicked(), true);
} else {
- adapter.setFavoriteContacts(contacts.getData());
+ mFavoriteAdapter.setFavoriteContacts(contacts.getData());
showContent();
}
});
- getRecyclerView().setAdapter(adapter);
+ getRecyclerView().setAdapter(mFavoriteAdapter);
}
@NonNull
@Override
protected RecyclerView.LayoutManager createLayoutManager() {
- int numOfColumn = getContext().getResources().getInteger(
- R.integer.favorite_fragment_grid_column);
- return new GridLayoutManager(getContext(), numOfColumn);
+ return new FavoriteLayoutManager(getContext());
}
private void onItemClicked(Contact contact) {
@@ -110,4 +111,21 @@
outRect.set(leftPadding, verticalPadding, 0, verticalPadding);
}
}
+
+ private class FavoriteLayoutManager extends GridLayoutManager {
+ private FavoriteLayoutManager(Context context) {
+ super(context,
+ context.getResources().getInteger(R.integer.favorite_fragment_grid_column));
+ SpanSizeLookup spanSizeLookup = new SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ if (mFavoriteAdapter.getItemViewType(position) == FavoriteAdapter.TYPE_HEADER) {
+ return getSpanCount();
+ }
+ return 1;
+ }
+ };
+ setSpanSizeLookup(spanSizeLookup);
+ }
+ }
}
diff --git a/src/com/android/car/dialer/ui/favorite/FavoriteViewModel.java b/src/com/android/car/dialer/ui/favorite/FavoriteViewModel.java
index 3f55d42..6e938ca 100644
--- a/src/com/android/car/dialer/ui/favorite/FavoriteViewModel.java
+++ b/src/com/android/car/dialer/ui/favorite/FavoriteViewModel.java
@@ -20,13 +20,18 @@
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
import com.android.car.arch.common.FutureData;
import com.android.car.arch.common.LiveDataFunctions;
+import com.android.car.dialer.R;
import com.android.car.dialer.storage.FavoriteNumberRepository;
+import com.android.car.dialer.ui.common.entity.ActionButton;
+import com.android.car.dialer.ui.common.entity.Header;
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.PhoneNumber;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -34,18 +39,52 @@
*/
public class FavoriteViewModel extends AndroidViewModel {
private final FavoriteNumberRepository mFavoriteNumberRepository;
- private final LiveData<FutureData<List<Contact>>> mFavoriteContacts;
+ private final LiveData<FutureData<List<Object>>> mFavoriteContacts;
+ private final LiveData<List<Contact>> mBluetoothFavoriteContacts;
public FavoriteViewModel(Application application) {
super(application);
mFavoriteNumberRepository = FavoriteNumberRepository.getRepository(application);
+ mBluetoothFavoriteContacts = new BluetoothFavoriteContactsLiveData(application);
+
+ MediatorLiveData<List<Object>> favoriteContacts = new MediatorLiveData<>();
+ favoriteContacts.addSource(mFavoriteNumberRepository.getFavoriteContacts(), contacts -> {
+ List<Object> contactList = new ArrayList<>();
+ if (mBluetoothFavoriteContacts.getValue() != null
+ && !mBluetoothFavoriteContacts.getValue().isEmpty()) {
+ contactList.add(new Header(application.getString(R.string.phone_favorites)));
+ contactList.addAll(mBluetoothFavoriteContacts.getValue());
+ }
+ contactList.add(new Header(application.getString(R.string.local_favorites)));
+ if (contacts != null) {
+ contactList.addAll(contacts);
+ }
+ contactList.add(new ActionButton());
+ favoriteContacts.setValue(contactList);
+ });
+ favoriteContacts.addSource(mBluetoothFavoriteContacts, contacts -> {
+ List<Object> contactList = new ArrayList<>();
+ if (contacts != null && !contacts.isEmpty()) {
+ contactList.add(new Header(application.getString(R.string.phone_favorites)));
+ contactList.addAll(contacts);
+ }
+ contactList.add(new Header(application.getString(R.string.local_favorites)));
+ if (mFavoriteNumberRepository.getFavoriteContacts().getValue() != null) {
+ contactList.addAll(mFavoriteNumberRepository.getFavoriteContacts().getValue());
+ }
+ contactList.add(new ActionButton());
+ favoriteContacts.setValue(contactList);
+ });
mFavoriteContacts = LiveDataFunctions.loadingSwitchMap(
- mFavoriteNumberRepository.getFavoriteContacts(),
+ favoriteContacts,
input -> LiveDataFunctions.dataOf(input == null || input.isEmpty() ? null : input));
+
}
- /** Returns favorite contact list live data. */
- public LiveData<FutureData<List<Contact>>> getFavoriteContacts() {
+ /**
+ * Returns favorite contact list live data.
+ */
+ public LiveData<FutureData<List<Object>>> getFavoriteContacts() {
return mFavoriteContacts;
}
diff --git a/src/com/android/car/dialer/ui/search/ContactResultViewHolder.java b/src/com/android/car/dialer/ui/search/ContactResultViewHolder.java
index ad1d139..19127e9 100644
--- a/src/com/android/car/dialer/ui/search/ContactResultViewHolder.java
+++ b/src/com/android/car/dialer/ui/search/ContactResultViewHolder.java
@@ -21,32 +21,46 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.car.apps.common.util.ViewUtils;
import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.UiCallManager;
+import com.android.car.dialer.ui.common.ContactResultsLiveData;
+import com.android.car.dialer.ui.common.DialerUtils;
import com.android.car.dialer.ui.view.ContactAvatarOutputlineProvider;
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.TelecomUtils;
+import com.bumptech.glide.Glide;
+
/**
- * A {@link androidx.recyclerview.widget.RecyclerView.ViewHolder} that will parse relevant
- * views out of a {@code contact_result} layout.
+ * A {@link androidx.recyclerview.widget.RecyclerView.ViewHolder} that will parse relevant views out
+ * of a {@code contact_result} layout.
*/
public class ContactResultViewHolder extends RecyclerView.ViewHolder {
+ private static final String TAG = "CD.ContactResultVH";
+
private final Context mContext;
private final View mContactCard;
private final TextView mContactName;
+ private final TextView mContactNumber;
private final ImageView mContactPicture;
private final ContactResultsAdapter.OnShowContactDetailListener mOnShowContactDetailListener;
public ContactResultViewHolder(View view,
- ContactResultsAdapter.OnShowContactDetailListener onShowContactDetailListener) {
+ @Nullable ContactResultsAdapter.OnShowContactDetailListener
+ onShowContactDetailListener) {
super(view);
mContext = view.getContext();
mContactCard = view.findViewById(R.id.contact_result);
mContactName = view.findViewById(R.id.contact_name);
+ mContactNumber = view.findViewById(R.id.phone_number);
mContactPicture = view.findViewById(R.id.contact_picture);
- mContactPicture.setOutlineProvider(ContactAvatarOutputlineProvider.get());
+ if (mContactPicture != null) {
+ mContactPicture.setOutlineProvider(ContactAvatarOutputlineProvider.get());
+ }
mOnShowContactDetailListener = onShowContactDetailListener;
}
@@ -54,11 +68,40 @@
* Populates the view that is represented by this ViewHolder with the information in the
* provided {@link Contact}.
*/
- public void bind(Contact contact) {
- mContactCard.setOnClickListener(
- v -> mOnShowContactDetailListener.onShowContactDetail(contact));
+ public void bindSearchResult(ContactResultsLiveData.ContactResultListItem contactResult) {
+ Contact contact = contactResult.getContact();
- mContactName.setText(contact.getDisplayName());
+ ViewUtils.setText(mContactName, contact.getDisplayName());
TelecomUtils.setContactBitmapAsync(mContext, mContactPicture, contact);
+
+ if (DialerUtils.hasContactDetail(itemView.getResources(), contact)) {
+ mContactCard.setOnClickListener(
+ v -> mOnShowContactDetailListener.onShowContactDetail(contact));
+ } else {
+ itemView.setEnabled(false);
+ }
+ }
+
+ /**
+ * Populates the view that is represented by this ViewHolder with the information in the
+ * provided {@link Contact}.
+ */
+ public void bindTypeDownResult(ContactResultsLiveData.ContactResultListItem contactResult) {
+ Contact contact = contactResult.getContact();
+ String number = contactResult.getNumber();
+
+ ViewUtils.setText(mContactNumber, number);
+ ViewUtils.setText(mContactName, contact.getDisplayName());
+ mContactCard.setOnClickListener(
+ v -> UiCallManager.get().placeCall(mContactNumber.getText().toString()));
+ TelecomUtils.setContactBitmapAsync(mContext, mContactPicture, contact);
+ }
+
+ void recycle() {
+ itemView.setEnabled(true);
+ mContactCard.setOnClickListener(null);
+ if (mContactPicture != null) {
+ Glide.with(mContext).clear(mContactPicture);
+ }
}
}
diff --git a/src/com/android/car/dialer/ui/search/ContactResultsAdapter.java b/src/com/android/car/dialer/ui/search/ContactResultsAdapter.java
index 7b1ac1f..cb35f2b 100644
--- a/src/com/android/car/dialer/ui/search/ContactResultsAdapter.java
+++ b/src/com/android/car/dialer/ui/search/ContactResultsAdapter.java
@@ -24,6 +24,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.car.dialer.R;
+import com.android.car.dialer.ui.common.ContactResultsLiveData;
import com.android.car.telephony.common.Contact;
import java.util.ArrayList;
@@ -39,7 +40,8 @@
void onShowContactDetail(Contact contact);
}
- private final List<Contact> mContacts = new ArrayList<>();
+ private final List<ContactResultsLiveData.ContactResultListItem> mContactResults =
+ new ArrayList<>();
private final OnShowContactDetailListener mOnShowContactDetailListener;
public ContactResultsAdapter(OnShowContactDetailListener onShowContactDetailListener) {
@@ -50,7 +52,7 @@
* Clears all contact results from this adapter.
*/
public void clear() {
- mContacts.clear();
+ mContactResults.clear();
notifyDataSetChanged();
}
@@ -58,9 +60,9 @@
* Sets the list of contacts that should be displayed. The given {@link Cursor} can be safely
* closed after this call.
*/
- public void setData(List<Contact> data) {
- mContacts.clear();
- mContacts.addAll(data);
+ public void setData(List<ContactResultsLiveData.ContactResultListItem> data) {
+ mContactResults.clear();
+ mContactResults.addAll(data);
notifyDataSetChanged();
}
@@ -73,7 +75,12 @@
@Override
public void onBindViewHolder(ContactResultViewHolder holder, int position) {
- holder.bind(mContacts.get(position));
+ holder.bindSearchResult(mContactResults.get(position));
+ }
+
+ @Override
+ public void onViewRecycled(ContactResultViewHolder holder) {
+ holder.recycle();
}
@Override
@@ -84,6 +91,13 @@
@Override
public int getItemCount() {
- return mContacts.size();
+ return mContactResults.size();
+ }
+
+ /**
+ * Returns contact results.
+ */
+ public List<ContactResultsLiveData.ContactResultListItem> getContactResults() {
+ return mContactResults;
}
}
diff --git a/src/com/android/car/dialer/ui/search/ContactResultsFragment.java b/src/com/android/car/dialer/ui/search/ContactResultsFragment.java
index b35378e..3603e94 100644
--- a/src/com/android/car/dialer/ui/search/ContactResultsFragment.java
+++ b/src/com/android/car/dialer/ui/search/ContactResultsFragment.java
@@ -89,6 +89,7 @@
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
getRecyclerView().setAdapter(mAdapter);
mOnScrollChangeListener = new RecyclerView.OnScrollListener() {
@@ -114,7 +115,7 @@
}
@Override
- public void setupToolbar(@NonNull Toolbar toolbar) {
+ protected void setupToolbar(@NonNull Toolbar toolbar) {
super.setupToolbar(toolbar);
mToolbar = toolbar;
mToolbar.registerOnSearchListener(this);
diff --git a/src/com/android/car/dialer/ui/search/ContactResultsViewModel.java b/src/com/android/car/dialer/ui/search/ContactResultsViewModel.java
index 43969af..7678da4 100644
--- a/src/com/android/car/dialer/ui/search/ContactResultsViewModel.java
+++ b/src/com/android/car/dialer/ui/search/ContactResultsViewModel.java
@@ -17,40 +17,23 @@
package com.android.car.dialer.ui.search;
import android.app.Application;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
import android.text.TextUtils;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.car.dialer.R;
import com.android.car.dialer.livedata.SharedPreferencesLiveData;
-import com.android.car.dialer.ui.common.entity.ContactSortingInfo;
-import com.android.car.telephony.common.Contact;
-import com.android.car.telephony.common.InMemoryPhoneBook;
-import com.android.car.telephony.common.ObservableAsyncQuery;
-import com.android.car.telephony.common.QueryParam;
+import com.android.car.dialer.ui.common.ContactResultsLiveData;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
* {link AndroidViewModel} used for search functionality.
*/
public class ContactResultsViewModel extends AndroidViewModel {
- private static final String[] CONTACT_DETAILS_PROJECTION = {
- ContactsContract.Contacts._ID,
- ContactsContract.Contacts.LOOKUP_KEY
- };
private final ContactResultsLiveData mContactSearchResultsLiveData;
private final MutableLiveData<String> mSearchQueryLiveData;
@@ -65,7 +48,10 @@
mSearchQueryLiveData, mSharedPreferencesLiveData);
}
- void setSearchQuery(String searchQuery) {
+ /**
+ * Sets search query.
+ */
+ public void setSearchQuery(String searchQuery) {
if (TextUtils.equals(mSearchQueryLiveData.getValue(), searchQuery)) {
return;
}
@@ -73,105 +59,31 @@
mSearchQueryLiveData.setValue(searchQuery);
}
- LiveData<List<Contact>> getContactSearchResults() {
+ /**
+ * Returns live data of search results.
+ */
+ public LiveData<List<ContactResultsLiveData.ContactResultListItem>> getContactSearchResults() {
return mContactSearchResultsLiveData;
}
- String getSearchQuery() {
+ /**
+ * Returns search query.
+ */
+ public String getSearchQuery() {
return mSearchQueryLiveData.getValue();
}
- private static class ContactResultsLiveData extends MediatorLiveData<List<Contact>> {
- private final Context mContext;
- private final SearchQueryParamProvider mSearchQueryParamProvider;
- private final ObservableAsyncQuery mObservableAsyncQuery;
- private final LiveData<String> mSearchQueryLiveData;
- private final LiveData<List<Contact>> mContactListLiveData;
- private final SharedPreferencesLiveData mSharedPreferencesLiveData;
-
- ContactResultsLiveData(Context context,
- LiveData<String> searchQueryLiveData,
- SharedPreferencesLiveData sharedPreferencesLiveData) {
- mContext = context;
- mSearchQueryParamProvider = new SearchQueryParamProvider(searchQueryLiveData);
- mObservableAsyncQuery = new ObservableAsyncQuery(mSearchQueryParamProvider,
- context.getContentResolver(), this::onQueryFinished);
-
- mContactListLiveData = InMemoryPhoneBook.get().getContactsLiveData();
- addSource(mContactListLiveData, this::onContactsChange);
- mSearchQueryLiveData = searchQueryLiveData;
- addSource(mSearchQueryLiveData, this::onSearchQueryChanged);
-
- mSharedPreferencesLiveData = sharedPreferencesLiveData;
- addSource(mSharedPreferencesLiveData, this::onSortOrderChanged);
- }
-
- private void onContactsChange(List<Contact> contactList) {
- if (contactList == null || contactList.isEmpty()) {
- mObservableAsyncQuery.stopQuery();
- setValue(Collections.emptyList());
- } else {
- onSearchQueryChanged(mSearchQueryLiveData.getValue());
- }
- }
-
- private void onSearchQueryChanged(String searchQuery) {
- if (TextUtils.isEmpty(searchQuery)) {
- mObservableAsyncQuery.stopQuery();
- List<Contact> contacts = mContactListLiveData.getValue();
- setValue(contacts == null ? Collections.emptyList() : contacts);
- } else {
- mObservableAsyncQuery.startQuery();
- }
- }
-
- private void onSortOrderChanged(SharedPreferences unusedSharedPreferences) {
- setValue(getValue());
- }
-
- private void onQueryFinished(@Nullable Cursor cursor) {
- if (cursor == null) {
- setValue(Collections.emptyList());
- return;
- }
-
- List<Contact> contacts = new ArrayList<>();
- while (cursor.moveToNext()) {
- int lookupKeyColIdx = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
- List<Contact> lookupResults = InMemoryPhoneBook.get().lookupContactByKey(
- cursor.getString(lookupKeyColIdx));
- contacts.addAll(lookupResults);
- }
- setValue(contacts);
- cursor.close();
- }
-
- @Override
- public void setValue(List<Contact> contacts) {
- if (contacts != null && !contacts.isEmpty()) {
- Collections.sort(contacts,
- ContactSortingInfo.getSortingInfo(mContext,
- mSharedPreferencesLiveData).first);
- }
- super.setValue(contacts);
- }
+ /**
+ * Returns Search Query LiveData.
+ */
+ public MutableLiveData<String> getSearchQueryLiveData() {
+ return mSearchQueryLiveData;
}
- private static class SearchQueryParamProvider implements QueryParam.Provider {
- private final LiveData<String> mSearchQueryLiveData;
-
- private SearchQueryParamProvider(LiveData<String> searchQueryLiveData) {
- mSearchQueryLiveData = searchQueryLiveData;
- }
-
- @Nullable
- @Override
- public QueryParam getQueryParam() {
- Uri lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
- Uri.encode(mSearchQueryLiveData.getValue()));
- return new QueryParam(lookupUri, CONTACT_DETAILS_PROJECTION,
- ContactsContract.Contacts.HAS_PHONE_NUMBER + "!=0",
- /* selectionArgs= */null, /* orderBy= */null);
- }
+ /**
+ * Returns Shared Preferences LiveData.
+ */
+ public SharedPreferencesLiveData getSharedPreferencesLiveData() {
+ return mSharedPreferencesLiveData;
}
}
diff --git a/src/com/android/car/dialer/widget/LoadingFrameLayout.java b/src/com/android/car/dialer/widget/LoadingFrameLayout.java
index 1011bed..0f21a47 100644
--- a/src/com/android/car/dialer/widget/LoadingFrameLayout.java
+++ b/src/com/android/car/dialer/widget/LoadingFrameLayout.java
@@ -228,7 +228,7 @@
if (mState != state) {
L.d(TAG, "Switch to state: %d", state);
// Hides, or shows, all the children, including the loading and error views.
- setChildVisibility(state == State.CONTENT ? View.VISIBLE : View.GONE);
+ ViewUtils.setVisible((View) findViewById(R.id.list_view), state == State.CONTENT);
// Corrects the visibility setting for error and loading views since they are
// shown independently of the views content.
@@ -240,13 +240,6 @@
}
}
- private void setChildVisibility(int visibility) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).setVisibility(visibility);
- }
- }
-
/**
* Container for views held by this LoadingFrameLayout. Used for deferring view inflation until
* the view is about to be shown.
diff --git a/tests/robotests/res/layout/test_activity.xml b/tests/robotests/res/layout/test_activity.xml
index e7e8291..0bc280c 100644
--- a/tests/robotests/res/layout/test_activity.xml
+++ b/tests/robotests/res/layout/test_activity.xml
@@ -19,7 +19,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.android.car.ui.toolbar.Toolbar
- android:id="@+id/car_ui_toolbar"
+ android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>
diff --git a/tests/robotests/src/com/android/car/dialer/telecom/InCallServiceImplTest.java b/tests/robotests/src/com/android/car/dialer/telecom/InCallServiceImplTest.java
index e0d8a48..f4f54b9 100644
--- a/tests/robotests/src/com/android/car/dialer/telecom/InCallServiceImplTest.java
+++ b/tests/robotests/src/com/android/car/dialer/telecom/InCallServiceImplTest.java
@@ -33,9 +33,11 @@
import android.content.Context;
import android.content.Intent;
import android.telecom.Call;
+import android.telecom.CallAudioState;
import com.android.car.dialer.CarDialerRobolectricTestRunner;
import com.android.car.dialer.testutils.ShadowCar;
+import com.android.car.dialer.ui.activecall.InCallActivity;
import org.junit.Before;
import org.junit.Test;
@@ -47,7 +49,9 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ServiceController;
import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowContextWrapper;
+import org.robolectric.shadows.ShadowIntent;
import org.robolectric.shadows.ShadowLooper;
/**
@@ -70,7 +74,9 @@
@Mock
private Call.Details mMockCallDetails;
@Mock
- private InCallServiceImpl.Callback mCallback;
+ private CallAudioState mMockCallAudioState;
+ @Mock
+ private InCallServiceImpl.CallAudioStateCallback mCallAudioStateCallback;
@Mock
private InCallServiceImpl.ActiveCallListChangedCallback mActiveCallListChangedCallback;
@@ -88,7 +94,6 @@
inCallServiceController.create().bind();
mInCallServiceImpl = inCallServiceController.get();
- mInCallServiceImpl.registerCallback(mCallback);
mInCallServiceImpl.addActiveCallListChangedCallback(mActiveCallListChangedCallback);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
@@ -102,8 +107,6 @@
mInCallServiceImpl.onCallAdded(mMockTelecomCall);
ArgumentCaptor<Call> callCaptor = ArgumentCaptor.forClass(Call.class);
- verify(mCallback).onTelecomCallAdded(callCaptor.capture());
- assertThat(callCaptor.getValue()).isEqualTo(mMockTelecomCall);
verify(mActiveCallListChangedCallback).onTelecomCallAdded(callCaptor.capture());
assertThat(callCaptor.getValue()).isEqualTo(mMockTelecomCall);
@@ -119,8 +122,6 @@
mInCallServiceImpl.onCallRemoved(mMockTelecomCall);
ArgumentCaptor<Call> callCaptor = ArgumentCaptor.forClass(Call.class);
- verify(mCallback).onTelecomCallRemoved(callCaptor.capture());
- assertThat(callCaptor.getValue()).isEqualTo(mMockTelecomCall);
verify(mActiveCallListChangedCallback).onTelecomCallRemoved(callCaptor.capture());
assertThat(callCaptor.getValue()).isEqualTo(mMockTelecomCall);
@@ -132,8 +133,6 @@
mInCallServiceImpl.onCallAdded(mMockTelecomCall);
ArgumentCaptor<Call> callCaptor = ArgumentCaptor.forClass(Call.class);
- verify(mCallback).onTelecomCallAdded(callCaptor.capture());
- assertThat(callCaptor.getValue()).isEqualTo(mMockTelecomCall);
verify(mActiveCallListChangedCallback).onTelecomCallAdded(callCaptor.capture());
assertThat(callCaptor.getValue()).isEqualTo(mMockTelecomCall);
@@ -148,17 +147,6 @@
}
@Test
- public void testUnregisterCallback() {
- mInCallServiceImpl.unregisterCallback(mCallback);
-
- mInCallServiceImpl.onCallAdded(mMockTelecomCall);
- verify(mCallback, never()).onTelecomCallAdded(any());
-
- mInCallServiceImpl.onCallRemoved(mMockTelecomCall);
- verify(mCallback, never()).onTelecomCallRemoved(any());
- }
-
- @Test
public void testRemoveActiveCallListChangedCallback() {
mInCallServiceImpl.removeActiveCallListChangedCallback(mActiveCallListChangedCallback);
@@ -168,4 +156,24 @@
mInCallServiceImpl.onCallRemoved(mMockTelecomCall);
verify(mActiveCallListChangedCallback, never()).onTelecomCallRemoved(any());
}
+
+ @Test
+ public void testAddCallAudioStateChangedCallback() {
+ mInCallServiceImpl.addCallAudioStateChangedCallback(mCallAudioStateCallback);
+
+ mInCallServiceImpl.onCallAudioStateChanged(mMockCallAudioState);
+ verify(mCallAudioStateCallback).onCallAudioStateChanged(any());
+ }
+
+ @Test
+ public void testOnBringToForeground() {
+ ShadowApplication shadow = shadowOf(mInCallServiceImpl.getApplication());
+
+ mInCallServiceImpl.onCallAdded(mMockTelecomCall);
+ mInCallServiceImpl.onBringToForeground(false);
+
+ Intent intent = shadow.getNextStartedActivity();
+ ShadowIntent shadowIntent = shadowOf(intent);
+ assertThat(InCallActivity.class).isEqualTo(shadowIntent.getIntentClass());
+ }
}
diff --git a/tests/robotests/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragmentTest.java b/tests/robotests/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragmentTest.java
index 0af882b..4eaafa7 100644
--- a/tests/robotests/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragmentTest.java
+++ b/tests/robotests/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragmentTest.java
@@ -66,6 +66,7 @@
private MutableLiveData<CallDetail> mCallDetailLiveData;
private MutableLiveData<Boolean> mDialpadStateLiveData;
private MutableLiveData<List<Call>> mCallListLiveData;
+ private MutableLiveData<CallAudioState> mCallAudioStateLiveData;
private List<Call> mCallList;
@Mock
private Call mMockCall;
@@ -89,6 +90,7 @@
mCallDetailLiveData = new MutableLiveData<>();
mDialpadStateLiveData = new MutableLiveData<>();
mCallListLiveData = new MutableLiveData<>();
+ mCallAudioStateLiveData = new MutableLiveData<>();
mCallList = new ArrayList<>();
mCallList.add(mMockCall);
mCallListLiveData.setValue(mCallList);
@@ -238,6 +240,7 @@
when(mMockInCallViewModel.getPrimaryCallState()).thenReturn(mCallStateLiveData);
when(mMockInCallViewModel.getDialpadOpenState()).thenReturn(mDialpadStateLiveData);
when(mMockInCallViewModel.getAllCallList()).thenReturn(mCallListLiveData);
+ when(mMockInCallViewModel.getCallAudioState()).thenReturn(mCallAudioStateLiveData);
MutableLiveData<Integer> audioRouteLiveData = new MutableLiveData<>();
audioRouteLiveData.setValue(CallAudioState.ROUTE_BLUETOOTH);
diff --git a/tests/robotests/src/com/android/car/dialer/ui/calllog/CallHistoryFragmentTest.java b/tests/robotests/src/com/android/car/dialer/ui/calllog/CallHistoryFragmentTest.java
index fa7a8f1..8ada441 100644
--- a/tests/robotests/src/com/android/car/dialer/ui/calllog/CallHistoryFragmentTest.java
+++ b/tests/robotests/src/com/android/car/dialer/ui/calllog/CallHistoryFragmentTest.java
@@ -21,7 +21,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.net.Uri;
import android.view.View;
import android.widget.TextView;
@@ -38,6 +37,7 @@
import com.android.car.dialer.ui.common.entity.HeaderViewHolder;
import com.android.car.dialer.ui.common.entity.UiCallLog;
import com.android.car.dialer.widget.CallTypeIconsView;
+import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.InMemoryPhoneBook;
import com.android.car.telephony.common.PhoneCallLog;
import com.android.car.ui.recyclerview.CarUiRecyclerView;
@@ -72,9 +72,9 @@
@Mock
private UiCallManager mMockUiCallManager;
@Mock
- private Uri mMockUri;
- @Mock
private CallHistoryViewModel mMockCallHistoryViewModel;
+ @Mock
+ private Contact mMockContact;
@Before
public void setup() {
@@ -87,8 +87,8 @@
CallHistoryLiveData.CallType.INCOMING_TYPE);
PhoneCallLog.Record record2 = new PhoneCallLog.Record(TIME_STAMP_2,
CallHistoryLiveData.CallType.OUTGOING_TYPE);
- UiCallLog uiCallLog = new UiCallLog(UI_CALLOG_TITLE, UI_CALLOG_TEXT, PHONE_NUMBER, null,
- mMockUri, Arrays.asList(record1, record2));
+ UiCallLog uiCallLog = new UiCallLog(UI_CALLOG_TITLE, UI_CALLOG_TEXT, PHONE_NUMBER,
+ mMockContact, Arrays.asList(record1, record2));
MutableLiveData<FutureData<List<Object>>> callLog = new MutableLiveData<>();
callLog.setValue(new FutureData<>(false, Arrays.asList(HEADER, uiCallLog)));
@@ -103,7 +103,7 @@
CarUiRecyclerView recyclerView = mCallHistoryFragment.getView()
.findViewById(R.id.list_view);
// set up layout for recyclerView
- recyclerView.layoutBothForTesting(0, 0, 100, 1000);
+ recyclerView.layout(0, 0, 100, 1000);
mHeaderViewHolder = recyclerView.findViewHolderForLayoutPosition(0);
mCalllogViewHolder = recyclerView.findViewHolderForLayoutPosition(1);
}
diff --git a/tests/robotests/src/com/android/car/dialer/ui/contact/ContactDetailsFragmentTest.java b/tests/robotests/src/com/android/car/dialer/ui/contact/ContactDetailsFragmentTest.java
index 65f00a7..40af9a4 100644
--- a/tests/robotests/src/com/android/car/dialer/ui/contact/ContactDetailsFragmentTest.java
+++ b/tests/robotests/src/com/android/car/dialer/ui/contact/ContactDetailsFragmentTest.java
@@ -116,7 +116,7 @@
mListView = mContactDetailsFragment.getView().findViewById(R.id.list_view);
// Set up layout for recyclerView
- mListView.layoutBothForTesting(0, 0, 100, 1000);
+ mListView.layout(0, 0, 100, 1000);
}
/**
diff --git a/tests/robotests/src/com/android/car/dialer/ui/contact/ContactListFragmentTest.java b/tests/robotests/src/com/android/car/dialer/ui/contact/ContactListFragmentTest.java
index 1ae465b..c5cb8f4 100644
--- a/tests/robotests/src/com/android/car/dialer/ui/contact/ContactListFragmentTest.java
+++ b/tests/robotests/src/com/android/car/dialer/ui/contact/ContactListFragmentTest.java
@@ -216,7 +216,7 @@
CarUiRecyclerView recyclerView = mContactListFragment.getView()
.findViewById(R.id.list_view);
//Force RecyclerView to layout to ensure findViewHolderForLayoutPosition works.
- recyclerView.layoutBothForTesting(0, 0, 100, 1000);
+ recyclerView.layout(0, 0, 100, 1000);
mViewHolder = (ContactListViewHolder) recyclerView.findViewHolderForLayoutPosition(0);
}
diff --git a/tests/robotests/src/com/android/car/dialer/ui/dialpad/DialpadFragmentTest.java b/tests/robotests/src/com/android/car/dialer/ui/dialpad/DialpadFragmentTest.java
index 5f7df33..ae3fb08 100644
--- a/tests/robotests/src/com/android/car/dialer/ui/dialpad/DialpadFragmentTest.java
+++ b/tests/robotests/src/com/android/car/dialer/ui/dialpad/DialpadFragmentTest.java
@@ -28,13 +28,18 @@
import android.widget.ImageButton;
import android.widget.TextView;
+import androidx.lifecycle.MutableLiveData;
+
import com.android.car.dialer.CarDialerRobolectricTestRunner;
import com.android.car.dialer.FragmentTestActivity;
import com.android.car.dialer.R;
import com.android.car.dialer.TestDialerApplication;
import com.android.car.dialer.telecom.UiCallManager;
+import com.android.car.dialer.testutils.ShadowAndroidViewModelFactory;
import com.android.car.dialer.testutils.ShadowCallLogCalls;
import com.android.car.dialer.testutils.ShadowInMemoryPhoneBook;
+import com.android.car.dialer.ui.common.ContactResultsLiveData;
+import com.android.car.dialer.ui.search.ContactResultsViewModel;
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.InMemoryPhoneBook;
import com.android.car.telephony.common.PhoneNumber;
@@ -51,8 +56,11 @@
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
+import java.util.List;
+
@RunWith(CarDialerRobolectricTestRunner.class)
-@Config(shadows = {ShadowCallLogCalls.class, ShadowInMemoryPhoneBook.class})
+@Config(shadows = {ShadowCallLogCalls.class, ShadowInMemoryPhoneBook.class,
+ ShadowAndroidViewModelFactory.class})
public class DialpadFragmentTest {
private static final String DIAL_NUMBER = "6505551234";
private static final String DIAL_NUMBER_LONG = "650555123465055512346505551234";
@@ -60,7 +68,12 @@
private static final String SPEC_CHAR = "123=_=%^&";
private static final String DISPALY_NAME = "Display Name";
+ private Context mContext;
private DialpadFragment mDialpadFragment;
+ private MutableLiveData<List<ContactResultsLiveData.ContactResultListItem>>
+ mContactResultsLiveData;
+ @Mock
+ private ContactResultsViewModel mMockContactResultsViewModel;
@Mock
private Contact mMockContact;
@@ -68,10 +81,16 @@
public void setup() {
MockitoAnnotations.initMocks(this);
- Context context = RuntimeEnvironment.application;
- ((TestDialerApplication) context).setupInCallServiceImpl();
- ((TestDialerApplication) context).initUiCallManager();
- InMemoryPhoneBook.init(context);
+ mContext = RuntimeEnvironment.application;
+ ((TestDialerApplication) mContext).setupInCallServiceImpl();
+ ((TestDialerApplication) mContext).initUiCallManager();
+ InMemoryPhoneBook.init(mContext);
+
+ mContactResultsLiveData = new MutableLiveData<>();
+ when(mMockContactResultsViewModel.getContactSearchResults())
+ .thenReturn(mContactResultsLiveData);
+ ShadowAndroidViewModelFactory.add(
+ ContactResultsViewModel.class, mMockContactResultsViewModel);
}
@After
@@ -212,7 +231,13 @@
mDialpadFragment.setDialedNumber(DIAL_NUMBER);
TextView displayName = mDialpadFragment.getView().findViewById(R.id.display_name);
- assertThat(displayName.getText()).isEqualTo(DISPALY_NAME);
+ if (!mContext.getResources().getBoolean(R.bool.config_show_type_down_list_on_dialpad)
+ && mContext.getResources()
+ .getBoolean(R.bool.config_show_type_down_list_on_dialpad)) {
+ assertThat(displayName.getText()).isEqualTo(DISPALY_NAME);
+ } else {
+ assertThat(displayName.getText()).isEqualTo("");
+ }
}
private void startPlaceCallActivity() {
diff --git a/tests/robotests/src/com/android/car/dialer/ui/favorite/FavoriteFragmentTest.java b/tests/robotests/src/com/android/car/dialer/ui/favorite/FavoriteFragmentTest.java
index 5fcb006..e6c326e 100644
--- a/tests/robotests/src/com/android/car/dialer/ui/favorite/FavoriteFragmentTest.java
+++ b/tests/robotests/src/com/android/car/dialer/ui/favorite/FavoriteFragmentTest.java
@@ -71,7 +71,7 @@
UiCallManager.set(mMockUiCallManager);
when(mMockPhoneNumber.getRawNumber()).thenReturn(RAW_NUMBER);
- MutableLiveData<FutureData<List<Contact>>> favoriteContacts = new MutableLiveData<>();
+ MutableLiveData<FutureData<List<Object>>> favoriteContacts = new MutableLiveData<>();
favoriteContacts.setValue(new FutureData<>(false, Arrays.asList(mMockContact)));
ShadowAndroidViewModelFactory.add(FavoriteViewModel.class, mMockFavoriteViewModel);
when(mMockFavoriteViewModel.getFavoriteContacts()).thenReturn(favoriteContacts);
@@ -83,7 +83,7 @@
CarUiRecyclerView recyclerView = mFavoriteFragment.getView().findViewById(R.id.list_view);
// set up layout for recyclerView
- recyclerView.layoutBothForTesting(0, 0, 100, 1000);
+ recyclerView.layout(0, 0, 100, 1000);
mViewHolder = (FavoriteContactViewHolder) recyclerView.findViewHolderForLayoutPosition(0);
}
diff --git a/tests/robotests/src/com/android/car/dialer/ui/search/ContactResultsFragmentTest.java b/tests/robotests/src/com/android/car/dialer/ui/search/ContactResultsFragmentTest.java
index 83c8e79..c6f4d1c 100644
--- a/tests/robotests/src/com/android/car/dialer/ui/search/ContactResultsFragmentTest.java
+++ b/tests/robotests/src/com/android/car/dialer/ui/search/ContactResultsFragmentTest.java
@@ -33,10 +33,12 @@
import com.android.car.dialer.FragmentTestActivity;
import com.android.car.dialer.R;
import com.android.car.dialer.testutils.ShadowAndroidViewModelFactory;
+import com.android.car.dialer.ui.common.ContactResultsLiveData;
import com.android.car.dialer.ui.contact.ContactDetailsFragment;
import com.android.car.dialer.ui.contact.ContactDetailsViewModel;
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.InMemoryPhoneBook;
+import com.android.car.telephony.common.PhoneNumber;
import com.android.car.ui.recyclerview.CarUiRecyclerView;
import org.junit.After;
@@ -50,6 +52,7 @@
import org.robolectric.annotation.Config;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
@Config(shadows = {ShadowAndroidViewModelFactory.class})
@@ -62,15 +65,19 @@
private ContactResultsFragment mContactResultsFragment;
private FragmentTestActivity mFragmentTestActivity;
private CarUiRecyclerView mListView;
- private MutableLiveData<List<Contact>> mContactSearchResultsLiveData;
+ private MutableLiveData<List<ContactResultsLiveData.ContactResultListItem>>
+ mContactSearchResultsLiveData;
@Mock
private ContactResultsViewModel mMockContactResultsViewModel;
@Mock
private ContactDetailsViewModel mMockContactDetailsViewModel;
@Mock
- private Contact mMockContact;
+ private Contact mMockContact, mContact1, mContact2, mContact3;
@Mock
- private Contact mContact1, mContact2, mContact3;
+ private ContactResultsLiveData.ContactResultListItem mContactResult1, mContactResult2,
+ mContactResult3;
+ @Mock
+ private PhoneNumber mPhoneNumber;
@Before
public void setUp() {
@@ -83,9 +90,15 @@
ShadowAndroidViewModelFactory.add(
ContactResultsViewModel.class, mMockContactResultsViewModel);
+ when(mContactResult1.getContact()).thenReturn(mContact1);
when(mContact1.getDisplayName()).thenReturn(DISPLAY_NAMES[0]);
+ when(mContact1.getNumbers()).thenReturn(Collections.singletonList(mPhoneNumber));
+ when(mContactResult2.getContact()).thenReturn(mContact2);
when(mContact2.getDisplayName()).thenReturn(DISPLAY_NAMES[1]);
+ when(mContact2.getNumbers()).thenReturn(Collections.singletonList(mPhoneNumber));
+ when(mContactResult3.getContact()).thenReturn(mContact3);
when(mContact3.getDisplayName()).thenReturn(DISPLAY_NAMES[2]);
+ when(mContact3.getNumbers()).thenReturn(Collections.singletonList(mPhoneNumber));
}
@After
@@ -104,7 +117,7 @@
@Test
public void testDisplaySearchResults_multipleResults() {
mContactSearchResultsLiveData.setValue(
- Arrays.asList(mContact1, mContact2, mContact3));
+ Arrays.asList(mContactResult1, mContactResult2, mContactResult3));
mContactResultsFragment = ContactResultsFragment.newInstance(INITIAL_SEARCH_QUERY);
setUpFragment();
@@ -117,7 +130,7 @@
@Test
public void testClickSearchResult_showContactDetailPage() {
mContactSearchResultsLiveData.setValue(
- Arrays.asList(mContact1, mContact2, mContact3));
+ Arrays.asList(mContactResult1, mContactResult2, mContactResult3));
MutableLiveData<FutureData<Contact>> contactDetailLiveData = new MutableLiveData<>();
contactDetailLiveData.setValue(new FutureData<>(false, mMockContact));
@@ -143,7 +156,7 @@
mListView = mContactResultsFragment.getView().findViewById(R.id.list_view);
// Set up layout for recyclerView
- mListView.layoutBothForTesting(0, 0, 100, 1000);
+ mListView.layout(0, 0, 100, 1000);
}
private void verifyChildAt(int position) {