summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2018-05-24 07:22:16 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2018-05-24 07:22:16 +0000
commitcf8666e3fcd21a9f92688e95ab350a4381f2b753 (patch)
treef72ccaa549b4ab5d1c1813e48713c14b4581c1aa
parent31318b64a09166f88358be549a566933d3e0ec80 (diff)
parent18a231df49285f5c5ddf330f70436a33a89eb807 (diff)
downloadDialer-cf8666e3fcd21a9f92688e95ab350a4381f2b753.tar.gz
Snap for 4801384 from 18a231df49285f5c5ddf330f70436a33a89eb807 to pi-release
Change-Id: I1558c73f57a18897b29e4e13af4f94875b310094
-rw-r--r--AndroidManifest.xml1
-rw-r--r--res/drawable/ic_arrow_down.xml (renamed from res/layout/ic_arrow_back.xml)10
-rw-r--r--res/drawable/ic_smartphone.xml24
-rw-r--r--res/drawable/ic_speaker_phone.xml24
-rw-r--r--res/layout/audio_route_list_item.xml47
-rw-r--r--res/layout/audio_route_switch_dialog.xml (renamed from res/layout/call_log_last_call_item_card.xml)20
-rw-r--r--res/layout/on_going_call_controller_bar_fragment.xml12
-rw-r--r--res/layout/ringing_call_controller_bar_fragment.xml49
-rw-r--r--res/values/strings.xml13
-rw-r--r--src/com/android/car/dialer/StrequentsAdapter.java65
-rw-r--r--src/com/android/car/dialer/StrequentsFragment.java2
-rw-r--r--src/com/android/car/dialer/StrequentsItemAnimator.java637
-rw-r--r--src/com/android/car/dialer/log/L.java13
-rw-r--r--src/com/android/car/dialer/telecom/UiCallManager.java86
-rw-r--r--src/com/android/car/dialer/ui/InCallFragment.java6
-rw-r--r--src/com/android/car/dialer/ui/OnGoingCallControllerBarFragment.java131
-rw-r--r--src/com/android/car/dialer/ui/RingingCallControllerBarFragment.java6
17 files changed, 451 insertions, 695 deletions
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/layout/ic_arrow_back.xml b/res/drawable/ic_arrow_down.xml
index 0283b490..60f49aa6 100644
--- a/res/layout/ic_arrow_back.xml
+++ b/res/drawable/ic_arrow_down.xml
@@ -14,11 +14,11 @@ 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="44dp"
- android:height="44dp">
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
<path
- android:pathData="M40 22L15.66 22 26.83 10.83 24 8 8 24 24 40 26.83 37.17 15.66 26 40 26 40 22Z"
+ android:pathData="M7 10l5 5 5 -5z"
android:fillColor="#000000" />
</vector> \ No newline at end of file
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/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/call_log_last_call_item_card.xml b/res/layout/audio_route_switch_dialog.xml
index f836fff0..6f89ce76 100644
--- a/res/layout/call_log_last_call_item_card.xml
+++ b/res/layout/audio_route_switch_dialog.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
@@ -16,14 +16,18 @@
<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="@dimen/call_log_item_height"
+ android:layout_height="match_parent"
app:cardBackgroundColor="@color/phone_theme"
- app:cardCornerRadius="@dimen/favorite_card_corner_radius"
+ app:contentPadding="@dimen/car_keyline_1"
+ app:cardCornerRadius="@dimen/car_radius_3"
app:cardElevation="@dimen/car_action_bar_elevation">
- <include layout="@layout/call_log_list_item_card_base"/>
-</android.support.v7.widget.CardView>
+ <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/on_going_call_controller_bar_fragment.xml b/res/layout/on_going_call_controller_bar_fragment.xml
index c457b75b..62a430be 100644
--- a/res/layout/on_going_call_controller_bar_fragment.xml
+++ b/res/layout/on_going_call_controller_bar_fragment.xml
@@ -74,6 +74,18 @@ limitations under the License.
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"
diff --git a/res/layout/ringing_call_controller_bar_fragment.xml b/res/layout/ringing_call_controller_bar_fragment.xml
index 2dcced83..82789807 100644
--- a/res/layout/ringing_call_controller_bar_fragment.xml
+++ b/res/layout/ringing_call_controller_bar_fragment.xml
@@ -23,27 +23,58 @@ limitations under the License.
<ImageView
android:id="@+id/answer_call_button"
- android:layout_width="@dimen/fab_button_size"
- android:layout_height="@dimen/fab_button_size"
+ 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_theme_light"
+ 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/end_call_button"
+ 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/fab_button_size"
- android:layout_height="@dimen/fab_button_size"
- android:layout_marginStart="@dimen/car_padding_6"
+ 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_theme_light"
+ android:tint="@color/phone_end_call"
+ app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@+id/answer_call_button"
+ 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/values/strings.xml b/res/values/strings.xml
index 63a84655..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,6 +71,14 @@
<!-- 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] -->
diff --git a/src/com/android/car/dialer/StrequentsAdapter.java b/src/com/android/car/dialer/StrequentsAdapter.java
index 8d14019a..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;
@@ -44,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>
@@ -54,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;
@@ -85,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();
}
@@ -139,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.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())
@@ -236,22 +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);
+
+ // 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());
}
/**
@@ -395,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 6e37c38c..7bd8d4a5 100644
--- a/src/com/android/car/dialer/StrequentsFragment.java
+++ b/src/com/android/car/dialer/StrequentsFragment.java
@@ -141,7 +141,7 @@ public class StrequentsFragment extends Fragment {
Log.d(TAG, "setItemAnimator");
}
- mListView.getRecyclerView().setItemAnimator(new StrequentsItemAnimator());
+ mListView.getRecyclerView().setItemAnimator(null);
return view;
}
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/log/L.java b/src/com/android/car/dialer/log/L.java
index f1322a4e..f85a51aa 100644
--- a/src/com/android/car/dialer/log/L.java
+++ b/src/com/android/car/dialer/log/L.java
@@ -23,6 +23,7 @@ import android.util.Log;
public class L {
private String mTag;
+
public L(String tag) {
mTag = tag;
}
@@ -39,6 +40,10 @@ public class L {
}
}
+ public void w(String msg) {
+ Log.w(mTag, msg);
+ }
+
public static L logger(String tag) {
return new L(tag);
}
@@ -54,4 +59,12 @@ public class L {
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/UiCallManager.java b/src/com/android/car/dialer/telecom/UiCallManager.java
index 63be020a..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,9 +30,11 @@ 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;
@@ -52,6 +59,8 @@ 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<>();
@@ -79,6 +88,7 @@ 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<>();
@@ -122,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() {
@@ -276,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;
@@ -285,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) {
diff --git a/src/com/android/car/dialer/ui/InCallFragment.java b/src/com/android/car/dialer/ui/InCallFragment.java
index 2ad04ce2..ff0e2ff9 100644
--- a/src/com/android/car/dialer/ui/InCallFragment.java
+++ b/src/com/android/car/dialer/ui/InCallFragment.java
@@ -72,6 +72,12 @@ public class InCallFragment extends Fragment implements
}
@Override
+ public void onPause() {
+ super.onPause();
+ mHandler.removeCallbacks(mUpdateDurationRunnable);
+ }
+
+ @Override
public void onOpenDialpad() {
mDialerFragment = new DialerFragment();
getChildFragmentManager().beginTransaction()
diff --git a/src/com/android/car/dialer/ui/OnGoingCallControllerBarFragment.java b/src/com/android/car/dialer/ui/OnGoingCallControllerBarFragment.java
index 0caa15a3..7eeedae2 100644
--- a/src/com/android/car/dialer/ui/OnGoingCallControllerBarFragment.java
+++ b/src/com/android/car/dialer/ui/OnGoingCallControllerBarFragment.java
@@ -15,25 +15,39 @@
*/
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();
@@ -60,6 +74,19 @@ public class OnGoingCallControllerBarFragment extends Fragment {
} else if (getHost() instanceof OnGoingCallControllerBarCallback) {
mOnGoingCallControllerBarCallback = (OnGoingCallControllerBarCallback) getHost();
}
+
+ View dialogView = LayoutInflater.from(getContext()).inflate(
+ R.layout.audio_route_switch_dialog, null, false);
+ PagedListView list = dialogView.findViewById(R.id.list);
+ List<Integer> availableRoutes = UiCallManager.get().getSupportedAudioRoute();
+ list.setDividerVisibilityManager(position -> position == (availableRoutes.size() - 1));
+
+ mAudioRouteSelectionDialog = new AlertDialog.Builder(getContext())
+ .setView(dialogView)
+ .create();
+ mAudioRouteSelectionDialog.getWindow().setBackgroundDrawableResource(
+ android.R.color.transpare‌​nt);
+ list.setAdapter(new AudioRouteListAdapter(getContext(), availableRoutes));
}
@Nullable
@@ -104,8 +131,17 @@ public class OnGoingCallControllerBarFragment extends Fragment {
}
onEndCall();
});
- fragmentView.findViewById(R.id.voice_channel_button).setOnClickListener((v) -> {
- });
+
+
+ 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) {
@@ -122,6 +158,14 @@ public class OnGoingCallControllerBarFragment extends Fragment {
return fragmentView;
}
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mAudioRouteSelectionDialog.isShowing()) {
+ mAudioRouteSelectionDialog.dismiss();
+ }
+ }
+
private void onMuteMic() {
UiCallManager.get().setMuted(true);
}
@@ -142,8 +186,10 @@ public class OnGoingCallControllerBarFragment extends Fragment {
uiCallManager.unholdCall(primaryCall);
}
- private void onVoiceOutputChannelChanged(int channel) {
- // TODO: implement this function.
+ private void onVoiceOutputChannelChanged(@CallAudioRoute int audioRoute) {
+ UiCallManager.get().setAudioRoute(audioRoute);
+ mAudioRouteSelectionDialog.dismiss();
+ mAudioRouteButton.setImageResource(getAudioRouteIconRes(audioRoute));
}
private void onEndCall() {
@@ -151,4 +197,81 @@ public class OnGoingCallControllerBarFragment extends Fragment {
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
index bedfe87a..d97368c2 100644
--- a/src/com/android/car/dialer/ui/RingingCallControllerBarFragment.java
+++ b/src/com/android/car/dialer/ui/RingingCallControllerBarFragment.java
@@ -28,9 +28,6 @@ public class RingingCallControllerBarFragment extends Fragment {
container, false);
ImageView endCallButton = fragmentView.findViewById(R.id.end_call_button);
- FabDrawable endCallDrawable = new FabDrawable(getContext());
- endCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_end_call));
- endCallButton.setBackground(endCallDrawable);
endCallButton.setOnClickListener((v) -> {
UiCallManager uiCallManager = UiCallManager.get();
UiCall primaryCall = uiCallManager.getPrimaryCall();
@@ -38,9 +35,6 @@ public class RingingCallControllerBarFragment extends Fragment {
});
ImageView answerCallButton = fragmentView.findViewById(R.id.answer_call_button);
- FabDrawable answerCallDrawable = new FabDrawable(getContext());
- answerCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_call));
- answerCallButton.setBackground(answerCallDrawable);
answerCallButton.setOnClickListener((v) -> {
UiCallManager uiCallManager = UiCallManager.get();
UiCall primaryCall = uiCallManager.getPrimaryCall();