summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2018-08-06 16:51:14 -0700
committerXin Li <delphij@google.com>2018-08-06 16:51:14 -0700
commitd777b143383779fc4807c92982584134d891acf3 (patch)
treecb068f225fc053c7b6ec9c80323ec03729c35f92
parentb04568fb1e3fff7959e68b238c7447e5d297469a (diff)
parent4d866aacf10d09b8177c4dbf514c401902cefede (diff)
downloadplatform_packages_apps_Car_Dialer-android-o-mr1-iot-release-1.0.4.tar.gz
platform_packages_apps_Car_Dialer-android-o-mr1-iot-release-1.0.4.tar.bz2
platform_packages_apps_Car_Dialer-android-o-mr1-iot-release-1.0.4.zip
Bug: 112104996 Change-Id: I4e2b80af0f9a7accfd88ef6394a0fb42e2f0b6be
-rw-r--r--Android.mk14
-rw-r--r--AndroidManifest.xml1
-rw-r--r--res/drawable/button_active_state_ring.xml22
-rw-r--r--res/drawable/car_card_rounded_bottom_background.xml6
-rw-r--r--res/drawable/car_card_rounded_top_background.xml6
-rw-r--r--res/drawable/car_card_rounded_top_bottom_background.xml5
-rw-r--r--res/drawable/car_list_item_background.xml22
-rw-r--r--res/drawable/dialer_ripple_background.xml2
-rw-r--r--res/drawable/ic_arrow_back.xml4
-rw-r--r--res/drawable/ic_arrow_down.xml24
-rw-r--r--res/drawable/ic_bluetooth.xml24
-rw-r--r--res/drawable/ic_call_end.xml6
-rw-r--r--res/drawable/ic_call_state_switch.xml24
-rw-r--r--res/drawable/ic_cancel.xml4
-rw-r--r--res/drawable/ic_contact.xml24
-rw-r--r--res/drawable/ic_dialpad_activated.xml80
-rw-r--r--res/drawable/ic_dialpad_normal.xml70
-rw-r--r--res/drawable/ic_favorite.xml24
-rw-r--r--res/drawable/ic_mute_call_activated.xml68
-rw-r--r--res/drawable/ic_mute_call_normal.xml58
-rw-r--r--res/drawable/ic_pause.xml21
-rw-r--r--res/drawable/ic_phone.xml4
-rw-r--r--res/drawable/ic_play.xml19
-rw-r--r--res/drawable/ic_search.xml4
-rw-r--r--res/drawable/ic_smartphone.xml24
-rw-r--r--res/drawable/ic_speaker_phone.xml24
-rw-r--r--res/drawable/in_call_card_background.xml15
-rw-r--r--res/drawable/ongoing_call_secondary_action_background.xml2
-rw-r--r--res/layout-h720dp/dialer_fragment.xml124
-rw-r--r--res/layout-port/dialer_fragment.xml106
-rw-r--r--res/layout-port/in_call_fragment.xml53
-rw-r--r--res/layout/audio_route_list_item.xml47
-rw-r--r--res/layout/audio_route_switch_dialog.xml33
-rw-r--r--res/layout/call_history_list_item.xml42
-rw-r--r--res/layout/call_list_fragment.xml30
-rw-r--r--res/layout/call_log_last_call_item_card.xml34
-rw-r--r--res/layout/call_log_list_item_card.xml27
-rw-r--r--res/layout/call_log_list_item_card_base.xml23
-rw-r--r--res/layout/call_log_list_item_empty.xml45
-rw-r--r--res/layout/contact_detail_card_content.xml55
-rw-r--r--res/layout/contact_detail_name_image.xml56
-rw-r--r--res/layout/contact_details.xml5
-rw-r--r--res/layout/contact_details_number.xml16
-rw-r--r--res/layout/contact_list_fragment.xml74
-rw-r--r--res/layout/contact_result.xml12
-rw-r--r--res/layout/contact_result_fragment.xml7
-rw-r--r--res/layout/contact_search_activity.xml30
-rw-r--r--res/layout/dialer_fragment.xml122
-rw-r--r--res/layout/dialer_info_fragment.xml91
-rw-r--r--res/layout/dialpad_button.xml4
-rw-r--r--res/layout/in_call_fragment.xml50
-rw-r--r--res/layout/on_going_call_controller_bar_fragment.xml100
-rw-r--r--res/layout/ongoing_call.xml49
-rw-r--r--res/layout/ringing_call_controller_bar_fragment.xml80
-rw-r--r--res/layout/strequents_fragment.xml8
-rw-r--r--res/layout/telecom_activity.xml13
-rw-r--r--res/layout/user_profile_large.xml42
-rw-r--r--res/values-h420dp/dimens.xml4
-rw-r--r--res/values-h480dp/dimens.xml18
-rw-r--r--res/values-h600dp/dimens.xml3
-rw-r--r--res/values-h720dp/dimens.xml3
-rw-r--r--res/values-night/colors.xml7
-rw-r--r--res/values-w1024dp/dimens.xml6
-rw-r--r--res/values-w748dp/dimens.xml5
-rw-r--r--res/values/colors.xml36
-rw-r--r--res/values/dimens.xml44
-rw-r--r--res/values/integer.xml5
-rw-r--r--res/values/strings.xml35
-rw-r--r--res/values/styles.xml35
-rw-r--r--res/values/themes.xml9
-rw-r--r--src/com/android/car/dialer/CallListener.java16
-rw-r--r--src/com/android/car/dialer/CallLogListingTask.java258
-rw-r--r--src/com/android/car/dialer/CallLogViewHolder.java38
-rw-r--r--src/com/android/car/dialer/ContactDetailsFragment.java91
-rw-r--r--src/com/android/car/dialer/ContactEntry.java190
-rw-r--r--src/com/android/car/dialer/ContactResultViewHolder.java32
-rw-r--r--src/com/android/car/dialer/ContactResultsAdapter.java4
-rw-r--r--src/com/android/car/dialer/ContactResultsFragment.java26
-rw-r--r--src/com/android/car/dialer/ContactSearchActivity.java27
-rw-r--r--src/com/android/car/dialer/DialerFragment.java288
-rw-r--r--src/com/android/car/dialer/OngoingCallFragment.java105
-rw-r--r--src/com/android/car/dialer/StrequentsAdapter.java106
-rw-r--r--src/com/android/car/dialer/StrequentsFragment.java125
-rw-r--r--src/com/android/car/dialer/StrequentsItemAnimator.java637
-rw-r--r--src/com/android/car/dialer/TelecomActivity.java279
-rw-r--r--src/com/android/car/dialer/livedata/CallHistoryLiveData.java90
-rw-r--r--src/com/android/car/dialer/livedata/MissedCallHistoryLiveData.java35
-rw-r--r--src/com/android/car/dialer/log/L.java70
-rw-r--r--src/com/android/car/dialer/telecom/InMemoryPhoneBook.java121
-rw-r--r--src/com/android/car/dialer/telecom/PhoneLoader.java51
-rw-r--r--src/com/android/car/dialer/telecom/TelecomUtils.java49
-rw-r--r--src/com/android/car/dialer/telecom/UiCallManager.java144
-rw-r--r--src/com/android/car/dialer/ui/CallHistoryFragment.java81
-rw-r--r--src/com/android/car/dialer/ui/CallHistoryListItemProvider.java60
-rw-r--r--src/com/android/car/dialer/ui/CallLogListingTask.java181
-rw-r--r--src/com/android/car/dialer/ui/CircleBitmapDrawable.java115
-rw-r--r--src/com/android/car/dialer/ui/ContactListFragment.java211
-rw-r--r--src/com/android/car/dialer/ui/ContactListItemProvider.java77
-rw-r--r--src/com/android/car/dialer/ui/DialerInfoController.java173
-rw-r--r--src/com/android/car/dialer/ui/DialpadFragment.java222
-rw-r--r--src/com/android/car/dialer/ui/InCallFragment.java188
-rw-r--r--src/com/android/car/dialer/ui/OnGoingCallControllerBarFragment.java277
-rw-r--r--src/com/android/car/dialer/ui/RingingCallControllerBarFragment.java49
-rw-r--r--src/com/android/car/dialer/ui/listitem/CallLogListItem.java89
-rw-r--r--src/com/android/car/dialer/ui/listitem/ContactListItem.java86
-rw-r--r--src/com/android/car/dialer/ui/viewmodel/CallHistoryViewModel.java58
106 files changed, 4504 insertions, 2334 deletions
diff --git a/Android.mk b/Android.mk
index 228e9ffd..c7270114 100644
--- a/Android.mk
+++ b/Android.mk
@@ -32,7 +32,16 @@ LOCAL_MODULE_TAGS := optional
LOCAL_USE_AAPT2 := true
LOCAL_STATIC_ANDROID_LIBRARIES += \
- android-support-v4
+ android-support-car \
+ android-arch-lifecycle-extensions \
+ android-support-constraint-layout \
+ android-support-v4 \
+ android-support-v7-cardview \
+ car-apps-common \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-constraint-layout-solver \
+ guava \
LOCAL_PROGUARD_ENABLED := disabled
@@ -40,9 +49,6 @@ LOCAL_PRIVILEGED_MODULE := true
LOCAL_DEX_PREOPT := false
-include packages/apps/Car/libs/car-stream-ui-lib/car-stream-ui-lib.mk
-include packages/apps/Car/libs/car-apps-common/car-apps-common.mk
-
include $(BUILD_PACKAGE)
endif
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d420c7e1..f5beec7d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion='24'/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
diff --git a/res/drawable/button_active_state_ring.xml b/res/drawable/button_active_state_ring.xml
new file mode 100644
index 00000000..8f717177
--- /dev/null
+++ b/res/drawable/button_active_state_ring.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="ring"
+ android:thickness="4dp"
+ android:innerRadius="34dp"
+ android:useLevel="false">
+</shape> \ No newline at end of file
diff --git a/res/drawable/car_card_rounded_bottom_background.xml b/res/drawable/car_card_rounded_bottom_background.xml
deleted file mode 100644
index 011f030c..00000000
--- a/res/drawable/car_card_rounded_bottom_background.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/car_card" />
- <corners
- android:bottomRightRadius="@dimen/call_log_last_card_corner_radius"
- android:bottomLeftRadius="@dimen/call_log_last_card_corner_radius"/>
-</shape>
diff --git a/res/drawable/car_card_rounded_top_background.xml b/res/drawable/car_card_rounded_top_background.xml
deleted file mode 100644
index 9a136e02..00000000
--- a/res/drawable/car_card_rounded_top_background.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/car_card" />
- <corners
- android:topRightRadius="@dimen/call_log_last_card_corner_radius"
- android:topLeftRadius="@dimen/call_log_last_card_corner_radius"/>
-</shape>
diff --git a/res/drawable/car_card_rounded_top_bottom_background.xml b/res/drawable/car_card_rounded_top_bottom_background.xml
deleted file mode 100644
index cc840567..00000000
--- a/res/drawable/car_card_rounded_top_bottom_background.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/car_card" />
- <corners
- android:radius="@dimen/call_log_last_card_corner_radius"/>
-</shape>
diff --git a/res/drawable/car_list_item_background.xml b/res/drawable/car_list_item_background.xml
new file mode 100644
index 00000000..cbc97c7a
--- /dev/null
+++ b/res/drawable/car_list_item_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/car_card_ripple_background">
+ <item android:id="@android:id/mask">
+ <color android:color="#ffffffff" />
+ </item>
+</ripple>
diff --git a/res/drawable/dialer_ripple_background.xml b/res/drawable/dialer_ripple_background.xml
index b7eff0f4..c4ee2291 100644
--- a/res/drawable/dialer_ripple_background.xml
+++ b/res/drawable/dialer_ripple_background.xml
@@ -14,5 +14,5 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/car_card_ripple_light_color_background_dark">
+ android:color="@color/car_card_ripple_background_dark">
</ripple>
diff --git a/res/drawable/ic_arrow_back.xml b/res/drawable/ic_arrow_back.xml
index fe02ea57..2c1655f3 100644
--- a/res/drawable/ic_arrow_back.xml
+++ b/res/drawable/ic_arrow_back.xml
@@ -14,8 +14,8 @@ 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/stream_button_icon_size"
- android:height="@dimen/stream_button_icon_size"
+ android:width="@dimen/car_primary_icon_size"
+ android:height="@dimen/car_primary_icon_size"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
diff --git a/res/drawable/ic_arrow_down.xml b/res/drawable/ic_arrow_down.xml
new file mode 100644
index 00000000..60f49aa6
--- /dev/null
+++ b/res/drawable/ic_arrow_down.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M7 10l5 5 5 -5z"
+ android:fillColor="#000000" />
+</vector> \ No newline at end of file
diff --git a/res/drawable/ic_bluetooth.xml b/res/drawable/ic_bluetooth.xml
new file mode 100644
index 00000000..01fc1159
--- /dev/null
+++ b/res/drawable/ic_bluetooth.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="44dp"
+ android:height="44dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82 -0.16,-1.59 -0.43,-2.31l-2.33,2.32zM19.53,6.71l-1.26,1.26c0.63,1.21 0.98,2.57 0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89 -0.55,-3.67 -1.48,-5.19zM15.71,7.71L10,2L9,2v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41L9,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM11,5.83l1.88,1.88L11,9.59L11,5.83zM12.88,16.29L11,18.17v-3.76l1.88,1.88z"/>
+</vector>
diff --git a/res/drawable/ic_call_end.xml b/res/drawable/ic_call_end.xml
index 140482a2..2b008b93 100644
--- a/res/drawable/ic_call_end.xml
+++ b/res/drawable/ic_call_end.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="56dp"
- android:height="56dp"
+ android:width="44dp"
+ android:height="44dp"
android:viewportWidth="24"
android:viewportHeight="24">
@@ -16,4 +16,4 @@
.18 -.43 .29 -.71 .29 -.27
0-.52-.11-.7-.28-.79-.74-1.69-1.36-2.67-1.85-.33-.16-.56-.5-.56-.9v-3.1C15.15
9.25 13.6 9 12 9z" />
-</vector> \ No newline at end of file
+</vector>
diff --git a/res/drawable/ic_call_state_switch.xml b/res/drawable/ic_call_state_switch.xml
new file mode 100644
index 00000000..b7142dde
--- /dev/null
+++ b/res/drawable/ic_call_state_switch.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<selector
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item
+ android:state_activated="true"
+ android:drawable="@drawable/ic_play" />
+ <item
+ android:state_activated="false"
+ android:drawable="@drawable/ic_pause" />
+</selector>
diff --git a/res/drawable/ic_cancel.xml b/res/drawable/ic_cancel.xml
index 2ade2e63..35749891 100644
--- a/res/drawable/ic_cancel.xml
+++ b/res/drawable/ic_cancel.xml
@@ -14,8 +14,8 @@ 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/stream_button_icon_size"
- android:height="@dimen/stream_button_icon_size"
+ android:width="@dimen/car_primary_icon_size"
+ android:height="@dimen/car_primary_icon_size"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
diff --git a/res/drawable/ic_contact.xml b/res/drawable/ic_contact.xml
new file mode 100644
index 00000000..7b547753
--- /dev/null
+++ b/res/drawable/ic_contact.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:width="48dp"
+ android:height="48dp">
+ <path
+ android:pathData="M6 10l0 28c0 2.21 1.79 4 4 4l28 0c2.21 0 4 -1.79 4 -4L42 10C42 7.79 40.21 6 38 6L10 6C7.79 6 6 7.79 6 10Zm24 8c0 3.32 -2.69 6 -6 6 -3.31 0 -6 -2.68 -6 -6 0 -3.31 2.69 -6 6 -6 3.31 0 6 2.69 6 6zM12 34c0 -4 8 -6.2 12 -6.2 4 0 12 2.2 12 6.2l0 2 -24 0 0 -2z"
+ android:fillColor="#000000" />
+</vector> \ No newline at end of file
diff --git a/res/drawable/ic_dialpad_activated.xml b/res/drawable/ic_dialpad_activated.xml
index 364ece76..42f5d8ce 100644
--- a/res/drawable/ic_dialpad_activated.xml
+++ b/res/drawable/ic_dialpad_activated.xml
@@ -1,62 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="72dp"
- android:height="72dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+<!-- Copyright (C) 2018 The Android Open Source Project
- <path
- android:strokeWidth="1"
- android:pathData="M 0 24 L 24 24 L 24 0 L 0 0 Z" />
- <path
- android:fillColor="#212121"
- android:strokeWidth="1"
- android:pathData="M12,0 C5.37257143,0 0,5.37257143 0,12 C0,18.6274286 5.37257143,24 12,24
-C18.6274286,24 24,18.6274286 24,12 C24,5.37257143 18.6274286,0 12,0
-M12,1.28571429 C17.9078571,1.28571429 22.7142857,6.09214286 22.7142857,12
-C22.7142857,17.9078571 17.9078571,22.7142857 12,22.7142857
-C6.09214286,22.7142857 1.28571429,17.9078571 1.28571429,12
-C1.28571429,6.09214286 6.09214286,1.28571429 12,1.28571429" />
- <path
- android:fillColor="#212121"
- android:strokeWidth="1"
- android:pathData="M12,16 C11.3688571,16 10.8571429,16.5117143 10.8571429,17.1428571
-C10.8571429,17.774 11.3688571,18.2857143 12,18.2857143 C12.6311429,18.2857143
-13.1428571,17.774 13.1428571,17.1428571 C13.1428571,16.5117143 12.6311429,16
-12,16 M15.4285714,12.5714286 C14.7974286,12.5714286 14.2857143,13.0831429
-14.2857143,13.7142857 C14.2857143,14.3454286 14.7974286,14.8571429
-15.4285714,14.8571429 C16.0597143,14.8571429 16.5714286,14.3454286
-16.5714286,13.7142857 C16.5714286,13.0831429 16.0597143,12.5714286
-15.4285714,12.5714286 M12,12.5714286 C11.3688571,12.5714286
-10.8571429,13.0831429 10.8571429,13.7142857 C10.8571429,14.3454286
-11.3688571,14.8571429 12,14.8571429 C12.6311429,14.8571429 13.1428571,14.3454286
-13.1428571,13.7142857 C13.1428571,13.0831429 12.6311429,12.5714286 12,12.5714286
-M8.57142857,12.5714286 C7.94028571,12.5714286 7.42857143,13.0831429
-7.42857143,13.7142857 C7.42857143,14.3454286 7.94028571,14.8571429
-8.57142857,14.8571429 C9.20257143,14.8571429 9.71428571,14.3454286
-9.71428571,13.7142857 C9.71428571,13.0831429 9.20257143,12.5714286
-8.57142857,12.5714286 M15.4285714,9.14285714 C14.7974286,9.14285714
-14.2857143,9.65457143 14.2857143,10.2857143 C14.2857143,10.9168571
-14.7974286,11.4285714 15.4285714,11.4285714 C16.0597143,11.4285714
-16.5714286,10.9168571 16.5714286,10.2857143 C16.5714286,9.65457143
-16.0597143,9.14285714 15.4285714,9.14285714 M12,9.14285714
-C11.3688571,9.14285714 10.8571429,9.65457143 10.8571429,10.2857143
-C10.8571429,10.9168571 11.3688571,11.4285714 12,11.4285714
-C12.6311429,11.4285714 13.1428571,10.9168571 13.1428571,10.2857143
-C13.1428571,9.65457143 12.6311429,9.14285714 12,9.14285714
-M8.57142857,9.14285714 C7.94028571,9.14285714 7.42857143,9.65457143
-7.42857143,10.2857143 C7.42857143,10.9168571 7.94028571,11.4285714
-8.57142857,11.4285714 C9.20257143,11.4285714 9.71428571,10.9168571
-9.71428571,10.2857143 C9.71428571,9.65457143 9.20257143,9.14285714
-8.57142857,9.14285714 M15.4285714,5.71428571 C14.7974286,5.71428571
-14.2857143,6.226 14.2857143,6.85714286 C14.2857143,7.48828571 14.7974286,8
-15.4285714,8 C16.0597143,8 16.5714286,7.48828571 16.5714286,6.85714286
-C16.5714286,6.226 16.0597143,5.71428571 15.4285714,5.71428571 M12,5.71428571
-C11.3688571,5.71428571 10.8571429,6.226 10.8571429,6.85714286
-C10.8571429,7.48828571 11.3688571,8 12,8 C12.6311429,8 13.1428571,7.48828571
-13.1428571,6.85714286 C13.1428571,6.226 12.6311429,5.71428571 12,5.71428571
-M8.57142857,5.71428571 C7.94028571,5.71428571 7.42857143,6.226
-7.42857143,6.85714286 C7.42857143,7.48828571 7.94028571,8 8.57142857,8
-C9.20257143,8 9.71428571,7.48828571 9.71428571,6.85714286 C9.71428571,6.226
-9.20257143,5.71428571 8.57142857,5.71428571" />
-</vector> \ No newline at end of file
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item
+ android:drawable="@drawable/button_active_state_ring"/>
+ <item
+ android:drawable="@drawable/ic_dialpad_normal"/>
+</layer-list>
diff --git a/res/drawable/ic_dialpad_normal.xml b/res/drawable/ic_dialpad_normal.xml
index 8e628ce2..be92de7f 100644
--- a/res/drawable/ic_dialpad_normal.xml
+++ b/res/drawable/ic_dialpad_normal.xml
@@ -1,53 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="72dp"
- android:height="72dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+<!-- Copyright (C) 2018 The Android Open Source Project
- <path
- android:strokeWidth="1"
- android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z" />
- <path
- android:fillColor="#212121"
- android:strokeWidth="1"
- android:pathData="M12,16 C11.3688571,16 10.8571429,16.5117143 10.8571429,17.1428571
-C10.8571429,17.774 11.3688571,18.2857143 12,18.2857143 C12.6311429,18.2857143
-13.1428571,17.774 13.1428571,17.1428571 C13.1428571,16.5117143 12.6311429,16
-12,16 M15.4285714,12.5714286 C14.7974286,12.5714286 14.2857143,13.0831429
-14.2857143,13.7142857 C14.2857143,14.3454286 14.7974286,14.8571429
-15.4285714,14.8571429 C16.0597143,14.8571429 16.5714286,14.3454286
-16.5714286,13.7142857 C16.5714286,13.0831429 16.0597143,12.5714286
-15.4285714,12.5714286 M12,12.5714286 C11.3688571,12.5714286
-10.8571429,13.0831429 10.8571429,13.7142857 C10.8571429,14.3454286
-11.3688571,14.8571429 12,14.8571429 C12.6311429,14.8571429 13.1428571,14.3454286
-13.1428571,13.7142857 C13.1428571,13.0831429 12.6311429,12.5714286 12,12.5714286
-M8.57142857,12.5714286 C7.94028571,12.5714286 7.42857143,13.0831429
-7.42857143,13.7142857 C7.42857143,14.3454286 7.94028571,14.8571429
-8.57142857,14.8571429 C9.20257143,14.8571429 9.71428571,14.3454286
-9.71428571,13.7142857 C9.71428571,13.0831429 9.20257143,12.5714286
-8.57142857,12.5714286 M15.4285714,9.14285714 C14.7974286,9.14285714
-14.2857143,9.65457143 14.2857143,10.2857143 C14.2857143,10.9168571
-14.7974286,11.4285714 15.4285714,11.4285714 C16.0597143,11.4285714
-16.5714286,10.9168571 16.5714286,10.2857143 C16.5714286,9.65457143
-16.0597143,9.14285714 15.4285714,9.14285714 M12,9.14285714
-C11.3688571,9.14285714 10.8571429,9.65457143 10.8571429,10.2857143
-C10.8571429,10.9168571 11.3688571,11.4285714 12,11.4285714
-C12.6311429,11.4285714 13.1428571,10.9168571 13.1428571,10.2857143
-C13.1428571,9.65457143 12.6311429,9.14285714 12,9.14285714
-M8.57142857,9.14285714 C7.94028571,9.14285714 7.42857143,9.65457143
-7.42857143,10.2857143 C7.42857143,10.9168571 7.94028571,11.4285714
-8.57142857,11.4285714 C9.20257143,11.4285714 9.71428571,10.9168571
-9.71428571,10.2857143 C9.71428571,9.65457143 9.20257143,9.14285714
-8.57142857,9.14285714 M15.4285714,5.71428571 C14.7974286,5.71428571
-14.2857143,6.226 14.2857143,6.85714286 C14.2857143,7.48828571 14.7974286,8
-15.4285714,8 C16.0597143,8 16.5714286,7.48828571 16.5714286,6.85714286
-C16.5714286,6.226 16.0597143,5.71428571 15.4285714,5.71428571 M12,5.71428571
-C11.3688571,5.71428571 10.8571429,6.226 10.8571429,6.85714286
-C10.8571429,7.48828571 11.3688571,8 12,8 C12.6311429,8 13.1428571,7.48828571
-13.1428571,6.85714286 C13.1428571,6.226 12.6311429,5.71428571 12,5.71428571
-M8.57142857,5.71428571 C7.94028571,5.71428571 7.42857143,6.226
-7.42857143,6.85714286 C7.42857143,7.48828571 7.94028571,8 8.57142857,8
-C9.20257143,8 9.71428571,7.48828571 9.71428571,6.85714286 C9.71428571,6.226
-9.20257143,5.71428571 8.57142857,5.71428571" />
-</vector> \ No newline at end of file
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 android:height="44dp" android:viewportHeight="48.0"
+ android:viewportWidth="48.0" android:width="44dp"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#000000"
+ android:pathData="M24,38c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,2C9.79,2 8,3.79 8,6s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,26c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM36,10c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM24,26c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM36,26c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM36,14c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM24,14c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM24,2c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"/>
+</vector>
diff --git a/res/drawable/ic_favorite.xml b/res/drawable/ic_favorite.xml
new file mode 100644
index 00000000..5c6b6556
--- /dev/null
+++ b/res/drawable/ic_favorite.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
+</vector>
diff --git a/res/drawable/ic_mute_call_activated.xml b/res/drawable/ic_mute_call_activated.xml
index 738854d9..822c7363 100644
--- a/res/drawable/ic_mute_call_activated.xml
+++ b/res/drawable/ic_mute_call_activated.xml
@@ -1,50 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="72dp"
- android:height="72dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+<!-- Copyright (C) 2018 The Android Open Source Project
- <path
- android:strokeWidth="1"
- android:pathData="M 0 24 L 24 24 L 24 -1.13686838e-13 L 0 -1.13686838e-13 Z" />
- <path
- android:fillColor="#000000"
- android:strokeWidth="1"
- android:pathData="M14,7.57142857 C14,6.466 13.1058571,5.57142857 12.0022857,5.57142857
-C10.899,5.57142857 10.0038571,6.466 10.0038571,7.57142857 L10.0038571,7.73857143
-L14,11.7251429 C14.0038571,11.6742857 14,7.57142857 14,7.57142857" />
- <path
- android:fillColor="#000000"
- android:strokeWidth="1"
- android:pathData="M10.0039286,9.53624286 L6.72921429,6.25838571 L5.86507143,7.1021
-L10.0039286,11.2238143 L10.0039286,11.5713857 C10.0039286,12.6756714
-10.8989286,13.5713857 12.0023571,13.5713857 C12.1112143,13.5713857
-12.2172143,13.5603857 12.3213571,13.5435286 L17.1650714,18.3919571
-L18.0079286,17.5482429 L13.4299286,12.9656714 L13.4299286,12.9656714
-L10.0039286,9.53624286 Z" />
- <path
- android:fillColor="#000000"
- android:strokeWidth="1"
- android:pathData="M16.7016571,11.7142857 L15.5582286,11.7142857 C15.5582286,12.1917143
-15.4619429,12.6338571 15.2916571,13.0315714 L16.1336571,13.8744286
-C16.4940857,13.2327143 16.7016571,12.4977143 16.7016571,11.7142857" />
- <path
- android:fillColor="#000000"
- android:strokeWidth="1"
- android:pathData="M11.9969857,15.1317857 C10.1408429,15.1317857 8.4347,13.7325 8.4347,11.7142143
-L7.29227143,11.7142143 C7.29227143,14.0117857 9.08298571,15.8945
-11.2884143,16.2205 L11.2884143,18.4285 L12.7155571,18.4285 L12.7155571,16.2205
-C13.7292714,16.0705 14.6529857,15.5907857 15.3558429,14.8933571
-L14.5601286,14.0969286 C13.8944143,14.7493571 12.9665571,15.1317857
-11.9969857,15.1317857" />
- <path
- android:fillColor="#000000"
- android:strokeWidth="1"
- android:pathData="M12,0 C5.37257143,0 0,5.37257143 0,12 C0,18.6274286 5.37257143,24 12,24
-C18.6274286,24 24,18.6274286 24,12 C24,5.37257143 18.6274286,0 12,0
-M12,1.28571429 C17.9078571,1.28571429 22.7142857,6.09214286 22.7142857,12
-C22.7142857,17.9078571 17.9078571,22.7142857 12,22.7142857
-C6.09214286,22.7142857 1.28571429,17.9078571 1.28571429,12
-C1.28571429,6.09214286 6.09214286,1.28571429 12,1.28571429" />
-</vector> \ No newline at end of file
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item
+ android:drawable="@drawable/button_active_state_ring"/>
+ <item
+ android:drawable="@drawable/ic_mute_call_normal"/>
+</layer-list>
diff --git a/res/drawable/ic_mute_call_normal.xml b/res/drawable/ic_mute_call_normal.xml
index 3aeebc29..78163878 100644
--- a/res/drawable/ic_mute_call_normal.xml
+++ b/res/drawable/ic_mute_call_normal.xml
@@ -1,41 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="72dp"
- android:height="72dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+<!-- Copyright (C) 2018 The Android Open Source Project
- <path
- android:strokeWidth="1"
- android:pathData="M 0 24 L 24 24 L 24 -1.13686838e-13 L 0 -1.13686838e-13 Z" />
- <path
- android:fillColor="#000000"
- android:strokeWidth="1"
- android:pathData="M14,7.57142857 C14,6.466 13.1058571,5.57142857 12.0022857,5.57142857
-C10.899,5.57142857 10.0038571,6.466 10.0038571,7.57142857 L10.0038571,7.73857143
-L14,11.7251429 C14.0038571,11.6742857 14,7.57142857 14,7.57142857" />
- <path
- android:fillColor="#000000"
- android:strokeWidth="1"
- android:pathData="M10.0039286,9.53624286 L6.72921429,6.25838571 L5.86507143,7.1021
-L10.0039286,11.2238143 L10.0039286,11.5713857 C10.0039286,12.6756714
-10.8989286,13.5713857 12.0023571,13.5713857 C12.1112143,13.5713857
-12.2172143,13.5603857 12.3213571,13.5435286 L17.1650714,18.3919571
-L18.0079286,17.5482429 L13.4299286,12.9656714 L13.4299286,12.9656714
-L10.0039286,9.53624286 Z" />
- <path
- android:fillColor="#000000"
- android:strokeWidth="1"
- android:pathData="M16.7016571,11.7142857 L15.5582286,11.7142857 C15.5582286,12.1917143
-15.4619429,12.6338571 15.2916571,13.0315714 L16.1336571,13.8744286
-C16.4940857,13.2327143 16.7016571,12.4977143 16.7016571,11.7142857" />
- <path
- android:fillColor="#000000"
- android:strokeWidth="1"
- android:pathData="M11.9969857,15.1317857 C10.1408429,15.1317857 8.4347,13.7325 8.4347,11.7142143
-L7.29227143,11.7142143 C7.29227143,14.0117857 9.08298571,15.8945
-11.2884143,16.2205 L11.2884143,18.4285 L12.7155571,18.4285 L12.7155571,16.2205
-C13.7292714,16.0705 14.6529857,15.5907857 15.3558429,14.8933571
-L14.5601286,14.0969286 C13.8944143,14.7493571 12.9665571,15.1317857
-11.9969857,15.1317857" />
-</vector> \ No newline at end of file
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 android:height="44dp" android:viewportHeight="48.0"
+ android:viewportWidth="48.0" android:width="44dp"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#000000"
+ android:pathData="M38,22h-3.4c0,1.49 -0.31,2.87 -0.87,4.1l2.46,2.46C37.33,26.61 38,24.38 38,22zM29.97,22.33c0,-0.11 0.03,-0.22 0.03,-0.33L30,10c0,-3.32 -2.69,-6 -6,-6s-6,2.68 -6,6v0.37l11.97,11.96zM8.55,6L6,8.55l12.02,12.02v1.44c0,3.31 2.67,6 5.98,6 0.45,0 0.88,-0.06 1.3,-0.15l3.32,3.32c-1.43,0.66 -3,1.03 -4.62,1.03 -5.52,0 -10.6,-4.2 -10.6,-10.2L10,22.01c0,6.83 5.44,12.47 12,13.44L22,42h4v-6.56c1.81,-0.27 3.53,-0.9 5.08,-1.81L39.45,42 42,39.46 8.55,6z"/>
+</vector>
diff --git a/res/drawable/ic_pause.xml b/res/drawable/ic_pause.xml
new file mode 100644
index 00000000..515720e1
--- /dev/null
+++ b/res/drawable/ic_pause.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<vector android:height="44dp" android:viewportHeight="48.0"
+ android:viewportWidth="48.0" android:width="44dp"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#000000"
+ android:pathData="M12,38h8L20,10h-8v28zM28,10v28h8L36,10h-8z"/>
+</vector>
diff --git a/res/drawable/ic_phone.xml b/res/drawable/ic_phone.xml
index 77172dcd..56db0642 100644
--- a/res/drawable/ic_phone.xml
+++ b/res/drawable/ic_phone.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="56dp"
- android:height="56dp"
+ android:width="44dp"
+ android:height="44dp"
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/res/drawable/ic_play.xml b/res/drawable/ic_play.xml
new file mode 100644
index 00000000..d0c77c1e
--- /dev/null
+++ b/res/drawable/ic_play.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<vector android:height="44dp" android:viewportHeight="48.0"
+ android:viewportWidth="48.0" android:width="44dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#000000" android:pathData="M16,10v28l22,-14z"/>
+</vector>
diff --git a/res/drawable/ic_search.xml b/res/drawable/ic_search.xml
index f0cdc700..b85f8a5f 100644
--- a/res/drawable/ic_search.xml
+++ b/res/drawable/ic_search.xml
@@ -14,8 +14,8 @@ 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/stream_button_icon_size"
- android:height="@dimen/stream_button_icon_size"
+ android:width="@dimen/car_primary_icon_size"
+ android:height="@dimen/car_primary_icon_size"
android:viewportWidth="48"
android:viewportHeight="48">
diff --git a/res/drawable/ic_smartphone.xml b/res/drawable/ic_smartphone.xml
new file mode 100644
index 00000000..a15382d7
--- /dev/null
+++ b/res/drawable/ic_smartphone.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="44dp"
+ android:height="44dp">
+ <path
+ android:pathData="M16 1L8 1C6.34 1 5 2.34 5 4l0 16c0 1.66 1.34 3 3 3l8 0c1.66 0 3 -1.34 3 -3L19 4C19 2.34 17.66 1 16 1Zm-2 20l-4 0 0 -1 4 0 0 1zm3.25 -3l-10.5 0 0 -14 10.5 0 0 14z"
+ android:fillColor="#000000" />
+</vector> \ No newline at end of file
diff --git a/res/drawable/ic_speaker_phone.xml b/res/drawable/ic_speaker_phone.xml
new file mode 100644
index 00000000..ed96b698
--- /dev/null
+++ b/res/drawable/ic_speaker_phone.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="44dp"
+ android:height="44dp">
+ <path
+ android:pathData="M7 7.07L8.43 8.5C9.34 7.59 10.61 7.02 12 7.02c1.39 0 2.66 0.57 3.57 1.48L17 7.07C15.72 5.79 13.95 5 12 5 10.05 5 8.28 5.79 7 7.07ZM12 1C8.98 1 6.24 2.23 4.25 4.21L5.66 5.62C7.28 4 9.53 3 12 3c2.47 0 4.72 1 6.34 2.62L19.75 4.21C17.76 2.23 15.02 1 12 1ZM14.86 10.01L9.14 10C8.51 10 8 10.51 8 11.14l0 9.71c0 0.63 0.51 1.14 1.14 1.14l5.71 0c0.63 0 1.14 -0.51 1.14 -1.14l0 -9.71C16 10.51 15.49 10.01 14.86 10.01ZM15 20l-6 0 0 -8 6 0 0 8z"
+ android:fillColor="#000000" />
+</vector> \ No newline at end of file
diff --git a/res/drawable/in_call_card_background.xml b/res/drawable/in_call_card_background.xml
index db61e98e..f1e45400 100644
--- a/res/drawable/in_call_card_background.xml
+++ b/res/drawable/in_call_card_background.xml
@@ -1,3 +1,18 @@
+<?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.
+-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/car_card" />
<corners
diff --git a/res/drawable/ongoing_call_secondary_action_background.xml b/res/drawable/ongoing_call_secondary_action_background.xml
index c0ce13a6..3b3c3caa 100644
--- a/res/drawable/ongoing_call_secondary_action_background.xml
+++ b/res/drawable/ongoing_call_secondary_action_background.xml
@@ -16,5 +16,5 @@
<inset
xmlns:android="http://schemas.android.com/apk/res/android"
android:inset="32dp" >
- <ripple android:color="@color/car_card_ripple_light_color_background" />
+ <ripple android:color="@color/car_card_ripple_background_dark" />
</inset>
diff --git a/res/layout-h720dp/dialer_fragment.xml b/res/layout-h720dp/dialer_fragment.xml
deleted file mode 100644
index e0980994..00000000
--- a/res/layout-h720dp/dialer_fragment.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<!-- There seems to be a bug in layout inflation where it can't use a resource to inflate a view
- group that sets layout_marginTop with a dimension. Work around by putting in a shell layout.
--->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:card_view="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <!-- This CardView is clickable so that clicks do not fall through to the fragment that
- is underneath the dialer_fragment. -->
- <android.support.v7.widget.CardView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/lens_header_height"
- android:clickable="true"
- card_view:cardBackgroundColor="@color/car_card"
- card_view:cardElevation="@dimen/dialer_card_elevation">
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TextView
- android:id="@+id/number"
- android:layout_width="match_parent"
- android:layout_height="@dimen/dialer_number_view_height"
- android:paddingTop="@dimen/dialer_number_view_padding"
- android:paddingBottom="@dimen/dialer_number_view_padding"
- android:gravity="center"
- android:focusable="true"
- android:text="@string/dial_a_number"
- android:layout_alignParentTop="true"
- style="@style/CarBody1" />
-
- <View
- android:id="@+id/line_divider"
- android:background="@color/car_list_divider"
- android:layout_width="match_parent"
- android:layout_height="@dimen/line_divider_height"
- android:layout_marginLeft="@dimen/stream_content_keyline_1"
- android:layout_marginRight="@dimen/stream_content_keyline_1"
- android:layout_below="@id/number" />
-
- <LinearLayout
- android:orientation="horizontal"
- android:layout_marginTop="@dimen/dial_container_vertical_margin"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/line_divider" >
-
- <FrameLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1" >
-
- <ImageButton
- android:id="@+id/call"
- android:scaleType="center"
- android:src="@drawable/ic_phone"
- style="@style/DialpadCall"
- android:elevation="@dimen/call_fab_elevation"
- android:layout_gravity="center" />
- </FrameLayout>
-
- <include
- android:layout_gravity="center"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- layout="@layout/dialpad" />
-
- <FrameLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1" >
-
- <ImageButton
- android:id="@+id/delete"
- android:layout_width="@dimen/bksp_button_width"
- android:layout_height="@dimen/bksp_button_width"
- android:scaleType="centerInside"
- android:src="@drawable/ic_backspace"
- android:tint="@color/car_tint"
- android:background="@drawable/dialpad_delete_button_background"
- android:layout_gravity="center" />
- </FrameLayout>
- </LinearLayout>
- </RelativeLayout>
-
- <!-- This FrameLayout ensures that the back button is centered within the
- exit_dialer_button despite the button's touch target
- being smaller. -->
- <FrameLayout
- android:layout_gravity="start|top"
- android:layout_width="@dimen/stream_content_keyline_1"
- android:layout_height="@dimen/stream_content_keyline_1">
-
- <ImageView
- android:id="@+id/exit_dialer_button"
- android:background="@drawable/dialpad_button_background"
- android:layout_gravity="center"
- android:layout_width="@dimen/stream_button_size"
- android:layout_height="@dimen/stream_button_size"
- android:scaleType="center"
- android:tint="@color/car_tint"
- android:src="@drawable/ic_down_outlined" />
- </FrameLayout>
- </android.support.v7.widget.CardView>
-</FrameLayout>
diff --git a/res/layout-port/dialer_fragment.xml b/res/layout-port/dialer_fragment.xml
new file mode 100644
index 00000000..8b042061
--- /dev/null
+++ b/res/layout-port/dialer_fragment.xml
@@ -0,0 +1,106 @@
+<?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.
+-->
+<!-- There seems to be a bug in layout inflation where it can't use a resource to inflate a view
+ group that sets layout_marginTop with a dimension. Work around by putting in a shell layout.
+-->
+<FrameLayout
+ 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="match_parent"
+ android:paddingTop="@dimen/car_app_bar_height">
+ <android.support.constraint.ConstraintLayout
+ android:id="@+id/dialer_info_fragment_container"
+ android:paddingLeft="@dimen/car_margin"
+ android:paddingRight="@dimen/car_margin"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/dialpad_fragment_container"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.Car.Headline2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/car_padding_4"
+ android:gravity="center"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/dial_a_number"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <TextView
+ android:id="@+id/body"
+ style="@style/TextAppearance.Car.Body1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/car_padding_3"
+ android:gravity="center"
+ android:visibility="gone"
+ android:maxLines="1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/title"/>
+
+ <ImageButton
+ android:id="@+id/call_button"
+ style="@style/DialpadPrimaryButton"
+ android:src="@drawable/ic_phone"
+ android:layout_marginBottom="@dimen/car_padding_4"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/delete_button"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <ImageButton
+ android:id="@+id/delete_button"
+ style="@style/DialpadSecondaryButton"
+ android:layout_marginBottom="@dimen/car_padding_4"
+ android:layout_marginStart="@dimen/car_padding_6"
+ android:src="@drawable/ic_backspace"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/call_button"/>
+
+ <ImageButton
+ android:id="@+id/end_call_button"
+ style="@style/DialpadPrimaryButton"
+ android:layout_marginBottom="@dimen/car_padding_4"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/mute_button"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <ImageButton
+ android:id="@+id/mute_button"
+ style="@style/DialpadSecondaryButton"
+ android:layout_marginBottom="@dimen/car_padding_4"
+ android:layout_marginStart="@dimen/car_padding_6"
+ android:src="@drawable/ic_mute_call_normal"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/end_call_button"/>
+ </android.support.constraint.ConstraintLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout-port/in_call_fragment.xml b/res/layout-port/in_call_fragment.xml
new file mode 100644
index 00000000..11808a43
--- /dev/null
+++ b/res/layout-port/in_call_fragment.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+<android.support.constraint.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="match_parent"
+ android:background="@color/phone_theme_secondary"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/dialer_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <FrameLayout
+ android:id="@+id/user_profile_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toTopOf="@+id/controller_bar_container"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent">
+ <include layout="@layout/user_profile_large"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/controller_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_action_bar_height"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+</android.support.constraint.ConstraintLayout
+> \ No newline at end of file
diff --git a/res/layout/audio_route_list_item.xml b/res/layout/audio_route_list_item.xml
new file mode 100644
index 00000000..79d6e7c4
--- /dev/null
+++ b/res/layout/audio_route_list_item.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+<android.support.constraint.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/car_action_bar_height"
+ android:background="@color/phone_theme"
+ android:elevation="@dimen/in_call_card_elevation">
+ <android.support.constraint.Guideline
+ android:id="@+id/text_start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="124dp"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/car_app_icon_size"
+ android:layout_height="@dimen/car_app_icon_size"
+ android:scaleType="fitCenter"
+ android:tint="@color/primary_icon_color"
+ 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"
+ style="@style/TextAppearance.Car.Body1"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="@+id/text_start"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+</android.support.constraint.ConstraintLayout>
diff --git a/res/layout/audio_route_switch_dialog.xml b/res/layout/audio_route_switch_dialog.xml
new file mode 100644
index 00000000..6f89ce76
--- /dev/null
+++ b/res/layout/audio_route_switch_dialog.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<android.support.v7.widget.CardView
+ 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="match_parent"
+ app:cardBackgroundColor="@color/phone_theme"
+ app:contentPadding="@dimen/car_keyline_1"
+ app:cardCornerRadius="@dimen/car_radius_3"
+ app:cardElevation="@dimen/car_action_bar_elevation">
+
+ <androidx.car.widget.PagedListView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ app:scrollBarEnabled="false"
+ app:gutter="none"/>
+</android.support.v7.widget.CardView> \ No newline at end of file
diff --git a/res/layout/call_history_list_item.xml b/res/layout/call_history_list_item.xml
new file mode 100644
index 00000000..812cb902
--- /dev/null
+++ b/res/layout/call_history_list_item.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+<android.support.constraint.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/car_action_bar_height"
+ android:background="@color/phone_theme"
+ android:paddingRight="@dimen/car_keyline_1"
+ android:paddingLeft="@dimen/car_keyline_1"
+ android:elevation = "@dimen/in_call_card_elevation">
+
+ <android.support.constraint.Guideline
+ android:id="@+id/list_item_left_edge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="@dimen/car_keyline_1" />
+
+ <ImageView
+ android:id="@+id/avatar"
+ android:layout_width="@dimen/car_avatar_size"
+ android:layout_height="@dimen/car_avatar_size"
+ android:scaleType="center"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/toggle_dialpad_button"
+ app:layout_constraintTop_toTopOf="parent"/>
+</android.support.constraint.ConstraintLayout>
diff --git a/res/layout/call_list_fragment.xml b/res/layout/call_list_fragment.xml
new file mode 100644
index 00000000..30086a98
--- /dev/null
+++ b/res/layout/call_list_fragment.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ 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="match_parent"
+ android:background="@color/call_list_fragment_background"
+ android:clipChildren="false">
+
+ <androidx.car.widget.PagedListView
+ android:id="@+id/list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/car_app_bar_height"
+ android:clipChildren="false" />
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout/call_log_last_call_item_card.xml b/res/layout/call_log_last_call_item_card.xml
deleted file mode 100644
index 72d568a5..00000000
--- a/res/layout/call_log_last_call_item_card.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<!-- This FrameLayout is used to center the CardView since the CardView's container will be larger
- than its width. -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/call_log_last_card_bottom_margin"
- android:focusable="true">
-
- <com.android.car.stream.ui.StreamCardView
- android:id="@+id/call_log_card"
- android:foreground="@drawable/dialer_ripple_background"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/call_log_item_height" >
-
- <include layout="@layout/call_log_list_item_card_base" />
- </com.android.car.stream.ui.StreamCardView>
-</FrameLayout>
diff --git a/res/layout/call_log_list_item_card.xml b/res/layout/call_log_list_item_card.xml
index 5da3fdf4..f836fff0 100644
--- a/res/layout/call_log_list_item_card.xml
+++ b/res/layout/call_log_list_item_card.xml
@@ -13,22 +13,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- This FrameLayout is used to center the CardView since the CardView's container will be larger
- than its width. -->
-<FrameLayout
+<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/call_log_card"
+ android:foreground="@drawable/dialer_ripple_background"
+ android:layout_gravity="center"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/call_log_card_overlap"
- android:focusable="true">
+ android:layout_height="@dimen/call_log_item_height"
+ app:cardBackgroundColor="@color/phone_theme"
+ app:cardCornerRadius="@dimen/favorite_card_corner_radius"
+ app:cardElevation="@dimen/car_action_bar_elevation">
- <com.android.car.stream.ui.StreamCardView
- android:id="@+id/call_log_card"
- android:foreground="@drawable/dialer_ripple_background"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/call_log_item_height" >
-
- <include layout="@layout/call_log_list_item_card_base" />
- </com.android.car.stream.ui.StreamCardView>
-</FrameLayout>
+ <include layout="@layout/call_log_list_item_card_base"/>
+</android.support.v7.widget.CardView>
diff --git a/res/layout/call_log_list_item_card_base.xml b/res/layout/call_log_list_item_card_base.xml
index f7c46166..6bb77eb9 100644
--- a/res/layout/call_log_list_item_card_base.xml
+++ b/res/layout/call_log_list_item_card_base.xml
@@ -18,23 +18,22 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@drawable/car_list_item_background"
android:duplicateParentState="true"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/icon_container"
- android:layout_width="@dimen/stream_card_keyline_2"
+ android:layout_width="@dimen/car_keyline_2"
android:layout_height="@dimen/icon_container_height"
android:layout_gravity="center_vertical"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="8dp" >
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="8dp" >
<ImageView
android:id="@+id/icon"
android:layout_width="@dimen/call_log_icon_size"
android:layout_height="@dimen/call_log_icon_size"
- android:layout_gravity="center_vertical|left"
- android:layout_marginLeft="@dimen/stream_card_keyline_1"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/car_keyline_1"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/small_icon"
@@ -42,7 +41,7 @@
android:layout_height="28dp"
android:padding="4dp"
android:scaleType="centerInside"
- android:layout_gravity="bottom|right"
+ android:layout_gravity="bottom|end"
android:background="@drawable/strequent_small_icon_bg"
android:visibility="gone" />
</FrameLayout>
@@ -58,13 +57,13 @@
<TextView
android:id="@+id/title"
- style="@style/CarBody1"
+ style="@style/TextAppearance.Car.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/car_text_vertical_margin"
- android:layout_marginRight="@dimen/stream_card_keyline_1"
+ android:layout_marginEnd="@dimen/car_keyline_1"
android:ellipsize="end"
- android:singleLine="true" />
+ android:maxLines="1" />
<LinearLayout
android:id="@+id/call_type"
@@ -83,7 +82,7 @@
<TextView
android:id="@+id/text"
- style="@style/CarBody2"
+ style="@style/TextAppearance.Car.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
@@ -95,7 +94,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginRight="@dimen/car_list_item_icon_right_margin"
+ android:layout_marginEnd="@dimen/car_padding_4"
android:scaleType="center"
android:visibility="gone" />
diff --git a/res/layout/call_log_list_item_empty.xml b/res/layout/call_log_list_item_empty.xml
new file mode 100644
index 00000000..88d1aa1a
--- /dev/null
+++ b/res/layout/call_log_list_item_empty.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:focusable="false"
+ android:orientation="vertical"
+ android:background="@drawable/car_list_item_background" >
+ <FrameLayout
+ android:id="@+id/icon_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="visible">
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="48dp"
+ android:layout_marginBottom="22dp" />
+ </FrameLayout>
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ style="@style/TextAppearance.Car.Body1" />
+</LinearLayout>
diff --git a/res/layout/contact_detail_card_content.xml b/res/layout/contact_detail_card_content.xml
new file mode 100644
index 00000000..3e2d8011
--- /dev/null
+++ b/res/layout/contact_detail_card_content.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_single_line_list_item_height"
+ android:layout_marginStart="@dimen/car_keyline_1"
+ android:layout_marginEnd="@dimen/car_keyline_1"
+ android:focusable="true"
+ android:backgroundTint="@color/contact_detail_card_background_color"
+ android:background="@drawable/car_list_item_background">
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/contact_detail_image_size"
+ android:layout_height="@dimen/contact_detail_image_size"
+ android:layout_gravity="center_vertical"
+ android:scaleType="centerCrop"/>
+
+ <LinearLayout
+ android:id="@+id/text_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical"
+ android:layout_marginStart="@dimen/car_keyline_2">
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/car_padding_1"
+ android:ellipsize="end"
+ android:maxLines="1"
+ style="@style/TextAppearance.Car.Body1"/>
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ style="@style/TextAppearance.Car.Body2"/>
+ </LinearLayout>
+</FrameLayout>
diff --git a/res/layout/contact_detail_name_image.xml b/res/layout/contact_detail_name_image.xml
index dc82d58d..31ff2ca5 100644
--- a/res/layout/contact_detail_name_image.xml
+++ b/res/layout/contact_detail_name_image.xml
@@ -16,47 +16,59 @@
<!-- This FrameLayout is used to center the inner layout. -->
<FrameLayout
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="wrap_content"
- android:layout_marginBottom="@dimen/call_log_card_overlap"
android:focusable="true">
<!-- Wraps everything in a card -->
- <com.android.car.stream.ui.StreamCardView
+ <androidx.car.widget.ColumnCardView
android:id="@+id/card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:backgroundTint="@color/car_grey_200"
- android:backgroundTintMode="multiply"
+ android:backgroundTint="@color/contact_detail_card_background_color"
android:layout_gravity="center">
<!-- Used to provide common margins and also allow for the textview to set its right margin
to the width of the image to allow it to ellipsize.
-->
- <FrameLayout
+ <android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginLeft="@dimen/stream_card_keyline_1"
- android:layout_marginRight="@dimen/stream_card_keyline_1"
- android:layout_marginTop="@dimen/contact_detail_vertical_margin"
- android:layout_marginBottom="@dimen/contact_detail_vertical_margin">
+ android:layout_height="@dimen/contact_detail_name_card_height"
+ android:layout_marginLeft="@dimen/car_keyline_1"
+ android:layout_marginRight="@dimen/car_keyline_1">
+
+ <ImageView
+ android:id="@+id/avatar"
+ android:layout_width="@dimen/contact_detail_image_size"
+ android:layout_height="@dimen/contact_detail_image_size"
+ android:background="@drawable/avatar_rounded_bg"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintBottom_toTopOf="@+id/title"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="bottom|left"
- android:layout_marginRight="@dimen/contact_detail_image_size"
android:singleLine="true"
android:ellipsize="end"
- style="@style/CarHeadline1" />
-
- <ImageView
- android:id="@+id/right_icon"
- android:layout_width="@dimen/contact_detail_image_size"
- android:layout_height="@dimen/contact_detail_image_size"
- android:background="@drawable/avatar_rounded_bg"
- android:layout_gravity="right" />
- </FrameLayout>
- </com.android.car.stream.ui.StreamCardView>
+ android:layout_marginTop="@dimen/car_padding_4"
+ style="@style/TextAppearance.Car.Body1"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/avatar"/>
+ </android.support.constraint.ConstraintLayout>
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_list_divider_height"
+ android:layout_marginStart="@dimen/car_keyline_1"
+ android:layout_marginEnd="@dimen/car_keyline_1"
+ android:background="@color/car_list_divider"
+ android:layout_gravity="bottom"/>
+ </androidx.car.widget.ColumnCardView>
</FrameLayout>
diff --git a/res/layout/contact_details.xml b/res/layout/contact_details.xml
index 5a85fec7..602f5729 100644
--- a/res/layout/contact_details.xml
+++ b/res/layout/contact_details.xml
@@ -18,13 +18,14 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:background="@color/phone_theme_secondary"
android:clickable="true"
android:clipChildren="false">
- <com.android.car.view.PagedListView
+ <androidx.car.widget.PagedListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
- app:showDivider="false" />
+ app:showPagedListViewDivider="false" />
</FrameLayout>
diff --git a/res/layout/contact_details_number.xml b/res/layout/contact_details_number.xml
index 928d789e..94c49dbb 100644
--- a/res/layout/contact_details_number.xml
+++ b/res/layout/contact_details_number.xml
@@ -18,21 +18,23 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/call_log_card_overlap"
android:focusable="true">
- <com.android.car.stream.ui.StreamCardView
+ <androidx.car.widget.ColumnCardView
android:id="@+id/card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
- <include layout="@layout/car_list_item_2" />
+ <include layout="@layout/contact_detail_card_content"/>
+
<View
+ android:id="@+id/divider"
android:layout_width="match_parent"
- android:layout_height="@dimen/car_divider_height"
+ android:layout_height="@dimen/car_list_divider_height"
+ android:layout_marginStart="@dimen/car_keyline_1"
+ android:layout_marginEnd="@dimen/car_keyline_1"
android:background="@color/car_list_divider"
- android:layout_gravity="bottom"
- android:layout_marginLeft="@dimen/stream_card_keyline_2" />
- </com.android.car.stream.ui.StreamCardView>
+ android:layout_gravity="bottom"/>
+ </androidx.car.widget.ColumnCardView>
</FrameLayout>
diff --git a/res/layout/contact_list_fragment.xml b/res/layout/contact_list_fragment.xml
new file mode 100644
index 00000000..1df07e2c
--- /dev/null
+++ b/res/layout/contact_list_fragment.xml
@@ -0,0 +1,74 @@
+<?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.
+-->
+<FrameLayout
+ 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="match_parent"
+ android:background="@color/contact_fragment_background"
+ android:clipChildren="false">
+
+ <androidx.car.widget.PagedListView
+ android:id="@+id/list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/car_app_bar_height"
+ android:clipChildren="false"/>
+
+ <FrameLayout
+ android:id="@+id/contact_detail_container"
+ android:background="@color/contact_detail_fragment_background_color"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone">
+ <android.support.constraint.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_app_bar_height"
+ android:background="@color/phone_theme_secondary">
+ <ImageView
+ android:id="@+id/back_button"
+ android:layout_width="@dimen/car_touch_target_size"
+ android:layout_height="@dimen/car_touch_target_size"
+ android:scaleType="center"
+ android:src="@drawable/ic_arrow_back"
+ android:tint="@color/car_tint"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="@+id/margin_guideline"
+ app:layout_constraintTop_toTopOf="parent"/>
+ <android.support.constraint.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/margin_guideline"
+ app:layout_constraintGuide_begin="@dimen/car_margin"
+ android:orientation="vertical"/>
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.Car.Body1"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/back_button"
+ app:layout_constraintTop_toTopOf="parent"/>
+ </android.support.constraint.ConstraintLayout>
+ <FrameLayout
+ android:id="@+id/contact_detail_fragment_container"
+ android:layout_marginTop="@dimen/car_app_bar_height"
+ android:background="@android:color/transparent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </FrameLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout/contact_result.xml b/res/layout/contact_result.xml
index 4b5f9140..ca36bb59 100644
--- a/res/layout/contact_result.xml
+++ b/res/layout/contact_result.xml
@@ -21,7 +21,7 @@
android:layout_height="wrap_content"
android:focusable="true">
- <com.android.car.stream.ui.StreamCardView
+ <androidx.car.widget.ColumnCardView
android:id="@+id/contact_result_card"
android:foreground="@drawable/dialer_ripple_background"
android:layout_gravity="center"
@@ -29,9 +29,9 @@
android:layout_height="@dimen/contact_result_card_height" >
<!-- Using this FrameLayout to center the ImageView within a width of
- stream_card_keyline_2. -->
+ car_keyline_2. -->
<FrameLayout
- android:layout_width="@dimen/stream_card_keyline_2"
+ android:layout_width="@dimen/car_keyline_2"
android:layout_height="match_parent">
<ImageView
@@ -44,12 +44,12 @@
<TextView
android:id="@+id/contact_name"
- android:layout_marginStart="@dimen/stream_card_keyline_2"
+ android:layout_marginStart="@dimen/car_keyline_2"
android:layout_gravity="center_vertical|start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
- style="@style/CarBody1" />
- </com.android.car.stream.ui.StreamCardView>
+ style="@style/TextAppearance.Car.Body1" />
+ </androidx.car.widget.ColumnCardView>
</FrameLayout>
diff --git a/res/layout/contact_result_fragment.xml b/res/layout/contact_result_fragment.xml
index 8b537dd2..3dcb87d1 100644
--- a/res/layout/contact_result_fragment.xml
+++ b/res/layout/contact_result_fragment.xml
@@ -19,13 +19,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.android.car.view.PagedListView
+ <androidx.car.widget.PagedListView
android:id="@+id/contact_result_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
- app:offsetRows="true"
- app:showDivider="true"
- app:dividerStartMargin="@dimen/stream_card_keyline_2"
+ app:showPagedListViewDivider="true"
+ app:dividerStartMargin="@dimen/car_keyline_2"
app:alignDividerEndTo="@id/contact_result_card"
app:alignDividerStartTo="@id/contact_result_card" />
</FrameLayout>
diff --git a/res/layout/contact_search_activity.xml b/res/layout/contact_search_activity.xml
index a9584baa..b1302069 100644
--- a/res/layout/contact_search_activity.xml
+++ b/res/layout/contact_search_activity.xml
@@ -33,7 +33,7 @@
android:id="@+id/search_container"
android:background="@color/phone_theme"
android:layout_width="match_parent"
- android:layout_height="@dimen/lens_header_height" >
+ android:layout_height="@dimen/car_app_bar_height" >
<FrameLayout
android:background="@color/telecom_display_scrim"
@@ -41,23 +41,23 @@
android:layout_height="match_parent" />
<!-- This FrameLayout is used to center the ImageView in a space that is the width of
- stream_content_keyline_1. -->
+ car_keyline_1. -->
<FrameLayout
- android:layout_width="@dimen/stream_content_keyline_1"
+ android:layout_width="@dimen/car_keyline_1"
android:layout_height="match_parent">
<ImageView
android:id="@+id/back"
android:background="@drawable/dialer_ripple_background"
android:layout_gravity="center"
- android:layout_width="@dimen/stream_button_size"
- android:layout_height="@dimen/stream_button_size"
+ android:layout_width="@dimen/car_touch_target_size"
+ android:layout_height="@dimen/car_touch_target_size"
android:src="@drawable/ic_arrow_back"
android:scaleType="center"
android:tint="@color/car_grey_50" />
</FrameLayout>
- <com.android.car.stream.ui.StreamCardView
+ <androidx.car.widget.ColumnCardView
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="@dimen/search_container_height"
@@ -67,39 +67,39 @@
<ImageView
android:layout_gravity="start|center_vertical"
- android:layout_width="@dimen/stream_button_size"
- android:layout_height="@dimen/stream_button_size"
+ android:layout_width="@dimen/car_touch_target_size"
+ android:layout_height="@dimen/car_touch_target_size"
android:src="@drawable/ic_search"
android:scaleType="center"
android:tint="@color/search_container_controls_tint"/>
<EditText
android:id="@+id/search_field"
- android:layout_marginStart="@dimen/stream_button_size"
- android:layout_marginEnd="@dimen/stream_button_size"
+ android:layout_marginStart="@dimen/car_touch_target_size"
+ android:layout_marginEnd="@dimen/car_touch_target_size"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName|textCapWords"
android:hint="@string/search_hint"
android:textColorHint="@color/search_container_text_hint"
- style="@style/CarBody1" />
+ style="@style/TextAppearance.Car.Body1" />
<ImageView
android:id="@+id/clear"
android:background="@drawable/dialer_ripple_background"
android:layout_gravity="end|center_vertical"
- android:layout_width="@dimen/stream_button_size"
- android:layout_height="@dimen/stream_button_size"
+ android:layout_width="@dimen/car_touch_target_size"
+ android:layout_height="@dimen/car_touch_target_size"
android:src="@drawable/ic_cancel"
android:scaleType="center"
android:tint="@color/search_container_controls_tint"/>
- </com.android.car.stream.ui.StreamCardView>
+ </androidx.car.widget.ColumnCardView>
</FrameLayout>
<FrameLayout
android:id="@+id/content_fragment_container"
- android:layout_marginTop="@dimen/lens_header_height"
+ android:layout_marginTop="@dimen/car_app_bar_height"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
diff --git a/res/layout/dialer_fragment.xml b/res/layout/dialer_fragment.xml
index c3c432f8..495d4d32 100644
--- a/res/layout/dialer_fragment.xml
+++ b/res/layout/dialer_fragment.xml
@@ -18,100 +18,46 @@
-->
<FrameLayout
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="match_parent"
- android:paddingTop="@dimen/lens_header_height">
+ android:paddingTop="@dimen/car_app_bar_height">
- <!-- This LinearLayout is clickable so that clicks do not fall through to the fragment that
- is underneath the dialer_fragment. -->
- <LinearLayout
- android:background="@color/car_card"
- android:clickable="true"
+ <android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingTop="@dimen/dial_container_vertical_margin"
- android:orientation="horizontal" >
+ android:layout_height="match_parent">
<FrameLayout
- android:layout_marginTop="@dimen/dialer_dialpad_top_margin"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1" >
-
- <!-- This FrameLayout ensures that the back button is centered within the
- exit_dialer_button despite the button's touch target
- being smaller. -->
- <FrameLayout
- android:layout_marginTop="@dimen/dialer_back_button_top_margin"
- android:layout_gravity="start|top"
- android:layout_width="@dimen/stream_content_keyline_1"
- android:layout_height="@dimen/stream_content_keyline_1">
-
- <ImageView
- android:id="@+id/exit_dialer_button"
- android:background="@drawable/dialpad_button_background"
- android:layout_gravity="center"
- android:layout_width="@dimen/stream_button_size"
- android:layout_height="@dimen/stream_button_size"
- android:scaleType="center"
- android:tint="@color/car_tint"
- android:src="@drawable/ic_down_outlined" />
- </FrameLayout>
+ android:id="@+id/dialpad_fragment_container"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/divider"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <View
+ android:id="@+id/divider"
+ android:background="@color/car_list_divider"
+ android:layout_gravity="end|top"
+ android:layout_height="0dp"
+ android:layout_width="@dimen/line_divider_height"
+ app:layout_constraintBottom_toBottomOf="@+id/dialpad_fragment_container"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/dialpad_fragment_container"/>
- <include
- android:layout_gravity="center_horizontal"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- layout="@layout/dialpad" />
+ <FrameLayout
+ android:id="@+id/dialer_info_fragment_container"
+ android:layout_height="0dp"
+ android:layout_width="0dp"
+ android:paddingStart="@dimen/car_keyline_1"
+ android:paddingEnd="@dimen/car_keyline_1"
+ app:layout_constraintBottom_toBottomOf="@+id/dialpad_fragment_container"
+ app:layout_constraintStart_toEndOf="@+id/divider"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/dialpad_fragment_container">
+ <include layout="@layout/dialer_info_fragment"/>
</FrameLayout>
-
- <RelativeLayout
- android:layout_width="@dimen/dialer_dialed_number_container"
- android:layout_height="match_parent"
- android:paddingStart="@dimen/stream_content_keyline_1"
- android:paddingEnd="@dimen/stream_content_keyline_1"
- android:paddingBottom="@dimen/dialer_dialed_number_bottom_padding">
-
- <TextView
- android:id="@+id/number"
- android:layout_width="match_parent"
- android:layout_height="@dimen/dialer_number_view_height"
- android:layout_alignParentTop="true"
- android:layout_alignParentStart="true"
- android:paddingTop="@dimen/dialer_number_view_padding"
- android:paddingBottom="@dimen/dialer_number_view_padding"
- android:gravity="center"
- android:focusable="true"
- android:text="@string/dial_a_number"
- style="@style/CarBody1" />
-
- <ImageButton
- android:id="@+id/call"
- android:scaleType="center"
- android:src="@drawable/ic_phone"
- android:layout_alignParentBottom="true"
- android:layout_centerHorizontal="true"
- android:elevation="@dimen/call_fab_elevation"
- style="@style/DialpadCall" />
-
- <ImageButton
- android:id="@+id/delete"
- android:layout_alignParentBottom="true"
- android:layout_width="@dimen/bksp_button_width"
- android:layout_height="@dimen/bksp_button_width"
- android:layout_toEndOf="@id/call"
- android:layout_marginStart="16dp"
- android:scaleType="centerInside"
- android:src="@drawable/ic_backspace"
- android:tint="@color/car_tint"
- android:background="@drawable/dialpad_delete_button_background" />
- </RelativeLayout>
- </LinearLayout>
-
- <View
- android:background="@color/car_list_divider"
- android:layout_gravity="end|top"
- android:layout_height="match_parent"
- android:layout_width="@dimen/line_divider_height"
- android:layout_marginEnd="@dimen/dialer_dialed_number_container" />
+ </android.support.constraint.ConstraintLayout>
</FrameLayout>
diff --git a/res/layout/dialer_info_fragment.xml b/res/layout/dialer_info_fragment.xml
new file mode 100644
index 00000000..1f900f61
--- /dev/null
+++ b/res/layout/dialer_info_fragment.xml
@@ -0,0 +1,91 @@
+<?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.
+-->
+<android.support.constraint.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="match_parent">
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.Car.Headline2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/car_padding_4"
+ android:paddingRight="@dimen/car_padding_4"
+ android:paddingBottom="@dimen/dialer_number_view_padding"
+ android:gravity="center"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/dial_a_number"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <TextView
+ android:id="@+id/body"
+ style="@style/TextAppearance.Car.Body1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/car_padding_4"
+ android:paddingRight="@dimen/car_padding_4"
+ android:paddingTop="@dimen/car_padding_3"
+ android:gravity="center"
+ android:visibility="gone"
+ android:maxLines="1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/title"/>
+
+ <ImageButton
+ android:id="@+id/call_button"
+ style="@style/DialpadPrimaryButton"
+ android:src="@drawable/ic_phone"
+ android:backgroundTint="@color/phone_call"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/delete_button"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <ImageButton
+ android:id="@+id/delete_button"
+ style="@style/DialpadSecondaryButton"
+ android:layout_marginStart="@dimen/car_padding_6"
+ android:src="@drawable/ic_backspace"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/call_button"/>
+
+ <ImageButton
+ android:id="@+id/end_call_button"
+ style="@style/DialpadPrimaryButton"
+ android:src="@drawable/ic_call_end"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/mute_button"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <ImageButton
+ android:id="@+id/mute_button"
+ style="@style/DialpadSecondaryButton"
+ android:layout_marginStart="@dimen/car_padding_6"
+ android:src="@drawable/ic_mute_call_normal"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/end_call_button"/>
+</android.support.constraint.ConstraintLayout> \ No newline at end of file
diff --git a/res/layout/dialpad_button.xml b/res/layout/dialpad_button.xml
index 132f5ea6..993953a7 100644
--- a/res/layout/dialpad_button.xml
+++ b/res/layout/dialpad_button.xml
@@ -27,7 +27,7 @@ limitations under the License.
android:layout_height="wrap_content"
android:layout_marginBottom="-8dp"
android:visibility="gone"
- style="@style/CarKey1" />
+ style="@style/TextAppearance.Car.Key1" />
<TextView
android:id="@+id/dialpad_letters"
@@ -36,7 +36,7 @@ limitations under the License.
android:layout_height="wrap_content"
android:textAllCaps="true"
android:visibility="gone"
- style="@style/CarKey2" />
+ style="@style/TextAppearance.Car.Key2" />
<ImageView
android:id="@+id/dialpad_image"
diff --git a/res/layout/in_call_fragment.xml b/res/layout/in_call_fragment.xml
new file mode 100644
index 00000000..08a39be7
--- /dev/null
+++ b/res/layout/in_call_fragment.xml
@@ -0,0 +1,50 @@
+<?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.
+-->
+<LinearLayout
+ 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="match_parent"
+ android:background="@color/phone_theme_secondary"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/dialer_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:visibility="gone"
+ android:layout_weight="1"/>
+
+ <FrameLayout
+ android:id="@+id/user_profile_container"
+ android:layout_width="match_parent"
+ android:gravity="center"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+ <include layout="@layout/user_profile_large"/>
+ </FrameLayout>
+
+ <android.support.v7.widget.CardView
+ android:id="@+id/controller_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_action_bar_height"
+ android:background="@android:color/white"
+ android:layout_marginStart="@dimen/car_margin"
+ android:layout_marginEnd="@dimen/car_margin"
+ android:layout_marginBottom="@dimen/car_padding_1"
+ app:cardCornerRadius="@dimen/car_radius_3"
+ app:cardElevation="@dimen/car_action_bar_elevation"/>
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/on_going_call_controller_bar_fragment.xml b/res/layout/on_going_call_controller_bar_fragment.xml
new file mode 100644
index 00000000..62a430be
--- /dev/null
+++ b/res/layout/on_going_call_controller_bar_fragment.xml
@@ -0,0 +1,100 @@
+<?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.
+-->
+<android.support.constraint.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_action_bar_height"
+ android:background="@color/phone_theme"
+ android:elevation = "@dimen/in_call_card_elevation">
+ <ImageView
+ android:id="@+id/mute_button"
+ android:layout_width="@dimen/in_call_button_size"
+ android:layout_height="@dimen/in_call_button_size"
+ android:scaleType="center"
+ android:src="@drawable/ic_mute"
+ android:tint="@color/primary_icon_color"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/toggle_dialpad_button"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@color/contact_badge"/>
+
+ <ImageView
+ android:id="@+id/toggle_dialpad_button"
+ android:layout_width="@dimen/in_call_button_size"
+ android:layout_height="@dimen/in_call_button_size"
+ android:scaleType="center"
+ android:src="@drawable/ic_dialpad"
+ android:tint="@color/primary_icon_color"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/mute_button"
+ app:layout_constraintEnd_toStartOf="@+id/end_call_button"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@color/contact_badge"/>
+
+ <ImageView
+ android:id="@+id/end_call_button"
+ android:layout_width="@dimen/fab_button_size"
+ android:layout_height="@dimen/fab_button_size"
+ android:scaleType="center"
+ android:src="@drawable/ic_call_end"
+ android:tint="@color/phone_theme_light"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/toggle_dialpad_button"
+ app:layout_constraintEnd_toStartOf="@+id/voice_channel_button"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@color/contact_badge"/>
+
+ <ImageView
+ android:id="@+id/voice_channel_button"
+ android:layout_width="@dimen/in_call_button_size"
+ android:layout_height="@dimen/in_call_button_size"
+ android:scaleType="center"
+ android:src="@drawable/ic_bluetooth"
+ android:tint="@color/primary_icon_color"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/end_call_button"
+ app:layout_constraintEnd_toStartOf="@+id/pause_button"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@color/contact_badge"/>
+
+ <ImageView
+ android:id="@+id/voice_channel_chevron"
+ android:layout_width="@dimen/car_secondary_icon_size"
+ android:layout_height="@dimen/car_secondary_icon_size"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_arrow_down"
+ android:tint="@color/primary_icon_color"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/voice_channel_button"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@color/contact_badge"/>
+
+ <ImageView
+ android:id="@+id/pause_button"
+ android:layout_width="@dimen/in_call_button_size"
+ android:layout_height="@dimen/in_call_button_size"
+ android:scaleType="center"
+ android:src="@drawable/ic_call_state_switch"
+ android:tint="@color/primary_icon_color"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/voice_channel_button"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@color/contact_badge"/>
+</android.support.constraint.ConstraintLayout>
diff --git a/res/layout/ongoing_call.xml b/res/layout/ongoing_call.xml
index 3faeca5a..5d8db5ac 100644
--- a/res/layout/ongoing_call.xml
+++ b/res/layout/ongoing_call.xml
@@ -52,7 +52,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxWidth="@dimen/in_call_text_max_width"
- style="@style/CarHeadline2.Light" />
+ android:textAppearance="@style/TextAppearance.Car.Headline2"
+ android:textColor="@color/car_headline1_light" />
<TextView
android:id="@+id/info_secondary"
android:layout_width="match_parent"
@@ -61,7 +62,7 @@
android:layout_below="@id/name_secondary"
android:maxWidth="@dimen/in_call_text_max_width"
android:alpha="0.6"
- style="@style/CarBody2"
+ style="@style/TextAppearance.Car.Body2"
android:textColor="@color/car_grey_100" />
</RelativeLayout>
@@ -71,7 +72,7 @@
android:layout_width="@dimen/in_call_card_dialpad_width"
android:layout_height="match_parent"
android:layout_gravity="top|start"
- android:layout_marginTop="@dimen/lens_header_height"
+ android:layout_marginTop="@dimen/car_app_bar_height"
app:cardBackgroundColor="@color/car_card"
app:cardCornerRadius="@dimen/in_call_card_dialpad_corner_radius"
app:cardElevation="@dimen/in_call_card_elevation"
@@ -85,7 +86,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/in_call_card_background"
- android:layout_marginTop="@dimen/lens_header_height"
+ android:layout_marginTop="@dimen/car_app_bar_height"
android:layout_marginEnd="@dimen/in_call_card_margin_right"
android:paddingStart="@dimen/in_call_content_card_margin_start" >
<LinearLayout
@@ -104,7 +105,7 @@
android:ellipsize="end"
android:maxLines="2"
android:visibility="gone"
- style="@style/CarHeadline2" />
+ style="@style/TextAppearance.Car.Headline2" />
<TextView
android:id="@+id/info"
android:layout_below="@id/name"
@@ -112,7 +113,7 @@
android:layout_height="wrap_content"
android:visibility="gone"
android:textColor="@color/car_caption"
- style="@style/CarBody1" />
+ style="@style/TextAppearance.Car.Body1" />
</RelativeLayout>
<ImageView
android:id="@+id/small_contact_photo"
@@ -130,14 +131,14 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
- android:layout_marginBottom="@dimen/action_panel_height"
+ android:layout_marginBottom="@dimen/car_action_bar_height"
android:background="@color/separator" />
<!-- TODO: Consider flattening out this part and updating logic. -->
<FrameLayout
android:id="@+id/controls_container"
android:layout_width="match_parent"
- android:layout_height="@dimen/action_panel_height"
+ android:layout_height="@dimen/car_action_bar_height"
android:layout_gravity="bottom" >
<LinearLayout
@@ -148,16 +149,16 @@
android:visibility="gone" >
<ImageButton
android:id="@+id/answer_call_button"
- android:layout_width="@dimen/stream_fab_size"
- android:layout_height="@dimen/stream_fab_size"
+ android:layout_width="@dimen/dialer_fab_size"
+ android:layout_height="@dimen/dialer_fab_size"
android:layout_gravity="center_vertical"
android:scaleType="center"
android:elevation="8dp"
android:src="@drawable/ic_phone" />
<ImageButton
android:id="@+id/reject_call_button"
- android:layout_width="@dimen/stream_fab_size"
- android:layout_height="@dimen/stream_fab_size"
+ android:layout_width="@dimen/dialer_fab_size"
+ android:layout_height="@dimen/dialer_fab_size"
android:layout_marginStart="@dimen/in_call_button_spacing"
android:layout_gravity="center_vertical"
android:scaleType="center"
@@ -175,16 +176,16 @@
android:visibility="gone" >
<ImageButton
android:id="@+id/end_call"
- android:layout_width="@dimen/stream_fab_size"
- android:layout_height="@dimen/stream_fab_size"
+ android:layout_width="@dimen/dialer_fab_size"
+ android:layout_height="@dimen/dialer_fab_size"
android:layout_gravity="center_vertical"
android:scaleType="center"
android:elevation="8dp"
android:src="@drawable/ic_call_end" />
<ImageButton
android:id="@+id/unhold_call"
- android:layout_width="@dimen/stream_fab_size"
- android:layout_height="@dimen/stream_fab_size"
+ android:layout_width="@dimen/dialer_fab_size"
+ android:layout_height="@dimen/dialer_fab_size"
android:layout_gravity="center_vertical"
android:scaleType="center"
android:elevation="8dp"
@@ -192,8 +193,8 @@
android:visibility="gone" />
<ImageButton
android:id="@+id/mute"
- android:layout_width="@dimen/stream_fab_size"
- android:layout_height="@dimen/stream_fab_size"
+ android:layout_width="@dimen/dialer_fab_size"
+ android:layout_height="@dimen/dialer_fab_size"
android:layout_marginStart="@dimen/in_call_button_spacing"
android:scaleType="center"
android:src="@drawable/ic_mute"
@@ -201,8 +202,8 @@
android:background="@drawable/ongoing_call_action_background" />
<ImageButton
android:id="@+id/toggle_dialpad"
- android:layout_width="@dimen/stream_fab_size"
- android:layout_height="@dimen/stream_fab_size"
+ android:layout_width="@dimen/dialer_fab_size"
+ android:layout_height="@dimen/dialer_fab_size"
android:layout_marginStart="@dimen/in_call_button_spacing"
android:scaleType="center"
android:src="@drawable/ic_dialpad"
@@ -223,15 +224,15 @@
android:visibility="gone" >
<ImageButton
android:id="@+id/swap"
- android:layout_width="@dimen/stream_fab_size"
- android:layout_height="@dimen/stream_fab_size"
+ android:layout_width="@dimen/dialer_fab_size"
+ android:layout_height="@dimen/dialer_fab_size"
android:scaleType="center"
android:src="@drawable/ic_swap_calls"
android:background="@drawable/ongoing_call_secondary_action_background" />
<ImageButton
android:id="@+id/merge"
- android:layout_width="@dimen/stream_fab_size"
- android:layout_height="@dimen/stream_fab_size"
+ android:layout_width="@dimen/dialer_fab_size"
+ android:layout_height="@dimen/dialer_fab_size"
android:scaleType="center"
android:src="@drawable/ic_call_merge"
android:background="@drawable/ongoing_call_secondary_action_background" />
diff --git a/res/layout/ringing_call_controller_bar_fragment.xml b/res/layout/ringing_call_controller_bar_fragment.xml
new file mode 100644
index 00000000..82789807
--- /dev/null
+++ b/res/layout/ringing_call_controller_bar_fragment.xml
@@ -0,0 +1,80 @@
+<?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.
+-->
+<android.support.constraint.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/car_action_bar_height"
+ android:background="@color/phone_theme"
+ android:elevation = "@dimen/in_call_card_elevation">
+
+ <ImageView
+ android:id="@+id/answer_call_button"
+ android:layout_width="@dimen/car_touch_target_size"
+ android:layout_height="@dimen/car_touch_target_size"
+ android:scaleType="center"
+ android:src="@drawable/ic_phone"
+ android:tint="@color/phone_call"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/answer_call_text"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <TextView
+ android:id="@+id/answer_call_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:text="@string/answer_call"
+ style="@style/TextAppearance.Car.Body1"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/answer_call_button"
+ app:layout_constraintEnd_toStartOf="@+id/mid_line"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <android.support.constraint.Guideline
+ android:id="@+id/mid_line"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.5"/>
+
+ <ImageView
+ android:id="@+id/end_call_button"
+ android:layout_width="@dimen/car_touch_target_size"
+ android:layout_height="@dimen/car_touch_target_size"
+ android:scaleType="center"
+ android:src="@drawable/ic_call_end"
+ android:tint="@color/phone_end_call"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/mid_line"
+ app:layout_constraintEnd_toStartOf="@+id/end_call_text"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <TextView
+ android:id="@+id/end_call_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:text="@string/decline_call"
+ style="@style/TextAppearance.Car.Body1"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/end_call_button"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+</android.support.constraint.ConstraintLayout>
diff --git a/res/layout/strequents_fragment.xml b/res/layout/strequents_fragment.xml
index d56fe71f..2bb51333 100644
--- a/res/layout/strequents_fragment.xml
+++ b/res/layout/strequents_fragment.xml
@@ -18,14 +18,14 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/telecom_display_scrim"
+ android:background="@color/phone_theme_secondary"
android:clipChildren="false" >
- <com.android.car.view.PagedListView
+ <androidx.car.widget.PagedListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="@dimen/lens_header_height"
+ android:layout_marginTop="@dimen/car_app_bar_height"
android:clipChildren="false"
- app:showDivider="false" />
+ app:showPagedListViewDivider="false" />
</FrameLayout>
diff --git a/res/layout/telecom_activity.xml b/res/layout/telecom_activity.xml
index 50f3f147..68f83812 100644
--- a/res/layout/telecom_activity.xml
+++ b/res/layout/telecom_activity.xml
@@ -16,19 +16,20 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:background="@color/telecom_activity_background_color">
<FrameLayout
android:layout_gravity="top|end"
- android:layout_width="@dimen/stream_content_keyline_1"
- android:layout_height="@dimen/lens_header_height">
+ android:layout_width="@dimen/car_keyline_1"
+ android:layout_height="@dimen/car_app_bar_height">
<ImageView
android:id="@+id/search"
android:background="@drawable/dialer_ripple_background"
android:layout_gravity="center"
- android:layout_width="@dimen/stream_button_size"
- android:layout_height="@dimen/stream_button_size"
+ android:layout_width="@dimen/car_touch_target_size"
+ android:layout_height="@dimen/car_touch_target_size"
android:src="@drawable/ic_search"
android:scaleType="center"
android:tint="@color/car_grey_50"/>
@@ -37,6 +38,6 @@
<FrameLayout
android:id="@+id/content_fragment_container"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"/>
</FrameLayout>
diff --git a/res/layout/user_profile_large.xml b/res/layout/user_profile_large.xml
new file mode 100644
index 00000000..193b671e
--- /dev/null
+++ b/res/layout/user_profile_large.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+ <ImageView
+ android:id="@+id/avatar"
+ android:layout_width="@dimen/car_large_avatar_size"
+ android:layout_height="@dimen/car_large_avatar_size"
+ android:layout_gravity="center"
+ android:scaleType="fitCenter" />
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.Car.Headline2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/car_padding_3"
+ android:focusable="true"
+ android:maxLines="1"
+ android:gravity="center"/>
+ <TextView
+ android:id="@+id/body"
+ style="@style/TextAppearance.Car.Body2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/car_padding_3"
+ android:gravity="center"
+ android:maxLines="1"/>
+</LinearLayout> \ No newline at end of file
diff --git a/res/values-h420dp/dimens.xml b/res/values-h420dp/dimens.xml
index d2d06781..d2e19f1c 100644
--- a/res/values-h420dp/dimens.xml
+++ b/res/values-h420dp/dimens.xml
@@ -14,10 +14,8 @@
limitations under the License.
-->
<resources>
- <dimen name="call_log_item_height">152dp</dimen>
<dimen name="in_call_controls_container_height">192dp</dimen>
- <dimen name="in_call_button_size">188dp</dimen>
<dimen name="in_call_info_margin_top">72dp</dimen>
- <dimen name="in_call_card_dialpad_vertical_spacing">16dp</dimen>
+ <dimen name="in_call_card_dialpad_vertical_spacing">@dimen/car_padding_2</dimen>
<dimen name="small_contact_photo_margin_end">72dp</dimen>
</resources>
diff --git a/res/values-h480dp/dimens.xml b/res/values-h480dp/dimens.xml
new file mode 100644
index 00000000..eb97fb6b
--- /dev/null
+++ b/res/values-h480dp/dimens.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<resources>
+ <dimen name="dialer_fab_size">100dp</dimen>
+</resources>
diff --git a/res/values-h600dp/dimens.xml b/res/values-h600dp/dimens.xml
index a3024e4e..8de61922 100644
--- a/res/values-h600dp/dimens.xml
+++ b/res/values-h600dp/dimens.xml
@@ -15,5 +15,6 @@
-->
<resources>
<dimen name="avatar_rounded_radius">98dp</dimen>
- <dimen name="in_call_small_contact_photo_size">196dp</dimen>
+ <dimen name="car_key1_size">50sp</dimen>
+ <dimen name="car_key2_size">22sp</dimen>
</resources>
diff --git a/res/values-h720dp/dimens.xml b/res/values-h720dp/dimens.xml
index 8f7d091c..c97e184e 100644
--- a/res/values-h720dp/dimens.xml
+++ b/res/values-h720dp/dimens.xml
@@ -17,6 +17,7 @@
<dimen name="avatar_rounded_radius">118dp</dimen>
<dimen name="in_call_controls_container_height">236dp</dimen>
<dimen name="in_call_info_margin_top">96dp</dimen>
- <dimen name="in_call_small_contact_photo_size">232dp</dimen>
<dimen name="small_contact_photo_margin_end">96dp</dimen>
+ <dimen name="in_call_card_dialpad_horizontal_spacing">@dimen/car_padding_6</dimen>
+ <dimen name="in_call_card_dialpad_vertical_spacing">@dimen/car_padding_5</dimen>
</resources>
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
index 89771162..09f85f74 100644
--- a/res/values-night/colors.xml
+++ b/res/values-night/colors.xml
@@ -14,8 +14,13 @@
limitations under the License.
-->
<resources>
+ <color name="phone_theme">@color/phone_theme_dark</color>
+ <color name="phone_theme_secondary">@color/phone_theme_secondary_dark</color>
+
<color name="telecom_display_scrim">#52000000</color> <!-- 32% black -->
- <color name="phone_status_bar_theme">#ff00457c</color>
<color name="search_container_controls_tint">@color/car_grey_100</color>
<color name="search_container_text_hint">@color/car_body2_light</color>
+ <color name="car_caption">@color/car_caption_light</color>
+ <color name="car_key1">@color/car_key1_light</color>
+ <color name="car_key2">@color/car_key2_light</color>
</resources>
diff --git a/res/values-w1024dp/dimens.xml b/res/values-w1024dp/dimens.xml
index b2ed8c4f..9a833b1a 100644
--- a/res/values-w1024dp/dimens.xml
+++ b/res/values-w1024dp/dimens.xml
@@ -22,17 +22,14 @@
<dimen name="call_fab_elevation">8dp</dimen>
<dimen name="dial_number_call_button_width">160dp</dimen>
- <dimen name="bksp_button_width">160dp</dimen>
<dimen name="in_call_card_corner_radius">160dp</dimen>
<dimen name="in_call_large_contact_photo_size">632dp</dimen>
<dimen name="in_call_card_margin_right">304dp</dimen>
- <dimen name="in_call_content_card_margin_start">@dimen/stream_content_keyline_1</dimen>
+ <dimen name="in_call_content_card_margin_start">@dimen/car_keyline_1</dimen>
<dimen name="in_call_text_max_width">460dp</dimen>
- <dimen name="in_call_button_size">160dp</dimen>
<dimen name="in_call_button_spacing">48dp</dimen>
<dimen name="in_call_card_dialpad_width">468dp</dimen>
- <dimen name="in_call_card_dialpad_horizontal_spacing">56dp</dimen>
<dimen name="car_card_view_corner_radius">0dp</dimen>
@@ -40,5 +37,4 @@
<dimen name="call_log_icon_size">76dp</dimen>
<dimen name="call_log_last_card_bottom_margin">@dimen/call_log_card_overlap</dimen>
- <dimen name="call_log_last_card_corner_radius">16dp</dimen>
</resources>
diff --git a/res/values-w748dp/dimens.xml b/res/values-w748dp/dimens.xml
index 045bff5b..1a856737 100644
--- a/res/values-w748dp/dimens.xml
+++ b/res/values-w748dp/dimens.xml
@@ -16,7 +16,4 @@
<resources>
<!-- The max width of content in apps for adaptive responsive -->
<dimen name="apps_max_content_width">748dp</dimen>
-
- <!-- Touch Keyboard -->
- <dimen name="in_call_button_size">116dp</dimen>
-</resources> \ No newline at end of file
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 595df7b0..046bb201 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -16,13 +16,30 @@
<resources>
<!-- rail -->
<color name="phone_theme">@color/phone_theme_light</color>
- <color name="phone_theme_light">@color/car_light_blue_800</color>
- <color name="phone_status_bar_theme">@color/car_light_blue_900</color>
+ <color name="phone_theme_secondary">@color/phone_theme_secondary_light</color>
+
+ <color name="phone_theme_light">@color/car_grey_50</color>
+ <color name="phone_theme_dark">@color/car_dark_blue_grey_700</color>
+ <color name="phone_theme_secondary_light">@color/car_grey_200</color>
+ <color name="phone_theme_secondary_dark">@color/car_dark_blue_grey_800</color>
+
+ <color name="primary_icon_color">@color/car_tint</color>
+
+ <color name="phone_status_bar_theme">@android:color/transparent</color>
<color name="phone_call">@color/car_green_700</color>
<color name="phone_end_call">@color/car_red_500a</color>
<color name="phone_secondary_call_scrim">#990288d1</color>
<color name="separator">#1f000000</color>
+ <color name="call_history_list_item_color">@color/phone_theme</color>
+ <color name="call_list_fragment_background">@color/phone_theme</color>
+
+ <color name="contact_fragment_background">@color/phone_theme</color>
+ <color name="contact_list_item_color">@color/phone_theme</color>
+
+ <color name="dialer_fragment_background_color">@color/phone_theme</color>
+ <color name="telecom_activity_background_color">@color/phone_theme</color>
+
<!-- phone -->
<color name="secondary_call_scrim">#990288d1</color>
@@ -34,7 +51,22 @@
<!-- The color of the icons in the contact details page. -->
<color name="contact_details_icon_tint">@color/search_container_controls_tint</color>
+ <color name="contact_detail_fragment_background_color">@color/phone_theme_secondary</color>
+ <color name="contact_detail_card_background_color">@color/phone_theme</color>
<!-- The color of the hint text in the search field. -->
<color name="search_container_text_hint">@color/car_grey_400</color>
+
+ <!-- TextAppearances -->
+ <color name="car_caption_light">@color/car_grey_400</color>
+ <color name="car_caption_dark">@color/car_grey_700</color>
+ <color name="car_caption">@color/car_caption_dark</color>
+
+ <color name="car_key1_light">@color/car_grey_100</color>
+ <color name="car_key1_dark">@color/car_light_blue_700</color>
+ <color name="car_key1">@color/car_key1_dark</color>
+
+ <color name="car_key2_light">@color/car_grey_400</color>
+ <color name="car_key2_dark">@color/car_grey_650</color>
+ <color name="car_key2">@color/car_key2_dark</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a489b87b..e319b79a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -30,18 +30,19 @@
<dimen name="in_call_text_max_width">362dp</dimen>
<dimen name="in_call_card_dialpad_width">300dp</dimen>
<dimen name="in_call_large_contact_photo_size">480dp</dimen>
- <dimen name="in_call_content_card_margin_start">@dimen/stream_content_keyline_1</dimen>
+ <dimen name="in_call_content_card_margin_start">@dimen/car_keyline_1</dimen>
<dimen name="avatar_rounded_radius">60dp</dimen>
- <dimen name="in_call_small_contact_photo_size">120dp</dimen>
+ <dimen name="in_call_small_contact_photo_size">@dimen/car_touch_target_size</dimen>
<dimen name="in_call_toggle_button_image_offset">30dp</dimen>
<dimen name="in_call_info_margin_top">72dp</dimen>
<dimen name="in_call_card_elevation">12dp</dimen>
+ <dimen name="in_call_card_height">200dp</dimen>
<!-- This should be -1 * in_call_card_right_margin -->
<dimen name="in_call_card_dialpad_translation_x">-308dp</dimen>
<dimen name="in_call_card_dialpad_corner_radius">16dp</dimen>
- <dimen name="in_call_card_dialpad_horizontal_spacing">24dp</dimen>
- <dimen name="in_call_card_dialpad_vertical_spacing">0dp</dimen>
+ <dimen name="in_call_card_dialpad_horizontal_spacing">@dimen/car_padding_5</dimen>
+ <dimen name="in_call_card_dialpad_vertical_spacing">@dimen/car_padding_4</dimen>
<dimen name="in_call_button_spacing">32dp</dimen>
<dimen name="in_call_card_corner_radius">@dimen/car_card_view_corner_radius</dimen>
@@ -61,13 +62,19 @@
in_call_card_right_margin + car_card_view_corner_radius - card_margin. -->
<dimen name="call_log_card_overlap">-4dp</dimen>
<dimen name="call_log_icon_margin">4dp</dimen>
- <dimen name="call_log_item_height">91dp</dimen>
- <dimen name="call_log_icon_size">@dimen/car_list_item_icon_size</dimen>
+ <dimen name="call_log_item_height">@dimen/car_single_line_list_item_height</dimen>
+ <dimen name="call_log_icon_size">@dimen/car_primary_icon_size</dimen>
+ <dimen name="car_card_view_corner_radius">2dp</dimen>
+ <dimen name="favorite_card_corner_radius">@dimen/car_radius_5</dimen>
+ <dimen name="favorite_card_space">@dimen/car_padding_1</dimen>
- <dimen name="icon_container_height">@dimen/car_list_item_icon_size</dimen>
+ <dimen name="icon_container_height">@dimen/car_primary_icon_size</dimen>
+ <dimen name="avatar_icon_size">@dimen/car_avatar_size</dimen>
- <dimen name="contact_detail_image_size">@dimen/in_call_small_contact_photo_size</dimen>
+ <dimen name="contact_detail_image_size">@dimen/car_primary_icon_size</dimen>
<dimen name="contact_detail_vertical_margin">16dp</dimen>
+ <dimen name="contact_detail_name_card_height">192dp</dimen>
+ <dimen name="contact_detail_card_corner_radius">@dimen/car_radius_3</dimen>
<!-- The height of the container that contains the contact search field. -->
<dimen name="search_container_height">128dp</dimen>
@@ -80,4 +87,25 @@
<!-- The height of each of the cards that list the contact search results. -->
<dimen name="contact_result_card_height">128dp</dimen>
+
+ <dimen name="call_log_last_card_bottom_margin">@dimen/call_log_card_overlap</dimen>
+
+ <dimen name="line_divider_height">1dp</dimen>
+
+ <dimen name="dialer_number_view_height">128dp</dimen>
+ <dimen name="dialer_number_view_padding">32dp</dimen>
+
+ <dimen name="dialer_fab_size">80dp</dimen>
+ <dimen name="call_fab_elevation">8dp</dimen>
+ <dimen name="bksp_button_width">@dimen/car_touch_target_size</dimen>
+ <!-- Fab button size is touch target (76dp) + stroke width (8dp) * 2-->
+ <dimen name="fab_button_size">92dp</dimen>
+
+ <dimen name="in_call_button_size">@dimen/car_touch_target_size</dimen>
+
+ <dimen name="user_profile_title_margin_top">@dimen/car_padding_3</dimen>
+ <dimen name="user_profile_body_margin_top">@dimen/car_padding_3</dimen>
+
+ <dimen name="car_key1_size">40sp</dimen>
+ <dimen name="car_key2_size">18sp</dimen>
</resources>
diff --git a/res/values/integer.xml b/res/values/integer.xml
new file mode 100644
index 00000000..b836fe40
--- /dev/null
+++ b/res/values/integer.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources >
+ <integer name="favorite_fragment_grid_column">2</integer>
+</resources> \ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d432c2f7..9699a4bf 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -54,6 +54,11 @@
<!-- Status label for phone state [CHAR LIMIT=20] -->
<string name="call_state_call_ending">Disconnecting</string>
+ <!-- Decline an in coming call [CHAR LIMIT=20] -->
+ <string name="decline_call">Decline</string>
+ <!-- Answer an in coming call [CHAR LIMIT=20] -->
+ <string name="answer_call">Answer</string>
+
<!-- Label for voicemail [CHAR LIMIT=30] -->
<string name="voicemail">Voicemail</string>
<!-- Label for current phone call [CHAR LIMIT=30] -->
@@ -66,15 +71,42 @@
<!-- Label for when a call is a conference call [CHAR LIMIT=30] -->
<string name="conference_call">Conference call</string>
+ <!-- Audio route -->
+ <!-- Label for routing phone audio to the vehicle [CHAR LIMIT=30] -->
+ <string name="audio_route_vehicle">Vehicle bluetooth</string>
+ <!-- Label for routing phone audio to the phone speaker [CHAR LIMIT=30] -->
+ <string name="audio_route_phone_speaker">Speaker phone</string>
+ <!-- Label for routing phone audio to the phone earpiece. [CHAR LIMIT=30] -->
+ <string name="audio_route_handset">Handset</string>
+
<!-- Menu label for the call history [CHAR LIMIT=30] -->
<string name="calllog_all">Call History</string>
<!-- Menu label for the missed call history [CHAR LIMIT=30] -->
<string name="calllog_missed">Missed</string>
+ <!-- Menu label for the favorites [CHAR LIMIT=30] -->
+ <string name="calllog_favorites">Favorites</string>
<!-- Menu label for dial a number [CHAR LIMIT=30] -->
- <string name="calllog_dial_number">Dial a number</string>
+ <string name="calllog_dial_number">Dialpad</string>
<!-- Button label to dial a manually entered phone number -->
<string name="dial_a_number">Dial a number</string>
+ <!-- Menu label for the call history [CHAR LIMIT=30] -->
+ <string name="contact_menu_label">Contact</string>
+
+ <!-- Titles -->
+ <!-- Title for the favorites [CHAR LIMIT=30] -->
+ <string name="favorites_title">Favorites</string>
+ <!-- Title for the call history [CHAR LIMIT=30] -->
+ <string name="call_history_title">Call History</string>
+ <!-- Title for the missed call history [CHAR LIMIT=30] -->
+ <string name="missed_call_title">Missed</string>
+ <!-- Title for the contacts [CHAR LIMIT=30] -->
+ <string name="contacts_title">Contacts</string>
+ <!-- Title for the dialpad [CHAR LIMIT=30] -->
+ <string name="dialpad_title">Dialpad</string>
+ <!-- Title for the in call [CHAR LIMIT=30] -->
+ <string name="in_call_title">In Call</string>
+
<!-- TODO: we do not need to localize this, figure out how to build ignoring it -->
<string name="one">1</string>
<string name="two">2</string>
@@ -109,5 +141,4 @@
<string name="type_work">Work</string>
<string name="type_mobile">Mobile</string>
<string name="type_other">Other</string>
-
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 5866d20e..ab9e5967 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -14,19 +14,42 @@
limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="TextAppearance.Car.Key1" >
+ <item name="android:textStyle">normal</item>
+ <item name="android:textSize">@dimen/car_key1_size</item>
+ <item name="android:textColor">@color/car_key1</item>
+ </style>
+
+ <style name="TextAppearance.Car.Key2" >
+ <item name="android:textStyle">normal</item>
+ <item name="android:textSize">@dimen/car_key2_size</item>
+ <item name="android:textColor">@color/car_key2</item>
+ </style>
<!-- Phone -->
<style name="DialpadKeyButtonStyle">
<item name="android:clickable">true</item>
- <item name="android:layout_width">@dimen/stream_button_size</item>
- <item name="android:layout_height">@dimen/stream_button_size</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:minWidth">@dimen/car_touch_target_size</item>
+ <item name="android:minHeight">@dimen/car_touch_target_size</item>
<item name="android:background">@drawable/dialpad_button_background</item>
<item name="android:focusable">true</item>
</style>
- <style name="DialpadCall">
- <item name="android:layout_width">@dimen/dial_number_call_button_width</item>
- <item name="android:layout_height">@dimen/dial_number_call_button_width</item>
+ <style name="DialpadPrimaryButton">
+ <item name="android:layout_width">@dimen/fab_button_size</item>
+ <item name="android:layout_height">@dimen/fab_button_size</item>
+ <item name="android:elevation">@dimen/call_fab_elevation</item>
+ <item name="android:scaleType">center</item>
+ </style>
+
+ <style name="DialpadSecondaryButton">
+ <item name="android:layout_width">@dimen/bksp_button_width</item>
+ <item name="android:layout_height">@dimen/bksp_button_width</item>
+ <item name="android:background">@drawable/dialpad_delete_button_background</item>
+ <item name="android:scaleType">centerInside</item>
+ <item name="android:tint">@color/car_tint</item>
</style>
<style name="InCallDialpad">
@@ -39,7 +62,7 @@
<item name="android:textColor">@color/car_headline1</item>
</style>
- <style name="NoHfpText" parent="CarBody2.Light">
+ <style name="NoHfpText" parent="TextAppearance.Car.Body2">
<item name="android:textColor">@color/car_body1_light</item>
<item name="android:gravity">center</item>
<item name="android:maxLines">3</item>
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 9da01d88..d09bffb0 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -15,12 +15,19 @@ limitations under the License.
-->
<resources>
<!-- The theme for the TelecomActivity. -->
- <style name="TelecomActivityTheme" parent="@style/CarDrawerActivityTheme" >
+ <style name="TelecomActivityTheme" parent="@style/Theme.Car.Light.NoActionBar.Drawer" >
<item name="android:statusBarColor">@color/phone_status_bar_theme</item>
+ <item name="android:colorPrimary">@android:color/transparent</item>
+ <item name="android:colorPrimaryDark">@color/phone_status_bar_theme</item>
+ <item name="android:textColorPrimary">@android:color/black</item>
+ <item name="drawerHeaderColor">@color/car_title2</item>
+ <item name="drawerBackgroundColor">@color/phone_theme</item>
</style>
<!-- The theme for the contact search page. -->
<style name="ContactSearchActivityTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@color/phone_status_bar_theme</item>
+ <item name="android:colorPrimary">@android:color/transparent</item>
+ <item name="android:colorPrimaryDark">@color/phone_status_bar_theme</item>
</style>
</resources>
diff --git a/src/com/android/car/dialer/CallListener.java b/src/com/android/car/dialer/CallListener.java
new file mode 100644
index 00000000..b09ec8d4
--- /dev/null
+++ b/src/com/android/car/dialer/CallListener.java
@@ -0,0 +1,16 @@
+package com.android.car.dialer;
+
+import com.android.car.dialer.telecom.UiCall;
+
+/** Interface for listening to call state changes. */
+public interface CallListener {
+ void onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask);
+
+ void onCallStateChanged(UiCall call, int state);
+
+ void onCallUpdated(UiCall call);
+
+ void onCallAdded(UiCall call);
+
+ void onCallRemoved(UiCall call);
+}
diff --git a/src/com/android/car/dialer/CallLogListingTask.java b/src/com/android/car/dialer/CallLogListingTask.java
deleted file mode 100644
index 171bf241..00000000
--- a/src/com/android/car/dialer/CallLogListingTask.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.os.AsyncTask;
-import android.provider.CallLog;
-import android.support.annotation.NonNull;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-
-import com.android.car.apps.common.CircleBitmapDrawable;
-import com.android.car.apps.common.LetterTileDrawable;
-import com.android.car.dialer.telecom.PhoneLoader;
-import com.android.car.dialer.telecom.TelecomUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class CallLogListingTask extends AsyncTask<Void, Void, Void> {
- static class CallLogItem {
- final String mTitle;
- final String mText;
- final String mNumber;
- final Bitmap mIcon;
-
- public CallLogItem(String title, String text, String number, Bitmap icon) {
- mTitle = title;
- mText = text;
- mNumber = number;
- mIcon = icon;
- }
- }
-
- interface LoadCompleteListener {
- void onLoadComplete(List<CallLogItem> items);
- }
-
-
- // Like a constant but needs a context so not static.
- private final String VOICEMAIL_NUMBER;
-
- private Context mContext;
- private Cursor mCursor;
- private List<CallLogItem> mItems;
- private LoadCompleteListener mListener;
-
- CallLogListingTask(Context context, Cursor cursor,
- @NonNull LoadCompleteListener listener) {
- mContext = context;
- mCursor = cursor;
- mItems = new ArrayList<>(mCursor.getCount());
- mListener = listener;
- VOICEMAIL_NUMBER = TelecomUtils.getVoicemailNumber(mContext);
- }
-
- private String maybeAppendCount(StringBuilder sb, int count) {
- if (count > 1) {
- sb.append(" (").append(count).append(")");
- }
- return sb.toString();
- }
-
- private String getContactName(String cachedName, String number,
- int count, boolean isVoicemail) {
- if (cachedName != null) {
- return maybeAppendCount(new StringBuilder(cachedName), count);
- }
-
- StringBuilder sb = new StringBuilder();
- if (isVoicemail) {
- sb.append(mContext.getString(R.string.voicemail));
- } else {
- String displayName = TelecomUtils.getDisplayName(mContext, number);
- if (TextUtils.isEmpty(displayName)) {
- displayName = mContext.getString(R.string.unknown);
- }
- sb.append(displayName);
- }
-
- return maybeAppendCount(sb, count);
- }
-
- private Bitmap getContactImage(Context context, ContentResolver contentResolver,
- String name, String number) {
- Resources r = context.getResources();
- int size = r.getDimensionPixelSize(R.dimen.dialer_menu_icon_container_width);
-
- Bitmap bitmap = TelecomUtils.getContactPhotoFromNumber(contentResolver, number);
- if (bitmap != null) {
- return new CircleBitmapDrawable(r, bitmap).toBitmap(size);
- }
-
- LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r);
- letterTileDrawable.setContactDetails(name, number);
- letterTileDrawable.setIsCircular(true);
- return letterTileDrawable.toBitmap(size);
- }
-
- private static CharSequence getRelativeTime(long millis) {
- boolean validTimestamp = millis > 0;
-
- return validTimestamp ? DateUtils.getRelativeTimeSpanString(
- millis, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE) : null;
- }
-
- @Override
- protected Void doInBackground(Void... voids) {
- ContentResolver resolver = mContext.getContentResolver();
-
- try {
- if (mCursor != null) {
- int cachedNameColumn = PhoneLoader.getNameColumnIndex(mCursor);
- int numberColumn = PhoneLoader.getNumberColumnIndex(mCursor);
- int dateColumn = mCursor.getColumnIndex(CallLog.Calls.DATE);
-
- while (mCursor.moveToNext()) {
- int count = 1;
- String number = mCursor.getString(numberColumn);
-
- // We want to group calls to the same number into one so seek forward as many
- // entries as possible as long as the number is the same.
- int position = mCursor.getPosition();
- while (mCursor.moveToNext()) {
- String nextNumber = mCursor.getString(numberColumn);
- if (equalNumbers(number, nextNumber)) {
- count++;
- } else {
- break;
- }
- }
- mCursor.moveToPosition(position);
-
- boolean isVoicemail = number.equals(VOICEMAIL_NUMBER);
- String name = getContactName(mCursor.getString(cachedNameColumn),
- number, count, isVoicemail);
-
- // Not sure why this is the only column checked here but I'm assuming this was
- // to work around some bug on some device.
- long millis = dateColumn == -1 ? 0 : mCursor.getLong(dateColumn);
-
- StringBuffer secondaryText = new StringBuffer();
- CharSequence relativeDate = getRelativeTime(millis);
-
- // Append the type (work, mobile etc.) if it isnt voicemail.
- if (!isVoicemail) {
- CharSequence type = TelecomUtils.getTypeFromNumber(mContext, number);
- secondaryText.append(type);
- if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(relativeDate)) {
- secondaryText.append(", ");
- }
- }
-
- // Add in the timestamp.
- if (relativeDate != null) {
- secondaryText.append(relativeDate);
- }
-
- Bitmap contactImage = getContactImage(mContext, resolver, name, number);
-
- CallLogItem item =
- new CallLogItem(name, secondaryText.toString(), number, contactImage);
- mItems.add(item);
-
- // Since we deduplicated count rows, we can move all the way to that row so the
- // next iteration takes us to the row following the last duplicate row.
- if (count > 1) {
- mCursor.moveToPosition(position + count - 1);
- }
- }
- }
- } finally {
- if (mCursor != null) {
- mCursor.close();
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- mListener.onLoadComplete(mItems);
- }
-
- /**
- * Determines if the specified number is actually a URI
- * (i.e. a SIP address) rather than a regular PSTN phone number,
- * based on whether or not the number contains an "@" character.
- *
- * @return true if number contains @
- *
- * from android.telephony.PhoneNumberUtils
- */
- public static boolean isUriNumber(String number) {
- // Note we allow either "@" or "%40" to indicate a URI, in case
- // the passed-in string is URI-escaped. (Neither "@" nor "%40"
- // will ever be found in a legal PSTN number.)
- return number != null && (number.contains("@") || number.contains("%40"));
- }
-
- private static boolean equalNumbers(String number1, String number2) {
- if (isUriNumber(number1) || isUriNumber(number2)) {
- return compareSipAddresses(number1, number2);
- } else {
- return PhoneNumberUtils.compare(number1, number2);
- }
- }
-
- private static boolean compareSipAddresses(String number1, String number2) {
- if (number1 == null || number2 == null) {
- return number1 == null && number2 == null;
- }
-
- String[] address1 = splitSipAddress(number1);
- String[] address2 = splitSipAddress(number2);
-
- return address1[0].equals(address2[0]) && address1[1].equals(address2[1]);
- }
-
- /**
- * Splits a sip address on either side of the @ sign and returns both halves.
- * If there is no @ sign, user info will be number and rest will be empty string
- * @param number the sip number to split
- * @return a string array of size 2. Element 0 is the user info (left side of @ sign) and
- * element 1 is the rest (right side of @ sign).
- */
- private static String[] splitSipAddress(String number) {
- String[] values = new String[2];
- int index = number.indexOf('@');
- if (index == -1) {
- values[0] = number;
- values[1] = "";
- } else {
- values[0] = number.substring(0, index);
- values[1] = number.substring(index);
- }
- return values;
- }
-}
diff --git a/src/com/android/car/dialer/CallLogViewHolder.java b/src/com/android/car/dialer/CallLogViewHolder.java
index c4fd6878..fa575ae6 100644
--- a/src/com/android/car/dialer/CallLogViewHolder.java
+++ b/src/com/android/car/dialer/CallLogViewHolder.java
@@ -15,26 +15,40 @@
*/
package com.android.car.dialer;
+import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import com.android.car.view.CarListItemViewHolder;
+import android.widget.TextView;
-public class CallLogViewHolder extends CarListItemViewHolder {
- public View card;
- public ViewGroup container;
- public LinearLayout callType;
- public CallTypeIconsView callTypeIconsView;
- public ImageView smallIcon;
+/**
+ * A {@link android.support.v7.widget.RecyclerView.ViewHolder} that will hold layouts that
+ * are inflated by {@link StrequentsAdapter}.
+ */
+public class CallLogViewHolder extends RecyclerView.ViewHolder {
+ public final FrameLayout iconContainer;
+ public final ImageView icon;
+ public final TextView title;
+ public final TextView text;
+ public final View card;
+ public final ViewGroup container;
+ public final LinearLayout callType;
+ public final CallTypeIconsView callTypeIconsView;
+ public final ImageView smallIcon;
public CallLogViewHolder(View v) {
- super(v, R.layout.car_textview);
+ super(v);
+ icon = v.findViewById(R.id.icon);
+ iconContainer = v.findViewById(R.id.icon_container);
+ title = v.findViewById(R.id.title);
+ text = v.findViewById(R.id.text);
card = v.findViewById(R.id.call_log_card);
- callType = (LinearLayout) v.findViewById(R.id.call_type);
- callTypeIconsView = (CallTypeIconsView) v.findViewById(R.id.call_type_icons);
- smallIcon = (ImageView) v.findViewById(R.id.small_icon);
- container = (ViewGroup) v.findViewById(R.id.container);
+ callType = v.findViewById(R.id.call_type);
+ callTypeIconsView = v.findViewById(R.id.call_type_icons);
+ smallIcon = v.findViewById(R.id.small_icon);
+ container = v.findViewById(R.id.container);
}
}
diff --git a/src/com/android/car/dialer/ContactDetailsFragment.java b/src/com/android/car/dialer/ContactDetailsFragment.java
index 9470c199..bb3b1b5c 100644
--- a/src/com/android/car/dialer/ContactDetailsFragment.java
+++ b/src/com/android/car/dialer/ContactDetailsFragment.java
@@ -15,17 +15,17 @@
*/
package com.android.car.dialer;
-import android.app.Fragment;
-import android.app.LoaderManager;
-import android.content.CursorLoader;
import android.content.Intent;
-import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.Pair;
@@ -36,11 +36,14 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.android.car.dialer.telecom.TelecomUtils;
-import com.android.car.view.PagedListView;
import java.util.ArrayList;
import java.util.List;
+import androidx.car.utils.ListItemBackgroundResolver;
+import androidx.car.widget.DayNightStyle;
+import androidx.car.widget.PagedListView;
+
/**
* A fragment that shows the name of the contact, the photo and all listed phone numbers. It is
* primarily used to respond to the results of search queries but supplyig it with the content://
@@ -57,10 +60,10 @@ public class ContactDetailsFragment extends Fragment
private static final String KEY_URI = "uri";
private static final String[] CONTACT_DETAILS_PROJECTION = {
- ContactsContract.Contacts._ID,
- ContactsContract.Contacts.DISPLAY_NAME,
- ContactsContract.Contacts.PHOTO_URI,
- ContactsContract.Contacts.HAS_PHONE_NUMBER
+ ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME,
+ ContactsContract.Contacts.PHOTO_URI,
+ ContactsContract.Contacts.HAS_PHONE_NUMBER
};
private PagedListView mListView;
@@ -69,7 +72,9 @@ public class ContactDetailsFragment extends Fragment
public static ContactDetailsFragment newInstance(Uri uri,
@Nullable RecyclerView.OnScrollListener listener) {
ContactDetailsFragment fragment = new ContactDetailsFragment();
- fragment.addOnScrollListener(listener);
+ if (listener != null) {
+ fragment.addOnScrollListener(listener);
+ }
Bundle args = new Bundle();
args.putParcelable(KEY_URI, uri);
@@ -87,7 +92,7 @@ public class ContactDetailsFragment extends Fragment
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mListView = view.findViewById(R.id.list_view);
- mListView.setLightMode();
+ mListView.setDayNightStyle(DayNightStyle.ALWAYS_LIGHT);
RecyclerView recyclerView = mListView.getRecyclerView();
for (RecyclerView.OnScrollListener listener : mOnScrollListeners) {
@@ -153,7 +158,8 @@ public class ContactDetailsFragment extends Fragment
}
@Override
- public void onLoaderReset(Loader loader) { }
+ public void onLoaderReset(Loader loader) {
+ }
private boolean vdebug() {
return Log.isLoggable(TAG, Log.DEBUG);
@@ -164,7 +170,8 @@ public class ContactDetailsFragment extends Fragment
public ImageView leftIcon;
public TextView title;
public TextView text;
- public ImageView rightIcon;
+ public ImageView avatar;
+ public View divier;
public ContactDetailViewHolder(View v) {
super(v);
@@ -172,7 +179,8 @@ public class ContactDetailsFragment extends Fragment
leftIcon = v.findViewById(R.id.icon);
title = v.findViewById(R.id.title);
text = v.findViewById(R.id.text);
- rightIcon = v.findViewById(R.id.right_icon);
+ avatar = v.findViewById(R.id.avatar);
+ divier = v.findViewById(R.id.divider);
}
}
@@ -183,7 +191,8 @@ public class ContactDetailsFragment extends Fragment
private static final int ID_CONTENT = 2;
private final String mContactName;
- @ColorInt private int mIconTint;
+ @ColorInt
+ private int mIconTint;
private List<Pair<String, String>> mPhoneNumbers = new ArrayList<>();
@@ -204,7 +213,8 @@ public class ContactDetailsFragment extends Fragment
}
// Fetch the phone number from the contacts db using another loader.
- getLoaderManager().initLoader(PHONE_LOADER_QUERY_ID, null,
+ LoaderManager.getInstance(ContactDetailsFragment.this).initLoader(PHONE_LOADER_QUERY_ID,
+ null,
new LoaderManager.LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
@@ -212,7 +222,7 @@ public class ContactDetailsFragment extends Fragment
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, /* All columns **/
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
- new String[] { contactId },
+ new String[]{contactId},
null /* sortOrder */);
}
@@ -245,35 +255,11 @@ public class ContactDetailsFragment extends Fragment
notifyDataSetChanged();
}
- public void onLoaderReset(Loader loader) { }
+ public void onLoaderReset(Loader loader) {
+ }
});
}
- /**
- * Appropriately sets the background for the View that is being bound. This method will
- * allow for rounded corners on either the top or bottom of a card.
- */
- private void setBackground(ContactDetailViewHolder viewHolder) {
- int itemCount = getItemCount();
- int adapterPosition = viewHolder.getAdapterPosition();
-
- if (itemCount == 1) {
- // Only element - all corners are rounded
- viewHolder.card.setBackgroundResource(
- R.drawable.car_card_rounded_top_bottom_background);
- } else if (adapterPosition == 0) {
- // First element gets rounded top
- viewHolder.card.setBackgroundResource(R.drawable.car_card_rounded_top_background);
- } else if (adapterPosition == itemCount - 1) {
- // Last one has a rounded bottom
- viewHolder.card.setBackgroundResource(
- R.drawable.car_card_rounded_bottom_background);
- } else {
- // Middle have no rounded corners
- viewHolder.card.setBackgroundResource(R.color.car_card);
- }
- }
-
@Override
public int getItemViewType(int position) {
return position == 0 ? ID_HEADER : ID_CONTENT;
@@ -304,7 +290,8 @@ public class ContactDetailsFragment extends Fragment
return null;
}
- View view = LayoutInflater.from(parent.getContext()).inflate(layoutResId, null);
+ View view = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent,
+ false);
return new ContactDetailViewHolder(view);
}
@@ -315,7 +302,7 @@ public class ContactDetailsFragment extends Fragment
viewHolder.title.setText(mContactName);
if (!mPhoneNumbers.isEmpty()) {
String firstNumber = mPhoneNumbers.get(0).second;
- TelecomUtils.setContactBitmapAsync(getContext(), viewHolder.rightIcon,
+ TelecomUtils.setContactBitmapAsync(getContext(), viewHolder.avatar,
mContactName, firstNumber);
}
// Just in case a viewholder object gets recycled.
@@ -323,8 +310,8 @@ public class ContactDetailsFragment extends Fragment
break;
case ID_CONTENT:
Pair<String, String> data = mPhoneNumbers.get(position - 1);
- viewHolder.title.setText(data.first); // Type.
- viewHolder.text.setText(data.second); // Number.
+ viewHolder.title.setText(data.second); // Type.
+ viewHolder.text.setText(data.first); // Number.
viewHolder.leftIcon.setImageResource(R.drawable.ic_phone);
viewHolder.leftIcon.setColorFilter(mIconTint);
viewHolder.card.setOnClickListener(v -> {
@@ -337,7 +324,15 @@ public class ContactDetailsFragment extends Fragment
Log.e(TAG, "Unknown view type " + viewHolder.getItemViewType());
return;
}
- setBackground(viewHolder);
+
+ if (position == (getItemCount() - 1)) {
+ // hide divider for last item.
+ viewHolder.divier.setVisibility(View.GONE);
+ } else {
+ viewHolder.divier.setVisibility(View.VISIBLE);
+ }
+ ListItemBackgroundResolver.setBackground(viewHolder.card,
+ viewHolder.getAdapterPosition(), getItemCount());
}
}
}
diff --git a/src/com/android/car/dialer/ContactEntry.java b/src/com/android/car/dialer/ContactEntry.java
index 34db70fb..27e43777 100644
--- a/src/com/android/car/dialer/ContactEntry.java
+++ b/src/com/android/car/dialer/ContactEntry.java
@@ -20,43 +20,108 @@ import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.annotation.Nullable;
import android.text.TextUtils;
+import android.util.Log;
+
import com.android.car.dialer.telecom.PhoneLoader;
import com.android.car.dialer.telecom.TelecomUtils;
/**
* Encapsulates data about a phone Contact entry. Typically loaded from the local Contact store.
*/
+// TODO: Refactor to use Builder design pattern.
public class ContactEntry implements Comparable<ContactEntry> {
private final Context mContext;
+ /**
+ * An unique primary key for searching an entry.
+ */
+ private int mId;
+
+ /**
+ * Whether this contact entry is starred by user.
+ */
+ private boolean mIsStarred;
+
+ /**
+ * Contact-specific information about whether or not a contact has been pinned by the user at
+ * a particular position within the system contact application's user interface.
+ */
+ private int mPinnedPosition;
+
+ /**
+ * Phone number.
+ */
+ private String mNumber;
+
+ /**
+ * The display name.
+ */
@Nullable
- public String name;
- public String number;
- public boolean isStarred;
- public int pinnedPosition;
+ private String mDisplayName;
+
+ /**
+ * A URI that can be used to retrieve a thumbnail of the contact's photo.
+ */
+ private String mAvatarThumbnailUri;
+
+ /**
+ * A URI that can be used to retrieve the contact's full-size photo.
+ */
+ private String mAvatarUri;
+
+ /**
+ * An opaque value that contains hints on how to find the contact if its row id changed
+ * as a result of a sync or aggregation
+ */
+ private String mLookupKey;
+
+ /**
+ * The type of data, for example Home or Work.
+ */
+ private int mType;
+
+ /**
+ * The user defined label for the the contact method.
+ */
+ private String mLabel;
/**
* Parses a Contact entry for a Cursor loaded from the OS Strequents DB.
*/
public static ContactEntry fromCursor(Cursor cursor, Context context) {
- int nameColumn = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
+ int idColumnIndex = PhoneLoader.getIdColumnIndex(cursor);
int starredColumn = cursor.getColumnIndex(ContactsContract.Contacts.STARRED);
int pinnedColumn = cursor.getColumnIndex("pinned");
+ int displayNameColumnIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
+ int avatarUriColumnIndex = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI);
+ int avatarThumbnailColumnIndex = cursor.getColumnIndex(
+ ContactsContract.Contacts.PHOTO_THUMBNAIL_URI);
+ int lookupKeyColumnIndex = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
+ int typeColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DATA2);
+ int labelColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DATA3);
- String name = cursor.getString(nameColumn);
+ String name = cursor.getString(displayNameColumnIndex);
String number = PhoneLoader.getPhoneNumber(cursor, context.getContentResolver());
int starred = cursor.getInt(starredColumn);
int pinnedPosition = cursor.getInt(pinnedColumn);
- return new ContactEntry(context, name, number, starred > 0, pinnedPosition);
+ ContactEntry contactEntry = new ContactEntry(context, name, number, starred > 0,
+ pinnedPosition);
+ contactEntry.setId(cursor.getInt(idColumnIndex));
+ contactEntry.setAvatarUri(cursor.getString(avatarUriColumnIndex));
+ contactEntry.setAvatarThumbnailUri(cursor.getString(avatarThumbnailColumnIndex));
+ contactEntry.setLookupKey(cursor.getString(lookupKeyColumnIndex));
+ contactEntry.setType(cursor.getInt(typeColumnIndex));
+ contactEntry.setLabel(cursor.getString(labelColumnIndex));
+ return contactEntry;
}
public ContactEntry(
Context context, String name, String number, boolean isStarred, int pinnedPosition) {
mContext = context;
- this.name = name;
- this.number = number;
- this.isStarred = isStarred;
- this.pinnedPosition = pinnedPosition;
+ this.mDisplayName = name;
+ this.mNumber = number;
+ this.mIsStarred = isStarred;
+ this.mPinnedPosition = pinnedPosition;
}
/**
@@ -64,13 +129,13 @@ public class ContactEntry implements Comparable<ContactEntry> {
* It takes into account the number associated with a name for fail cases.
*/
public String getDisplayName() {
- if (!TextUtils.isEmpty(name)) {
- return name;
+ if (!TextUtils.isEmpty(mDisplayName)) {
+ return mDisplayName;
}
if (isVoicemail()) {
return mContext.getResources().getString(R.string.voicemail);
} else {
- String displayName = TelecomUtils.getFormattedNumber(mContext, number);
+ String displayName = TelecomUtils.getFormattedNumber(mContext, mNumber);
if (TextUtils.isEmpty(displayName)) {
displayName = mContext.getString(R.string.unknown);
}
@@ -79,23 +144,23 @@ public class ContactEntry implements Comparable<ContactEntry> {
}
public boolean isVoicemail() {
- return number.equals(TelecomUtils.getVoicemailNumber(mContext));
+ return mNumber.equals(TelecomUtils.getVoicemailNumber(mContext));
}
@Override
public int compareTo(ContactEntry strequentContactEntry) {
- if (isStarred == strequentContactEntry.isStarred) {
- if (pinnedPosition == strequentContactEntry.pinnedPosition) {
- if (name == strequentContactEntry.name) {
- return compare(number, strequentContactEntry.number);
+ if (mIsStarred == strequentContactEntry.mIsStarred) {
+ if (mPinnedPosition == strequentContactEntry.mPinnedPosition) {
+ if (mDisplayName == strequentContactEntry.mDisplayName) {
+ return compare(mNumber, strequentContactEntry.mNumber);
}
- return compare(name, strequentContactEntry.name);
+ return compare(mDisplayName, strequentContactEntry.mDisplayName);
} else {
- if (pinnedPosition > 0 && strequentContactEntry.pinnedPosition > 0) {
- return pinnedPosition - strequentContactEntry.pinnedPosition;
+ if (mPinnedPosition > 0 && strequentContactEntry.mPinnedPosition > 0) {
+ return mPinnedPosition - strequentContactEntry.mPinnedPosition;
}
- if (pinnedPosition > 0) {
+ if (mPinnedPosition > 0) {
return -1;
}
@@ -103,7 +168,7 @@ public class ContactEntry implements Comparable<ContactEntry> {
}
}
- if (isStarred) {
+ if (mIsStarred) {
return -1;
}
@@ -114,10 +179,10 @@ public class ContactEntry implements Comparable<ContactEntry> {
public boolean equals(Object obj) {
if (obj instanceof ContactEntry) {
ContactEntry other = (ContactEntry) obj;
- if (compare(name, other.name) == 0
- && compare(number, other.number) == 0
- && isStarred == other.isStarred
- && pinnedPosition == other.pinnedPosition) {
+ if (compare(mDisplayName, other.mDisplayName) == 0
+ && compare(mNumber, other.mNumber) == 0
+ && mIsStarred == other.mIsStarred
+ && mPinnedPosition == other.mPinnedPosition) {
return true;
}
}
@@ -127,10 +192,10 @@ public class ContactEntry implements Comparable<ContactEntry> {
@Override
public int hashCode() {
int result = 17;
- result = 31 * result + (isStarred ? 1 : 0);
- result = 31 * result + pinnedPosition;
- result = 31 * result + (name == null ? 0 : name.hashCode());
- result = 31 * result + (number == null ? 0 : number.hashCode());
+ result = 31 * result + (mIsStarred ? 1 : 0);
+ result = 31 * result + mPinnedPosition;
+ result = 31 * result + (mDisplayName == null ? 0 : mDisplayName.hashCode());
+ result = 31 * result + (mNumber == null ? 0 : mNumber.hashCode());
return result;
}
@@ -145,4 +210,65 @@ public class ContactEntry implements Comparable<ContactEntry> {
return one.compareTo(two);
}
+
+ public int getId() {
+ return mId;
+ }
+
+ private void setId(int id) {
+ mId = id;
+ }
+
+ public String getAvatarUri() {
+ return mAvatarUri;
+ }
+
+ private void setAvatarUri(String avatarUri) {
+ mAvatarUri = avatarUri;
+ }
+
+ public String getLookupKey() {
+ return mLookupKey;
+ }
+
+ private void setLookupKey(String lookupKey) {
+ mLookupKey = lookupKey;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ private void setType(int type) {
+ mType = type;
+ }
+
+ public String getLabel() {
+ return mLabel;
+ }
+
+ private void setLabel(String label) {
+ mLabel = label;
+ }
+
+ public String getAvatarThumbnailUri() {
+ return mAvatarThumbnailUri;
+ }
+
+ private void setAvatarThumbnailUri(String avatarThumbnailUri) {
+ mAvatarThumbnailUri = avatarThumbnailUri;
+ }
+
+ public String getNumber() {
+ return mNumber;
+ }
+
+ public boolean isStarred() {
+ return mIsStarred;
+ }
+
+ public int getPinnedPosition() {
+ return mPinnedPosition;
+ }
+
}
diff --git a/src/com/android/car/dialer/ContactResultViewHolder.java b/src/com/android/car/dialer/ContactResultViewHolder.java
index df8e2df1..00766e1f 100644
--- a/src/com/android/car/dialer/ContactResultViewHolder.java
+++ b/src/com/android/car/dialer/ContactResultViewHolder.java
@@ -27,8 +27,10 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-import com.android.car.apps.common.CircleBitmapDrawable;
+import androidx.car.utils.ListItemBackgroundResolver;
+
import com.android.car.apps.common.LetterTileDrawable;
+import com.android.car.dialer.ui.CircleBitmapDrawable;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -56,7 +58,7 @@ public class ContactResultViewHolder extends RecyclerView.ViewHolder {
* provided {@link ContactDetails}.
*/
public void bind(ContactDetails details, int itemCount) {
- updateBackground(itemCount);
+ ListItemBackgroundResolver.setBackground(mContactCard, getAdapterPosition(), itemCount);
mContactCard.setOnClickListener(v -> {
Intent intent = new Intent();
@@ -95,32 +97,6 @@ public class ContactResultViewHolder extends RecyclerView.ViewHolder {
}
/**
- * Sets the appropriate background on the card containing the preset information. The cards
- * need to have rounded corners depending on its position in the list and the number of items
- * in the list.
- */
- private void updateBackground(int itemCount) {
- int position = getAdapterPosition();
-
- // Correctly set the background for each card. Only the top and last card should
- // have rounded corners.
- if (itemCount == 1) {
- // One card - all corners are rounded
- mContactCard.setBackgroundResource(
- R.drawable.car_card_rounded_top_bottom_background);
- } else if (position == 0) {
- // First card gets rounded top
- mContactCard.setBackgroundResource(R.drawable.car_card_rounded_top_background);
- } else if (position == itemCount - 1) {
- // Last one has a rounded bottom
- mContactCard.setBackgroundResource(R.drawable.car_card_rounded_bottom_background);
- } else {
- // Middle has no rounded corners
- mContactCard.setBackgroundResource(R.color.car_card);
- }
- }
-
- /**
* Retrieves the picture that is specified by the given {@link Uri}.
*/
@Nullable
diff --git a/src/com/android/car/dialer/ContactResultsAdapter.java b/src/com/android/car/dialer/ContactResultsAdapter.java
index d637f9d9..07c63510 100644
--- a/src/com/android/car/dialer/ContactResultsAdapter.java
+++ b/src/com/android/car/dialer/ContactResultsAdapter.java
@@ -24,11 +24,11 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import com.android.car.view.PagedListView;
-
import java.util.ArrayList;
import java.util.List;
+import androidx.car.widget.PagedListView;
+
/**
* An adapter that will parse a list of contacts given by a {@link Cursor} that display the
* results as a list.
diff --git a/src/com/android/car/dialer/ContactResultsFragment.java b/src/com/android/car/dialer/ContactResultsFragment.java
index 062c8b24..417581d6 100644
--- a/src/com/android/car/dialer/ContactResultsFragment.java
+++ b/src/com/android/car/dialer/ContactResultsFragment.java
@@ -16,14 +16,14 @@
package com.android.car.dialer;
-import android.app.Fragment;
-import android.app.LoaderManager;
-import android.content.CursorLoader;
-import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract.Contacts;
import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
@@ -32,11 +32,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.net.Uri;
-import com.android.car.view.PagedListView;
-
import java.util.ArrayList;
import java.util.List;
+import androidx.car.widget.DayNightStyle;
+import androidx.car.widget.PagedListView;
+
/**
* A fragment that will take a search query, look up contacts that match and display those
* results as a list.
@@ -79,9 +80,8 @@ public class ContactResultsFragment extends Fragment implements
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mContactResultList = view.findViewById(R.id.contact_result_list);
- mContactResultList.setLightMode();
+ mContactResultList.setDayNightStyle(DayNightStyle.ALWAYS_LIGHT);
mContactResultList.setAdapter(mAdapter);
- mContactResultList.getLayoutManager().setOffsetRows(false);
RecyclerView recyclerView = mContactResultList.getRecyclerView();
for (RecyclerView.OnScrollListener listener : mOnScrollListeners) {
@@ -124,7 +124,7 @@ public class ContactResultsFragment extends Fragment implements
if (!TextUtils.isEmpty(mSearchQuery)) {
// Calling restartLoader so that the loader is always re-created with the new
// search query.
- getLoaderManager().restartLoader(0, null /* args */, this /* callback */);
+ LoaderManager.getInstance(this).restartLoader(0, null /* args */, this /* callback */);
}
}
@@ -161,7 +161,8 @@ public class ContactResultsFragment extends Fragment implements
}
@Override
- public void onLoaderReset(Loader<Cursor> loader) {}
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
@Override
public void onDestroy() {
@@ -173,8 +174,9 @@ public class ContactResultsFragment extends Fragment implements
/**
* Creates a new instance of the {@link ContactResultsFragment}.
*
- * @param listener A scroll listener that will be notified when the list of search results has
- * been scrolled.
+ * @param listener A scroll listener that will be notified when the list of search
+ * results has
+ * been scrolled.
* @param initialSearchQuery An optional search query that will be inputted when the fragment
* starts up.
*/
diff --git a/src/com/android/car/dialer/ContactSearchActivity.java b/src/com/android/car/dialer/ContactSearchActivity.java
index 30a9cd77..03243f36 100644
--- a/src/com/android/car/dialer/ContactSearchActivity.java
+++ b/src/com/android/car/dialer/ContactSearchActivity.java
@@ -18,13 +18,15 @@ package com.android.car.dialer;
import android.animation.ValueAnimator;
import android.app.Activity;
-import android.app.Fragment;
import android.app.SearchManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
@@ -32,13 +34,11 @@ import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
-import com.android.car.view.CarLayoutManager;
-
/**
* An activity that manages contact searching. This activity will display the result of a search
* as well as show the details of a contact when that contact is clicked.
*/
-public class ContactSearchActivity extends Activity {
+public class ContactSearchActivity extends FragmentActivity {
private static final String CONTENT_FRAGMENT_TAG = "CONTENT_FRAGMENT_TAG";
private static final int ANIMATION_DURATION_MS = 100;
@@ -76,10 +76,12 @@ public class ContactSearchActivity extends Activity {
mSearchField.addTextChangedListener(new TextWatcher() {
@Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
@Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {}
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
@Override
public void afterTextChanged(Editable s) {
@@ -187,7 +189,7 @@ public class ContactSearchActivity extends Activity {
// changes since any lists in it will be reset to the top.
resetSearchPanelElevation();
- getFragmentManager().beginTransaction()
+ getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.fade_in, R.animator.fade_out)
.replace(R.id.content_fragment_container, fragment, CONTENT_FRAGMENT_TAG)
.commitNow();
@@ -198,7 +200,7 @@ public class ContactSearchActivity extends Activity {
*/
@Nullable
private Fragment getCurrentFragment() {
- return getFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG);
+ return getSupportFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG);
}
@Override
@@ -254,11 +256,12 @@ public class ContactSearchActivity extends Activity {
public class ContactScrollListener extends RecyclerView.OnScrollListener {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- // Assuming CarLayoutManager is the layout manager as all car applications should be
- // using a PagedListView.
- CarLayoutManager layoutManager = (CarLayoutManager) recyclerView.getLayoutManager();
+ // The default LayoutManager for PagedListView is a LinearLayoutManager. Dialer does
+ // not change this.
+ LinearLayoutManager layoutManager =
+ (LinearLayoutManager) recyclerView.getLayoutManager();
- if (layoutManager.isAtTop()) {
+ if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
resetSearchPanelElevation();
} else {
// No animation needed when adding the elevation because the scroll masks the adding
diff --git a/src/com/android/car/dialer/DialerFragment.java b/src/com/android/car/dialer/DialerFragment.java
index 642bf198..86bd97fe 100644
--- a/src/com/android/car/dialer/DialerFragment.java
+++ b/src/com/android/car/dialer/DialerFragment.java
@@ -15,102 +15,37 @@
*/
package com.android.car.dialer;
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.ToneGenerator;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
-import com.android.car.apps.common.FabDrawable;
-import com.android.car.dialer.telecom.TelecomUtils;
+import com.android.car.dialer.log.L;
import com.android.car.dialer.telecom.UiCallManager;
-import com.android.car.dialer.telecom.UiCallManager.CallListener;
+import com.android.car.dialer.ui.DialerInfoController;
+import com.android.car.dialer.ui.DialpadFragment;
/**
* Fragment that controls the dialpad.
*/
-public class DialerFragment extends Fragment {
+public class DialerFragment extends Fragment implements DialpadFragment.DialpadCallback {
private static final String TAG = "Em.DialerFragment";
- private static final String INPUT_ACTIVE_KEY = "INPUT_ACTIVE_KEY";
- private static final String DIAL_NUMBER_KEY = "DIAL_NUMBER_KEY";
-
- private static final int TONE_LENGTH_MS = 150;
- private static final int TONE_RELATIVE_VOLUME = 80;
- private static final int MAX_DIAL_NUMBER = 20;
-
- private static final SparseIntArray mToneMap = new SparseIntArray();
- private static final SparseArray<String> mDialValueMap = new SparseArray<>();
-
- static {
- mToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1);
- mToneMap.put(KeyEvent.KEYCODE_2, ToneGenerator.TONE_DTMF_2);
- mToneMap.put(KeyEvent.KEYCODE_3, ToneGenerator.TONE_DTMF_3);
- mToneMap.put(KeyEvent.KEYCODE_4, ToneGenerator.TONE_DTMF_4);
- mToneMap.put(KeyEvent.KEYCODE_5, ToneGenerator.TONE_DTMF_5);
- mToneMap.put(KeyEvent.KEYCODE_6, ToneGenerator.TONE_DTMF_6);
- mToneMap.put(KeyEvent.KEYCODE_7, ToneGenerator.TONE_DTMF_7);
- mToneMap.put(KeyEvent.KEYCODE_8, ToneGenerator.TONE_DTMF_8);
- mToneMap.put(KeyEvent.KEYCODE_9, ToneGenerator.TONE_DTMF_9);
- mToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0);
- mToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S);
- mToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P);
- mDialValueMap.put(KeyEvent.KEYCODE_1, "1");
- mDialValueMap.put(KeyEvent.KEYCODE_2, "2");
- mDialValueMap.put(KeyEvent.KEYCODE_3, "3");
- mDialValueMap.put(KeyEvent.KEYCODE_4, "4");
- mDialValueMap.put(KeyEvent.KEYCODE_5, "5");
- mDialValueMap.put(KeyEvent.KEYCODE_6, "6");
- mDialValueMap.put(KeyEvent.KEYCODE_7, "7");
- mDialValueMap.put(KeyEvent.KEYCODE_8, "8");
- mDialValueMap.put(KeyEvent.KEYCODE_9, "9");
- mDialValueMap.put(KeyEvent.KEYCODE_0, "0");
- mDialValueMap.put(KeyEvent.KEYCODE_STAR, "*");
- mDialValueMap.put(KeyEvent.KEYCODE_POUND, "#");
- }
-
- private Context mContext;
- private UiCallManager mUiCallManager;
- private final StringBuffer mNumber = new StringBuffer(MAX_DIAL_NUMBER);
- private ToneGenerator mToneGenerator;
- private final Object mToneGeneratorLock = new Object();
- private TextView mNumberView;
- private boolean mShowInput = true;
- private Runnable mPendingRunnable;
+ private static final String DIAL_NUMBER_KEY = "DIAL_NUMBER_KEY";
+ private static final String PLUS_DIGIT = "+";
- private DialerBackButtonListener mBackListener;
-
- /**
- * Interface for a class that will be notified when the back button of the dialer has been
- * clicked.
- */
- public interface DialerBackButtonListener {
- /**
- * Called when the back button has been clicked on the dialer. This action should dismiss
- * the dialer fragment.
- */
- void onDialerBackClick();
- }
+ private DialerInfoController mDialerInfoController;
+ private L logger = L.logger(TAG);
/**
* Creates a new instance of the {@link DialerFragment} and display the given number as the one
* to dial.
*/
- static DialerFragment newInstance(UiCallManager callManager,
- DialerBackButtonListener listener, @Nullable String dialNumber) {
+ static DialerFragment newInstance(@Nullable String dialNumber) {
DialerFragment fragment = new DialerFragment();
- fragment.mUiCallManager = callManager;
- fragment.mBackListener = listener;
if (!TextUtils.isEmpty(dialNumber)) {
Bundle args = new Bundle();
@@ -122,211 +57,36 @@ public class DialerFragment extends Fragment {
}
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null && savedInstanceState.containsKey(INPUT_ACTIVE_KEY)) {
- mShowInput = savedInstanceState.getBoolean(INPUT_ACTIVE_KEY);
- }
-
- Bundle args = getArguments();
- if (args != null) {
- setDialNumber(args.getString(DIAL_NUMBER_KEY));
- }
- }
-
- @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "onCreateView");
- }
-
- mContext = getContext();
+ logger.d("onCreateView");
View view = inflater.inflate(R.layout.dialer_fragment, container, false);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "onCreateView: inflated successfully");
- }
-
- view.findViewById(R.id.exit_dialer_button).setOnClickListener(v -> {
- if (mBackListener != null) {
- mBackListener.onDialerBackClick();
- }
- });
+ Fragment dialpadFragment = DialpadFragment.newInstance();
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.dialpad_fragment_container, dialpadFragment)
+ .commit();
- mNumberView = (TextView) view.findViewById(R.id.number);
+ View dialerInfoContainer = view.findViewById(R.id.dialer_info_fragment_container);
+ mDialerInfoController = new DialerInfoController(getContext(), dialerInfoContainer);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "mShowInput: " + mShowInput);
+ if (getArguments() != null) {
+ mDialerInfoController.appendDialedNumber(getArguments().getString(DIAL_NUMBER_KEY));
}
- FabDrawable answerCallDrawable = new FabDrawable(mContext);
- answerCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_call));
-
- View callButton = view.findViewById(R.id.call);
- callButton.setBackground(answerCallDrawable);
- callButton.setVisibility(View.VISIBLE);
- callButton.setOnClickListener((unusedView) -> {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Call button clicked, placing a call: " + mNumber.toString());
- }
-
- if (!TextUtils.isEmpty(mNumber.toString())) {
- mUiCallManager.safePlaceCall(mNumber.toString(), false);
- }
- });
-
- View deleteButton = view.findViewById(R.id.delete);
- deleteButton.setVisibility(View.VISIBLE);
- deleteButton.setOnClickListener(v -> {
- if (mNumber.length() != 0) {
- mNumber.deleteCharAt(mNumber.length() - 1);
- mNumberView.setText(getFormattedNumber(mNumber.toString()));
- }
- });
-
- setupKeypadClickListeners(view);
-
return view;
}
- /**
- * The default click listener for all dialpad buttons. This click listener will append its
- * associated value to {@link #mNumber}.
- */
- private class DialpadClickListener implements View.OnClickListener {
- private final int mTone;
- private final String mValue;
-
- DialpadClickListener(int keyCode) {
- mTone = mToneMap.get(keyCode);
- mValue = mDialValueMap.get(keyCode);
- }
-
- @Override
- public void onClick(View v) {
- mNumber.append(mValue);
- mNumberView.setText(getFormattedNumber(mNumber.toString()));
- playTone(mTone);
- }
- }
-
- private void setupKeypadClickListeners(View parent) {
- parent.findViewById(R.id.zero).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_0));
- parent.findViewById(R.id.one).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_1));
- parent.findViewById(R.id.two).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_2));
- parent.findViewById(R.id.three).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_3));
- parent.findViewById(R.id.four).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_4));
- parent.findViewById(R.id.five).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_5));
- parent.findViewById(R.id.six).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_6));
- parent.findViewById(R.id.seven).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_7));
- parent.findViewById(R.id.eight).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_8));
- parent.findViewById(R.id.nine).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_9));
- parent.findViewById(R.id.star).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_STAR));
- parent.findViewById(R.id.pound).setOnClickListener(
- new DialpadClickListener(KeyEvent.KEYCODE_POUND));
- }
-
@Override
- public void onResume() {
- super.onResume();
- synchronized (mToneGeneratorLock) {
- if (mToneGenerator == null) {
- mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, TONE_RELATIVE_VOLUME);
- }
- }
- mUiCallManager.addListener(mCallListener);
-
- if (mPendingRunnable != null) {
- mPendingRunnable.run();
- mPendingRunnable = null;
- }
+ public void onDialVoiceMail() {
+ UiCallManager.get().callVoicemail();
}
@Override
- public void onPause() {
- super.onPause();
- mUiCallManager.removeListener(mCallListener);
- stopTone();
- synchronized (mToneGeneratorLock) {
- if (mToneGenerator != null) {
- mToneGenerator.release();
- mToneGenerator = null;
- }
- }
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- mContext = null;
- mNumberView = null;
- }
-
- private void setDialNumber(final String number) {
- if (TextUtils.isEmpty(number)) {
- return;
- }
-
- if (mContext != null && mNumberView != null) {
- setDialNumberInternal(number);
- } else {
- mPendingRunnable = () -> setDialNumberInternal(number);
- }
- }
-
- private void setDialNumberInternal(final String number) {
- // Clear existing content in mNumber.
- mNumber.setLength(0);
- mNumber.append(number);
- mNumberView.setText(getFormattedNumber(mNumber.toString()));
- }
-
- private void playTone(int tone) {
- synchronized (mToneGeneratorLock) {
- if (mToneGenerator == null) {
- Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone);
- return;
- }
-
- // Start the new tone (will stop any playing tone)
- mToneGenerator.startTone(tone, TONE_LENGTH_MS);
+ public void onAppendDigit(String digit) {
+ if (PLUS_DIGIT.equals(digit)) {
+ mDialerInfoController.removeLastDigit();
}
+ mDialerInfoController.appendDialedNumber(digit);
}
-
- private void stopTone() {
- synchronized (mToneGeneratorLock) {
- if (mToneGenerator == null) {
- Log.w(TAG, "stopTone: mToneGenerator == null");
- return;
- }
- mToneGenerator.stopTone();
- }
- }
-
- private String getFormattedNumber(String number) {
- return TelecomUtils.getFormattedNumber(mContext, number);
- }
-
- private final CallListener mCallListener = new CallListener() {
- @Override
- public void dispatchPhoneKeyEvent(KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_CALL &&
- event.getAction() == KeyEvent.ACTION_UP &&
- !TextUtils.isEmpty(mNumber.toString())) {
- mUiCallManager.safePlaceCall(mNumber.toString(), false);
- }
- }
- };
}
diff --git a/src/com/android/car/dialer/OngoingCallFragment.java b/src/com/android/car/dialer/OngoingCallFragment.java
index 87d4ebb0..1c6445a7 100644
--- a/src/com/android/car/dialer/OngoingCallFragment.java
+++ b/src/com/android/car/dialer/OngoingCallFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -38,15 +38,13 @@ import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.ImageButton;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.car.apps.common.CircleBitmapDrawable;
import com.android.car.apps.common.FabDrawable;
import com.android.car.dialer.telecom.TelecomUtils;
import com.android.car.dialer.telecom.UiCall;
import com.android.car.dialer.telecom.UiCallManager;
-import com.android.car.dialer.telecom.UiCallManager.CallListener;
+import com.android.car.dialer.ui.CircleBitmapDrawable;
import java.util.Arrays;
import java.util.List;
@@ -55,7 +53,8 @@ import java.util.Objects;
/**
* A fragment that displays information about an on-going call with options to hang up.
*/
-public class OngoingCallFragment extends Fragment {
+@Deprecated
+public class OngoingCallFragment extends Fragment implements CallListener {
private static final String TAG = "OngoingCall";
private static final SparseArray<Character> mDialpadButtonMap = new SparseArray<>();
@@ -150,8 +149,6 @@ public class OngoingCallFragment extends Fragment {
dialpadView.setOnKeyListener(mDialpadKeyListener);
}
- mUiCallManager.addListener(mCallListener);
-
updateCalls();
return view;
@@ -268,7 +265,6 @@ public class OngoingCallFragment extends Fragment {
@Override
public void onDestroyView() {
super.onDestroyView();
- mUiCallManager.removeListener(mCallListener);
}
@Override
@@ -580,6 +576,51 @@ public class OngoingCallFragment extends Fragment {
private final Runnable mStopDtmfToneRunnable =
() -> mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall());
+ @Override
+ public void onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format(
+ "onAudioStateChanged(); isMuted: %b, audioRoute: %d, supportedAudioRouteMask: %d",
+ isMuted, route, supportedRouteMask));
+ }
+ mMuteButton.setActivated(isMuted);
+ trySpeakerAudioRouteIfNecessary();
+ }
+
+ @Override
+ public void onCallStateChanged(UiCall call, int state) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("onCallStateChanged(); call: %s, state: %s", call, state));
+ }
+ updateCalls();
+ }
+
+ @Override
+ public void onCallUpdated(UiCall call) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCallUpdated(); call: " + call);
+ }
+ updateCalls();
+ }
+
+ @Override
+ public void onCallAdded(UiCall call) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCallAdded(); call: " + call);
+ }
+ updateCalls();
+ trySpeakerAudioRouteIfNecessary();
+ }
+
+ @Override
+ public void onCallRemoved(UiCall call) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCallRemoved(); call: " + call);
+ }
+ mLastRemovedCall = call;
+ updateCalls();
+ }
+
private final class DialpadAnimation extends Animation {
private static final int DURATION = 300;
private static final float MAX_SCRIM_ALPHA = 0.6f;
@@ -622,52 +663,4 @@ public class OngoingCallFragment extends Fragment {
mSecondaryStateTextView.setAlpha(1f - interpolatedTime);
}
}
-
- private final CallListener mCallListener = new CallListener() {
- @Override
- public void onCallAdded(UiCall call) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "onCallAdded(); call: " + call);
- }
- updateCalls();
- trySpeakerAudioRouteIfNecessary();
- }
-
- @Override
- public void onCallRemoved(UiCall call) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "onCallRemoved(); call: " + call);
- }
- mLastRemovedCall = call;
- updateCalls();
- }
-
- @Override
- public void onAudioStateChanged(boolean isMuted, int audioRoute,
- int supportedAudioRouteMask) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, String.format("onAudioStateChanged(); isMuted: %b, audioRoute: %d, "
- + " supportedAudioRouteMask: %d", isMuted, audioRoute,
- supportedAudioRouteMask));
- }
- mMuteButton.setActivated(isMuted);
- trySpeakerAudioRouteIfNecessary();
- }
-
- @Override
- public void onStateChanged(UiCall call, int state) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "onStateChanged(); call: " + call + ", state: " + state);
- }
- updateCalls();
- }
-
- @Override
- public void onCallUpdated(UiCall call) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "onCallUpdated(); call: " + call);
- }
- updateCalls();
- }
- };
}
diff --git a/src/com/android/car/dialer/StrequentsAdapter.java b/src/com/android/car/dialer/StrequentsAdapter.java
index 1bf11b5f..da07b236 100644
--- a/src/com/android/car/dialer/StrequentsAdapter.java
+++ b/src/com/android/car/dialer/StrequentsAdapter.java
@@ -19,6 +19,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.PorterDuff;
+import android.os.Handler;
import android.provider.CallLog;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
@@ -28,10 +29,11 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.car.widget.PagedListView;
+
import com.android.car.dialer.telecom.PhoneLoader;
import com.android.car.dialer.telecom.TelecomUtils;
import com.android.car.dialer.telecom.UiCallManager;
-import com.android.car.view.PagedListView;
import java.util.ArrayList;
import java.util.Collections;
@@ -43,8 +45,8 @@ import java.util.List;
* It handles two types of contacts:
* <p>
* <ul>
- * <li>Strequent contacts (starred and/or frequent)
- * <li>Last call contact
+ * <li>Strequent contacts (starred and/or frequent)
+ * <li>Last call contact
* </ul>
*/
public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
@@ -53,12 +55,16 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
private static final int VIEW_TYPE_EMPTY = 0;
private static final int VIEW_TYPE_LASTCALL = 1;
private static final int VIEW_TYPE_STREQUENT = 2;
+ private static final long LAST_CALL_REFRESH_INTERVAL_MILLIS = 60 * 1000L;
private final Context mContext;
private final UiCallManager mUiCallManager;
private List<ContactEntry> mData;
+ private Handler mMainThreadHandler = new Handler();
private LastCallData mLastCallData;
+ private Cursor mLastCallCursor;
+ private LastCallPeriodicalUpdater mLastCallPeriodicalUpdater = new LastCallPeriodicalUpdater();
private final ContentResolver mContentResolver;
@@ -84,7 +90,15 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
}
public void setLastCallCursor(@Nullable Cursor cursor) {
+ mLastCallCursor = cursor;
mLastCallData = convertLastCallCursor(cursor);
+ if (cursor != null) {
+ mMainThreadHandler.postDelayed(mLastCallPeriodicalUpdater,
+ LAST_CALL_REFRESH_INTERVAL_MILLIS);
+ } else {
+ mMainThreadHandler.removeCallbacks(mLastCallPeriodicalUpdater);
+ }
+
notifyDataSetChanged();
}
@@ -138,16 +152,12 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
public CallLogViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
switch (viewType) {
- case VIEW_TYPE_LASTCALL:
- view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.call_log_last_call_item_card, parent, false);
- return new CallLogViewHolder(view);
-
case VIEW_TYPE_EMPTY:
view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.car_list_item_empty, parent, false);
+ .inflate(R.layout.call_log_list_item_empty, parent, false);
return new CallLogViewHolder(view);
+ case VIEW_TYPE_LASTCALL:
case VIEW_TYPE_STREQUENT:
default:
view = LayoutInflater.from(parent.getContext())
@@ -235,24 +245,26 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
String primaryText = mLastCallData.getPrimaryText();
String number = mLastCallData.getNumber();
- viewHolder.title.setText(mLastCallData.getPrimaryText());
- viewHolder.text.setText(mLastCallData.getSecondaryText());
- viewHolder.itemView.setTag(number);
- viewHolder.callTypeIconsView.clear();
- viewHolder.callTypeIconsView.setVisibility(View.VISIBLE);
-
- // mHasFirstItem is true only in main screen, or else it is in drawer, then we need to add
- // call type icons for call history items.
- viewHolder.smallIcon.setVisibility(View.GONE);
- int[] callTypes = mLastCallData.getCallTypes();
- int icons = Math.min(callTypes.length, CallTypeIconsView.MAX_CALL_TYPE_ICONS);
- for (int i = 0; i < icons; i++) {
- viewHolder.callTypeIconsView.add(callTypes[i]);
- }
+ if (!number.equals(viewHolder.itemView.getTag())) {
+ viewHolder.title.setText(mLastCallData.getPrimaryText());
+ viewHolder.itemView.setTag(number);
+ viewHolder.callTypeIconsView.clear();
+ viewHolder.callTypeIconsView.setVisibility(View.VISIBLE);
- setBackground(viewHolder);
+ // mHasFirstItem is true only in main screen, or else it is in drawer, then we need
+ // to add
+ // call type icons for call history items.
+ viewHolder.smallIcon.setVisibility(View.GONE);
+ int[] callTypes = mLastCallData.getCallTypes();
+ int icons = Math.min(callTypes.length, CallTypeIconsView.MAX_CALL_TYPE_ICONS);
+ for (int i = 0; i < icons; i++) {
+ viewHolder.callTypeIconsView.add(callTypes[i]);
+ }
+
+ TelecomUtils.setContactBitmapAsync(mContext, viewHolder.icon, primaryText, number);
+ }
- TelecomUtils.setContactBitmapAsync(mContext, viewHolder.icon, primaryText, number);
+ viewHolder.text.setText(mLastCallData.getSecondaryText());
}
/**
@@ -321,10 +333,9 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
private void onBindView(final CallLogViewHolder viewHolder, final ContactEntry entry) {
viewHolder.itemView.setOnClickListener(v -> onViewClicked(viewHolder));
- final String number = entry.number;
- // TODO(mcrico): Why is being a voicemail related to not having a name?
- boolean isVoicemail = (entry.name == null)
- && (number.equals(TelecomUtils.getVoicemailNumber(mContext)));
+ final String number = entry.getNumber();
+ // TODO: Why is being a voicemail related to not having a name?
+ boolean isVoicemail = entry.isVoicemail();
String secondaryText = "";
if (!isVoicemail) {
secondaryText = String.valueOf(TelecomUtils.getTypeFromNumber(mContext, number));
@@ -339,7 +350,7 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
TelecomUtils.setContactBitmapAsync(mContext, viewHolder.icon, displayName, number);
- if (entry.isStarred) {
+ if (entry.isStarred()) {
viewHolder.smallIcon.setVisibility(View.VISIBLE);
final int iconColor = mContext.getColor(android.R.color.white);
viewHolder.smallIcon.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
@@ -348,31 +359,6 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
viewHolder.smallIcon.setVisibility(View.GONE);
}
- setBackground(viewHolder);
- }
-
- /**
- * Appropriately sets the background for the View that is being bound. This method will allow
- * for rounded corners on either the top or bottom of a card.
- */
- private void setBackground(CallLogViewHolder viewHolder) {
- int itemCount = getItemCount();
- int adapterPosition = viewHolder.getAdapterPosition();
-
- if (itemCount == 1) {
- // Only element - all corners are rounded
- viewHolder.card.setBackgroundResource(
- R.drawable.car_card_rounded_top_bottom_background);
- } else if (adapterPosition == 0) {
- // First element gets rounded top
- viewHolder.card.setBackgroundResource(R.drawable.car_card_rounded_top_background);
- } else if (adapterPosition == itemCount - 1) {
- // Last one has a rounded bottom
- viewHolder.card.setBackgroundResource(R.drawable.car_card_rounded_bottom_background);
- } else {
- // Middle have no rounded corners
- viewHolder.card.setBackgroundResource(R.color.car_card);
- }
}
/**
@@ -422,4 +408,14 @@ public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
return mCallTypes;
}
}
+
+ private class LastCallPeriodicalUpdater implements Runnable {
+
+ @Override
+ public void run() {
+ mLastCallData = convertLastCallCursor(mLastCallCursor);
+ notifyItemChanged(0);
+ mMainThreadHandler.postDelayed(this, LAST_CALL_REFRESH_INTERVAL_MILLIS);
+ }
+ }
}
diff --git a/src/com/android/car/dialer/StrequentsFragment.java b/src/com/android/car/dialer/StrequentsFragment.java
index f6f94ee1..7bd8d4a5 100644
--- a/src/com/android/car/dialer/StrequentsFragment.java
+++ b/src/com/android/car/dialer/StrequentsFragment.java
@@ -18,26 +18,27 @@ package com.android.car.dialer;
import android.content.ContentResolver;
import android.content.Context;
import android.content.CursorLoader;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
-import android.graphics.Canvas;
-import android.graphics.Paint;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
+import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.LinearLayout;
+
+import androidx.car.widget.DayNightStyle;
+import androidx.car.widget.PagedListView;
import com.android.car.dialer.telecom.PhoneLoader;
import com.android.car.dialer.telecom.UiCallManager;
-import com.android.car.view.PagedListView;
/**
* Contains a list of contacts. The call types can be any of the CALL_TYPE_* fields from
@@ -59,10 +60,8 @@ public class StrequentsFragment extends Fragment {
private Cursor mCallLogCursor;
private boolean mHasLoadedData;
- public static StrequentsFragment newInstance(UiCallManager callManager) {
- StrequentsFragment fragment = new StrequentsFragment();
- fragment.mUiCallManager = callManager;
- return fragment;
+ public static StrequentsFragment newInstance() {
+ return new StrequentsFragment();
}
@Override
@@ -81,32 +80,33 @@ public class StrequentsFragment extends Fragment {
}
mContext = getContext();
+ mUiCallManager = UiCallManager.get();
View view = inflater.inflate(R.layout.strequents_fragment, container, false);
- mListView = (PagedListView) view.findViewById(R.id.list_view);
- mListView.getLayoutManager().setOffsetRows(true);
+ mListView = view.findViewById(R.id.list_view);
+ int numOfColumn = getContext().getResources().getInteger(
+ R.integer.favorite_fragment_grid_column);
+ mListView.getRecyclerView().setLayoutManager(
+ new GridLayoutManager(getContext(), numOfColumn));
+ mListView.getRecyclerView().addItemDecoration(new ItemSpacingDecoration());
mSpeedialCursorLoader = PhoneLoader.registerCallObserver(PhoneLoader.CALL_TYPE_SPEED_DIAL,
- mContext, (loader, cursor) -> {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "PhoneLoader: onLoadComplete (CALL_TYPE_SPEED_DIAL)");
- }
-
- onLoadStrequentCursor(cursor);
+ mContext, (loader, cursor) -> {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "PhoneLoader: onLoadComplete (CALL_TYPE_SPEED_DIAL)");
+ }
- if (mContext != null) {
- mListView.addItemDecoration(new Decoration(mContext));
- }
- });
+ onLoadStrequentCursor(cursor);
+ });
// Get the latest call log from the call logs history.
mCallLogCursorLoader = PhoneLoader.registerCallObserver(PhoneLoader.CALL_TYPE_ALL, mContext,
- (loader, cursor) -> {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "PhoneLoader: onLoadComplete (CALL_TYPE_ALL)");
- }
- onLoadCallLogCursor(cursor);
- });
+ (loader, cursor) -> {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "PhoneLoader: onLoadComplete (CALL_TYPE_ALL)");
+ }
+ onLoadCallLogCursor(cursor);
+ });
ContentResolver contentResolver = mContext.getContentResolver();
contentResolver.registerContentObserver(mSpeedialCursorLoader.getUri(),
@@ -126,7 +126,6 @@ public class StrequentsFragment extends Fragment {
Log.v(TAG, "Max clicks: " + maxClicks + ", Max pages: " + maxPages);
}
- mListView.setLightMode();
mAdapter = new StrequentsAdapter(mContext, mUiCallManager);
mAdapter.setStrequentsListener(viewHolder -> {
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -142,7 +141,7 @@ public class StrequentsFragment extends Fragment {
Log.d(TAG, "setItemAnimator");
}
- mListView.getRecyclerView().setItemAnimator(new StrequentsItemAnimator());
+ mListView.getRecyclerView().setItemAnimator(null);
return view;
}
@@ -255,64 +254,24 @@ public class StrequentsFragment extends Fragment {
}
}
- /**
- * Decoration for the speed dial cards. This ItemDecoration will not show a divider between
- * the dialpad item and the first speed dial item and the divider is offset but a couple of
- * pixels to offset the fact that the cards overlap.
- */
- private static class Decoration extends RecyclerView.ItemDecoration {
- private final Paint mPaint;
- private final int mPaintAlpha;
- private final int mDividerHeight;
-
- public Decoration(Context context) {
- Resources res = context.getResources();
- mPaint = new Paint();
- mPaint.setColor(res.getColor(R.color.car_list_divider));
- mDividerHeight = res.getDimensionPixelSize(R.dimen.car_divider_height);
- mPaintAlpha = mPaint.getAlpha();
- }
+ private class ItemSpacingDecoration extends RecyclerView.ItemDecoration {
@Override
- public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
- StrequentsAdapter adapter = (StrequentsAdapter) parent.getAdapter();
-
- if (adapter.getItemCount() <= 0) {
- return;
+ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
+ @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ int carPadding1 = mContext.getResources().getDimensionPixelOffset(
+ R.dimen.car_padding_1);
+
+ int leftPadding = 0;
+ int rightPadding = 0;
+ if (parent.getChildAdapterPosition(view) % 2 == 0) {
+ rightPadding = carPadding1;
+ } else {
+ leftPadding = carPadding1;
}
- final int childCount = parent.getChildCount();
-
- // Don't draw decoration line on last item of the list.
- for (int i = 0; i < childCount - 1; i++) {
- final View child = parent.getChildAt(i);
-
- // If the child is focused then the decoration will look bad with the focus
- // highlight so don't draw it.
- if (child.isFocused()) {
- continue;
- }
-
- // The left edge of the divider should align with the left edge of text_container.
- LinearLayout container = child.findViewById(R.id.container);
- View textContainer = child.findViewById(R.id.text_container);
- View card = child.findViewById(R.id.call_log_card);
-
- int left = textContainer.getLeft() + container.getLeft() + card.getLeft();
- int right = left + textContainer.getWidth();
-
- RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
- int bottom = child.getBottom() + lp.bottomMargin
- + Math.round(child.getTranslationY());
- int top = bottom - mDividerHeight;
-
- if (top >= c.getHeight() || top < 0) {
- break;
- }
-
- mPaint.setAlpha(Math.round(container.getAlpha() * mPaintAlpha));
- c.drawRect(left, top, right, bottom, mPaint);
- }
+ outRect.set(leftPadding, carPadding1, rightPadding, carPadding1);
}
}
}
diff --git a/src/com/android/car/dialer/StrequentsItemAnimator.java b/src/com/android/car/dialer/StrequentsItemAnimator.java
deleted file mode 100644
index 23e8b1d8..00000000
--- a/src/com/android/car/dialer/StrequentsItemAnimator.java
+++ /dev/null
@@ -1,637 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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;
-
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.ViewPropertyAnimatorCompat;
-import android.support.v4.view.ViewPropertyAnimatorListener;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.support.v7.widget.SimpleItemAnimator;
-import android.view.View;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Branch from {@link android.support.v7.widget.DefaultItemAnimator} with changes on
- * {@link #animateAddImpl}, {@link #animateAdd} and {@link #animateRemoveImpl}.
- */
-public class StrequentsItemAnimator extends SimpleItemAnimator {
- private static final boolean DEBUG = false;
-
- private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
- private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
- private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
- private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
-
- private ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>();
- private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
- private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
-
- private ArrayList<ViewHolder> mAddAnimations = new ArrayList<>();
- private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>();
- private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
- private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>();
-
- private static class MoveInfo {
- public ViewHolder holder;
- public int fromX, fromY, toX, toY;
-
- private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
- this.holder = holder;
- this.fromX = fromX;
- this.fromY = fromY;
- this.toX = toX;
- this.toY = toY;
- }
- }
-
- private static class ChangeInfo {
- public ViewHolder oldHolder, newHolder;
- public int fromX, fromY, toX, toY;
- private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
- this.oldHolder = oldHolder;
- this.newHolder = newHolder;
- }
-
- private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
- int fromX, int fromY, int toX, int toY) {
- this(oldHolder, newHolder);
- this.fromX = fromX;
- this.fromY = fromY;
- this.toX = toX;
- this.toY = toY;
- }
-
- @Override
- public String toString() {
- return "ChangeInfo{" +
- "oldHolder=" + oldHolder +
- ", newHolder=" + newHolder +
- ", fromX=" + fromX +
- ", fromY=" + fromY +
- ", toX=" + toX +
- ", toY=" + toY +
- '}';
- }
- }
-
- @Override
- public void runPendingAnimations() {
- boolean removalsPending = !mPendingRemovals.isEmpty();
- boolean movesPending = !mPendingMoves.isEmpty();
- boolean changesPending = !mPendingChanges.isEmpty();
- boolean additionsPending = !mPendingAdditions.isEmpty();
- if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
- // nothing to animate
- return;
- }
- // First, remove stuff
- for (ViewHolder holder : mPendingRemovals) {
- animateRemoveImpl(holder);
- }
- mPendingRemovals.clear();
- // Next, move stuff
- if (movesPending) {
- final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>();
- moves.addAll(mPendingMoves);
- mMovesList.add(moves);
- mPendingMoves.clear();
- Runnable mover = new Runnable() {
- @Override
- public void run() {
- for (MoveInfo moveInfo : moves) {
- animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
- moveInfo.toX, moveInfo.toY);
- }
- moves.clear();
- mMovesList.remove(moves);
- }
- };
- if (removalsPending) {
- View view = moves.get(0).holder.itemView;
- ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
- } else {
- mover.run();
- }
- }
- // Next, change stuff, to run in parallel with move animations
- if (changesPending) {
- final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>();
- changes.addAll(mPendingChanges);
- mChangesList.add(changes);
- mPendingChanges.clear();
- Runnable changer = new Runnable() {
- @Override
- public void run() {
- for (ChangeInfo change : changes) {
- animateChangeImpl(change);
- }
- changes.clear();
- mChangesList.remove(changes);
- }
- };
- if (removalsPending) {
- ViewHolder holder = changes.get(0).oldHolder;
- ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
- } else {
- changer.run();
- }
- }
- // Next, add stuff
- if (additionsPending) {
- final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
- additions.addAll(mPendingAdditions);
- mAdditionsList.add(additions);
- mPendingAdditions.clear();
- Runnable adder = new Runnable() {
- public void run() {
- for (ViewHolder holder : additions) {
- animateAddImpl(holder);
- }
- additions.clear();
- mAdditionsList.remove(additions);
- }
- };
- if (removalsPending || movesPending || changesPending) {
- long removeDuration = removalsPending ? getRemoveDuration() : 0;
- long moveDuration = movesPending ? getMoveDuration() : 0;
- long changeDuration = changesPending ? getChangeDuration() : 0;
- long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
- View view = additions.get(0).itemView;
- ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
- } else {
- adder.run();
- }
- }
- }
-
- @Override
- public boolean animateRemove(final ViewHolder holder) {
- endAnimation(holder);
- mPendingRemovals.add(holder);
- return true;
- }
-
- private void animateRemoveImpl(final ViewHolder holder) {
- // Animate on the content if it's CallLogViewHolder.
- final View view = holder instanceof CallLogViewHolder ?
- ((CallLogViewHolder) holder).container : holder.itemView;
- final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
- mRemoveAnimations.add(holder);
- animation.setDuration(getRemoveDuration())
- .alpha(0).setListener(new VpaListenerAdapter() {
- @Override
- public void onAnimationStart(View view) {
- dispatchRemoveStarting(holder);
- }
-
- @Override
- public void onAnimationEnd(View view) {
- animation.setListener(null);
- ViewCompat.setAlpha(view, 1);
- dispatchRemoveFinished(holder);
- mRemoveAnimations.remove(holder);
- dispatchFinishedWhenDone();
- }
- }).start();
- }
-
- @Override
- public boolean animateAdd(final ViewHolder holder) {
- endAnimation(holder);
- // for CallLogViewHolder, instead of fade out the whole card, fade out only the content.
- if (holder instanceof CallLogViewHolder) {
- ViewCompat.setAlpha(((CallLogViewHolder) holder).container, 0);
- } else {
- ViewCompat.setAlpha(holder.itemView, 0);
- }
- mPendingAdditions.add(holder);
- return true;
- }
-
- private void animateAddImpl(final ViewHolder holder) {
- // Animate on the content if it's CallLogViewHolder.
- final View view = holder instanceof CallLogViewHolder ?
- ((CallLogViewHolder) holder).container : holder.itemView;
- final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
- mAddAnimations.add(holder);
- animation.alpha(1).setDuration(getAddDuration()).
- setListener(new VpaListenerAdapter() {
- @Override
- public void onAnimationStart(View view) {
- dispatchAddStarting(holder);
- }
- @Override
- public void onAnimationCancel(View view) {
- ViewCompat.setAlpha(view, 1);
- }
-
- @Override
- public void onAnimationEnd(View view) {
- animation.setListener(null);
- dispatchAddFinished(holder);
- mAddAnimations.remove(holder);
- dispatchFinishedWhenDone();
- }
- }).start();
- }
-
- @Override
- public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
- int toX, int toY) {
- final View view = holder.itemView;
- fromX += ViewCompat.getTranslationX(holder.itemView);
- fromY += ViewCompat.getTranslationY(holder.itemView);
- endAnimation(holder);
- int deltaX = toX - fromX;
- int deltaY = toY - fromY;
- if (deltaX == 0 && deltaY == 0) {
- dispatchMoveFinished(holder);
- return false;
- }
- if (deltaX != 0) {
- ViewCompat.setTranslationX(view, -deltaX);
- }
- if (deltaY != 0) {
- ViewCompat.setTranslationY(view, -deltaY);
- }
- mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
- return true;
- }
-
- private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
- final View view = holder.itemView;
- final int deltaX = toX - fromX;
- final int deltaY = toY - fromY;
- if (deltaX != 0) {
- ViewCompat.animate(view).translationX(0);
- }
- if (deltaY != 0) {
- ViewCompat.animate(view).translationY(0);
- }
- // TODO: make EndActions end listeners instead, since end actions aren't called when
- // vpas are canceled (and can't end them. why?)
- // need listener functionality in VPACompat for this. Ick.
- final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
- mMoveAnimations.add(holder);
- animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
- @Override
- public void onAnimationStart(View view) {
- dispatchMoveStarting(holder);
- }
- @Override
- public void onAnimationCancel(View view) {
- if (deltaX != 0) {
- ViewCompat.setTranslationX(view, 0);
- }
- if (deltaY != 0) {
- ViewCompat.setTranslationY(view, 0);
- }
- }
- @Override
- public void onAnimationEnd(View view) {
- animation.setListener(null);
- dispatchMoveFinished(holder);
- mMoveAnimations.remove(holder);
- dispatchFinishedWhenDone();
- }
- }).start();
- }
-
- @Override
- public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
- int fromX, int fromY, int toX, int toY) {
- final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
- final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
- final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
- endAnimation(oldHolder);
- int deltaX = (int) (toX - fromX - prevTranslationX);
- int deltaY = (int) (toY - fromY - prevTranslationY);
- // recover prev translation state after ending animation
- ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
- ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
- ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
- if (newHolder != null && newHolder.itemView != null) {
- // carry over translation values
- endAnimation(newHolder);
- ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
- ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
- ViewCompat.setAlpha(newHolder.itemView, 0);
- }
- mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
- return true;
- }
-
- private void animateChangeImpl(final ChangeInfo changeInfo) {
- final ViewHolder holder = changeInfo.oldHolder;
- final View view = holder == null ? null : holder.itemView;
- final ViewHolder newHolder = changeInfo.newHolder;
- final View newView = newHolder != null ? newHolder.itemView : null;
- if (view != null) {
- final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
- getChangeDuration());
- mChangeAnimations.add(changeInfo.oldHolder);
- oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
- oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
- oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
- @Override
- public void onAnimationStart(View view) {
- dispatchChangeStarting(changeInfo.oldHolder, true);
- }
-
- @Override
- public void onAnimationEnd(View view) {
- oldViewAnim.setListener(null);
- ViewCompat.setAlpha(view, 1);
- ViewCompat.setTranslationX(view, 0);
- ViewCompat.setTranslationY(view, 0);
- dispatchChangeFinished(changeInfo.oldHolder, true);
- mChangeAnimations.remove(changeInfo.oldHolder);
- dispatchFinishedWhenDone();
- }
- }).start();
- }
- if (newView != null) {
- final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
- mChangeAnimations.add(changeInfo.newHolder);
- newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
- alpha(1).setListener(new VpaListenerAdapter() {
- @Override
- public void onAnimationStart(View view) {
- dispatchChangeStarting(changeInfo.newHolder, false);
- }
- @Override
- public void onAnimationEnd(View view) {
- newViewAnimation.setListener(null);
- ViewCompat.setAlpha(newView, 1);
- ViewCompat.setTranslationX(newView, 0);
- ViewCompat.setTranslationY(newView, 0);
- dispatchChangeFinished(changeInfo.newHolder, false);
- mChangeAnimations.remove(changeInfo.newHolder);
- dispatchFinishedWhenDone();
- }
- }).start();
- }
- }
-
- private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
- for (int i = infoList.size() - 1; i >= 0; i--) {
- ChangeInfo changeInfo = infoList.get(i);
- if (endChangeAnimationIfNecessary(changeInfo, item)) {
- if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
- infoList.remove(changeInfo);
- }
- }
- }
- }
-
- private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
- if (changeInfo.oldHolder != null) {
- endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
- }
- if (changeInfo.newHolder != null) {
- endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
- }
- }
- private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
- boolean oldItem = false;
- if (changeInfo.newHolder == item) {
- changeInfo.newHolder = null;
- } else if (changeInfo.oldHolder == item) {
- changeInfo.oldHolder = null;
- oldItem = true;
- } else {
- return false;
- }
- ViewCompat.setAlpha(item.itemView, 1);
- ViewCompat.setTranslationX(item.itemView, 0);
- ViewCompat.setTranslationY(item.itemView, 0);
- dispatchChangeFinished(item, oldItem);
- return true;
- }
-
- @Override
- public void endAnimation(ViewHolder item) {
- final View view = item.itemView;
- // this will trigger end callback which should set properties to their target values.
- ViewCompat.animate(view).cancel();
- // TODO if some other animations are chained to end, how do we cancel them as well?
- for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
- MoveInfo moveInfo = mPendingMoves.get(i);
- if (moveInfo.holder == item) {
- ViewCompat.setTranslationY(view, 0);
- ViewCompat.setTranslationX(view, 0);
- dispatchMoveFinished(item);
- mPendingMoves.remove(i);
- }
- }
- endChangeAnimation(mPendingChanges, item);
- if (mPendingRemovals.remove(item)) {
- ViewCompat.setAlpha(view, 1);
- dispatchRemoveFinished(item);
- }
- if (mPendingAdditions.remove(item)) {
- ViewCompat.setAlpha(view, 1);
- dispatchAddFinished(item);
- }
-
- for (int i = mChangesList.size() - 1; i >= 0; i--) {
- ArrayList<ChangeInfo> changes = mChangesList.get(i);
- endChangeAnimation(changes, item);
- if (changes.isEmpty()) {
- mChangesList.remove(i);
- }
- }
- for (int i = mMovesList.size() - 1; i >= 0; i--) {
- ArrayList<MoveInfo> moves = mMovesList.get(i);
- for (int j = moves.size() - 1; j >= 0; j--) {
- MoveInfo moveInfo = moves.get(j);
- if (moveInfo.holder == item) {
- ViewCompat.setTranslationY(view, 0);
- ViewCompat.setTranslationX(view, 0);
- dispatchMoveFinished(item);
- moves.remove(j);
- if (moves.isEmpty()) {
- mMovesList.remove(i);
- }
- break;
- }
- }
- }
- for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
- ArrayList<ViewHolder> additions = mAdditionsList.get(i);
- if (additions.remove(item)) {
- ViewCompat.setAlpha(view, 1);
- dispatchAddFinished(item);
- if (additions.isEmpty()) {
- mAdditionsList.remove(i);
- }
- }
- }
-
- // animations should be ended by the cancel above.
- if (mRemoveAnimations.remove(item) && DEBUG) {
- throw new IllegalStateException("after animation is cancelled, item should not be in "
- + "mRemoveAnimations list");
- }
-
- if (mAddAnimations.remove(item) && DEBUG) {
- throw new IllegalStateException("after animation is cancelled, item should not be in "
- + "mAddAnimations list");
- }
-
- if (mChangeAnimations.remove(item) && DEBUG) {
- throw new IllegalStateException("after animation is cancelled, item should not be in "
- + "mChangeAnimations list");
- }
-
- if (mMoveAnimations.remove(item) && DEBUG) {
- throw new IllegalStateException("after animation is cancelled, item should not be in "
- + "mMoveAnimations list");
- }
- dispatchFinishedWhenDone();
- }
-
- @Override
- public boolean isRunning() {
- return (!mPendingAdditions.isEmpty() ||
- !mPendingChanges.isEmpty() ||
- !mPendingMoves.isEmpty() ||
- !mPendingRemovals.isEmpty() ||
- !mMoveAnimations.isEmpty() ||
- !mRemoveAnimations.isEmpty() ||
- !mAddAnimations.isEmpty() ||
- !mChangeAnimations.isEmpty() ||
- !mMovesList.isEmpty() ||
- !mAdditionsList.isEmpty() ||
- !mChangesList.isEmpty());
- }
-
- /**
- * Check the state of currently pending and running animations. If there are none
- * pending/running, call {@link #dispatchAnimationsFinished()} to notify any
- * listeners.
- */
- private void dispatchFinishedWhenDone() {
- if (!isRunning()) {
- dispatchAnimationsFinished();
- }
- }
-
- @Override
- public void endAnimations() {
- int count = mPendingMoves.size();
- for (int i = count - 1; i >= 0; i--) {
- MoveInfo item = mPendingMoves.get(i);
- View view = item.holder.itemView;
- ViewCompat.setTranslationY(view, 0);
- ViewCompat.setTranslationX(view, 0);
- dispatchMoveFinished(item.holder);
- mPendingMoves.remove(i);
- }
- count = mPendingRemovals.size();
- for (int i = count - 1; i >= 0; i--) {
- ViewHolder item = mPendingRemovals.get(i);
- dispatchRemoveFinished(item);
- mPendingRemovals.remove(i);
- }
- count = mPendingAdditions.size();
- for (int i = count - 1; i >= 0; i--) {
- ViewHolder item = mPendingAdditions.get(i);
- View view = item.itemView;
- ViewCompat.setAlpha(view, 1);
- dispatchAddFinished(item);
- mPendingAdditions.remove(i);
- }
- count = mPendingChanges.size();
- for (int i = count - 1; i >= 0; i--) {
- endChangeAnimationIfNecessary(mPendingChanges.get(i));
- }
- mPendingChanges.clear();
- if (!isRunning()) {
- return;
- }
-
- int listCount = mMovesList.size();
- for (int i = listCount - 1; i >= 0; i--) {
- ArrayList<MoveInfo> moves = mMovesList.get(i);
- count = moves.size();
- for (int j = count - 1; j >= 0; j--) {
- MoveInfo moveInfo = moves.get(j);
- ViewHolder item = moveInfo.holder;
- View view = item.itemView;
- ViewCompat.setTranslationY(view, 0);
- ViewCompat.setTranslationX(view, 0);
- dispatchMoveFinished(moveInfo.holder);
- moves.remove(j);
- if (moves.isEmpty()) {
- mMovesList.remove(moves);
- }
- }
- }
- listCount = mAdditionsList.size();
- for (int i = listCount - 1; i >= 0; i--) {
- ArrayList<ViewHolder> additions = mAdditionsList.get(i);
- count = additions.size();
- for (int j = count - 1; j >= 0; j--) {
- ViewHolder item = additions.get(j);
- View view = item.itemView;
- ViewCompat.setAlpha(view, 1);
- dispatchAddFinished(item);
- additions.remove(j);
- if (additions.isEmpty()) {
- mAdditionsList.remove(additions);
- }
- }
- }
- listCount = mChangesList.size();
- for (int i = listCount - 1; i >= 0; i--) {
- ArrayList<ChangeInfo> changes = mChangesList.get(i);
- count = changes.size();
- for (int j = count - 1; j >= 0; j--) {
- endChangeAnimationIfNecessary(changes.get(j));
- if (changes.isEmpty()) {
- mChangesList.remove(changes);
- }
- }
- }
-
- cancelAll(mRemoveAnimations);
- cancelAll(mMoveAnimations);
- cancelAll(mAddAnimations);
- cancelAll(mChangeAnimations);
-
- dispatchAnimationsFinished();
- }
-
- void cancelAll(List<ViewHolder> viewHolders) {
- for (int i = viewHolders.size() - 1; i >= 0; i--) {
- ViewCompat.animate(viewHolders.get(i).itemView).cancel();
- }
- }
-
- private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
- @Override
- public void onAnimationStart(View view) {}
-
- @Override
- public void onAnimationEnd(View view) {}
-
- @Override
- public void onAnimationCancel(View view) {}
- };
-}
diff --git a/src/com/android/car/dialer/TelecomActivity.java b/src/com/android/car/dialer/TelecomActivity.java
index 6738d04e..5ec54938 100644
--- a/src/com/android/car/dialer/TelecomActivity.java
+++ b/src/com/android/car/dialer/TelecomActivity.java
@@ -15,8 +15,8 @@
*/
package com.android.car.dialer;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
+import static com.android.car.dialer.ui.CallHistoryFragment.CALL_TYPE_KEY;
+
import android.content.Intent;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
@@ -24,21 +24,23 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.telecom.Call;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
-import android.view.View;
-import com.android.car.app.CarDrawerActivity;
-import com.android.car.app.CarDrawerAdapter;
-import com.android.car.app.DrawerItemViewHolder;
+import androidx.car.drawer.CarDrawerActivity;
+import androidx.car.drawer.CarDrawerAdapter;
+import androidx.car.drawer.DrawerItemViewHolder;
+
+import com.android.car.dialer.telecom.InMemoryPhoneBook;
import com.android.car.dialer.telecom.PhoneLoader;
import com.android.car.dialer.telecom.UiCall;
import com.android.car.dialer.telecom.UiCallManager;
-import com.android.car.dialer.telecom.UiCallManager.CallListener;
+import com.android.car.dialer.ui.CallHistoryFragment;
+import com.android.car.dialer.ui.ContactListFragment;
+import com.android.car.dialer.ui.InCallFragment;
-import java.util.List;
+import java.util.stream.Stream;
/**
* Main activity for the Dialer app. Displays different fragments depending on call and
@@ -50,8 +52,7 @@ import java.util.List;
* <li>StrequentFragment
* </ul>
*/
-public class TelecomActivity extends CarDrawerActivity implements
- DialerFragment.DialerBackButtonListener {
+public class TelecomActivity extends CarDrawerActivity implements CallListener {
private static final String TAG = "TelecomActivity";
private static final String ACTION_ANSWER_CALL = "com.android.car.dialer.ANSWER_CALL";
@@ -79,6 +80,7 @@ public class TelecomActivity extends CarDrawerActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setToolbarElevation(0f);
if (vdebug()) {
Log.d(TAG, "onCreate");
@@ -86,13 +88,17 @@ public class TelecomActivity extends CarDrawerActivity implements
setMainContent(R.layout.telecom_activity);
getWindow().getDecorView().setBackgroundColor(getColor(R.color.phone_theme));
- setTitle(getString(R.string.phone_app_name));
+ updateTitle();
- mUiCallManager = new UiCallManager(this);
+ mUiCallManager = UiCallManager.init(getApplicationContext());
mUiBluetoothMonitor = new UiBluetoothMonitor(this);
+ InMemoryPhoneBook.init(getApplicationContext());
+
findViewById(R.id.search).setOnClickListener(
v -> startActivity(new Intent(this, ContactSearchActivity.class)));
+
+ getDrawerController().setRootAdapter(new DialerRootAdapter());
}
@Override
@@ -102,6 +108,7 @@ public class TelecomActivity extends CarDrawerActivity implements
Log.d(TAG, "onDestroy");
}
mUiBluetoothMonitor.tearDown();
+ InMemoryPhoneBook.tearDown();
mUiCallManager.tearDown();
mUiCallManager = null;
}
@@ -109,7 +116,7 @@ public class TelecomActivity extends CarDrawerActivity implements
@Override
protected void onStop() {
super.onStop();
- mUiCallManager.removeListener(mCarCallListener);
+ mUiCallManager.removeListener(this);
mUiBluetoothMonitor.removeListener(mBluetoothListener);
}
@@ -143,7 +150,7 @@ public class TelecomActivity extends CarDrawerActivity implements
updateCurrentFragment();
handleIntent();
- mUiCallManager.addListener(mCarCallListener);
+ mUiCallManager.addListener(this);
mUiBluetoothMonitor.addListener(mBluetoothListener);
}
@@ -222,7 +229,7 @@ public class TelecomActivity extends CarDrawerActivity implements
+ getCurrentFragment());
}
- if (ongoingCall == null && getCurrentFragment() instanceof OngoingCallFragment) {
+ if (ongoingCall == null && getCurrentFragment() instanceof InCallFragment) {
showSpeedDialFragment();
} else if (ongoingCall != null) {
showOngoingCallFragment();
@@ -245,29 +252,24 @@ public class TelecomActivity extends CarDrawerActivity implements
return;
}
- Fragment fragment = StrequentsFragment.newInstance(mUiCallManager);
- if (getCurrentFragment() instanceof DialerFragment) {
- setContentFragmentWithSlideAndDelayAnimation(fragment);
- } else {
- setContentFragmentWithFadeAnimation(fragment);
- }
+ Fragment fragment = StrequentsFragment.newInstance();
+ setContentFragment(fragment);
}
private void showOngoingCallFragment() {
if (vdebug()) {
Log.d(TAG, "showOngoingCallFragment");
}
- if (!mAllowFragmentCommits || getCurrentFragment() instanceof OngoingCallFragment) {
+ if (!mAllowFragmentCommits || getCurrentFragment() instanceof InCallFragment) {
// in case the dialer is still open, (e.g. when dialing the second phone during
// a phone call), close it
maybeHideDialer();
- closeDrawer();
+ getDrawerController().closeDrawer();
return;
}
-
- Fragment fragment = OngoingCallFragment.newInstance(mUiCallManager, mUiBluetoothMonitor);
+ Fragment fragment = InCallFragment.newInstance();
setContentFragmentWithFadeAnimation(fragment);
- closeDrawer();
+ getDrawerController().closeDrawer();
}
private void showDialer() {
@@ -291,24 +293,9 @@ public class TelecomActivity extends CarDrawerActivity implements
return;
}
- Fragment fragment =
- DialerFragment.newInstance(mUiCallManager, this /* listener */, dialNumber);
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "adding dialer to fragment backstack");
- }
-
+ Fragment fragment = DialerFragment.newInstance(dialNumber);
// Add the dialer fragment to the backstack so that it can be popped off to dismiss it.
- getSupportFragmentManager().beginTransaction()
- .setCustomAnimations(R.anim.telecom_slide_in, R.anim.telecom_slide_out,
- R.anim.telecom_slide_in, R.anim.telecom_slide_out)
- .add(R.id.content_fragment_container, fragment, DIALER_FRAGMENT_TAG)
- .addToBackStack(DIALER_BACKSTACK)
- .commit();
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "done adding fragment to backstack");
- }
+ setContentFragment(fragment);
}
/**
@@ -322,11 +309,6 @@ public class TelecomActivity extends CarDrawerActivity implements
}
}
- @Override
- public void onDialerBackClick() {
- maybeHideDialer();
- }
-
private void showNoHfpFragment(@StringRes int stringResId) {
if (!mAllowFragmentCommits) {
return;
@@ -364,6 +346,7 @@ public class TelecomActivity extends CarDrawerActivity implements
}
maybeHideDialer();
+
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(enter, exit)
.replace(R.id.content_fragment_container, fragment, CONTENT_FRAGMENT_TAG)
@@ -380,6 +363,7 @@ public class TelecomActivity extends CarDrawerActivity implements
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_fragment_container, fragment, CONTENT_FRAGMENT_TAG)
.commitNow();
+ updateTitle();
}
/**
@@ -393,97 +377,85 @@ public class TelecomActivity extends CarDrawerActivity implements
return getSupportFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG);
}
- private final CallListener mCarCallListener = new UiCallManager.CallListener() {
- @Override
- public void onCallAdded(UiCall call) {
- if (vdebug()) {
- Log.d(TAG, "onCallAdded");
- }
- updateCurrentFragment();
- }
-
- @Override
- public void onCallRemoved(UiCall call) {
- if (vdebug()) {
- Log.d(TAG, "onCallRemoved");
- }
- updateCurrentFragment();
- }
-
- @Override
- public void onStateChanged(UiCall call, int state) {
- if (vdebug()) {
- Log.d(TAG, "onStateChanged");
- }
- updateCurrentFragment();
- }
-
- @Override
- public void onCallUpdated(UiCall call) {
- if (vdebug()) {
- Log.d(TAG, "onCallUpdated");
- }
- updateCurrentFragment();
- }
- };
-
private static boolean vdebug() {
return Log.isLoggable(TAG, Log.DEBUG);
}
@Override
- protected CarDrawerAdapter getRootAdapter() {
- return new DialerRootAdapter();
+ public void onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask) {
+ fragmentsToPropagateCallback().forEach(fragment -> ((CallListener) fragment)
+ .onAudioStateChanged(isMuted, route, supportedRouteMask));
}
- class CallLogAdapter extends CarDrawerAdapter {
- private List<CallLogListingTask.CallLogItem> mItems;
-
- public CallLogAdapter(int titleResId, List<CallLogListingTask.CallLogItem> items) {
- super(TelecomActivity.this, true /* showDisabledListOnEmpty */);
- setTitle(getString(titleResId));
- mItems = items;
+ @Override
+ public void onCallStateChanged(UiCall call, int state) {
+ if (vdebug()) {
+ Log.d(TAG, "onCallStateChanged");
}
+ updateCurrentFragment();
- @Override
- protected boolean usesSmallLayout(int position) {
- return false;
- }
+ fragmentsToPropagateCallback().forEach(fragment -> ((CallListener) fragment)
+ .onCallStateChanged(call, state));
+ }
- @Override
- protected int getActualItemCount() {
- return mItems.size();
+ @Override
+ public void onCallUpdated(UiCall call) {
+ if (vdebug()) {
+ Log.d(TAG, "onCallUpdated");
}
+ updateCurrentFragment();
- @Override
- public void populateViewHolder(DrawerItemViewHolder holder, int position) {
- CallLogListingTask.CallLogItem item = mItems.get(position);
- holder.getTitle().setText(item.mTitle);
- holder.getText().setText(item.mText);
- holder.getIcon().setImageBitmap(item.mIcon);
+ fragmentsToPropagateCallback().forEach(fragment -> ((CallListener) fragment)
+ .onCallUpdated(call));
+ }
+
+ @Override
+ public void onCallAdded(UiCall call) {
+ if (vdebug()) {
+ Log.d(TAG, "onCallAdded");
}
+ updateCurrentFragment();
- @Override
- public void onItemClick(int position) {
- closeDrawer();
- mUiCallManager.safePlaceCall(mItems.get(position).mNumber, false);
+ fragmentsToPropagateCallback().forEach(fragment -> ((CallListener) fragment)
+ .onCallAdded(call));
+ }
+
+ @Override
+ public void onCallRemoved(UiCall call) {
+ if (vdebug()) {
+ Log.d(TAG, "onCallRemoved");
}
+ updateCurrentFragment();
+
+ fragmentsToPropagateCallback().forEach(fragment -> ((CallListener) fragment)
+ .onCallRemoved(call));
+ }
+
+ private static boolean shouldPropagateCallback(Fragment fragment) {
+ return fragment instanceof CallListener && fragment.isAdded();
+ }
+
+ private Stream<Fragment> fragmentsToPropagateCallback() {
+ return getSupportFragmentManager().getFragments().stream()
+ .filter(fragment -> shouldPropagateCallback(fragment));
}
private class DialerRootAdapter extends CarDrawerAdapter {
- private static final int ITEM_DIAL = 0;
+ private static final int ITEM_FAVORITES = 0;
private static final int ITEM_CALLLOG_ALL = 1;
private static final int ITEM_CALLLOG_MISSED = 2;
- private static final int ITEM_MAX = 3;
+ private static final int ITEM_CONTACT = 3;
+ private static final int ITEM_DIAL = 4;
+
+ private static final int ITEM_COUNT = 5;
DialerRootAdapter() {
super(TelecomActivity.this, false /* showDisabledListOnEmpty */);
- setTitle(getString(R.string.phone_app_name));
}
@Override
protected int getActualItemCount() {
- return ITEM_MAX;
+ return ITEM_COUNT;
}
@Override
@@ -501,7 +473,15 @@ public class TelecomActivity extends CarDrawerActivity implements
break;
case ITEM_CALLLOG_MISSED:
textResId = R.string.calllog_missed;
- iconResId = R.drawable.ic_drawer_call_missed;
+ iconResId = R.drawable.ic_call_missed;
+ break;
+ case ITEM_FAVORITES:
+ textResId = R.string.calllog_favorites;
+ iconResId = R.drawable.ic_favorite;
+ break;
+ case ITEM_CONTACT:
+ textResId = R.string.contact_menu_label;
+ iconResId = R.drawable.ic_contact;
break;
default:
Log.wtf(TAG, "Unexpected position: " + position);
@@ -511,48 +491,69 @@ public class TelecomActivity extends CarDrawerActivity implements
Drawable drawable = getDrawable(iconResId);
drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
holder.getIcon().setImageDrawable(drawable);
- if (position > 0) {
- drawable = getDrawable(R.drawable.ic_chevron_right);
- drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
- holder.getRightIcon().setImageDrawable(drawable);
- }
}
@Override
public void onItemClick(int position) {
+ getDrawerController().closeDrawer();
switch (position) {
case ITEM_DIAL:
- closeDrawer();
showDialer();
break;
case ITEM_CALLLOG_ALL:
- loadCallHistoryAsync(PhoneLoader.CALL_TYPE_ALL, R.string.calllog_all);
+ showCallHistory(PhoneLoader.CallType.CALL_TYPE_ALL);
break;
case ITEM_CALLLOG_MISSED:
- loadCallHistoryAsync(PhoneLoader.CALL_TYPE_MISSED, R.string.calllog_missed);
+ showCallHistory(PhoneLoader.CallType.MISSED_TYPE);
+ break;
+ case ITEM_FAVORITES:
+ showSpeedDialFragment();
+ break;
+ case ITEM_CONTACT:
+ showContact();
break;
default:
Log.w(TAG, "Invalid position in ROOT menu! " + position);
}
+ setTitle(getTitleString());
}
}
- private void loadCallHistoryAsync(final int callType, final int titleResId) {
- showLoadingProgressBar(true);
- // Warning: much callbackiness!
- // First load up the call log cursor using the PhoneLoader so that happens in a
- // background thread. TODO: Why isn't PhoneLoader using a LoaderManager?
- PhoneLoader.registerCallObserver(callType, this,
- (loader, data) -> {
- // This callback runs on the thread that created the loader which is
- // the ui thread so spin off another async task because we still need
- // to pull together all the data along with the contact photo.
- CallLogListingTask task = new CallLogListingTask(TelecomActivity.this, data,
- (items) -> {
- showLoadingProgressBar(false);
- switchToAdapter(new CallLogAdapter(titleResId, items));
- });
- task.execute();
- });
+ private void showCallHistory(@PhoneLoader.CallType int callType) {
+ setContentFragment(CallHistoryFragment.newInstance(callType));
+ }
+
+ private void showContact() {
+ setContentFragment(ContactListFragment.newInstance());
+ }
+
+ private void updateTitle() {
+ setTitle(getTitleString());
+ }
+
+ private String getTitleString() {
+ Fragment currentFragment = getCurrentFragment();
+
+ int titleResId = R.string.phone_app_name;
+
+ if (currentFragment instanceof StrequentsFragment) {
+ titleResId = R.string.contacts_title;
+ } else if (currentFragment instanceof CallHistoryFragment) {
+ int callType = currentFragment.getArguments().getInt(CALL_TYPE_KEY);
+ if (callType == PhoneLoader.CallType.MISSED_TYPE) {
+ titleResId = R.string.missed_call_title;
+ } else {
+ titleResId = R.string.call_history_title;
+ }
+ } else if (currentFragment instanceof ContactListFragment) {
+ titleResId = R.string.contacts_title;
+ } else if (currentFragment instanceof DialerFragment) {
+ titleResId = R.string.dialpad_title;
+ } else if (currentFragment instanceof InCallFragment
+ || currentFragment instanceof OngoingCallFragment) {
+ titleResId = R.string.in_call_title;
+ }
+
+ return getString(titleResId);
}
}
diff --git a/src/com/android/car/dialer/livedata/CallHistoryLiveData.java b/src/com/android/car/dialer/livedata/CallHistoryLiveData.java
new file mode 100644
index 00000000..ea093fa1
--- /dev/null
+++ b/src/com/android/car/dialer/livedata/CallHistoryLiveData.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.livedata;
+
+import android.arch.lifecycle.LiveData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+
+import com.android.car.dialer.telecom.PhoneLoader;
+import com.android.car.dialer.ui.CallLogListingTask;
+
+import java.util.List;
+
+/**
+ * Live data which loads call history.
+ */
+public class CallHistoryLiveData extends LiveData<List<CallLogListingTask.CallLogItem>> {
+
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private CursorLoader mCursorLoader;
+ private CallLogContentObserver mCallLogContentObserver = new CallLogContentObserver(
+ new Handler());
+
+ public CallHistoryLiveData(Context context) {
+ this.mContext = context;
+ mContentResolver = context.getContentResolver();
+ }
+
+ @Override
+ protected void onActive() {
+ super.onActive();
+ mCursorLoader = PhoneLoader.registerCallObserver(getCallHistoryFilterType(),
+ mContext,
+ (loader, cursor) -> {
+ CallLogListingTask task = new CallLogListingTask(mContext, cursor,
+ this::setValue);
+ task.execute();
+ });
+
+ mContentResolver.registerContentObserver(mCursorLoader.getUri(),
+ false, mCallLogContentObserver);
+ }
+
+ @Override
+ protected void onInactive() {
+ super.onInactive();
+ mContentResolver.unregisterContentObserver(mCallLogContentObserver);
+ }
+
+ protected int getCallHistoryFilterType() {
+ return PhoneLoader.CALL_TYPE_ALL;
+ }
+
+ /**
+ * A {@link ContentObserver} that is responsible for reloading the user's recent calls.
+ */
+ private class CallLogContentObserver extends ContentObserver {
+ public CallLogContentObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ mCursorLoader.startLoading();
+ }
+ }
+}
diff --git a/src/com/android/car/dialer/livedata/MissedCallHistoryLiveData.java b/src/com/android/car/dialer/livedata/MissedCallHistoryLiveData.java
new file mode 100644
index 00000000..26a7f2ec
--- /dev/null
+++ b/src/com/android/car/dialer/livedata/MissedCallHistoryLiveData.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.livedata;
+
+import android.content.Context;
+
+import com.android.car.dialer.telecom.PhoneLoader;
+
+/**
+ * Live data which loads missed call history.
+ */
+public class MissedCallHistoryLiveData extends CallHistoryLiveData {
+
+ public MissedCallHistoryLiveData(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected int getCallHistoryFilterType() {
+ return PhoneLoader.MISSED_TYPE;
+ }
+}
diff --git a/src/com/android/car/dialer/log/L.java b/src/com/android/car/dialer/log/L.java
new file mode 100644
index 00000000..f85a51aa
--- /dev/null
+++ b/src/com/android/car/dialer/log/L.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.log;
+
+import android.util.Log;
+
+/**
+ * Util class for logging.
+ */
+public class L {
+
+ private String mTag;
+
+ public L(String tag) {
+ mTag = tag;
+ }
+
+ public void v(String msg) {
+ if (Log.isLoggable(mTag, Log.VERBOSE)) {
+ Log.v(mTag, msg);
+ }
+ }
+
+ public void d(String msg) {
+ if (Log.isLoggable(mTag, Log.DEBUG)) {
+ Log.d(mTag, msg);
+ }
+ }
+
+ public void w(String msg) {
+ Log.w(mTag, msg);
+ }
+
+ public static L logger(String tag) {
+ return new L(tag);
+ }
+
+ public static void v(String tag, String msg) {
+ if (Log.isLoggable(tag, Log.VERBOSE)) {
+ Log.v(tag, msg);
+ }
+ }
+
+ public static void d(String tag, String msg) {
+ if (Log.isLoggable(tag, Log.DEBUG)) {
+ Log.d(tag, msg);
+ }
+ }
+
+ public static void w(String tag, String msg) {
+ Log.w(tag, msg);
+ }
+
+ public static void i(String tag, String msg) {
+ Log.i(tag, msg);
+ }
+}
diff --git a/src/com/android/car/dialer/telecom/InMemoryPhoneBook.java b/src/com/android/car/dialer/telecom/InMemoryPhoneBook.java
new file mode 100644
index 00000000..3aaa21fc
--- /dev/null
+++ b/src/com/android/car/dialer/telecom/InMemoryPhoneBook.java
@@ -0,0 +1,121 @@
+package com.android.car.dialer.telecom;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.telephony.PhoneNumberUtils;
+
+import com.android.car.dialer.ContactEntry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A singleton statically accessible helper class which pre-loads contacts list into memory so
+ * that they can be accessed more easily and quickly.
+ */
+public class InMemoryPhoneBook implements Loader.OnLoadCompleteListener<Cursor> {
+ private static InMemoryPhoneBook sInMemoryPhoneBook;
+
+ private final Context mContext;
+
+ private boolean mIsLoaded = false;
+ private List<ContactEntry> mContactEntries = new ArrayList<>();
+ Map<Integer, List<ContactEntry>> mIdToContactEntryMap;
+
+ private InMemoryPhoneBook(Context context) {
+ mContext = context;
+ }
+
+ public static InMemoryPhoneBook init(Context context) {
+ if (sInMemoryPhoneBook == null) {
+ sInMemoryPhoneBook = new InMemoryPhoneBook(context);
+ sInMemoryPhoneBook.onInit();
+ } else {
+ throw new IllegalStateException("Call teardown before reinitialized PhoneBook");
+ }
+ return get();
+ }
+
+ public static InMemoryPhoneBook get() {
+ if (sInMemoryPhoneBook != null) {
+ return sInMemoryPhoneBook;
+ } else {
+ throw new IllegalStateException("Call init before get InMemoryPhoneBook");
+ }
+ }
+
+ public static void tearDown() {
+ sInMemoryPhoneBook = null;
+ }
+
+ private void onInit() {
+ CursorLoader cursorLoader = createPhoneBookCursorLoader();
+ cursorLoader.registerListener(0, this);
+ cursorLoader.startLoading();
+ }
+
+ public boolean isLoaded() {
+ return mIsLoaded;
+ }
+
+ /**
+ * Returns a alphabetically sorted contact list.
+ */
+ public List<ContactEntry> getOrderedContactEntries() {
+ return mContactEntries;
+ }
+
+ @Nullable
+ public ContactEntry lookupContactEntry(String phoneNumber) {
+ for (ContactEntry contactEntry : mContactEntries) {
+ if (PhoneNumberUtils.compare(mContext, phoneNumber, contactEntry.getNumber())) {
+ return contactEntry;
+ }
+ }
+ return null;
+ }
+
+ public Map<Integer, List<ContactEntry>> getIdToContactEntryMap() {
+ if (mIdToContactEntryMap == null) {
+ mIdToContactEntryMap = new HashMap<>();
+ for (ContactEntry contactEntry : mContactEntries) {
+ List<ContactEntry> list;
+ if (mIdToContactEntryMap.containsKey(contactEntry.getId())) {
+ list = mIdToContactEntryMap.get(contactEntry.getId());
+ } else {
+ list = new ArrayList<>();
+ }
+ list.add(contactEntry);
+ }
+ }
+ return mIdToContactEntryMap;
+ }
+
+ @Override
+ public void onLoadComplete(@NonNull Loader<Cursor> loader, @Nullable Cursor cursor) {
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ mContactEntries.add(ContactEntry.fromCursor(cursor, mContext));
+ }
+ }
+ mIsLoaded = true;
+ }
+
+ private CursorLoader createPhoneBookCursorLoader() {
+ return new CursorLoader(mContext,
+ ContactsContract.Data.CONTENT_URI,
+ null,
+ ContactsContract.Data.MIMETYPE + " = '"
+ + ContactsContract.CommonDataKinds.Phone
+ .CONTENT_ITEM_TYPE + "'",
+ null,
+ ContactsContract.Contacts.DISPLAY_NAME + " ASC ");
+ }
+}
diff --git a/src/com/android/car/dialer/telecom/PhoneLoader.java b/src/com/android/car/dialer/telecom/PhoneLoader.java
index 67fee4d6..6ac5c6ed 100644
--- a/src/com/android/car/dialer/telecom/PhoneLoader.java
+++ b/src/com/android/car/dialer/telecom/PhoneLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -24,6 +24,7 @@ import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.CallLog;
import android.provider.ContactsContract;
+import android.support.annotation.IntDef;
import android.text.TextUtils;
import android.util.Log;
@@ -32,11 +33,11 @@ import java.util.HashMap;
import java.util.List;
/**
- * Manage loading different types of call logs.
+ * Manages loading different types of call logs.
* Currently supports:
- * All calls
- * Missed calls
- * speed dial calls
+ * All calls
+ * Missed calls
+ * speed dial calls
*/
public class PhoneLoader {
private static final String TAG = "Em.PhoneLoader";
@@ -47,6 +48,19 @@ public class PhoneLoader {
/** Starred and frequent **/
public final static int CALL_TYPE_SPEED_DIAL = 2;
+ @IntDef({
+ CallType.CALL_TYPE_ALL,
+ CallType.INCOMING_TYPE,
+ CallType.OUTGOING_TYPE,
+ CallType.MISSED_TYPE,
+ })
+ public @interface CallType {
+ int CALL_TYPE_ALL = -1;
+ int INCOMING_TYPE = CallLog.Calls.INCOMING_TYPE;
+ int OUTGOING_TYPE = CallLog.Calls.OUTGOING_TYPE;
+ int MISSED_TYPE = CallLog.Calls.MISSED_TYPE;
+ }
+
private static final int NUM_LOGS_TO_DISPLAY = 100;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
@@ -67,7 +81,7 @@ public class PhoneLoader {
Log.d(TAG, "registerCallObserver: type: " + type + ", listener: " + listener);
}
- switch(type) {
+ switch (type) {
case CALL_TYPE_ALL:
case CALL_TYPE_MISSED:
return fetchCallLog(type, context, listener);
@@ -129,8 +143,8 @@ public class PhoneLoader {
/**
* @return The column index of the contact id. It should be {@link BaseColumns#_ID}. However,
- * if that fails use {@link android.provider.ContactsContract.RawContacts#CONTACT_ID}.
- * If that also fails, we use the first column in the table.
+ * if that fails use {@link android.provider.ContactsContract.RawContacts#CONTACT_ID}.
+ * If that also fails, we use the first column in the table.
*/
public static int getIdColumnIndex(Cursor cursor) {
int ret = cursor.getColumnIndex(BaseColumns._ID);
@@ -153,7 +167,7 @@ public class PhoneLoader {
/**
* @return The column index of the number.
- * Will return a valid column for call log or contacts queries.
+ * Will return a valid column for call log or contacts queries.
*/
public static int getNumberColumnIndex(Cursor cursor) {
int numberColumn = cursor.getColumnIndex(CallLog.Calls.NUMBER);
@@ -166,7 +180,7 @@ public class PhoneLoader {
/**
* @return The column index of the number type.
- * Will return a valid column for call log or contacts queries.
+ * Will return a valid column for call log or contacts queries.
*/
public static int getTypeColumnIndex(Cursor cursor) {
int typeColumn = cursor.getColumnIndex(CallLog.Calls.TYPE);
@@ -178,7 +192,7 @@ public class PhoneLoader {
/**
* @return The column index of the name.
- * Will return a valid column for call log or contacts queries.
+ * Will return a valid column for call log or contacts queries.
*/
public static int getNameColumnIndex(Cursor cursor) {
int typeColumn = cursor.getColumnIndex(CallLog.Calls.CACHED_NAME);
@@ -190,10 +204,10 @@ public class PhoneLoader {
/**
* @return The phone number for the contact. Most phones will simply get the value in the
- * column returned by {@link #getNumberColumnIndex(Cursor)}. However, some devices
- * such as the Galaxy S6 return null for those columns. In those cases, we use the
- * contact id (which we hopefully do have) to look up just the phone number for that
- * specific contact.
+ * column returned by {@link #getNumberColumnIndex(Cursor)}. However, some devices
+ * such as the Galaxy S6 return null for those columns. In those cases, we use the
+ * contact id (which we hopefully do have) to look up just the phone number for that
+ * specific contact.
*/
public static String getPhoneNumber(Cursor cursor, ContentResolver cr) {
int columnIndex = getNumberColumnIndex(cursor);
@@ -210,8 +224,9 @@ public class PhoneLoader {
/**
* Return the phone number for the given contact id.
+ *
* @param columnName On some phones, we have to use non-standard columns for the primary key.
- * @param id The value in the columnName for the desired contact.
+ * @param id The value in the columnName for the desired contact.
* @return The phone number for the given contact or empty string if there was an error.
*/
public static String getNumberFromContactId(ContentResolver cr, String columnName, String id) {
@@ -227,8 +242,8 @@ public class PhoneLoader {
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
Cursor phoneNumberCursor = cr.query(uri,
- new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER},
- columnName + " = ?" , new String[] {id}, null);
+ new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER},
+ columnName + " = ?", new String[]{id}, null);
if (!phoneNumberCursor.moveToFirst()) {
Log.e(TAG, "Unable to move phone number cursor to the first item.");
diff --git a/src/com/android/car/dialer/telecom/TelecomUtils.java b/src/com/android/car/dialer/telecom/TelecomUtils.java
index ffae7654..a0bd4d81 100644
--- a/src/com/android/car/dialer/telecom/TelecomUtils.java
+++ b/src/com/android/car/dialer/telecom/TelecomUtils.java
@@ -38,9 +38,10 @@ import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.widget.ImageView;
-import com.android.car.apps.common.CircleBitmapDrawable;
+
import com.android.car.apps.common.LetterTileDrawable;
import com.android.car.dialer.R;
+import com.android.car.dialer.ui.CircleBitmapDrawable;
import java.io.InputStream;
import java.util.Locale;
@@ -48,7 +49,7 @@ import java.util.Locale;
public class TelecomUtils {
private final static String TAG = "Em.TelecomUtils";
- private static final String[] CONTACT_ID_PROJECTION = new String[] {
+ private static final String[] CONTACT_ID_PROJECTION = new String[]{
ContactsContract.PhoneLookup.DISPLAY_NAME,
ContactsContract.PhoneLookup.TYPE,
ContactsContract.PhoneLookup.LABEL,
@@ -73,6 +74,7 @@ public class TelecomUtils {
/**
* Return the contact id for the given contact id
+ *
* @param id the contact id to get the photo for
* @return the contact photo if it is found, null otherwise.
*/
@@ -97,6 +99,7 @@ public class TelecomUtils {
/**
* Return the contact id for the given phone number.
+ *
* @param number Caller phone number
* @return the contact id if it is found, 0 otherwise.
*/
@@ -115,8 +118,7 @@ public class TelecomUtils {
int id = cursor.getInt(cursor.getColumnIndex(ContactsContract.PhoneLookup._ID));
return id;
}
- }
- finally {
+ } finally {
if (cursor != null) {
cursor.close();
}
@@ -126,6 +128,7 @@ public class TelecomUtils {
/**
* Return the label for the given phone number.
+ *
* @param number Caller phone number
* @return the label if it is found, 0 otherwise.
*/
@@ -154,8 +157,7 @@ public class TelecomUtils {
Phone.getTypeLabel(res, type, label);
return typeLabel;
}
- }
- finally {
+ } finally {
if (cursor != null) {
cursor.close();
}
@@ -220,7 +222,8 @@ public class TelecomUtils {
return getDisplayName(context, number, null);
}
- private static String getDisplayName(Context context, String number, Uri gatewayOriginalAddress) {
+ private static String getDisplayName(Context context, String number,
+ Uri gatewayOriginalAddress) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "getDisplayName: " + number
+ ", gatewayOriginalAddress: " + gatewayOriginalAddress);
@@ -257,7 +260,7 @@ public class TelecomUtils {
String name = null;
try {
cursor = cr.query(uri,
- new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null);
+ new String[]{ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
name = cursor.getString(0);
}
@@ -306,7 +309,7 @@ public class TelecomUtils {
*/
public static String callStateToUiString(Context context, int state) {
Resources res = context.getResources();
- switch(state) {
+ switch (state) {
case Call.STATE_ACTIVE:
return res.getString(R.string.call_state_call_active);
case Call.STATE_HOLDING:
@@ -348,23 +351,21 @@ public class TelecomUtils {
* @param number A key to have a consisten color per phone number.
* @return A worker task if a new one was needed to load the bitmap.
*/
- @Nullable public static ContactBitmapWorker setContactBitmapAsync(Context context,
+ @Nullable
+ public static ContactBitmapWorker setContactBitmapAsync(Context context,
final ImageView icon, final @Nullable String name, final String number) {
return ContactBitmapWorker.loadBitmap(context.getContentResolver(), icon, number,
- new ContactBitmapWorker.BitmapWorkerListener() {
- @Override
- public void onBitmapLoaded(@Nullable Bitmap bitmap) {
- Resources r = icon.getResources();
- if (bitmap != null) {
- icon.setScaleType(ImageView.ScaleType.CENTER_CROP);
- icon.setImageDrawable(new CircleBitmapDrawable(r, bitmap));
- } else {
- icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r);
- letterTileDrawable.setContactDetails(name, number);
- letterTileDrawable.setIsCircular(true);
- icon.setImageDrawable(letterTileDrawable);
- }
+ bitmap -> {
+ Resources r = icon.getResources();
+ if (bitmap != null) {
+ icon.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ icon.setImageDrawable(new CircleBitmapDrawable(r, bitmap));
+ } else {
+ icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r);
+ letterTileDrawable.setContactDetails(name, number);
+ letterTileDrawable.setIsCircular(true);
+ icon.setImageDrawable(letterTileDrawable);
}
});
}
diff --git a/src/com/android/car/dialer/telecom/UiCallManager.java b/src/com/android/car/dialer/telecom/UiCallManager.java
index 95e5139b..0e9c2a1a 100644
--- a/src/com/android/car/dialer/telecom/UiCallManager.java
+++ b/src/com/android/car/dialer/telecom/UiCallManager.java
@@ -15,6 +15,11 @@
*/
package com.android.car.dialer.telecom;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHeadsetClientCall;
+import android.bluetooth.BluetoothProfile;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -25,15 +30,17 @@ import android.os.IBinder;
import android.provider.CallLog;
import android.telecom.Call;
import android.telecom.CallAudioState;
+import android.telecom.CallAudioState.CallAudioRoute;
import android.telecom.DisconnectCause;
import android.telecom.GatewayInfo;
import android.telecom.InCallService;
+import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
-import android.view.KeyEvent;
+import com.android.car.dialer.CallListener;
import com.android.car.dialer.R;
import java.lang.ref.WeakReference;
@@ -52,9 +59,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class UiCallManager {
private static String TAG = "Em.TelecomMgr";
+ private static final String HFP_CLIENT_CONNECTION_SERVICE_CLASS_NAME
+ = "com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService";
// Rate limit how often you can place outgoing calls.
private static final long MIN_TIME_BETWEEN_CALLS_MS = 3000;
private static final List<Integer> sCallStateRank = new ArrayList<>();
+ private static UiCallManager sUiCallManager;
// Used to assign id's to UiCall objects as they're created.
private static int nextCarPhoneCallId = 0;
@@ -78,10 +88,39 @@ public class UiCallManager {
private TelecomManager mTelecomManager;
private InCallServiceImpl mInCallService;
+ private BluetoothHeadsetClient mBluetoothHeadsetClient;
private final Map<UiCall, Call> mCallMapping = new HashMap<>();
private final List<CallListener> mCallListeners = new CopyOnWriteArrayList<>();
- public UiCallManager(Context context) {
+ /**
+ * Initialized a globally accessible {@link UiCallManager} which can be retrieved by
+ * {@link #get}. If this function is called a second time before calling {@link #tearDown()},
+ * an exception will be thrown.
+ *
+ * @param applicationContext Application context.
+ */
+ public static UiCallManager init(Context applicationContext) {
+ if (sUiCallManager == null) {
+ sUiCallManager = new UiCallManager(applicationContext);
+ } else {
+ throw new IllegalStateException("UiCallManager has been initialized.");
+ }
+ return sUiCallManager;
+ }
+
+ /**
+ * Gets the global {@link UiCallManager} instance. Make sure
+ * {@link #init(Context)} is called before calling this method.
+ */
+ public static UiCallManager get() {
+ if (sUiCallManager == null) {
+ throw new IllegalStateException(
+ "Call UiCallManager.init(Context) before calling this function");
+ }
+ return sUiCallManager;
+ }
+
+ private UiCallManager(Context context) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "SetUp");
}
@@ -93,6 +132,20 @@ public class UiCallManager {
Intent intent = new Intent(context, InCallServiceImpl.class);
intent.setAction(InCallServiceImpl.ACTION_LOCAL_BIND);
context.bindService(intent, mInCallServiceConnection, Context.BIND_AUTO_CREATE);
+
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ adapter.getProfileProxy(mContext, new BluetoothProfile.ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (profile == BluetoothProfile.HEADSET_CLIENT) {
+ mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ }
+ }, BluetoothProfile.HEADSET_CLIENT);
}
private final ServiceConnection mInCallServiceConnection = new ServiceConnection() {
@@ -140,12 +193,20 @@ public class UiCallManager {
};
};
+ /**
+ * Tears down the {@link UiCallManager}. Calling this function will null out the global
+ * accessible {@link UiCallManager} instance. Remember to re-initialize the
+ * {@link UiCallManager}.
+ */
public void tearDown() {
if (mInCallService != null) {
mContext.unbindService(mInCallServiceConnection);
mInCallService = null;
}
mCallMapping.clear();
+ // Clear out the mContext reference to avoid memory leak.
+ mContext = null;
+ sUiCallManager = null;
}
public void addListener(CallListener listener) {
@@ -239,6 +300,46 @@ public class UiCallManager {
return audioState != null ? audioState.getSupportedRouteMask() : 0;
}
+ public List<Integer> getSupportedAudioRoute() {
+ List<Integer> audioRouteList = new ArrayList<>();
+
+ boolean isBluetoothPhoneCall = isBluetoothCall();
+ if (isBluetoothPhoneCall) {
+ // if this is bluetooth phone call, we can only select audio route between vehicle
+ // and phone.
+ // Vehicle speaker route.
+ audioRouteList.add(CallAudioState.ROUTE_BLUETOOTH);
+ // Headset route.
+ audioRouteList.add(CallAudioState.ROUTE_EARPIECE);
+ } else {
+ // Most likely we are making phone call with on board SIM card.
+ int supportedAudioRouteMask = getSupportedAudioRouteMask();
+
+ if ((supportedAudioRouteMask & CallAudioState.ROUTE_EARPIECE) != 0) {
+ audioRouteList.add(CallAudioState.ROUTE_EARPIECE);
+ } else if ((supportedAudioRouteMask & CallAudioState.ROUTE_BLUETOOTH) != 0) {
+ audioRouteList.add(CallAudioState.ROUTE_BLUETOOTH);
+ } else if ((supportedAudioRouteMask & CallAudioState.ROUTE_WIRED_HEADSET) != 0) {
+ audioRouteList.add(CallAudioState.ROUTE_WIRED_HEADSET);
+ } else if ((supportedAudioRouteMask & CallAudioState.ROUTE_SPEAKER) != 0) {
+ audioRouteList.add(CallAudioState.ROUTE_SPEAKER);
+ }
+ }
+
+ return audioRouteList;
+ }
+
+ public boolean isBluetoothCall() {
+ PhoneAccountHandle phoneAccountHandle =
+ mTelecomManager.getUserSelectedOutgoingPhoneAccount();
+ if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
+ return HFP_CLIENT_CONNECTION_SERVICE_CLASS_NAME.equals(
+ phoneAccountHandle.getComponentName().getClassName());
+ } else {
+ return false;
+ }
+ }
+
public int getAudioRoute() {
CallAudioState audioState = getCallAudioStateOrNull();
int audioRoute = audioState != null ? audioState.getRoute() : 0;
@@ -248,10 +349,24 @@ public class UiCallManager {
return audioRoute;
}
- public void setAudioRoute(int audioRoute) {
- // In case of embedded where the CarKitt is always connected to one kind of speaker we
- // should simply ignore any setAudioRoute requests.
- Log.w(TAG, "setAudioRoute ignoring request " + audioRoute);
+ /**
+ * Re-route the audio out phone of the ongoing phone call.
+ */
+ public void setAudioRoute(@CallAudioRoute int audioRoute) {
+ if (mBluetoothHeadsetClient != null && isBluetoothCall()) {
+ for (BluetoothDevice device : mBluetoothHeadsetClient.getConnectedDevices()) {
+ List<BluetoothHeadsetClientCall> currentCalls =
+ mBluetoothHeadsetClient.getCurrentCalls(device);
+ if (currentCalls != null && !currentCalls.isEmpty()) {
+ if (audioRoute == CallAudioState.ROUTE_BLUETOOTH) {
+ mBluetoothHeadsetClient.connectAudio(device);
+ } else if ((audioRoute & CallAudioState.ROUTE_WIRED_OR_EARPIECE) != 0) {
+ mBluetoothHeadsetClient.disconnectAudio(device);
+ }
+ }
+ }
+ }
+ // TODO: Implement routing audio if current call is not a bluetooth call.
}
public void holdCall(UiCall uiCall) {
@@ -372,7 +487,7 @@ public class UiCallManager {
private void onStateChanged(UiCall uiCall, int state) {
for (CallListener listener : mCallListeners) {
- listener.onStateChanged(uiCall, state);
+ listener.onCallStateChanged(uiCall, state);
}
}
@@ -432,21 +547,6 @@ public class UiCallManager {
return mInCallService != null ? mInCallService.getCallAudioState() : null;
}
- public static class CallListener {
- @SuppressWarnings("unused")
- public void dispatchPhoneKeyEvent(KeyEvent event) {}
- @SuppressWarnings("unused")
- public void onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask) {}
- @SuppressWarnings("unused")
- public void onCallAdded(UiCall call) {}
- @SuppressWarnings("unused")
- public void onStateChanged(UiCall call, int state) {}
- @SuppressWarnings("unused")
- public void onCallUpdated(UiCall call) {}
- @SuppressWarnings("unused")
- public void onCallRemoved(UiCall call) {}
- }
-
/** Returns a first call that matches at least one provided call state */
public UiCall getCallWithState(int... callStates) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
diff --git a/src/com/android/car/dialer/ui/CallHistoryFragment.java b/src/com/android/car/dialer/ui/CallHistoryFragment.java
new file mode 100644
index 00000000..d5f8e5dc
--- /dev/null
+++ b/src/com/android/car/dialer/ui/CallHistoryFragment.java
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.ViewModelProviders;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.car.widget.ListItemAdapter;
+import androidx.car.widget.PagedListView;
+
+import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.PhoneLoader;
+import com.android.car.dialer.ui.viewmodel.CallHistoryViewModel;
+
+import java.util.List;
+
+public class CallHistoryFragment extends Fragment {
+ public static final String CALL_TYPE_KEY = "CALL_TYPE_KEY";
+
+ public static CallHistoryFragment newInstance(@PhoneLoader.CallType int callType) {
+ CallHistoryFragment fragment = new CallHistoryFragment();
+ Bundle arg = new Bundle();
+ arg.putInt(CALL_TYPE_KEY, callType);
+ fragment.setArguments(arg);
+ return fragment;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View fragmentView = inflater.inflate(R.layout.call_list_fragment, container, false);
+ PagedListView pagedListView = fragmentView.findViewById(R.id.list_view);
+ CallHistoryListItemProvider callHistoryListItemProvider = new CallHistoryListItemProvider();
+ ListItemAdapter adapter = new ListItemAdapter(getContext(), callHistoryListItemProvider);
+ pagedListView.setAdapter(adapter);
+
+ int callType = getArguments().getInt(CALL_TYPE_KEY);
+
+ CallHistoryViewModel viewModel = ViewModelProviders.of(this).get(
+ CallHistoryViewModel.class);
+
+ LiveData<List<CallLogListingTask.CallLogItem>> liveData = null;
+ if (callType == PhoneLoader.CallType.CALL_TYPE_ALL) {
+ liveData = viewModel.getCallHistory();
+ } else if (callType == PhoneLoader.CallType.MISSED_TYPE) {
+ liveData = viewModel.getMissedCallHistory();
+ }
+
+ if (liveData != null) {
+ liveData.observe(this,
+ callHistoryItems -> {
+ callHistoryListItemProvider.setCallHistoryListItems(getContext(),
+ callHistoryItems);
+ adapter.notifyDataSetChanged();
+ });
+ }
+
+ return fragmentView;
+ }
+}
diff --git a/src/com/android/car/dialer/ui/CallHistoryListItemProvider.java b/src/com/android/car/dialer/ui/CallHistoryListItemProvider.java
new file mode 100644
index 00000000..2eaff9f9
--- /dev/null
+++ b/src/com/android/car/dialer/ui/CallHistoryListItemProvider.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+
+import androidx.car.widget.ListItem;
+import androidx.car.widget.ListItemProvider;
+import androidx.car.widget.TextListItem;
+
+import com.android.car.dialer.telecom.UiCallManager;
+import com.android.car.dialer.ui.listitem.CallLogListItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CallHistoryListItemProvider extends ListItemProvider {
+
+ private List<TextListItem> mItems = new ArrayList<>();
+
+ public void setCallHistoryListItems(Context context,
+ List<CallLogListingTask.CallLogItem> items) {
+ for (CallLogListingTask.CallLogItem callLogItem : items) {
+ TextListItem callLogListItem = new CallLogListItem(context, callLogItem);
+
+ callLogListItem.setPrimaryActionIcon(
+ new BitmapDrawable(context.getResources(), callLogItem.mIcon), true);
+ callLogListItem.setTitle(callLogItem.mTitle);
+ callLogListItem.setBody(callLogItem.mText);
+ callLogListItem.setOnClickListener(
+ (v) -> UiCallManager.get().safePlaceCall(callLogItem.mNumber, false));
+
+ mItems.add(callLogListItem);
+ }
+ }
+
+ @Override
+ public ListItem get(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public int size() {
+ return mItems.size();
+ }
+}
diff --git a/src/com/android/car/dialer/ui/CallLogListingTask.java b/src/com/android/car/dialer/ui/CallLogListingTask.java
new file mode 100644
index 00000000..5a73d1bb
--- /dev/null
+++ b/src/com/android/car/dialer/ui/CallLogListingTask.java
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import static android.provider.ContactsContract.CommonDataKinds.Phone.getTypeLabel;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.provider.CallLog;
+import android.support.annotation.NonNull;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+
+import com.android.car.dialer.ContactEntry;
+import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.InMemoryPhoneBook;
+import com.android.car.dialer.telecom.PhoneLoader;
+import com.android.car.dialer.telecom.TelecomUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Async task which loads call history.
+ */
+public class CallLogListingTask extends AsyncTask<Void, Void, Void> {
+ public static class CallLogItem {
+ public final String mTitle;
+ public final String mText;
+ public final String mNumber;
+ public final Bitmap mIcon;
+
+ public CallLogItem(String title, String text, String number, Bitmap icon) {
+ mTitle = title;
+ mText = text;
+ mNumber = number;
+ mIcon = icon;
+ }
+ }
+
+ public interface LoadCompleteListener {
+ void onLoadComplete(List<CallLogItem> items);
+ }
+
+ private Context mContext;
+ private Cursor mCursor;
+ private List<CallLogItem> mItems;
+ private LoadCompleteListener mListener;
+
+ public CallLogListingTask(Context context, Cursor cursor,
+ @NonNull LoadCompleteListener listener) {
+ mContext = context;
+ mCursor = cursor;
+ mItems = new ArrayList<>(mCursor.getCount());
+ mListener = listener;
+ }
+
+ private String maybeAppendCount(StringBuilder sb, int count) {
+ if (count > 1) {
+ sb.append(" (").append(count).append(")");
+ }
+ return sb.toString();
+ }
+
+ private String getContactName(String cachedName, String number, int count,
+ boolean isVoicemail) {
+ StringBuilder sb = new StringBuilder();
+ if (isVoicemail) {
+ sb.append(mContext.getString(R.string.voicemail));
+ } else if (cachedName != null) {
+ sb.append(cachedName);
+ } else if (!TextUtils.isEmpty(number)) {
+ sb.append(TelecomUtils.getFormattedNumber(mContext, number));
+ } else {
+ sb.append(mContext.getString(R.string.unknown));
+ }
+ return maybeAppendCount(sb, count);
+ }
+
+ private static CharSequence getRelativeTime(long millis) {
+ boolean validTimestamp = millis > 0;
+
+ return validTimestamp ? DateUtils.getRelativeTimeSpanString(
+ millis, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_RELATIVE) : null;
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ if (mCursor != null) {
+ try {
+ int numberColumn = PhoneLoader.getNumberColumnIndex(mCursor);
+ int dateColumn = mCursor.getColumnIndex(CallLog.Calls.DATE);
+
+ while (mCursor.moveToNext()) {
+ int count = 1;
+ String number = mCursor.getString(numberColumn);
+ // We want to group calls to the same number into one so seek
+ // forward as many
+ // entries as possible as long as the number is the same.
+ int position = mCursor.getPosition();
+ while (mCursor.moveToNext()) {
+ String nextNumber = mCursor.getString(numberColumn);
+ if (PhoneNumberUtils.compare(mContext, number, nextNumber)) {
+ count++;
+ } else {
+ break;
+ }
+ }
+
+ mCursor.moveToPosition(position);
+
+ boolean isVoicemail = PhoneNumberUtils.isVoiceMailNumber(number);
+ ContactEntry contactEntry = InMemoryPhoneBook.get().lookupContactEntry(number);
+ String nameWithCount = getContactName(
+ contactEntry != null ? contactEntry.getDisplayName() : null,
+ number,
+ count,
+ isVoicemail);
+
+ // Not sure why this is the only column checked here but I'm
+ // assuming this was to work around some bug on some device.
+ long millis = dateColumn == -1 ? 0 : mCursor.getLong(dateColumn);
+
+ StringBuffer secondaryTextStringBuilder = new StringBuffer();
+ CharSequence relativeDate = getRelativeTime(millis);
+
+ // Append the type (work, mobile etc.) if it isn't voicemail.
+ if (!isVoicemail) {
+ CharSequence type = contactEntry != null
+ ? getTypeLabel(mContext.getResources(), contactEntry.getType(),
+ contactEntry.getLabel())
+ : "";
+ secondaryTextStringBuilder.append(type);
+ if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(relativeDate)) {
+ secondaryTextStringBuilder.append(", ");
+ }
+ }
+ // Add in the timestamp.
+ if (relativeDate != null) {
+ secondaryTextStringBuilder.append(relativeDate);
+ }
+
+ CallLogItem item = new CallLogItem(nameWithCount,
+ secondaryTextStringBuilder.toString(),
+ number, null);
+ mItems.add(item);
+ // Since we deduplicated count rows, we can move all the way to that row so the
+ // next iteration takes us to the row following the last duplicate row.
+ if (count > 1) {
+ mCursor.moveToPosition(position + count - 1);
+ }
+ }
+ } finally {
+ mCursor.close();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ mListener.onLoadComplete(mItems);
+ }
+}
diff --git a/src/com/android/car/dialer/ui/CircleBitmapDrawable.java b/src/com/android/car/dialer/ui/CircleBitmapDrawable.java
new file mode 100644
index 00000000..1d357267
--- /dev/null
+++ b/src/com/android/car/dialer/ui/CircleBitmapDrawable.java
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
+
+
+/**
+ * A drawable for displaying a circular bitmap. This is a wrapper over {@link RoundedBitmapDrawable}
+ * since that implementation doesn't behave quite as desired.
+ *
+ * <p>Note that not all drawable functionality is passed to the RoundedBitmapDrawable at this
+ * time. Feel free to add more as necessary.
+ */
+public class CircleBitmapDrawable extends Drawable {
+ private final Resources mResources;
+
+ private Bitmap mBitmap;
+ private RoundedBitmapDrawable mDrawable;
+ private int mAlpha = -1;
+ private ColorFilter mCf = null;
+
+ public CircleBitmapDrawable(@NonNull Resources res, @NonNull Bitmap bitmap) {
+ mBitmap = bitmap;
+ mResources = res;
+ }
+
+ @Override
+ public void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ int width = bounds.right - bounds.left;
+ int height = bounds.bottom - bounds.top;
+
+ Bitmap processed = mBitmap;
+ mDrawable = RoundedBitmapDrawableFactory.create(mResources, processed);
+ mDrawable.setBounds(bounds);
+ mDrawable.setAntiAlias(true);
+ mDrawable.setCornerRadius(Math.min(width, height) / 2f);
+ if (mAlpha != -1) {
+ mDrawable.setAlpha(mAlpha);
+ }
+ if (mCf != null) {
+ mDrawable.setColorFilter(mCf);
+ }
+ invalidateSelf();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mDrawable != null) {
+ mDrawable.draw(canvas);
+ }
+ }
+
+ @Override
+ public int getOpacity() {
+ return mDrawable != null ? mDrawable.getOpacity() : PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mAlpha = alpha;
+ if (mDrawable != null) {
+ mDrawable.setAlpha(alpha);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mCf = cf;
+ if (mDrawable != null) {
+ mDrawable.setColorFilter(cf);
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * Convert the drawable to a bitmap.
+ * @param size The target size of the bitmap in pixels.
+ * @return A bitmap representation of the drawable.
+ */
+ public Bitmap toBitmap(int size) {
+ Bitmap largeIcon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(largeIcon);
+ Rect bounds = getBounds();
+ setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ draw(canvas);
+ setBounds(bounds);
+ return largeIcon;
+ }
+}
+
diff --git a/src/com/android/car/dialer/ui/ContactListFragment.java b/src/com/android/car/dialer/ui/ContactListFragment.java
new file mode 100644
index 00000000..21e2613e
--- /dev/null
+++ b/src/com/android/car/dialer/ui/ContactListFragment.java
@@ -0,0 +1,211 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.car.widget.AlphaJumpBucketer;
+import androidx.car.widget.IAlphaJumpAdapter;
+import androidx.car.widget.ListItemAdapter;
+import androidx.car.widget.PagedListView;
+
+import com.android.car.dialer.ContactDetailsFragment;
+import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.PhoneLoader;
+import com.android.car.dialer.telecom.TelecomUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Contact Fragment.
+ */
+public class ContactListFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>,
+ ContactListItemProvider.OnShowContactDetailListener {
+ private static final int CONTACT_LOADER_ID = 1;
+
+ private PagedListView mPagedListView;
+
+ public static ContactListFragment newInstance() {
+ ContactListFragment fragment = new ContactListFragment();
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LoaderManager loaderManager = LoaderManager.getInstance(this);
+ loaderManager.initLoader(CONTACT_LOADER_ID, null, this);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View fragmentView = inflater.inflate(R.layout.contact_list_fragment, container, false);
+ mPagedListView = fragmentView.findViewById(R.id.list_view);
+ ((TextView) fragmentView.findViewById(R.id.title)).setText(
+ getContext().getString(R.string.contacts_title));
+ return fragmentView;
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int i, @Nullable Bundle bundle) {
+ return new CursorLoader(
+ getContext(),
+ ContactsContract.Data.CONTENT_URI,
+ null,
+ ContactsContract.Data.MIMETYPE + " = '" + ContactsContract.CommonDataKinds.Phone
+ .CONTENT_ITEM_TYPE + "'",
+ null,
+ ContactsContract.Contacts.DISPLAY_NAME + " ASC "
+ );
+ }
+
+ @Override
+ public void onLoadFinished(@NonNull Loader loader, Cursor cursor) {
+ List<ContactItem> contactItems = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ String number = PhoneLoader.getPhoneNumber(cursor, getContext().getContentResolver());
+
+ int idColumnIndex = PhoneLoader.getIdColumnIndex(cursor);
+ int id = cursor.getInt(idColumnIndex);
+
+ int displayNameColumnIndex = cursor.getColumnIndex(
+ ContactsContract.Contacts.DISPLAY_NAME);
+ String displayName = cursor.getString(displayNameColumnIndex);
+
+ int lookupKeyColumnIndex = cursor.getColumnIndex(
+ ContactsContract.Contacts.LOOKUP_KEY);
+ String lookupKey = cursor.getString(lookupKeyColumnIndex);
+
+ contactItems.add(new ContactItem(number, id, displayName, null, lookupKey));
+ }
+
+ ListItemAdapter contactListAdapter = new ListItemAdapter(getContext(),
+ new ContactListItemProvider(getContext(), contactItems, ContactListFragment.this));
+ mPagedListView.setAdapter(contactListAdapter);
+ contactListAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onLoaderReset(@NonNull Loader loader) {
+ }
+
+ @Override
+ public void onShowContactDetail(int contactId, String lookupKey) {
+ View contactDetailContainer =
+ getView().findViewById(R.id.contact_detail_container);
+ contactDetailContainer.setVisibility(View.VISIBLE);
+ setActivityActionBarVisibility(View.GONE);
+
+ final Uri uri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+ Fragment contactDetailFragment = ContactDetailsFragment.newInstance(uri, null);
+ getChildFragmentManager().beginTransaction().replace(R.id.contact_detail_fragment_container,
+ contactDetailFragment).commit();
+
+ getView().findViewById(R.id.back_button).setOnClickListener((v) -> {
+ getChildFragmentManager().beginTransaction().remove(contactDetailFragment).commit();
+ contactDetailContainer.setVisibility(View.GONE);
+ setActivityActionBarVisibility(View.VISIBLE);
+ });
+
+ }
+
+ private void setActivityActionBarVisibility(int visibility) {
+ View actionBar = getActivity().getWindow().getDecorView().findViewById(R.id.car_toolbar);
+ if (actionBar != null) {
+ actionBar.setVisibility(visibility);
+ }
+ }
+
+ /**
+ * Pojo which holds a contact entry information.
+ */
+ public static class ContactItem {
+ public final String mNumber;
+ public final int mId;
+ public final String mDisplayName;
+ public final Bitmap mIcon;
+ public final String mLookupKey;
+
+ private ContactItem(String number, int id, String displayName, Bitmap icon,
+ String lookupKey) {
+ mNumber = number;
+ mId = id;
+ mDisplayName = displayName;
+ mIcon = icon;
+ mLookupKey = lookupKey;
+ }
+ }
+
+ /**
+ * Use this Adapter to enabled AlphaJump.
+ */
+ private class ContactListAdapter extends ListItemAdapter implements IAlphaJumpAdapter {
+
+ List<ContactItem> mContactItems;
+
+ public ContactListAdapter(Context context,
+ List<ContactItem> contactItems) {
+ super(context,
+ new ContactListItemProvider(context, contactItems, ContactListFragment.this));
+ mContactItems = contactItems;
+ }
+
+ public ContactListAdapter(Context context, List<ContactItem> contactItems,
+ int backgroundStyle) {
+ super(context,
+ new ContactListItemProvider(context, contactItems, ContactListFragment.this),
+ backgroundStyle);
+ mContactItems = contactItems;
+ }
+
+ @Override
+ public Collection<Bucket> getAlphaJumpBuckets() {
+ AlphaJumpBucketer alphaJumpBucketer = new AlphaJumpBucketer();
+ List<String> values = new ArrayList<>();
+ for (ContactItem contactItem : mContactItems) {
+ values.add(contactItem.mDisplayName);
+ }
+
+ return alphaJumpBucketer.createBuckets(values.toArray(new String[]{}));
+ }
+
+ @Override
+ public void onAlphaJumpEnter() {
+ }
+
+ @Override
+ public void onAlphaJumpLeave(Bucket bucket) {
+ }
+ }
+}
diff --git a/src/com/android/car/dialer/ui/ContactListItemProvider.java b/src/com/android/car/dialer/ui/ContactListItemProvider.java
new file mode 100644
index 00000000..7ad56c42
--- /dev/null
+++ b/src/com/android/car/dialer/ui/ContactListItemProvider.java
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import androidx.car.widget.ListItem;
+import androidx.car.widget.ListItemProvider;
+import androidx.car.widget.TextListItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.UiCallManager;
+import com.android.car.dialer.ui.listitem.ContactListItem;
+
+/**
+ * Provides ListItem for contact list.
+ */
+public class ContactListItemProvider extends ListItemProvider {
+
+ private final List<TextListItem> mItems = new ArrayList<>();
+ private final OnShowContactDetailListener mOnShowContactDetailListener;
+
+ public interface OnShowContactDetailListener {
+ void onShowContactDetail(int contactId, String lookupKey);
+ }
+
+ public ContactListItemProvider(Context context, List<ContactListFragment.ContactItem> items,
+ OnShowContactDetailListener onShowContactDetailListener) {
+ mOnShowContactDetailListener = onShowContactDetailListener;
+ for (ContactListFragment.ContactItem contactItem : items) {
+ ContactListItem textListItem = new ContactListItem(context, contactItem);
+ textListItem.setPrimaryActionIcon(
+ new BitmapDrawable(context.getResources(), contactItem.mIcon), true);
+ textListItem.setTitle(contactItem.mDisplayName);
+ textListItem.setOnClickListener(
+ (v) -> UiCallManager.get().safePlaceCall(contactItem.mNumber, true));
+ Drawable supplementalIconDrawable = context.getDrawable(R.drawable.ic_contact);
+ supplementalIconDrawable.setTint(context.getColor(R.color.car_tint));
+ int iconSize = context.getResources().getDimensionPixelSize(
+ R.dimen.car_primary_icon_size);
+ supplementalIconDrawable.setBounds(0, 0, iconSize, iconSize);
+ textListItem.setSupplementalIcon(supplementalIconDrawable, true, (v) -> {
+ mOnShowContactDetailListener.onShowContactDetail(contactItem.mId,
+ contactItem.mLookupKey);
+ });
+ mItems.add(textListItem);
+ }
+ }
+
+ @Override
+ public ListItem get(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public int size() {
+ return mItems.size();
+ }
+}
diff --git a/src/com/android/car/dialer/ui/DialerInfoController.java b/src/com/android/car/dialer/ui/DialerInfoController.java
new file mode 100644
index 00000000..239c4916
--- /dev/null
+++ b/src/com/android/car/dialer/ui/DialerInfoController.java
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import android.content.Context;
+import android.telecom.Call;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.car.apps.common.FabDrawable;
+import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.TelecomUtils;
+import com.android.car.dialer.telecom.UiCall;
+import com.android.car.dialer.telecom.UiCallManager;
+
+/**
+ * Controls dialer information such as dialed number and shows proper action based on current call
+ * state.
+ */
+public class DialerInfoController {
+ private static final int MAX_DIAL_NUMBER = 20;
+
+ private TextView mTitleView;
+ private TextView mBodyView;
+
+ private ImageButton mCallButton;
+ private ImageButton mDeleteButton;
+
+ private ImageButton mEndCallButton;
+ private ImageButton mMuteButton;
+
+ private Context mContext;
+
+ private final StringBuffer mNumber = new StringBuffer(MAX_DIAL_NUMBER);
+
+ public DialerInfoController(Context context, View container) {
+ mContext = context;
+ init(container);
+ }
+
+ public View init(View container) {
+ mTitleView = container.findViewById(R.id.title);
+ mBodyView = container.findViewById(R.id.body);
+ mCallButton = container.findViewById(R.id.call_button);
+ mDeleteButton = container.findViewById(R.id.delete_button);
+ mEndCallButton = container.findViewById(R.id.end_call_button);
+ mMuteButton = container.findViewById(R.id.mute_button);
+
+ FabDrawable answerCallDrawable = new FabDrawable(mContext);
+ answerCallDrawable.setFabAndStrokeColor(mContext.getColor(R.color.phone_call));
+ mCallButton.setBackground(answerCallDrawable);
+ mCallButton.setOnClickListener((unusedView) -> {
+ if (!TextUtils.isEmpty(mNumber.toString())) {
+ UiCallManager.get().safePlaceCall(mNumber.toString(), false);
+ }
+ });
+ mDeleteButton.setOnClickListener(v -> {
+ removeLastDigit();
+ });
+ mDeleteButton.setOnLongClickListener(v -> {
+ // Clear all on long-press
+ clearDialedNumber();
+ return true;
+ });
+
+ updateView();
+
+ return container;
+ }
+
+ /**
+ * Append more number to the end of dialed number.
+ */
+ public void appendDialedNumber(String number) {
+ if (mNumber.length() < MAX_DIAL_NUMBER) {
+ mNumber.append(number);
+ mTitleView.setText(getFormattedNumber(mNumber.toString()));
+ }
+ }
+
+ /**
+ * Remove last digit of the dialed number. If there's no number left to delete, there's no
+ * operation to be take.
+ */
+ public void removeLastDigit() {
+ if (mNumber.length() != 0) {
+ mNumber.deleteCharAt(mNumber.length() - 1);
+ mTitleView.setText(getFormattedNumber(mNumber.toString()));
+ }
+ UiCall primaryCall = UiCallManager.get().getPrimaryCall();
+
+ if (mNumber.length() == 0 && primaryCall != null
+ && primaryCall.getState() != Call.STATE_ACTIVE) {
+ mTitleView.setText(R.string.dial_a_number);
+ }
+ }
+
+ private void updateView() {
+ UiCall onGoingCall = UiCallManager.get().getPrimaryCall();
+ if (onGoingCall == null) {
+ showPreDialUi();
+ } else if (onGoingCall.getState() == Call.STATE_CONNECTING) {
+ showDialingUi(onGoingCall);
+ } else if (onGoingCall.getState() == Call.STATE_ACTIVE) {
+ showInCallUi();
+ }
+ }
+
+ private void showPreDialUi() {
+ mCallButton.setVisibility(View.VISIBLE);
+ mDeleteButton.setVisibility(View.VISIBLE);
+
+ mEndCallButton.setVisibility(View.GONE);
+ mMuteButton.setVisibility(View.GONE);
+ }
+
+ private void showDialingUi(UiCall uiCall) {
+ if (mTitleView.getText().equals(mContext.getString(R.string.dial_a_number))) {
+ mTitleView.setText("");
+ }
+ FabDrawable endCallDrawable = new FabDrawable(mContext);
+ endCallDrawable.setFabAndStrokeColor(mContext.getColor(R.color.phone_end_call));
+ mEndCallButton.setBackground(endCallDrawable);
+ mEndCallButton.setVisibility(View.VISIBLE);
+ mMuteButton.setVisibility(View.VISIBLE);
+ mBodyView.setVisibility(View.VISIBLE);
+
+ mDeleteButton.setVisibility(View.GONE);
+ mCallButton.setVisibility(View.GONE);
+ bindUserProfileView(uiCall);
+ }
+
+ private void showInCallUi() {
+ if (mTitleView.getText().equals(mContext.getString(R.string.dial_a_number))) {
+ mTitleView.setText("");
+ }
+ mEndCallButton.setVisibility(View.GONE);
+ mDeleteButton.setVisibility(View.GONE);
+ mCallButton.setVisibility(View.GONE);
+ }
+
+ private String getFormattedNumber(String number) {
+ return TelecomUtils.getFormattedNumber(mContext, number);
+ }
+
+ private void clearDialedNumber() {
+ mNumber.setLength(0);
+ mTitleView.setText(getFormattedNumber(mNumber.toString()));
+ }
+
+ private void bindUserProfileView(UiCall primaryCall) {
+ if (primaryCall == null) {
+ return;
+ }
+ mTitleView.setText(TelecomUtils.getDisplayName(mContext, primaryCall));
+ }
+}
diff --git a/src/com/android/car/dialer/ui/DialpadFragment.java b/src/com/android/car/dialer/ui/DialpadFragment.java
new file mode 100644
index 00000000..863f7405
--- /dev/null
+++ b/src/com/android/car/dialer/ui/DialpadFragment.java
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import android.media.AudioManager;
+import android.media.ToneGenerator;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.UiCallManager;
+
+/**
+ * Dialpad Fragment which displays a dialpad.
+ */
+public class DialpadFragment extends Fragment {
+ private static final SparseIntArray sToneMap = new SparseIntArray();
+ private static final SparseArray<String> sDialValueMap = new SparseArray<>();
+ private static final SparseArray<Integer> sRIdMap = new SparseArray<>();
+
+ private static final int TONE_LENGTH_INFINITE = -1;
+ private static final int TONE_RELATIVE_VOLUME = 80;
+
+ static {
+ sToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1);
+ sToneMap.put(KeyEvent.KEYCODE_2, ToneGenerator.TONE_DTMF_2);
+ sToneMap.put(KeyEvent.KEYCODE_3, ToneGenerator.TONE_DTMF_3);
+ sToneMap.put(KeyEvent.KEYCODE_4, ToneGenerator.TONE_DTMF_4);
+ sToneMap.put(KeyEvent.KEYCODE_5, ToneGenerator.TONE_DTMF_5);
+ sToneMap.put(KeyEvent.KEYCODE_6, ToneGenerator.TONE_DTMF_6);
+ sToneMap.put(KeyEvent.KEYCODE_7, ToneGenerator.TONE_DTMF_7);
+ sToneMap.put(KeyEvent.KEYCODE_8, ToneGenerator.TONE_DTMF_8);
+ sToneMap.put(KeyEvent.KEYCODE_9, ToneGenerator.TONE_DTMF_9);
+ sToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0);
+ sToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S);
+ sToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P);
+
+ sDialValueMap.put(KeyEvent.KEYCODE_1, "1");
+ sDialValueMap.put(KeyEvent.KEYCODE_2, "2");
+ sDialValueMap.put(KeyEvent.KEYCODE_3, "3");
+ sDialValueMap.put(KeyEvent.KEYCODE_4, "4");
+ sDialValueMap.put(KeyEvent.KEYCODE_5, "5");
+ sDialValueMap.put(KeyEvent.KEYCODE_6, "6");
+ sDialValueMap.put(KeyEvent.KEYCODE_7, "7");
+ sDialValueMap.put(KeyEvent.KEYCODE_8, "8");
+ sDialValueMap.put(KeyEvent.KEYCODE_9, "9");
+ sDialValueMap.put(KeyEvent.KEYCODE_0, "0");
+ sDialValueMap.put(KeyEvent.KEYCODE_STAR, "*");
+ sDialValueMap.put(KeyEvent.KEYCODE_POUND, "#");
+
+ sRIdMap.put(KeyEvent.KEYCODE_1, R.id.one);
+ sRIdMap.put(KeyEvent.KEYCODE_2, R.id.two);
+ sRIdMap.put(KeyEvent.KEYCODE_3, R.id.three);
+ sRIdMap.put(KeyEvent.KEYCODE_4, R.id.four);
+ sRIdMap.put(KeyEvent.KEYCODE_5, R.id.five);
+ sRIdMap.put(KeyEvent.KEYCODE_6, R.id.six);
+ sRIdMap.put(KeyEvent.KEYCODE_7, R.id.seven);
+ sRIdMap.put(KeyEvent.KEYCODE_8, R.id.eight);
+ sRIdMap.put(KeyEvent.KEYCODE_9, R.id.nine);
+ sRIdMap.put(KeyEvent.KEYCODE_0, R.id.zero);
+ sRIdMap.put(KeyEvent.KEYCODE_STAR, R.id.star);
+ sRIdMap.put(KeyEvent.KEYCODE_POUND, R.id.pound);
+ }
+
+ public static DialpadFragment newInstance() {
+ return new DialpadFragment();
+ }
+
+ /**
+ * Callback for dialpad to interact with its host.
+ */
+ public interface DialpadCallback {
+ /**
+ * Called when voice mail should be dialed.
+ */
+ void onDialVoiceMail();
+
+ /**
+ * Called when a digit should be append.
+ */
+ void onAppendDigit(String digit);
+ }
+
+ private ToneGenerator mToneGenerator;
+ private DialpadCallback mDialpadCallback;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, TONE_RELATIVE_VOLUME);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ if (getParentFragment() != null && getParentFragment() instanceof DialpadCallback) {
+ mDialpadCallback = (DialpadCallback) getParentFragment();
+ } else if (getHost() instanceof DialpadCallback) {
+ mDialpadCallback = (DialpadCallback) getHost();
+ }
+
+ View dialpadView = inflater.inflate(R.layout.dialpad, container, false);
+ setupKeypadClickListeners(dialpadView);
+ return dialpadView;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ stopTone();
+ }
+
+ /**
+ * The click listener for all dialpad buttons. Reacts to touch-down and touch-up events, as
+ * well as long-press for certain keys. Mimics the behavior of the phone dialer app.
+ */
+ private class DialpadClickListener implements View.OnTouchListener,
+ View.OnLongClickListener {
+ private final int mTone;
+ private final String mValue;
+
+ DialpadClickListener(int keyCode) {
+ mTone = sToneMap.get(keyCode);
+ mValue = sDialValueMap.get(keyCode);
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ switch (mValue) {
+ case "0":
+ if (mDialpadCallback != null) {
+ mDialpadCallback.onAppendDigit("+");
+ }
+ stopTone();
+ return true;
+ case "1":
+ // TODO: this currently does not work (at least over bluetooth HFP), because
+ // the framework is unable to get the voicemail number. Revisit later...
+ if (mDialpadCallback != null) {
+ mDialpadCallback.onDialVoiceMail();
+ }
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ UiCallManager uiCallmanager = UiCallManager.get();
+ boolean hasActiveCall = uiCallmanager.getPrimaryCall() != null;
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (mDialpadCallback != null) {
+ mDialpadCallback.onAppendDigit(mValue);
+ }
+ if (hasActiveCall) {
+ uiCallmanager.playDtmfTone(uiCallmanager.getPrimaryCall(), mValue.charAt(0));
+ } else {
+ playTone(mTone);
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (hasActiveCall) {
+ uiCallmanager.stopDtmfTone(uiCallmanager.getPrimaryCall());
+ } else {
+ stopTone();
+ }
+ }
+
+ // Continue propagating the touch event
+ return false;
+ }
+ }
+
+ private void playTone(int tone) {
+ if (mToneGenerator == null) {
+ return;
+ }
+
+ // Start the new tone
+ mToneGenerator.startTone(tone, TONE_LENGTH_INFINITE);
+ }
+
+ private void stopTone() {
+ if (mToneGenerator == null) {
+ return;
+ }
+
+ mToneGenerator.stopTone();
+ }
+
+ private void setupKeypadClickListeners(View parent) {
+ for (int i = 0; i < sRIdMap.size(); i++) {
+ int key = sRIdMap.keyAt(i);
+ DialpadClickListener clickListener = new DialpadClickListener(key);
+ View v = parent.findViewById(sRIdMap.get(key));
+ v.setOnTouchListener(clickListener);
+ v.setOnLongClickListener(clickListener);
+ }
+ }
+}
diff --git a/src/com/android/car/dialer/ui/InCallFragment.java b/src/com/android/car/dialer/ui/InCallFragment.java
new file mode 100644
index 00000000..ff0e2ff9
--- /dev/null
+++ b/src/com/android/car/dialer/ui/InCallFragment.java
@@ -0,0 +1,188 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import static android.telecom.Call.STATE_RINGING;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.telecom.Call;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.car.dialer.CallListener;
+import com.android.car.dialer.DialerFragment;
+import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.TelecomUtils;
+import com.android.car.dialer.telecom.UiCall;
+import com.android.car.dialer.telecom.UiCallManager;
+
+/**
+ * A fragment that displays information about an on-going call with options to hang up.
+ */
+public class InCallFragment extends Fragment implements
+ OnGoingCallControllerBarFragment.OnGoingCallControllerBarCallback, CallListener {
+
+ private Fragment mDialerFragment;
+ private View mUserProfileContainerView;
+ private View mDialerFragmentContainer;
+ private TextView mUserProfileBodyText;
+
+ private Handler mHandler = new Handler();
+ private CharSequence mCallInfoLabel;
+
+ public static InCallFragment newInstance() {
+ return new InCallFragment();
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View fragmentView = inflater.inflate(R.layout.in_call_fragment, container, false);
+ mUserProfileContainerView = fragmentView.findViewById(R.id.user_profile_container);
+ mDialerFragmentContainer = fragmentView.findViewById(R.id.dialer_container);
+ mUserProfileBodyText = mUserProfileContainerView.findViewById(R.id.body);
+ mDialerFragment = new DialerFragment();
+
+ updateControllerBarFragment(UiCallManager.get().getPrimaryCall().getState());
+ bindUserProfileView(fragmentView.findViewById(R.id.user_profile_container));
+ return fragmentView;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHandler.removeCallbacks(mUpdateDurationRunnable);
+ }
+
+ @Override
+ public void onOpenDialpad() {
+ mDialerFragment = new DialerFragment();
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.dialer_container, mDialerFragment)
+ .commit();
+ mDialerFragmentContainer.setVisibility(View.VISIBLE);
+ mUserProfileContainerView.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onCloseDialpad() {
+ getFragmentManager().beginTransaction()
+ .remove(mDialerFragment)
+ .commit();
+ mDialerFragmentContainer.setVisibility(View.GONE);
+ mUserProfileContainerView.setVisibility(View.VISIBLE);
+ }
+
+ private void bindUserProfileView(View container) {
+ UiCall primaryCall = UiCallManager.get().getPrimaryCall();
+ if (primaryCall == null) {
+ return;
+ }
+ String number = primaryCall.getNumber();
+ String displayName = TelecomUtils.getDisplayName(getContext(), primaryCall);
+
+ TextView nameView = container.findViewById(R.id.title);
+ nameView.setText(displayName);
+
+ ImageView avatar = container.findViewById(R.id.avatar);
+ TelecomUtils.setContactBitmapAsync(getContext(), avatar, displayName, number);
+
+ mCallInfoLabel = TelecomUtils.getTypeFromNumber(getContext(), primaryCall.getNumber());
+ }
+
+ private void updateControllerBarFragment(int callState) {
+ Fragment controllerBarFragment;
+ if (callState == Call.STATE_RINGING) {
+ controllerBarFragment = RingingCallControllerBarFragment.newInstance();
+ } else {
+ controllerBarFragment = OnGoingCallControllerBarFragment.newInstance();
+ }
+
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.controller_bar_container, controllerBarFragment)
+ .commit();
+ }
+
+ @Override
+ public void onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask) {
+
+ }
+
+ @Override
+ public void onCallStateChanged(UiCall call, int state) {
+ int callState = call.getState();
+ switch (callState) {
+ case Call.STATE_NEW:
+ case Call.STATE_CONNECTING:
+ case Call.STATE_DIALING:
+ case Call.STATE_SELECT_PHONE_ACCOUNT:
+ case Call.STATE_HOLDING:
+ case Call.STATE_DISCONNECTED:
+ mHandler.removeCallbacks(mUpdateDurationRunnable);
+ updateBody(call);
+ break;
+ case Call.STATE_ACTIVE:
+ mHandler.post(mUpdateDurationRunnable);
+ updateControllerBarFragment(call.getState());
+ break;
+ }
+ }
+
+ @Override
+ public void onCallUpdated(UiCall call) {
+
+ }
+
+ @Override
+ public void onCallAdded(UiCall call) {
+
+ }
+
+ @Override
+ public void onCallRemoved(UiCall call) {
+
+ }
+
+ private final Runnable mUpdateDurationRunnable = new Runnable() {
+ @Override
+ public void run() {
+ UiCall primaryCall = UiCallManager.get().getPrimaryCall();
+ if (primaryCall.getState() != Call.STATE_ACTIVE) {
+ return;
+ }
+ updateBody(primaryCall);
+ mHandler.postDelayed(this /* runnable */, DateUtils.SECOND_IN_MILLIS);
+ }
+ };
+
+ private void updateBody(UiCall primaryCall) {
+ String callInfoText = TelecomUtils.getCallInfoText(getContext(),
+ primaryCall, mCallInfoLabel);
+ mUserProfileBodyText.setText(callInfoText);
+ mUserProfileBodyText.setVisibility(
+ TextUtils.isEmpty(callInfoText) ? View.GONE : View.VISIBLE);
+ }
+}
diff --git a/src/com/android/car/dialer/ui/OnGoingCallControllerBarFragment.java b/src/com/android/car/dialer/ui/OnGoingCallControllerBarFragment.java
new file mode 100644
index 00000000..7eeedae2
--- /dev/null
+++ b/src/com/android/car/dialer/ui/OnGoingCallControllerBarFragment.java
@@ -0,0 +1,277 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.RecyclerView;
+import android.telecom.CallAudioState;
+import android.telecom.CallAudioState.CallAudioRoute;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.car.widget.PagedListView;
+
+import com.android.car.apps.common.FabDrawable;
+import com.android.car.dialer.R;
+import com.android.car.dialer.log.L;
+import com.android.car.dialer.telecom.UiCall;
+import com.android.car.dialer.telecom.UiCallManager;
+
+import java.util.List;
+
+/**
+ * A Fragment of the bar which controls on going call. Its host or parent Fragment is expected to
+ * implement {@link OnGoingCallControllerBarCallback}.
+ */
+public class OnGoingCallControllerBarFragment extends Fragment {
+ private static String TAG = "CDialer.OngoingCallCtlFrg";
+ private AlertDialog mAudioRouteSelectionDialog;
+ private ImageView mAudioRouteButton;
+
+ public static OnGoingCallControllerBarFragment newInstance() {
+ return new OnGoingCallControllerBarFragment();
+ }
+
+ /**
+ * Callback for control bar buttons.
+ */
+ public interface OnGoingCallControllerBarCallback {
+ void onOpenDialpad();
+
+ void onCloseDialpad();
+ }
+
+ private OnGoingCallControllerBarCallback mOnGoingCallControllerBarCallback;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getParentFragment() != null
+ && getParentFragment() instanceof OnGoingCallControllerBarCallback) {
+ mOnGoingCallControllerBarCallback =
+ (OnGoingCallControllerBarCallback) getParentFragment();
+ } else if (getHost() instanceof OnGoingCallControllerBarCallback) {
+ mOnGoingCallControllerBarCallback = (OnGoingCallControllerBarCallback) getHost();
+ }
+
+ View dialogView = LayoutInflater.from(getContext()).inflate(
+ R.layout.audio_route_switch_dialog, null, false);
+ PagedListView list = dialogView.findViewById(R.id.list);
+ List<Integer> availableRoutes = UiCallManager.get().getSupportedAudioRoute();
+ list.setDividerVisibilityManager(position -> position == (availableRoutes.size() - 1));
+
+ mAudioRouteSelectionDialog = new AlertDialog.Builder(getContext())
+ .setView(dialogView)
+ .create();
+ mAudioRouteSelectionDialog.getWindow().setBackgroundDrawableResource(
+ android.R.color.transpare‌​nt);
+ list.setAdapter(new AudioRouteListAdapter(getContext(), availableRoutes));
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View fragmentView = inflater.inflate(R.layout.on_going_call_controller_bar_fragment,
+ container, false);
+ fragmentView.findViewById(R.id.mute_button).setOnClickListener((v) -> {
+ if (mOnGoingCallControllerBarCallback == null) {
+ return;
+ }
+ if (v.isActivated()) {
+ v.setActivated(false);
+ onMuteMic();
+ } else {
+ v.setActivated(true);
+ onUnmuteMic();
+ }
+ });
+
+ fragmentView.findViewById(R.id.toggle_dialpad_button).setOnClickListener((v) -> {
+ if (mOnGoingCallControllerBarCallback == null) {
+ return;
+ }
+ if (v.isActivated()) {
+ v.setActivated(false);
+ mOnGoingCallControllerBarCallback.onCloseDialpad();
+ } else {
+ v.setActivated(true);
+ mOnGoingCallControllerBarCallback.onOpenDialpad();
+ }
+ });
+
+ ImageView endCallButton = fragmentView.findViewById(R.id.end_call_button);
+ FabDrawable answerCallDrawable = new FabDrawable(getContext());
+ answerCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_end_call));
+ endCallButton.setBackground(answerCallDrawable);
+ endCallButton.setOnClickListener((v) -> {
+ if (mOnGoingCallControllerBarCallback == null) {
+ return;
+ }
+ onEndCall();
+ });
+
+
+ List<Integer> audioRoutes = UiCallManager.get().getSupportedAudioRoute();
+ mAudioRouteButton = fragmentView.findViewById(R.id.voice_channel_button);
+ if (audioRoutes.size() > 1) {
+ fragmentView.findViewById(R.id.voice_channel_chevron).setVisibility(View.VISIBLE);
+ mAudioRouteButton.setOnClickListener(
+ (v) -> mAudioRouteSelectionDialog.show());
+ } else {
+ fragmentView.findViewById(R.id.voice_channel_chevron).setVisibility(View.GONE);
+ }
+
+ fragmentView.findViewById(R.id.pause_button).setOnClickListener((v) -> {
+ if (mOnGoingCallControllerBarCallback == null) {
+ return;
+ }
+ if (v.isActivated()) {
+ v.setActivated(false);
+ onHoldCall();
+ } else {
+ v.setActivated(true);
+ onUnholdCall();
+ }
+ });
+ return fragmentView;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mAudioRouteSelectionDialog.isShowing()) {
+ mAudioRouteSelectionDialog.dismiss();
+ }
+ }
+
+ private void onMuteMic() {
+ UiCallManager.get().setMuted(true);
+ }
+
+ private void onUnmuteMic() {
+ UiCallManager.get().setMuted(false);
+ }
+
+ private void onHoldCall() {
+ UiCallManager uiCallManager = UiCallManager.get();
+ UiCall primaryCall = UiCallManager.get().getPrimaryCall();
+ uiCallManager.holdCall(primaryCall);
+ }
+
+ private void onUnholdCall() {
+ UiCallManager uiCallManager = UiCallManager.get();
+ UiCall primaryCall = UiCallManager.get().getPrimaryCall();
+ uiCallManager.unholdCall(primaryCall);
+ }
+
+ private void onVoiceOutputChannelChanged(@CallAudioRoute int audioRoute) {
+ UiCallManager.get().setAudioRoute(audioRoute);
+ mAudioRouteSelectionDialog.dismiss();
+ mAudioRouteButton.setImageResource(getAudioRouteIconRes(audioRoute));
+ }
+
+ private void onEndCall() {
+ UiCallManager uiCallManager = UiCallManager.get();
+ UiCall primaryCall = UiCallManager.get().getPrimaryCall();
+ uiCallManager.disconnectCall(primaryCall);
+ }
+
+ private int getAudioRouteIconRes(@CallAudioRoute int audioRoute) {
+ switch (audioRoute) {
+ case CallAudioState.ROUTE_WIRED_HEADSET:
+ case CallAudioState.ROUTE_EARPIECE:
+ return R.drawable.ic_smartphone;
+ case CallAudioState.ROUTE_BLUETOOTH:
+ return R.drawable.ic_bluetooth;
+ case CallAudioState.ROUTE_SPEAKER:
+ return R.drawable.ic_speaker_phone;
+ default:
+ L.w(TAG, "Unknown audio route: " + audioRoute);
+ return -1;
+ }
+ }
+
+ private int getAudioRouteLabelRes(@CallAudioRoute int audioRoute) {
+ switch (audioRoute) {
+ case CallAudioState.ROUTE_WIRED_HEADSET:
+ case CallAudioState.ROUTE_EARPIECE:
+ return R.string.audio_route_handset;
+ case CallAudioState.ROUTE_BLUETOOTH:
+ return R.string.audio_route_vehicle;
+ case CallAudioState.ROUTE_SPEAKER:
+ return R.string.audio_route_phone_speaker;
+ default:
+ L.w(TAG, "Unknown audio route: " + audioRoute);
+ return -1;
+ }
+ }
+
+ private class AudioRouteListAdapter extends
+ RecyclerView.Adapter<AudioRouteItemViewHolder> {
+ private List<Integer> mSupportedRoutes;
+ private Context mContext;
+
+ public AudioRouteListAdapter(Context context, List<Integer> supportedRoutes) {
+ mSupportedRoutes = supportedRoutes;
+ 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);
+ }
+ }
+
+ @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);
+ viewHolder.mBody.setText(mContext.getString(getAudioRouteLabelRes(audioRoute)));
+ viewHolder.mIcon.setImageResource(getAudioRouteIconRes(audioRoute));
+ viewHolder.itemView.setOnClickListener((v) -> onVoiceOutputChannelChanged(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);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/car/dialer/ui/RingingCallControllerBarFragment.java b/src/com/android/car/dialer/ui/RingingCallControllerBarFragment.java
new file mode 100644
index 00000000..7b315b9c
--- /dev/null
+++ b/src/com/android/car/dialer/ui/RingingCallControllerBarFragment.java
@@ -0,0 +1,49 @@
+package com.android.car.dialer.ui;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.car.apps.common.FabDrawable;
+import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.UiCall;
+import com.android.car.dialer.telecom.UiCallManager;
+
+public class RingingCallControllerBarFragment extends Fragment {
+
+ public static RingingCallControllerBarFragment newInstance() {
+ return new RingingCallControllerBarFragment();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View fragmentView = inflater.inflate(R.layout.ringing_call_controller_bar_fragment,
+ container, false);
+
+ fragmentView.findViewById(R.id.answer_call_button).setOnClickListener((v) -> answerCall());
+ fragmentView.findViewById(R.id.answer_call_text).setOnClickListener((v) -> answerCall());
+ fragmentView.findViewById(R.id.end_call_button).setOnClickListener((v) -> declineCall());
+ fragmentView.findViewById(R.id.end_call_text).setOnClickListener((v) -> declineCall());
+
+ return fragmentView;
+ }
+
+ private void answerCall() {
+ UiCallManager uiCallManager = UiCallManager.get();
+ UiCall primaryCall = uiCallManager.getPrimaryCall();
+ uiCallManager.answerCall(primaryCall);
+ }
+
+ private void declineCall() {
+ UiCallManager uiCallManager = UiCallManager.get();
+ UiCall primaryCall = uiCallManager.getPrimaryCall();
+ uiCallManager.rejectCall(primaryCall, false, null);
+ }
+}
diff --git a/src/com/android/car/dialer/ui/listitem/CallLogListItem.java b/src/com/android/car/dialer/ui/listitem/CallLogListItem.java
new file mode 100644
index 00000000..e48e92df
--- /dev/null
+++ b/src/com/android/car/dialer/ui/listitem/CallLogListItem.java
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui.listitem;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ScaleDrawable;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.car.widget.TextListItem;
+
+import com.android.car.apps.common.LetterTileDrawable;
+import com.android.car.dialer.telecom.ContactBitmapWorker;
+import com.android.car.dialer.ui.CallHistoryListItemProvider;
+import com.android.car.dialer.ui.CallLogListingTask;
+import com.android.car.dialer.ui.CircleBitmapDrawable;
+import com.android.car.dialer.R;
+
+/**
+ * List item which is created by {@link CallHistoryListItemProvider} binds a call list item to a
+ * list view item.
+ */
+public class CallLogListItem extends TextListItem {
+ private final CallLogListingTask.CallLogItem mCallLogItem;
+ private final Context mContext;
+
+ public CallLogListItem(Context context, CallLogListingTask.CallLogItem callLog) {
+ super(context);
+ mCallLogItem = callLog;
+ mContext = context;
+ }
+
+ @Override
+ public void onBind(ViewHolder viewHolder) {
+ super.onBind(viewHolder);
+ ContactBitmapWorker.loadBitmap(mContext.getContentResolver(), viewHolder.getPrimaryIcon(),
+ mCallLogItem.mNumber,
+ bitmap -> {
+ Resources r = mContext.getResources();
+ viewHolder.getPrimaryIcon().setScaleType(ImageView.ScaleType.CENTER);
+ Drawable avatarDrawable;
+ if (bitmap != null) {
+ avatarDrawable = new CircleBitmapDrawable(r, bitmap);
+ setPrimaryActionIcon(new CircleBitmapDrawable(r, bitmap), true);
+ } else {
+ LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r);
+ letterTileDrawable.setContactDetails(mCallLogItem.mTitle,
+ mCallLogItem.mNumber);
+ letterTileDrawable.setIsCircular(true);
+ avatarDrawable = letterTileDrawable;
+ }
+
+ int iconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.avatar_icon_size);
+ setPrimaryActionIcon(scaleDrawable(avatarDrawable, iconSize), true);
+ super.onBind(viewHolder);
+ });
+
+ viewHolder.getContainerLayout().setBackgroundColor(
+ mContext.getColor(R.color.call_history_list_item_color));
+ }
+
+ private Drawable scaleDrawable(Drawable targetDrawable, int sizeInPixel) {
+ Bitmap bitmap = null;
+ if (targetDrawable instanceof CircleBitmapDrawable) {
+ bitmap = ((CircleBitmapDrawable) targetDrawable).toBitmap(sizeInPixel);
+ } else if (targetDrawable instanceof LetterTileDrawable){
+ bitmap = ((LetterTileDrawable) targetDrawable).toBitmap(sizeInPixel);
+ }
+ return new BitmapDrawable(mContext.getResources(), bitmap);
+ }
+}
diff --git a/src/com/android/car/dialer/ui/listitem/ContactListItem.java b/src/com/android/car/dialer/ui/listitem/ContactListItem.java
new file mode 100644
index 00000000..8a7b344c
--- /dev/null
+++ b/src/com/android/car/dialer/ui/listitem/ContactListItem.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+package com.android.car.dialer.ui.listitem;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.widget.ImageView;
+
+import androidx.car.widget.TextListItem;
+
+import com.android.car.apps.common.LetterTileDrawable;
+import com.android.car.dialer.R;
+import com.android.car.dialer.telecom.ContactBitmapWorker;
+import com.android.car.dialer.ui.CircleBitmapDrawable;
+import com.android.car.dialer.ui.ContactListFragment;
+
+/**
+ * ListItem for contact.
+ */
+public class ContactListItem extends TextListItem {
+ private Context mContext;
+ private ContactListFragment.ContactItem mContactItem;
+
+ public ContactListItem(Context context, ContactListFragment.ContactItem contactItem) {
+ super(context);
+ mContext = context;
+ mContactItem = contactItem;
+ }
+
+ @Override
+ public void onBind(ViewHolder viewHolder) {
+ super.onBind(viewHolder);
+ ContactBitmapWorker.loadBitmap(mContext.getContentResolver(), viewHolder.getPrimaryIcon(),
+ mContactItem.mNumber,
+ bitmap -> {
+ Resources r = mContext.getResources();
+ viewHolder.getPrimaryIcon().setScaleType(ImageView.ScaleType.CENTER);
+ Drawable avatarDrawable;
+ if (bitmap != null) {
+ avatarDrawable = new CircleBitmapDrawable(r, bitmap);
+ } else {
+ LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r);
+ letterTileDrawable.setContactDetails(mContactItem.mDisplayName,
+ mContactItem.mNumber);
+ letterTileDrawable.setIsCircular(true);
+ avatarDrawable = letterTileDrawable;
+ }
+
+ int iconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.avatar_icon_size);
+ setPrimaryActionIcon(scaleDrawable(avatarDrawable, iconSize), true);
+ super.onBind(viewHolder);
+
+ // force rebind the view.
+ super.onBind(viewHolder);
+ });
+ viewHolder.getContainerLayout().setBackgroundColor(
+ mContext.getColor(R.color.contact_list_item_color));
+ }
+
+ private Drawable scaleDrawable(Drawable targetDrawable, int sizeInPixel) {
+ Bitmap bitmap = null;
+ if (targetDrawable instanceof CircleBitmapDrawable) {
+ bitmap = ((CircleBitmapDrawable) targetDrawable).toBitmap(sizeInPixel);
+ } else if (targetDrawable instanceof LetterTileDrawable){
+ bitmap = ((LetterTileDrawable) targetDrawable).toBitmap(sizeInPixel);
+ }
+ return new BitmapDrawable(mContext.getResources(), bitmap);
+ }
+}
diff --git a/src/com/android/car/dialer/ui/viewmodel/CallHistoryViewModel.java b/src/com/android/car/dialer/ui/viewmodel/CallHistoryViewModel.java
new file mode 100644
index 00000000..8afe0efd
--- /dev/null
+++ b/src/com/android/car/dialer/ui/viewmodel/CallHistoryViewModel.java
@@ -0,0 +1,58 @@
+package com.android.car.dialer.ui.viewmodel;
+/*
+ * 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.
+ */
+import android.app.Application;
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.LiveData;
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import com.android.car.dialer.livedata.CallHistoryLiveData;
+import com.android.car.dialer.livedata.MissedCallHistoryLiveData;
+import com.android.car.dialer.ui.CallLogListingTask;
+
+import java.util.List;
+
+/**
+ * View model for CallHistoryFragment which provides call history live data.
+ */
+public class CallHistoryViewModel extends AndroidViewModel {
+ private CallHistoryLiveData mCallHistoryLiveData;
+ private MissedCallHistoryLiveData mMissedCallHistoryLiveData;
+
+ private Context mContext;
+
+ public CallHistoryViewModel(
+ @NonNull Application application) {
+ super(application);
+ mContext = application;
+ }
+
+ public LiveData<List<CallLogListingTask.CallLogItem>> getCallHistory() {
+ if (mCallHistoryLiveData == null) {
+ mCallHistoryLiveData = new CallHistoryLiveData(mContext);
+ }
+ return mCallHistoryLiveData;
+ }
+
+ public LiveData<List<CallLogListingTask.CallLogItem>> getMissedCallHistory() {
+ if (mMissedCallHistoryLiveData == null) {
+ mMissedCallHistoryLiveData = new MissedCallHistoryLiveData(mContext);
+ }
+
+ return mMissedCallHistoryLiveData;
+ }
+}