summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2020-05-15 07:03:49 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2020-05-15 07:03:49 +0000
commite76536007ef3be4f05d14f29fa15aa069b0975e1 (patch)
treea92736292490c8140f6df033708a3df5d3d85234
parent66b49e51c480bbdd38630bfcb025609dd134fdc9 (diff)
parent99c2d8d54c5e842449e3a8dec14a3d5a2caa1275 (diff)
downloadplatform_packages_apps_Car_Dialer-e76536007ef3be4f05d14f29fa15aa069b0975e1.tar.gz
platform_packages_apps_Car_Dialer-e76536007ef3be4f05d14f29fa15aa069b0975e1.tar.bz2
platform_packages_apps_Car_Dialer-e76536007ef3be4f05d14f29fa15aa069b0975e1.zip
Snap for 6497471 from 99c2d8d54c5e842449e3a8dec14a3d5a2caa1275 to mainline-release
Change-Id: I7925112b1b15801685fb42236b7a82e3c9f9b8ca
-rw-r--r--res/drawable/ic_merge.xml26
-rw-r--r--res/drawable/ic_swap_calls.xml27
-rw-r--r--res/layout/conference_call_user_list.xml66
-rw-r--r--res/layout/in_call_activity.xml6
-rw-r--r--res/layout/on_going_call_controller_bar_fragment.xml38
-rw-r--r--res/layout/ongoing_call_fragment.xml12
-rw-r--r--res/layout/ongoing_conf_call_fragment.xml83
-rw-r--r--res/layout/onhold_user_profile.xml70
-rw-r--r--res/layout/user_profile_list_item.xml51
-rw-r--r--res/values-w1280dp-land/dimens.xml2
-rw-r--r--res/values/colors.xml2
-rw-r--r--res/values/dimens.xml8
-rw-r--r--res/values/strings.xml9
-rw-r--r--src/com/android/car/dialer/livedata/CallDetailLiveData.java32
-rw-r--r--src/com/android/car/dialer/ui/activecall/ConferenceProfileAdapter.java74
-rw-r--r--src/com/android/car/dialer/ui/activecall/ConferenceProfileViewHolder.java106
-rw-r--r--src/com/android/car/dialer/ui/activecall/InCallActivity.java60
-rw-r--r--src/com/android/car/dialer/ui/activecall/InCallFragment.java5
-rw-r--r--src/com/android/car/dialer/ui/activecall/InCallViewModel.java102
-rw-r--r--src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragment.java47
-rw-r--r--src/com/android/car/dialer/ui/activecall/OnHoldCallUserProfileFragment.java28
-rw-r--r--src/com/android/car/dialer/ui/activecall/OngoingCallFragment.java1
-rw-r--r--src/com/android/car/dialer/ui/activecall/OngoingConfCallFragment.java156
-rw-r--r--tests/robotests/src/com/android/car/dialer/livedata/CallDetailLiveDataTest.java3
24 files changed, 880 insertions, 134 deletions
diff --git a/res/drawable/ic_merge.xml b/res/drawable/ic_merge.xml
new file mode 100644
index 00000000..443fed62
--- /dev/null
+++ b/res/drawable/ic_merge.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/primary_icon_size"
+ android:height="@dimen/primary_icon_size"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="@color/icon_tint_state_list">
+ <path
+ android:pathData="M18.41,18.59L17,20l-3.41,-3.41L15,15.18l3.41,3.41zM16.5,7.5L12,3 7.5,7.5l1.41,1.41L11,6.83v6.35l-5.41,5.41L7,20l6,-6V6.83l2.09,2.09L16.5,7.5z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/res/drawable/ic_swap_calls.xml b/res/drawable/ic_swap_calls.xml
deleted file mode 100644
index 098ca973..00000000
--- a/res/drawable/ic_swap_calls.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/primary_icon_size"
- android:height="@dimen/primary_icon_size"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
-
- <path
- android:pathData="M0 0h24v24H0z" />
- <path
- android:fillColor="#fffafafa"
- android:pathData="M18 4l-4 4h3v7c0 1.1-.9 2-2 2s-2-.9-2-2V8c0-2.21-1.79-4-4-4S5 5.79 5 8v7H2l4 4 4-4H7V8c0-1.1.9-2 2-2s2 .9 2 2v7c0 2.21 1.79 4 4 4s4-1.79 4-4V8h3l-4-4z"/>
-</vector>
diff --git a/res/layout/conference_call_user_list.xml b/res/layout/conference_call_user_list.xml
new file mode 100644
index 00000000..018a7269
--- /dev/null
+++ b/res/layout/conference_call_user_list.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent">
+
+ <LinearLayout
+ android:id="@+id/conference_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="35dp"
+ android:layout_marginBottom="35dp"
+ android:layout_marginLeft="135dp"
+ app:layout_constraintBottom_toTopOf="@+id/recycler_view"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent">
+
+ <TextView
+ android:id="@+id/conference_title"
+ android:textAppearance="@style/TextAppearance.InCallState"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <!-- A textView without a fixed width will call requestLayout() every time its text is changed -->
+ <!-- So we have to make this Chronometer match_parent to avoid redrawing the whole screen -->
+ <Chronometer
+ android:id="@+id/call_duration"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.InCallState"
+ android:singleLine="true"/>
+ </LinearLayout>
+
+ <com.android.car.ui.recyclerview.CarUiRecyclerView
+ android:id="@+id/recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_marginTop="30dp"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/conference_header"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/res/layout/in_call_activity.xml b/res/layout/in_call_activity.xml
index c7a0684b..23dc0fed 100644
--- a/res/layout/in_call_activity.xml
+++ b/res/layout/in_call_activity.xml
@@ -28,4 +28,10 @@
android:id="@+id/ongoing_call_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
+
+ <fragment
+ android:name="com.android.car.dialer.ui.activecall.OngoingConfCallFragment"
+ android:id="@+id/ongoing_conf_call_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
</FrameLayout>
diff --git a/res/layout/on_going_call_controller_bar_fragment.xml b/res/layout/on_going_call_controller_bar_fragment.xml
index c9902c20..8061b712 100644
--- a/res/layout/on_going_call_controller_bar_fragment.xml
+++ b/res/layout/on_going_call_controller_bar_fragment.xml
@@ -52,14 +52,13 @@ limitations under the License.
app:layout_constraintEnd_toStartOf="@+id/voice_channel_view"
app:layout_constraintTop_toTopOf="parent"/>
- <LinearLayout
+ <FrameLayout
android:id="@+id/voice_channel_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/end_call_button"
- app:layout_constraintEnd_toStartOf="@+id/pause_button"
+ app:layout_constraintEnd_toStartOf="@+id/button_wrapper"
app:layout_constraintTop_toTopOf="parent">
<ImageView
@@ -76,17 +75,32 @@ limitations under the License.
android:layout_height="wrap_content"
android:visibility="gone"/>
- </LinearLayout>
+ </FrameLayout>
- <ImageView
- android:id="@+id/pause_button"
- android:layout_width="@dimen/in_call_button_size"
- android:layout_height="@dimen/in_call_button_size"
- android:background="@drawable/dialer_ripple_background"
- android:scaleType="center"
- android:src="@drawable/ic_pause_activatable"
+ <LinearLayout
+ android:id="@+id/button_wrapper"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/voice_channel_view"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"/>
+ app:layout_constraintTop_toTopOf="parent">
+
+ <ImageView
+ android:id="@+id/merge_button"
+ android:layout_width="@dimen/in_call_button_size"
+ android:layout_height="@dimen/in_call_button_size"
+ android:background="@drawable/dialer_ripple_background"
+ android:scaleType="center"
+ android:src="@drawable/ic_merge"/>
+
+ <ImageView
+ android:id="@+id/pause_button"
+ android:layout_width="@dimen/in_call_button_size"
+ android:layout_height="@dimen/in_call_button_size"
+ android:background="@drawable/dialer_ripple_background"
+ android:scaleType="center"
+ android:src="@drawable/ic_pause_activatable"/>
+ </LinearLayout>
+
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/ongoing_call_fragment.xml b/res/layout/ongoing_call_fragment.xml
index 007531b1..02b28180 100644
--- a/res/layout/ongoing_call_fragment.xml
+++ b/res/layout/ongoing_call_fragment.xml
@@ -45,15 +45,19 @@ limitations under the License.
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/ongoing_call_control_bar"/>
- <include
- layout="@layout/user_profile_large"
- android:id="@+id/user_profile_container"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/ongoing_call_control_bar"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"/>
+ app:layout_constraintStart_toStartOf="parent">
+
+ <include
+ layout="@layout/user_profile_large"
+ android:id="@+id/user_profile_container"/>
+
+ </LinearLayout>
<fragment
android:id="@+id/onhold_user_profile"
diff --git a/res/layout/ongoing_conf_call_fragment.xml b/res/layout/ongoing_conf_call_fragment.xml
new file mode 100644
index 00000000..2fdf0d5a
--- /dev/null
+++ b/res/layout/ongoing_conf_call_fragment.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- This ConstraintLayout is to make a full-screen transparent background -->
+ <!-- so that the ripple effects in the controller bar buttons work. -->
+ <!-- If you put the transparent background on the root element of -->
+ <!-- in_call_fragment, the BackgroundImageView will cover up the ripples. -->
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:background="@android:color/transparent">
+
+ <fragment
+ android:name="com.android.car.dialer.ui.dialpad.InCallDialpadFragment"
+ android:id="@+id/incall_dialpad_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/ongoing_call_control_bar"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/ongoing_call_control_bar"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent">
+
+ <include
+ layout="@layout/conference_call_user_list"
+ android:id="@+id/conference_profiles"/>
+
+ </LinearLayout>
+
+ <fragment
+ android:id="@+id/onhold_user_profile"
+ android:name="com.android.car.dialer.ui.activecall.OnHoldCallUserProfileFragment"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/onhold_user_info_height"
+ android:layout_marginTop="@dimen/onhold_profile_margin_y"
+ android:layout_marginStart="@dimen/onhold_profile_margin_x"
+ android:layout_marginEnd="@dimen/onhold_profile_margin_x"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <fragment
+ android:id="@+id/ongoing_call_control_bar"
+ android:name="com.android.car.dialer.ui.activecall.OnGoingCallControllerBarFragment"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/in_call_controller_bar_height"
+ android:layout_marginStart="@dimen/in_call_controller_bar_margin"
+ android:layout_marginEnd="@dimen/in_call_controller_bar_margin"
+ android:layout_marginBottom="@dimen/in_call_controller_bar_margin_bottom"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/onhold_user_profile.xml b/res/layout/onhold_user_profile.xml
index 22afc237..235a013c 100644
--- a/res/layout/onhold_user_profile.xml
+++ b/res/layout/onhold_user_profile.xml
@@ -34,8 +34,8 @@
<ImageView
android:id="@+id/icon"
- android:layout_width="@dimen/avatar_icon_size"
- android:layout_height="@dimen/avatar_icon_size"
+ android:layout_width="@dimen/small_avatar_icon_size"
+ android:layout_height="@dimen/small_avatar_icon_size"
android:scaleType="centerCrop"
android:layout_marginStart="@dimen/onhold_profile_avatar_margin"
app:layout_constraintTop_toTopOf="parent"
@@ -44,37 +44,65 @@
<TextView
android:id="@+id/title"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/onhold_profile_status_margin"
android:textAppearance="?android:attr/textAppearanceLarge"
android:singleLine="true"
- app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/text"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/guideline"
- app:layout_constraintEnd_toStartOf="@+id/swap_call_icon"/>
+ app:layout_constraintEnd_toStartOf="@+id/time"/>
+
+ <Chronometer
+ android:id="@id/time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/onhold_profile_status_margin"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="@color/onhold_time_color"
+ android:singleLine="true"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/title"
+ app:layout_constraintEnd_toStartOf="@+id/separator"/>
<TextView
- android:id="@id/text"
- android:layout_width="0dp"
+ android:id="@id/separator"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/onhold_call_label"
- android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_marginStart="@dimen/onhold_profile_status_margin"
+ android:layout_marginEnd="@dimen/onhold_profile_status_margin"
+ android:textAppearance="?android:attr/textAppearanceLarge"
android:singleLine="true"
- app:layout_constraintTop_toBottomOf="@id/title"
+ android:text="@string/onhold_call_separator"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="@id/guideline"
- app:layout_constraintEnd_toStartOf="@+id/swap_call_icon"/>
+ app:layout_constraintStart_toEndOf="@id/time"
+ app:layout_constraintEnd_toStartOf="@+id/onhold_label"/>
- <ImageView
- android:id="@+id/swap_call_icon"
+ <TextView
+ android:id="@id/onhold_label"
android:layout_width="0dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_swap_calls"
- android:scaleType="center"
- android:tint="@color/secondary_icon_color"
- android:paddingLeft="@dimen/swap_call_button_margin"
- android:paddingRight="@dimen/swap_call_button_margin"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:singleLine="true"
+ android:text="@string/call_state_hold"
+ android:textColor="@color/onhold_label_color"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/separator"
+ app:layout_constraintEnd_toStartOf="@+id/swap_call"/>
+
+ <TextView
+ android:id="@id/swap_call"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/onhold_profile_avatar_margin"
+ android:text="@string/swap_call_label"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:singleLine="true"
+ android:textColor="?android:attr/colorAccent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
diff --git a/res/layout/user_profile_list_item.xml b/res/layout/user_profile_list_item.xml
new file mode 100644
index 00000000..d9fbeaf3
--- /dev/null
+++ b/res/layout/user_profile_list_item.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/user_profile_list_item_height"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/in_call_user_profile_list_margin"
+ android:paddingEnd="@dimen/in_call_user_profile_list_margin">
+ <ImageView
+ android:id="@+id/user_profile_avatar"
+ android:layout_width="@dimen/in_call_avatar_icon_size_small"
+ android:layout_height="@dimen/in_call_avatar_icon_size_small"
+ android:scaleType="fitCenter"/>
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:orientation="vertical"
+ android:paddingStart="@dimen/in_call_margin_between_avatar_and_text">
+ <TextView
+ android:id="@+id/user_profile_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Display3"
+ android:singleLine="true"/>
+ <TextView
+ android:id="@+id/user_profile_phone_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Body3"
+ android:singleLine="true"
+ android:layout_marginTop="@dimen/in_call_phone_number_margin_top"/>
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/values-w1280dp-land/dimens.xml b/res/values-w1280dp-land/dimens.xml
index cddf4f66..8790808e 100644
--- a/res/values-w1280dp-land/dimens.xml
+++ b/res/values-w1280dp-land/dimens.xml
@@ -15,5 +15,5 @@
-->
<resources>
<dimen name="dialpad_info_title_text_size_max">36sp</dimen>
- <dimen name="onhold_profile_margin_x">112dp</dimen>
+ <dimen name="onhold_profile_margin_x">165dp</dimen>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 49d2e522..1ae967e0 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -18,7 +18,9 @@
<color name="phone_call">@*android:color/car_green_700</color>
<color name="phone_end_call">@*android:color/car_red_500a</color>
<color name="onhold_call_background">@*android:color/car_grey_868</color>
+ <color name="onhold_label_color">#FFA000</color>
<color name="audio_output_accent">@*android:color/car_accent</color>
+ <color name="onhold_time_color">#B8FFFFFF</color>
<!-- Dialpad page -->
<color name="call_button_outline">@*android:color/car_green_500</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 16d2ce11..858c0651 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -57,17 +57,20 @@
<dimen name="in_call_controller_bar_margin">@*android:dimen/car_padding_5</dimen>
<dimen name="in_call_controller_bar_margin_bottom">0dp</dimen>
<dimen name="in_call_avatar_icon_size">196dp</dimen>
+ <dimen name="in_call_avatar_icon_size_small">98dp</dimen>
<dimen name="in_call_phone_number_margin_top">@*android:dimen/car_padding_2</dimen>
<dimen name="in_call_state_margin_top">@*android:dimen/car_padding_2</dimen>
<dimen name="in_call_margin_between_avatar_and_text">48dp</dimen>
<dimen name="in_call_user_profile_margin">@*android:dimen/car_margin</dimen>
- <dimen name="onhold_user_info_height">@dimen/list_item_height</dimen>
+ <dimen name="in_call_user_profile_list_margin">20dp</dimen>
+ <dimen name="onhold_user_info_height">90dp</dimen>
<dimen name="onhold_profile_margin_x">@dimen/list_item_padding</dimen>
<dimen name="onhold_profile_margin_y">@*android:dimen/car_padding_3</dimen>
<dimen name="onhold_profile_corner_radius">8dp</dimen>
<dimen name="onhold_profile_avatar_margin">@*android:dimen/car_keyline_1</dimen>
<dimen name="onhold_profile_guideline">@dimen/list_item_guideline</dimen>
- <dimen name="swap_call_button_margin">@*android:dimen/car_keyline_1</dimen>
+ <dimen name="onhold_profile_status_margin">6dp</dimen>
+ <dimen name="user_profile_list_item_height">150dp</dimen>
<!-- Ringing call dimensions -->
<dimen name="ringing_call_button_touch_target_size">@dimen/touch_target_size</dimen>
@@ -134,6 +137,7 @@
<dimen name="vertical_divider_inset">@*android:dimen/car_padding_2</dimen>
<dimen name="vertical_divider_width">2dp</dimen>
<dimen name="primary_icon_size">@*android:dimen/car_primary_icon_size</dimen>
+ <dimen name="small_avatar_icon_size">56dp</dimen>
<dimen name="avatar_icon_size">76dp</dimen>
<dimen name="large_avatar_icon_size">96dp</dimen>
<dimen name="primary_icon_enclosing_circle_size">64dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a890cd17..936ef2e5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -179,7 +179,8 @@
<!-- Onhold User Profile Info -->
<!-- Text to show the call is onhold [CHAR LIMIT=40]-->
- <string name="onhold_call_label">On Hold</string>
+ <string name="onhold_call_separator">•</string>
+ <string name="swap_call_label">Switch calls</string>
<!-- Contact list headers -->
<!-- Contact list label for contact names starting with special characters -->
@@ -209,4 +210,10 @@
<!-- Audio route selection dialog. Placeholder resources for overriding -->
<string name="audio_route_dialog_title">Output call audio to:</string>
<string name="audio_route_dialog_subtitle"></string>
+
+ <!-- Ongoing Conference Call -->
+ <!-- Title of conference page -->
+ <string name="ongoing_conf_title">Conference</string>
+ <string name="ongoing_conf_title_format">%1$s (%2$d) -\u0020</string>
+
</resources>
diff --git a/src/com/android/car/dialer/livedata/CallDetailLiveData.java b/src/com/android/car/dialer/livedata/CallDetailLiveData.java
index 4d818016..d0ad0b3d 100644
--- a/src/com/android/car/dialer/livedata/CallDetailLiveData.java
+++ b/src/com/android/car/dialer/livedata/CallDetailLiveData.java
@@ -19,7 +19,7 @@ package com.android.car.dialer.livedata;
import android.telecom.Call;
import android.telecom.InCallService;
-import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import com.android.car.telephony.common.CallDetail;
@@ -31,23 +31,23 @@ import java.util.List;
*/
public class CallDetailLiveData extends LiveData<CallDetail> {
- private final Call mTelecomCall;
-
- public CallDetailLiveData(@NonNull Call telecomCall) {
- mTelecomCall = telecomCall;
- }
+ private Call mTelecomCall;
@Override
protected void onActive() {
super.onActive();
setTelecomCallDetail(mTelecomCall);
- mTelecomCall.registerCallback(mCallback);
+ if (mTelecomCall != null) {
+ mTelecomCall.registerCallback(mCallback);
+ }
}
@Override
protected void onInactive() {
super.onInactive();
- mTelecomCall.unregisterCallback(mCallback);
+ if (mTelecomCall != null) {
+ mTelecomCall.unregisterCallback(mCallback);
+ }
}
private Call.Callback mCallback = new Call.Callback() {
@@ -88,7 +88,19 @@ public class CallDetailLiveData extends LiveData<CallDetail> {
}
};
- private void setTelecomCallDetail(Call telecomCall) {
- setValue(CallDetail.fromTelecomCallDetail(telecomCall.getDetails()));
+ /**
+ * Sets the {@link Call} of which this live data sources.
+ */
+ public void setTelecomCall(Call telecomCall) {
+ mTelecomCall = telecomCall;
+ setTelecomCallDetail(mTelecomCall);
+ if (mTelecomCall != null) {
+ mTelecomCall.registerCallback(mCallback);
+ }
+ }
+
+ private void setTelecomCallDetail(@Nullable Call telecomCall) {
+ setValue(telecomCall != null ? CallDetail.fromTelecomCallDetail(telecomCall.getDetails())
+ : null);
}
}
diff --git a/src/com/android/car/dialer/ui/activecall/ConferenceProfileAdapter.java b/src/com/android/car/dialer/ui/activecall/ConferenceProfileAdapter.java
new file mode 100644
index 00000000..224b6eda
--- /dev/null
+++ b/src/com/android/car/dialer/ui/activecall/ConferenceProfileAdapter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.dialer.ui.activecall;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.dialer.R;
+import com.android.car.telephony.common.CallDetail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adapter for holding user profiles of a conference.
+ */
+public class ConferenceProfileAdapter extends RecyclerView.Adapter<ConferenceProfileViewHolder> {
+
+ private Context mContext;
+ private List<CallDetail> mConferenceList = new ArrayList<>();
+
+ public ConferenceProfileAdapter(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Sets {@link #mConferenceList} based on live data.
+ */
+ public void setConferenceList(List<CallDetail> list) {
+ mConferenceList.clear();
+ if (list != null) {
+ mConferenceList.addAll(list);
+ }
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public ConferenceProfileViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+ View view = LayoutInflater.from(mContext).inflate(R.layout.user_profile_list_item,
+ viewGroup, false);
+ return new ConferenceProfileViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ConferenceProfileViewHolder conferenceProfileViewHolder,
+ int i) {
+ conferenceProfileViewHolder.bind(mConferenceList.get(i));
+ }
+
+ @Override
+ public int getItemCount() {
+ return mConferenceList.size();
+ }
+}
diff --git a/src/com/android/car/dialer/ui/activecall/ConferenceProfileViewHolder.java b/src/com/android/car/dialer/ui/activecall/ConferenceProfileViewHolder.java
new file mode 100644
index 00000000..7d020aa6
--- /dev/null
+++ b/src/com/android/car/dialer/ui/activecall/ConferenceProfileViewHolder.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.dialer.ui.activecall;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.apps.common.LetterTileDrawable;
+import com.android.car.dialer.R;
+import com.android.car.dialer.ui.view.ContactAvatarOutputlineProvider;
+import com.android.car.telephony.common.CallDetail;
+import com.android.car.telephony.common.TelecomUtils;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.bumptech.glide.request.transition.Transition;
+
+/**
+ * View holder for a user profile of a conference
+ */
+public class ConferenceProfileViewHolder extends RecyclerView.ViewHolder {
+
+ private ImageView mAvatar;
+ private TextView mTitle;
+ private TextView mNumber;
+ private Context mContext;
+
+ ConferenceProfileViewHolder(View v) {
+ super(v);
+
+ mAvatar = v.findViewById(R.id.user_profile_avatar);
+ mAvatar.setOutlineProvider(ContactAvatarOutputlineProvider.get());
+ mTitle = v.findViewById(R.id.user_profile_title);
+ mNumber = v.findViewById(R.id.user_profile_phone_number);
+ mContext = v.getContext();
+ }
+
+ /**
+ * Binds call details to the profile views
+ */
+ public void bind(CallDetail callDetail) {
+ String number = callDetail.getNumber();
+ TelecomUtils.getPhoneNumberInfo(mContext, number)
+ .thenAcceptAsync((info) -> {
+ if (mContext == null) {
+ return;
+ }
+
+ mAvatar.setImageDrawable(TelecomUtils.createLetterTile(mContext, null, null));
+ mTitle.setText(info.getDisplayName());
+
+ String phoneNumberLabel = info.getTypeLabel();
+ if (!phoneNumberLabel.isEmpty()) {
+ phoneNumberLabel += " ";
+ }
+ phoneNumberLabel += TelecomUtils.getFormattedNumber(mContext, number);
+ if (!TextUtils.isEmpty(phoneNumberLabel)
+ && !phoneNumberLabel.equals(info.getDisplayName())) {
+ mNumber.setText(phoneNumberLabel);
+ } else {
+ mNumber.setText(null);
+ }
+
+ LetterTileDrawable letterTile = TelecomUtils.createLetterTile(
+ mContext, info.getInitials(), info.getDisplayName());
+
+ Glide.with(mContext)
+ .load(info.getAvatarUri())
+ .apply(new RequestOptions().centerCrop().error(letterTile))
+ .into(new SimpleTarget<Drawable>() {
+ @Override
+ public void onResourceReady(Drawable resource,
+ Transition<? super Drawable> glideAnimation) {
+ mAvatar.setImageDrawable(resource);
+ }
+
+ @Override
+ public void onLoadFailed(Drawable errorDrawable) {
+ mAvatar.setImageDrawable(letterTile);
+ }
+ });
+
+ }, mContext.getMainExecutor());
+ }
+}
diff --git a/src/com/android/car/dialer/ui/activecall/InCallActivity.java b/src/com/android/car/dialer/ui/activecall/InCallActivity.java
index af438a18..bf22b279 100644
--- a/src/com/android/car/dialer/ui/activecall/InCallActivity.java
+++ b/src/com/android/car/dialer/ui/activecall/InCallActivity.java
@@ -23,6 +23,7 @@ import android.telecom.Call;
import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProviders;
@@ -32,13 +33,13 @@ import com.android.car.dialer.Constants;
import com.android.car.dialer.R;
import com.android.car.dialer.log.L;
import com.android.car.dialer.notification.InCallNotificationController;
-
-import java.util.List;
+import com.android.car.telephony.common.CallDetail;
/** Activity for ongoing call and incoming call. */
public class InCallActivity extends FragmentActivity {
private static final String TAG = "CD.InCallActivity";
private Fragment mOngoingCallFragment;
+ private Fragment mOngoingConfCallFragment;
private Fragment mIncomingCallFragment;
private InCallViewModel mInCallViewModel;
@@ -54,14 +55,23 @@ public class InCallActivity extends FragmentActivity {
mOngoingCallFragment = getSupportFragmentManager().findFragmentById(
R.id.ongoing_call_fragment);
+ mOngoingConfCallFragment = getSupportFragmentManager().findFragmentById(
+ R.id.ongoing_conf_call_fragment);
mIncomingCallFragment = getSupportFragmentManager().findFragmentById(
R.id.incoming_call_fragment);
+ // Initially hide all fragments to prevent animation flicker
+ getSupportFragmentManager().beginTransaction()
+ .hide(mIncomingCallFragment)
+ .hide(mOngoingCallFragment)
+ .hide(mOngoingConfCallFragment)
+ .commit();
+
mShowIncomingCall = new MutableLiveData<>();
mInCallViewModel = ViewModelProviders.of(this).get(InCallViewModel.class);
mIncomingCallLiveData = LiveDataFunctions.iff(mShowIncomingCall,
mInCallViewModel.getIncomingCall());
- LiveDataFunctions.pair(mInCallViewModel.getOngoingCallList(),
+ LiveDataFunctions.pair(mInCallViewModel.getPrimaryCallDetail(),
mIncomingCallLiveData).observe(this, this::updateVisibility);
handleIntent();
@@ -85,14 +95,32 @@ public class InCallActivity extends FragmentActivity {
handleIntent();
}
- private void updateVisibility(Pair<List<Call>, Call> callList) {
- if ((callList.first == null || callList.first.isEmpty()) && callList.second == null) {
+ private void updateVisibility(Pair<CallDetail, Call> callList) {
+ CallDetail detail = callList.first;
+ Call incomingCall = callList.second;
+
+ if (detail == null && incomingCall == null) {
L.d(TAG, "No call to show. Finish InCallActivity");
finish();
return;
}
- updateIncomingCallVisibility(callList.second);
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+
+ if (incomingCall == null) {
+ ft.show(detail.isConference() ? mOngoingConfCallFragment : mOngoingCallFragment)
+ .hide(detail.isConference() ? mOngoingCallFragment : mOngoingConfCallFragment)
+ .hide(mIncomingCallFragment);
+
+ mShowIncomingCall.setValue(false);
+ setIntent(null);
+ } else {
+ ft.show(mIncomingCallFragment)
+ .hide(mOngoingCallFragment)
+ .hide(mOngoingConfCallFragment);
+ }
+
+ ft.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out).commit();
}
private void handleIntent() {
@@ -107,24 +135,4 @@ public class InCallActivity extends FragmentActivity {
mShowIncomingCall.setValue(false);
}
}
-
- private void updateIncomingCallVisibility(Call incomingCall) {
- if (incomingCall == null) {
- getSupportFragmentManager()
- .beginTransaction()
- .show(mOngoingCallFragment)
- .hide(mIncomingCallFragment)
- .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
- .commit();
- mShowIncomingCall.setValue(false);
- setIntent(null);
- } else {
- getSupportFragmentManager()
- .beginTransaction()
- .show(mIncomingCallFragment)
- .hide(mOngoingCallFragment)
- .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
- .commit();
- }
- }
}
diff --git a/src/com/android/car/dialer/ui/activecall/InCallFragment.java b/src/com/android/car/dialer/ui/activecall/InCallFragment.java
index 0f74255d..42408584 100644
--- a/src/com/android/car/dialer/ui/activecall/InCallFragment.java
+++ b/src/com/android/car/dialer/ui/activecall/InCallFragment.java
@@ -164,9 +164,8 @@ public abstract class InCallFragment extends Fragment {
mUserProfileCallStateText.start();
} else {
mUserProfileCallStateText.stop();
- mUserProfileCallStateText.setText(
- TelecomUtils.callStateToUiString(getContext(),
- callStateAndConnectTime.first));
+ mUserProfileCallStateText.setText(TelecomUtils.callStateToUiString(getContext(),
+ callStateAndConnectTime.first));
}
}
diff --git a/src/com/android/car/dialer/ui/activecall/InCallViewModel.java b/src/com/android/car/dialer/ui/activecall/InCallViewModel.java
index 39210fa2..57bb39e9 100644
--- a/src/com/android/car/dialer/ui/activecall/InCallViewModel.java
+++ b/src/com/android/car/dialer/ui/activecall/InCallViewModel.java
@@ -59,20 +59,24 @@ public class InCallViewModel extends AndroidViewModel implements
private final MutableLiveData<List<Call>> mCallListLiveData;
private final MutableLiveData<List<Call>> mOngoingCallListLiveData;
+ private final MutableLiveData<List<Call>> mConferenceCallListLiveData;
+ private final LiveData<List<CallDetail>> mConferenceCallDetailListLiveData;
private final Comparator<Call> mCallComparator;
private final MutableLiveData<Call> mIncomingCallLiveData;
- private final LiveData<CallDetail> mCallDetailLiveData;
+ private final CallDetailLiveData mCallDetailLiveData;
private final LiveData<Integer> mCallStateLiveData;
private final LiveData<Call> mPrimaryCallLiveData;
private final LiveData<Call> mSecondaryCallLiveData;
- private final LiveData<CallDetail> mSecondaryCallDetailLiveData;
+ private final CallDetailLiveData mSecondaryCallDetailLiveData;
+ private final LiveData<Pair<Call, Call>> mOngoingCallPairLiveData;
private final LiveData<Integer> mAudioRouteLiveData;
private MutableLiveData<CallAudioState> mCallAudioStateLiveData;
private final MutableLiveData<Boolean> mDialpadIsOpen;
private final ShowOnholdCallLiveData mShowOnholdCall;
private LiveData<Long> mCallConnectTimeLiveData;
+ private LiveData<Long> mSecondaryCallConnectTimeLiveData;
private LiveData<Pair<Integer, Long>> mCallStateAndConnectTimeLiveData;
private final Context mContext;
@@ -109,12 +113,25 @@ public class InCallViewModel extends AndroidViewModel implements
// Sets value to trigger incoming call and active call list to update.
mCallListLiveData.setValue(mCallListLiveData.getValue());
}
+
+ @Override
+ public void onParentChanged(Call call, Call parent) {
+ L.d(TAG, "onParentChanged %s", call);
+ updateCallList();
+ }
+
+ @Override
+ public void onChildrenChanged(Call call, List<Call> children) {
+ L.d(TAG, "onChildrenChanged %s", call);
+ updateCallList();
+ }
};
public InCallViewModel(@NonNull Application application) {
super(application);
mContext = application.getApplicationContext();
+ mConferenceCallListLiveData = new MutableLiveData<>();
mIncomingCallLiveData = new MutableLiveData<>();
mOngoingCallListLiveData = new MutableLiveData<>();
mCallAudioStateLiveData = new MutableLiveData<>();
@@ -126,16 +143,37 @@ public class InCallViewModel extends AndroidViewModel implements
List<Call> activeCallList = filter(callList,
call -> call != null && call.getState() != Call.STATE_RINGING);
activeCallList.sort(mCallComparator);
- mOngoingCallListLiveData.setValue(activeCallList);
+ List<Call> conferenceList = filter(activeCallList,
+ call -> call.getParent() != null);
+ List<Call> ongoingCallList = filter(activeCallList,
+ call -> call.getParent() == null);
+ mConferenceCallListLiveData.setValue(conferenceList);
+ mOngoingCallListLiveData.setValue(ongoingCallList);
mIncomingCallLiveData.setValue(firstMatch(callList,
call -> call != null && call.getState() == Call.STATE_RINGING));
+
+ L.d(TAG, "size:" + activeCallList.size() + " activeList" + activeCallList);
+ L.d(TAG, "conf:%s" + conferenceList, conferenceList.size());
+ L.d(TAG, "ongoing:%s" + ongoingCallList, ongoingCallList.size());
}
};
- mPrimaryCallLiveData = Transformations.map(mOngoingCallListLiveData,
- input -> input.isEmpty() ? null : input.get(0));
- mCallDetailLiveData = Transformations.switchMap(mPrimaryCallLiveData,
- input -> input != null ? new CallDetailLiveData(input) : null);
+ mConferenceCallDetailListLiveData = Transformations.map(mConferenceCallListLiveData,
+ callList -> {
+ List<CallDetail> detailList = new ArrayList<>();
+ for (Call call : callList) {
+ detailList.add(CallDetail.fromTelecomCallDetail(call.getDetails()));
+ }
+ return detailList;
+ });
+
+ mCallDetailLiveData = new CallDetailLiveData();
+ mPrimaryCallLiveData = Transformations.map(mOngoingCallListLiveData, input -> {
+ Call call = input.isEmpty() ? null : input.get(0);
+ mCallDetailLiveData.setTelecomCall(call);
+ return call;
+ });
+
mCallStateLiveData = Transformations.switchMap(mPrimaryCallLiveData,
input -> input != null ? new CallStateLiveData(input) : null);
mCallConnectTimeLiveData = Transformations.map(mCallDetailLiveData, (details) -> {
@@ -147,11 +185,23 @@ public class InCallViewModel extends AndroidViewModel implements
mCallStateAndConnectTimeLiveData =
LiveDataFunctions.pair(mCallStateLiveData, mCallConnectTimeLiveData);
- mSecondaryCallLiveData = Transformations.map(mOngoingCallListLiveData,
- callList -> (callList != null && callList.size() > 1) ? callList.get(1) : null);
+ mSecondaryCallDetailLiveData = new CallDetailLiveData();
+ mSecondaryCallLiveData = Transformations.map(mOngoingCallListLiveData, callList -> {
+ Call call = (callList != null && callList.size() > 1) ? callList.get(1) : null;
+ mSecondaryCallDetailLiveData.setTelecomCall(call);
+ return call;
+ });
+
+ mSecondaryCallConnectTimeLiveData = Transformations.map(mSecondaryCallDetailLiveData,
+ details -> {
+ if (details == null) {
+ return 0L;
+ }
+ return details.getConnectTimeMillis();
+ });
- mSecondaryCallDetailLiveData = Transformations.switchMap(mSecondaryCallLiveData,
- input -> input != null ? new CallDetailLiveData(input) : null);
+ mOngoingCallPairLiveData = LiveDataFunctions.pair(mPrimaryCallLiveData,
+ mSecondaryCallLiveData);
mAudioRouteLiveData = new AudioRouteLiveData(mContext);
@@ -166,6 +216,22 @@ public class InCallViewModel extends AndroidViewModel implements
mContext.bindService(intent, mInCallServiceConnection, Context.BIND_AUTO_CREATE);
}
+ /** Merge primary and secondary calls into a conference */
+ public void mergeConference() {
+ Call call = mPrimaryCallLiveData.getValue();
+ Call otherCall = mSecondaryCallLiveData.getValue();
+
+ if (call == null || otherCall == null) {
+ return;
+ }
+ call.conference(otherCall);
+ }
+
+ /** Returns the live data which monitors conference calls */
+ public LiveData<List<CallDetail>> getConferenceCallDetailList() {
+ return mConferenceCallDetailListLiveData;
+ }
+
/** Returns the live data which monitors all the calls. */
public LiveData<List<Call>> getAllCallList() {
return mCallListLiveData;
@@ -229,6 +295,20 @@ public class InCallViewModel extends AndroidViewModel implements
}
/**
+ * Returns the live data which monitors the secondary call connect time.
+ */
+ public LiveData<Long> getSecondaryCallConnectTime() {
+ return mSecondaryCallConnectTimeLiveData;
+ }
+
+ /**
+ * Returns the live data that monitors the primary and secondary calls.
+ */
+ public LiveData<Pair<Call, Call>> getOngoingCallPair() {
+ return mOngoingCallPairLiveData;
+ }
+
+ /**
* Returns current audio route.
*/
public LiveData<Integer> getAudioRoute() {
diff --git a/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragment.java b/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragment.java
index 19fa3c94..f5350f08 100644
--- a/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragment.java
+++ b/src/com/android/car/dialer/ui/activecall/OnGoingCallControllerBarFragment.java
@@ -33,6 +33,7 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
+import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -54,7 +55,7 @@ import java.util.List;
/** A Fragment of the bar which controls on going call. */
public class OnGoingCallControllerBarFragment extends Fragment {
- private static String TAG = "CDialer.OngoingCallCtlFrg";
+ private static final String TAG = "CD.OngoingCallCtlFrg";
private static final ImmutableMap<Integer, AudioRouteInfo> AUDIO_ROUTES =
ImmutableMap.<Integer, AudioRouteInfo>builder()
@@ -76,6 +77,8 @@ public class OnGoingCallControllerBarFragment extends Fragment {
R.drawable.ic_audio_route_speaker_activatable))
.build();
+ private InCallViewModel mInCallViewModel;
+
private AlertDialog mAudioRouteSelectionDialog;
private List<CarUiListItem> mAudioRouteListItems;
private List<Integer> mAvailableRoutes;
@@ -84,8 +87,11 @@ public class OnGoingCallControllerBarFragment extends Fragment {
private View mAudioRouteView;
private ImageView mAudioRouteButton;
private TextView mAudioRouteText;
+ private View mMergeButton;
private View mPauseButton;
private LiveData<Call> mPrimaryCallLiveData;
+ private LiveData<List<Call>> mOngoingCallListLiveData;
+ private LiveData<Pair<Call, Call>> mOngoingCallPairLiveData;
private MutableLiveData<Boolean> mDialpadState;
private LiveData<List<Call>> mCallListLiveData;
private int mPrimaryCallState;
@@ -135,18 +141,31 @@ public class OnGoingCallControllerBarFragment extends Fragment {
mAudioRouteSelectionDialog = audioRouteSelectionDialogBuilder.create();
- InCallViewModel inCallViewModel = ViewModelProviders.of(getActivity()).get(
- InCallViewModel.class);
+ mInCallViewModel = ViewModelProviders.of(getActivity()).get(InCallViewModel.class);
- inCallViewModel.getPrimaryCallState().observe(this, this::setCallState);
- mPrimaryCallLiveData = inCallViewModel.getPrimaryCall();
- inCallViewModel.getAudioRoute().observe(this, this::updateViewBasedOnAudioRoute);
+ mInCallViewModel.getPrimaryCallState().observe(this, this::setCallState);
+ mPrimaryCallLiveData = mInCallViewModel.getPrimaryCall();
+ mOngoingCallPairLiveData = mInCallViewModel.getOngoingCallPair();
- mDialpadState = inCallViewModel.getDialpadOpenState();
- mCallAudioState = inCallViewModel.getCallAudioState();
+ mOngoingCallListLiveData = mInCallViewModel.getOngoingCallList();
+ mInCallViewModel.getAudioRoute().observe(this, this::updateViewBasedOnAudioRoute);
+ mDialpadState = mInCallViewModel.getDialpadOpenState();
+ mCallAudioState = mInCallViewModel.getCallAudioState();
- mCallListLiveData = inCallViewModel.getAllCallList();
+ mCallListLiveData = mInCallViewModel.getAllCallList();
mCallListLiveData.observe(this, v -> updatePauseButtonEnabledState());
+
+ mOngoingCallPairLiveData.observe(this, pair -> {
+ boolean isPrimaryCallConference = pair.first != null
+ && pair.first.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE);
+ if (!isPrimaryCallConference && pair.second != null) {
+ mPauseButton.setVisibility(View.GONE);
+ mMergeButton.setVisibility(View.VISIBLE);
+ } else {
+ mPauseButton.setVisibility(View.VISIBLE);
+ mMergeButton.setVisibility(View.GONE);
+ }
+ });
}
@Nullable
@@ -190,6 +209,11 @@ public class OnGoingCallControllerBarFragment extends Fragment {
mAudioRouteSelectionDialog.setOnDismissListener(
(dialog) -> mAudioRouteView.setActivated(false));
+ mMergeButton = fragmentView.findViewById(R.id.merge_button);
+ mMergeButton.setOnClickListener((v) -> {
+ mInCallViewModel.mergeConference();
+ });
+
mPauseButton = fragmentView.findViewById(R.id.pause_button);
mPauseButton.setOnClickListener((v) -> {
if (mPrimaryCallState == Call.STATE_ACTIVE) {
@@ -200,6 +224,7 @@ public class OnGoingCallControllerBarFragment extends Fragment {
L.i(TAG, "Pause button is clicked while call in %s state", mPrimaryCallState);
}
});
+
updatePauseButtonEnabledState();
return fragmentView;
@@ -242,8 +267,8 @@ public class OnGoingCallControllerBarFragment extends Fragment {
}
private void updatePauseButtonEnabledState() {
- boolean hasOnlyOneCall = mCallListLiveData.getValue() != null
- && mCallListLiveData.getValue().size() == 1;
+ boolean hasOnlyOneCall = mOngoingCallListLiveData.getValue() != null
+ && mOngoingCallListLiveData.getValue().size() == 1;
boolean shouldEnablePauseButton = hasOnlyOneCall && (mPrimaryCallState == Call.STATE_HOLDING
|| mPrimaryCallState == Call.STATE_ACTIVE);
diff --git a/src/com/android/car/dialer/ui/activecall/OnHoldCallUserProfileFragment.java b/src/com/android/car/dialer/ui/activecall/OnHoldCallUserProfileFragment.java
index d86f804e..673cf260 100644
--- a/src/com/android/car/dialer/ui/activecall/OnHoldCallUserProfileFragment.java
+++ b/src/com/android/car/dialer/ui/activecall/OnHoldCallUserProfileFragment.java
@@ -17,10 +17,12 @@
package com.android.car.dialer.ui.activecall;
import android.os.Bundle;
+import android.os.SystemClock;
import android.telecom.Call;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Chronometer;
import android.widget.ImageView;
import android.widget.TextView;
@@ -47,9 +49,9 @@ public class OnHoldCallUserProfileFragment extends Fragment {
private ImageView mAvatarView;
private View mSwapCallsView;
private LiveData<Call> mPrimaryCallLiveData;
- private LiveData<Call> mSecondaryCallLiveData;
private CompletableFuture<Void> mPhoneNumberInfoFuture;
private LetterTileDrawable mDefaultAvatar;
+ private Chronometer mTimeTextView;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -74,23 +76,43 @@ public class OnHoldCallUserProfileFragment extends Fragment {
InCallViewModel.class);
inCallViewModel.getSecondaryCallDetail().observe(this, this::updateProfile);
mPrimaryCallLiveData = inCallViewModel.getPrimaryCall();
- mSecondaryCallLiveData = inCallViewModel.getSecondaryCall();
+
+ mTimeTextView = fragmentView.findViewById(R.id.time);
+ inCallViewModel.getSecondaryCallConnectTime().observe(this, this::updateConnectTime);
return fragmentView;
}
+ /** Presents the onhold call duration. */
+ protected void updateConnectTime(Long connectTime) {
+ if (connectTime == null) {
+ mTimeTextView.stop();
+ mTimeTextView.setText("");
+ return;
+ }
+ mTimeTextView.setBase(connectTime
+ - System.currentTimeMillis() + SystemClock.elapsedRealtime());
+ mTimeTextView.start();
+ }
+
private void updateProfile(@Nullable CallDetail callDetail) {
if (callDetail == null) {
return;
}
+ mAvatarView.setImageDrawable(mDefaultAvatar);
+
if (mPhoneNumberInfoFuture != null) {
mPhoneNumberInfoFuture.cancel(true);
}
+ if (callDetail.isConference()) {
+ mTitle.setText(getString(R.string.ongoing_conf_title));
+ return;
+ }
+
String number = callDetail.getNumber();
mTitle.setText(TelecomUtils.getFormattedNumber(getContext(), number));
- mAvatarView.setImageDrawable(mDefaultAvatar);
mPhoneNumberInfoFuture = TelecomUtils.getPhoneNumberInfo(getContext(), number)
.thenAcceptAsync((info) -> {
diff --git a/src/com/android/car/dialer/ui/activecall/OngoingCallFragment.java b/src/com/android/car/dialer/ui/activecall/OngoingCallFragment.java
index c0ffaf2d..6318eb5c 100644
--- a/src/com/android/car/dialer/ui/activecall/OngoingCallFragment.java
+++ b/src/com/android/car/dialer/ui/activecall/OngoingCallFragment.java
@@ -58,7 +58,6 @@ public class OngoingCallFragment extends InCallFragment {
inCallViewModel.getCallStateAndConnectTime().observe(this, this::updateCallDescription);
mDialpadState = inCallViewModel.getDialpadOpenState();
- mDialpadState.setValue(savedInstanceState == null ? false : !mDialpadFragment.isHidden());
mDialpadState.observe(this, isDialpadOpen -> {
if (isDialpadOpen) {
onOpenDialpad();
diff --git a/src/com/android/car/dialer/ui/activecall/OngoingConfCallFragment.java b/src/com/android/car/dialer/ui/activecall/OngoingConfCallFragment.java
new file mode 100644
index 00000000..663449fc
--- /dev/null
+++ b/src/com/android/car/dialer/ui/activecall/OngoingConfCallFragment.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.dialer.ui.activecall;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.telecom.Call;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Chronometer;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.util.Pair;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModelProviders;
+
+import com.android.car.dialer.R;
+import com.android.car.telephony.common.TelecomUtils;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+
+/**
+ * A fragment that displays information about an on-going call with options to hang up.
+ */
+public class OngoingConfCallFragment extends Fragment {
+ private static final String TAG = "CD.OngoingConfCallFrag";
+
+ private Fragment mDialpadFragment;
+ private Fragment mOnholdCallFragment;
+ private View mConferenceCallProfilesView;
+ private MutableLiveData<Boolean> mDialpadState;
+ private CarUiRecyclerView mRecyclerView;
+ private Chronometer mConferenceTimeTextView;
+ private TextView mConferenceTitle;
+
+ private ConferenceProfileAdapter mConfProfileAdapter;
+
+ private String mConferenceTitleString;
+ private String mConfStrTitleFormat;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mConferenceTitleString = getString(R.string.ongoing_conf_title);
+ mConfStrTitleFormat = getString(R.string.ongoing_conf_title_format);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View fragmentView = inflater.inflate(R.layout.ongoing_conf_call_fragment,
+ container, false);
+
+ mOnholdCallFragment = getChildFragmentManager().findFragmentById(R.id.onhold_user_profile);
+ mDialpadFragment = getChildFragmentManager().findFragmentById(R.id.incall_dialpad_fragment);
+ mConferenceCallProfilesView = fragmentView.findViewById(R.id.conference_profiles);
+ mRecyclerView = mConferenceCallProfilesView.findViewById(R.id.recycler_view);
+ mConferenceTimeTextView = mConferenceCallProfilesView.findViewById(R.id.call_duration);
+ mConferenceTitle = mConferenceCallProfilesView.findViewById(R.id.conference_title);
+
+ if (mConfProfileAdapter == null) {
+ mConfProfileAdapter = new ConferenceProfileAdapter(getContext());
+ }
+ mRecyclerView.setAdapter(mConfProfileAdapter);
+
+ InCallViewModel inCallViewModel = ViewModelProviders.of(getActivity()).get(
+ InCallViewModel.class);
+
+ inCallViewModel.getCallStateAndConnectTime().observe(this,
+ this::updateCallDescription);
+
+ inCallViewModel.getConferenceCallDetailList().observe(this, list -> {
+ mConfProfileAdapter.setConferenceList(list);
+ updateTitle(list.size());
+ });
+
+ mDialpadState = inCallViewModel.getDialpadOpenState();
+ mDialpadState.observe(this, isDialpadOpen -> {
+ if (isDialpadOpen) {
+ onOpenDialpad();
+ } else {
+ onCloseDialpad();
+ }
+ });
+
+ inCallViewModel.shouldShowOnholdCall().observe(this,
+ this::updateOnholdCallFragmentVisibility);
+
+ return fragmentView;
+ }
+
+ @VisibleForTesting
+ void onOpenDialpad() {
+ getChildFragmentManager().beginTransaction()
+ .show(mDialpadFragment)
+ .commit();
+ mConferenceCallProfilesView.setVisibility(View.GONE);
+ }
+
+ @VisibleForTesting
+ void onCloseDialpad() {
+ getChildFragmentManager().beginTransaction()
+ .hide(mDialpadFragment)
+ .commit();
+ mConferenceCallProfilesView.setVisibility(View.VISIBLE);
+ }
+
+ private void updateTitle(int numParticipants) {
+ String title = String.format(mConfStrTitleFormat, mConferenceTitleString, numParticipants);
+ mConferenceTitle.setText(title);
+ }
+
+ /** Presents the call state and call duration. */
+ private void updateCallDescription(@Nullable Pair<Integer, Long> callStateAndConnectTime) {
+ if (callStateAndConnectTime == null || callStateAndConnectTime.first == null) {
+ mConferenceTimeTextView.stop();
+ mConferenceTimeTextView.setText("");
+ return;
+ }
+ if (callStateAndConnectTime.first == Call.STATE_ACTIVE) {
+ mConferenceTimeTextView.setBase(callStateAndConnectTime.second
+ - System.currentTimeMillis() + SystemClock.elapsedRealtime());
+ mConferenceTimeTextView.start();
+ } else {
+ mConferenceTimeTextView.stop();
+ mConferenceTimeTextView.setText(TelecomUtils.callStateToUiString(getContext(),
+ callStateAndConnectTime.first));
+ }
+ }
+
+ private void updateOnholdCallFragmentVisibility(Boolean showOnholdCall) {
+ if (showOnholdCall) {
+ getChildFragmentManager().beginTransaction().show(mOnholdCallFragment).commit();
+ } else {
+ getChildFragmentManager().beginTransaction().hide(mOnholdCallFragment).commit();
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/car/dialer/livedata/CallDetailLiveDataTest.java b/tests/robotests/src/com/android/car/dialer/livedata/CallDetailLiveDataTest.java
index 3e3ad7d0..e29251c4 100644
--- a/tests/robotests/src/com/android/car/dialer/livedata/CallDetailLiveDataTest.java
+++ b/tests/robotests/src/com/android/car/dialer/livedata/CallDetailLiveDataTest.java
@@ -66,7 +66,8 @@ public class CallDetailLiveDataTest {
doNothing().when(mMockCall).registerCallback(mCallbackCaptor.capture());
- mCallDetailLiveData = new CallDetailLiveData(mMockCall);
+ mCallDetailLiveData = new CallDetailLiveData();
+ mCallDetailLiveData.setTelecomCall(mMockCall);
mLifecycleRegistry = new LifecycleRegistry(mMockLifecycleOwner);
when(mMockLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
}