diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2018-05-24 07:22:16 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2018-05-24 07:22:16 +0000 |
commit | cf8666e3fcd21a9f92688e95ab350a4381f2b753 (patch) | |
tree | f72ccaa549b4ab5d1c1813e48713c14b4581c1aa | |
parent | 31318b64a09166f88358be549a566933d3e0ec80 (diff) | |
parent | 18a231df49285f5c5ddf330f70436a33a89eb807 (diff) | |
download | Dialer-cf8666e3fcd21a9f92688e95ab350a4381f2b753.tar.gz |
Snap for 4801384 from 18a231df49285f5c5ddf330f70436a33a89eb807 to pi-release
Change-Id: I1558c73f57a18897b29e4e13af4f94875b310094
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.transparent); + 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(); |