diff options
author | Xin Li <delphij@google.com> | 2018-08-06 16:51:14 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2018-08-06 16:51:14 -0700 |
commit | d777b143383779fc4807c92982584134d891acf3 (patch) | |
tree | cb068f225fc053c7b6ec9c80323ec03729c35f92 | |
parent | b04568fb1e3fff7959e68b238c7447e5d297469a (diff) | |
parent | 4d866aacf10d09b8177c4dbf514c401902cefede (diff) | |
download | platform_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 |
Merge Android Pie into masterandroid-o-mr1-iot-release-smart-display-r3android-o-mr1-iot-release-1.0.5android-o-mr1-iot-release-1.0.4android-o-mr1-iot-release-1.0.3oreo-mr1-1.2-iot-releasemaster-cuttlefish-testing-release
Bug: 112104996
Change-Id: I4e2b80af0f9a7accfd88ef6394a0fb42e2f0b6be
106 files changed, 4504 insertions, 2334 deletions
@@ -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.transparent); + 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; + } +} |