• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.dialer;
17 
18 import android.content.ContentResolver;
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Color;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.support.v4.app.Fragment;
25 import android.telecom.Call;
26 import android.telecom.CallAudioState;
27 import android.text.TextUtils;
28 import android.text.format.DateUtils;
29 import android.util.Log;
30 import android.util.SparseArray;
31 import android.view.KeyEvent;
32 import android.view.LayoutInflater;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.animation.AccelerateDecelerateInterpolator;
37 import android.view.animation.Animation;
38 import android.view.animation.Transformation;
39 import android.widget.ImageButton;
40 import android.widget.ImageView;
41 import android.widget.LinearLayout;
42 import android.widget.TextView;
43 
44 import com.android.car.apps.common.CircleBitmapDrawable;
45 import com.android.car.apps.common.FabDrawable;
46 import com.android.car.dialer.telecom.TelecomUtils;
47 import com.android.car.dialer.telecom.UiCall;
48 import com.android.car.dialer.telecom.UiCallManager;
49 import com.android.car.dialer.telecom.UiCallManager.CallListener;
50 
51 import java.util.Arrays;
52 import java.util.List;
53 import java.util.Objects;
54 
55 /**
56  * A fragment that displays information about an on-going call with options to hang up.
57  */
58 public class OngoingCallFragment extends Fragment {
59     private static final String TAG = "OngoingCall";
60     private static final SparseArray<Character> mDialpadButtonMap = new SparseArray<>();
61 
62     static {
mDialpadButtonMap.put(R.id.one, '1')63         mDialpadButtonMap.put(R.id.one, '1');
mDialpadButtonMap.put(R.id.two, '2')64         mDialpadButtonMap.put(R.id.two, '2');
mDialpadButtonMap.put(R.id.three, '3')65         mDialpadButtonMap.put(R.id.three, '3');
mDialpadButtonMap.put(R.id.four, '4')66         mDialpadButtonMap.put(R.id.four, '4');
mDialpadButtonMap.put(R.id.five, '5')67         mDialpadButtonMap.put(R.id.five, '5');
mDialpadButtonMap.put(R.id.six, '6')68         mDialpadButtonMap.put(R.id.six, '6');
mDialpadButtonMap.put(R.id.seven, '7')69         mDialpadButtonMap.put(R.id.seven, '7');
mDialpadButtonMap.put(R.id.eight, '8')70         mDialpadButtonMap.put(R.id.eight, '8');
mDialpadButtonMap.put(R.id.nine, '9')71         mDialpadButtonMap.put(R.id.nine, '9');
mDialpadButtonMap.put(R.id.zero, '0')72         mDialpadButtonMap.put(R.id.zero, '0');
mDialpadButtonMap.put(R.id.star, '*')73         mDialpadButtonMap.put(R.id.star, '*');
mDialpadButtonMap.put(R.id.pound, '#')74         mDialpadButtonMap.put(R.id.pound, '#');
75     }
76 
77     private final Handler mHandler = new Handler();
78 
79     private UiCall mLastRemovedCall;
80     private UiCallManager mUiCallManager;
81     private View mRingingCallControls;
82     private View mActiveCallControls;
83     private ImageButton mEndCallButton;
84     private ImageButton mUnholdCallButton;
85     private ImageButton mMuteButton;
86     private ImageButton mToggleDialpadButton;
87     private ImageButton mSwapButton;
88     private ImageButton mMergeButton;
89     private ImageButton mAnswerCallButton;
90     private ImageButton mRejectCallButton;
91     private TextView mNameTextView;
92     private TextView mSecondaryNameTextView;
93     private TextView mStateTextView;
94     private TextView mSecondaryStateTextView;
95     private ImageView mLargeContactPhotoView;
96     private ImageView mSmallContactPhotoView;
97     private View mDialpadContainer;
98     private View mSecondaryCallContainer;
99     private View mSecondaryCallControls;
100     private String mLoadedNumber;
101     private CharSequence mCallInfoLabel;
102     private UiBluetoothMonitor mUiBluetoothMonitor;
103 
newInstance(UiCallManager callManager, UiBluetoothMonitor btMonitor)104     static OngoingCallFragment newInstance(UiCallManager callManager,
105             UiBluetoothMonitor btMonitor) {
106         OngoingCallFragment fragment = new OngoingCallFragment();
107         fragment.mUiCallManager = callManager;
108         fragment.mUiBluetoothMonitor = btMonitor;
109         return fragment;
110     }
111 
112     @Override
onCreate(Bundle savedInstanceState)113     public void onCreate(Bundle savedInstanceState) {
114         super.onCreate(savedInstanceState);
115     }
116 
117     @Override
onDestroy()118     public void onDestroy() {
119         super.onDestroy();
120         mHandler.removeCallbacks(mUpdateDurationRunnable);
121         mHandler.removeCallbacks(mStopDtmfToneRunnable);
122         mLoadedNumber = null;
123     }
124 
125     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)126     public View onCreateView(LayoutInflater inflater, ViewGroup container,
127             Bundle savedInstanceState) {
128         View view = inflater.inflate(R.layout.ongoing_call, container, false);
129         initializeViews(view);
130         initializeClickListeners();
131 
132         List<View> dialpadViews = Arrays.asList(
133                 mDialpadContainer.findViewById(R.id.one),
134                 mDialpadContainer.findViewById(R.id.two),
135                 mDialpadContainer.findViewById(R.id.three),
136                 mDialpadContainer.findViewById(R.id.four),
137                 mDialpadContainer.findViewById(R.id.five),
138                 mDialpadContainer.findViewById(R.id.six),
139                 mDialpadContainer.findViewById(R.id.seven),
140                 mDialpadContainer.findViewById(R.id.eight),
141                 mDialpadContainer.findViewById(R.id.nine),
142                 mDialpadContainer.findViewById(R.id.zero),
143                 mDialpadContainer.findViewById(R.id.pound),
144                 mDialpadContainer.findViewById(R.id.star));
145 
146         // In touch screen, we need to adjust the InCall card for the narrow screen to show the
147         // full dial pad.
148         for (View dialpadView : dialpadViews) {
149             dialpadView.setOnTouchListener(mDialpadTouchListener);
150             dialpadView.setOnKeyListener(mDialpadKeyListener);
151         }
152 
153         mUiCallManager.addListener(mCallListener);
154 
155         updateCalls();
156 
157         return view;
158     }
159 
initializeViews(View parent)160     private void initializeViews(View parent) {
161         mRingingCallControls = parent.findViewById(R.id.ringing_call_controls);
162         mActiveCallControls = parent.findViewById(R.id.active_call_controls);
163         mEndCallButton = parent.findViewById(R.id.end_call);
164         mUnholdCallButton = parent.findViewById(R.id.unhold_call);
165         mMuteButton = parent.findViewById(R.id.mute);
166         mToggleDialpadButton = parent.findViewById(R.id.toggle_dialpad);
167         mDialpadContainer = parent.findViewById(R.id.dialpad_container);
168         mNameTextView = parent.findViewById(R.id.name);
169         mSecondaryNameTextView = parent.findViewById(R.id.name_secondary);
170         mStateTextView = parent.findViewById(R.id.info);
171         mSecondaryStateTextView = parent.findViewById(R.id.info_secondary);
172         mLargeContactPhotoView = parent.findViewById(R.id.large_contact_photo);
173         mSmallContactPhotoView = parent.findViewById(R.id.small_contact_photo);
174         mSecondaryCallContainer = parent.findViewById(R.id.secondary_call_container);
175         mSecondaryCallControls = parent.findViewById(R.id.secondary_call_controls);
176         mSwapButton = parent.findViewById(R.id.swap);
177         mMergeButton = parent.findViewById(R.id.merge);
178         mAnswerCallButton = parent.findViewById(R.id.answer_call_button);
179         mRejectCallButton = parent.findViewById(R.id.reject_call_button);
180 
181         Context context = getContext();
182         FabDrawable drawable = new FabDrawable(context);
183         drawable.setFabAndStrokeColor(context.getColor(R.color.phone_call));
184         mAnswerCallButton.setBackground(drawable);
185 
186         drawable = new FabDrawable(context);
187         drawable.setFabAndStrokeColor(context.getColor(R.color.phone_end_call));
188         mEndCallButton.setBackground(drawable);
189 
190         drawable = new FabDrawable(context);
191         drawable.setFabAndStrokeColor(context.getColor(R.color.phone_call));
192         mUnholdCallButton.setBackground(drawable);
193     }
194 
initializeClickListeners()195     private void initializeClickListeners() {
196         mAnswerCallButton.setOnClickListener((unusedView) -> {
197             UiCall call = mUiCallManager.getCallWithState(Call.STATE_RINGING);
198             if (call == null) {
199                 Log.w(TAG, "There is no incoming call to answer.");
200                 return;
201             }
202             mUiCallManager.answerCall(call);
203         });
204 
205         mRejectCallButton.setOnClickListener((unusedView) -> {
206             UiCall call = mUiCallManager.getCallWithState(Call.STATE_RINGING);
207             if (call == null) {
208                 Log.w(TAG, "There is no incoming call to reject.");
209                 return;
210             }
211             mUiCallManager.rejectCall(call, false, null);
212         });
213 
214         mEndCallButton.setOnClickListener((unusedView) -> {
215             UiCall call = mUiCallManager.getPrimaryCall();
216             if (call == null) {
217                 Log.w(TAG, "There is no active call to end.");
218                 return;
219             }
220             mUiCallManager.disconnectCall(call);
221         });
222 
223         mUnholdCallButton.setOnClickListener((unusedView) -> {
224             UiCall call = mUiCallManager.getPrimaryCall();
225             if (call == null) {
226                 Log.w(TAG, "There is no active call to unhold.");
227                 return;
228             }
229             mUiCallManager.unholdCall(call);
230         });
231 
232         mMuteButton.setOnClickListener(
233                 (unusedView) -> mUiCallManager.setMuted(!mUiCallManager.getMuted()));
234 
235         mSwapButton.setOnClickListener((unusedView) -> {
236             UiCall call = mUiCallManager.getPrimaryCall();
237             if (call == null) {
238                 Log.w(TAG, "There is no active call to hold.");
239                 return;
240             }
241             if (call.getState() == Call.STATE_HOLDING) {
242                 mUiCallManager.unholdCall(call);
243             } else {
244                 mUiCallManager.holdCall(call);
245             }
246         });
247 
248         mMergeButton.setOnClickListener((unusedView) -> {
249             UiCall call = mUiCallManager.getPrimaryCall();
250             UiCall secondaryCall = mUiCallManager.getSecondaryCall();
251             if (call == null || secondaryCall == null) {
252                 Log.w(TAG, "There aren't two call to merge.");
253                 return;
254             }
255 
256             mUiCallManager.conference(call, secondaryCall);
257         });
258 
259         mToggleDialpadButton.setOnClickListener((unusedView) -> {
260             if (mToggleDialpadButton.isActivated()) {
261                 closeDialpad();
262             } else {
263                 openDialpad(true /*animate*/);
264             }
265         });
266     }
267 
268     @Override
onDestroyView()269     public void onDestroyView() {
270         super.onDestroyView();
271         mUiCallManager.removeListener(mCallListener);
272     }
273 
274     @Override
onStart()275     public void onStart() {
276         super.onStart();
277         trySpeakerAudioRouteIfNecessary();
278     }
279 
updateCalls()280     private void updateCalls() {
281         if (Log.isLoggable(TAG, Log.DEBUG)) {
282             Log.d(TAG, "updateCalls(); Primary call: " + mUiCallManager.getPrimaryCall()
283                     + "; Secondary call:" + mUiCallManager.getSecondaryCall());
284         }
285 
286         mHandler.removeCallbacks(mUpdateDurationRunnable);
287 
288         UiCall primaryCall = mUiCallManager.getPrimaryCall();
289         CharSequence disconnectCauseLabel = mLastRemovedCall == null
290                 ? null : mLastRemovedCall.getDisconnectCause();
291         if (primaryCall == null && !TextUtils.isEmpty(disconnectCauseLabel)) {
292             closeDialpad();
293             setStateText(disconnectCauseLabel);
294             return;
295         }
296 
297         if (primaryCall == null || primaryCall.getState() == Call.STATE_DISCONNECTED) {
298             closeDialpad();
299             setStateText(getString(R.string.call_state_call_ended));
300             mRingingCallControls.setVisibility(View.GONE);
301             mActiveCallControls.setVisibility(View.GONE);
302             return;
303         }
304 
305         if (primaryCall.getState() == Call.STATE_RINGING) {
306             mRingingCallControls.setVisibility(View.VISIBLE);
307             mActiveCallControls.setVisibility(View.GONE);
308         } else {
309             mRingingCallControls.setVisibility(View.GONE);
310             mActiveCallControls.setVisibility(View.VISIBLE);
311         }
312 
313         loadContactPhotoForPrimaryNumber(primaryCall.getNumber());
314 
315         String displayName = TelecomUtils.getDisplayName(getContext(), primaryCall);
316         mNameTextView.setText(displayName);
317         mNameTextView.setVisibility(TextUtils.isEmpty(displayName) ? View.GONE : View.VISIBLE);
318 
319         Context context = getContext();
320         switch (primaryCall.getState()) {
321             case Call.STATE_NEW:
322                 // Since the content resolver call is only cached when a contact is found,
323                 // this should only be called once on a new call to avoid jank.
324                 // TODO: consider moving TelecomUtils.getTypeFromNumber into a CursorLoader
325                 mCallInfoLabel = TelecomUtils.getTypeFromNumber(context, primaryCall.getNumber());
326             case Call.STATE_CONNECTING:
327             case Call.STATE_DIALING:
328             case Call.STATE_SELECT_PHONE_ACCOUNT:
329             case Call.STATE_HOLDING:
330             case Call.STATE_DISCONNECTED:
331                 mHandler.removeCallbacks(mUpdateDurationRunnable);
332                 String callInfoText = TelecomUtils.getCallInfoText(context,
333                         primaryCall, mCallInfoLabel);
334                 setStateText(callInfoText);
335                 break;
336             case Call.STATE_ACTIVE:
337                 if (mUiBluetoothMonitor.isHfpConnected()) {
338                     mHandler.post(mUpdateDurationRunnable);
339                 }
340                 break;
341             case Call.STATE_RINGING:
342                 Log.w(TAG, "There should not be a ringing call in the ongoing call fragment.");
343                 break;
344             default:
345                 Log.w(TAG, "Unhandled call state: " + primaryCall.getState());
346         }
347 
348         // If it is a voicemail call, open the dialpad (with no animation).
349         if (Objects.equals(primaryCall.getNumber(), TelecomUtils.getVoicemailNumber(context))) {
350             openDialpad(false /*animate*/);
351             mToggleDialpadButton.setVisibility(View.GONE);
352         } else {
353             mToggleDialpadButton.setVisibility(View.VISIBLE);
354         }
355 
356         // Handle the holding case.
357         if (primaryCall.getState() == Call.STATE_HOLDING) {
358             mEndCallButton.setVisibility(View.GONE);
359             mUnholdCallButton.setVisibility(View.VISIBLE);
360             mMuteButton.setVisibility(View.INVISIBLE);
361             mToggleDialpadButton.setVisibility(View.INVISIBLE);
362         } else {
363             mEndCallButton.setVisibility(View.VISIBLE);
364             mUnholdCallButton.setVisibility(View.GONE);
365             mMuteButton.setVisibility(View.VISIBLE);
366             mToggleDialpadButton.setVisibility(View.VISIBLE);
367         }
368 
369         updateSecondaryCall(primaryCall, mUiCallManager.getSecondaryCall());
370     }
371 
updateSecondaryCall(UiCall primaryCall, UiCall secondaryCall)372     private void updateSecondaryCall(UiCall primaryCall, UiCall secondaryCall) {
373         if (primaryCall == null || secondaryCall == null) {
374             mSecondaryCallContainer.setVisibility(View.GONE);
375             mSecondaryCallControls.setVisibility(View.GONE);
376             return;
377         }
378 
379         mSecondaryCallContainer.setVisibility(View.VISIBLE);
380 
381         if (primaryCall.getState() == Call.STATE_ACTIVE
382                 && secondaryCall.getState() == Call.STATE_HOLDING) {
383             mSecondaryCallControls.setVisibility(View.VISIBLE);
384         } else {
385             mSecondaryCallControls.setVisibility(View.GONE);
386         }
387 
388         Context context = getContext();
389         mSecondaryNameTextView.setText(TelecomUtils.getDisplayName(context, secondaryCall));
390         mSecondaryStateTextView.setText(
391                 TelecomUtils.callStateToUiString(context, secondaryCall.getState()));
392 
393         loadContactPhotoForSecondaryNumber(secondaryCall.getNumber());
394     }
395 
396     /**
397      * Loads the contact photo associated with the given number and sets it in the views that
398      * correspond with a primary number.
399      */
loadContactPhotoForPrimaryNumber(String primaryNumber)400     private void loadContactPhotoForPrimaryNumber(String primaryNumber) {
401         // Don't reload the image if the number is the same.
402         if (Objects.equals(primaryNumber, mLoadedNumber)) {
403             return;
404         }
405 
406         final ContentResolver cr = getContext().getContentResolver();
407         BitmapWorkerTask.BitmapRunnable runnable = new BitmapWorkerTask.BitmapRunnable() {
408             @Override
409             public void run() {
410                 if (mBitmap != null) {
411                     Resources r = getResources();
412                     mSmallContactPhotoView.setImageDrawable(new CircleBitmapDrawable(r, mBitmap));
413                     mLargeContactPhotoView.setImageBitmap(mBitmap);
414                     mLargeContactPhotoView.clearColorFilter();
415                 } else {
416                     mSmallContactPhotoView.setImageResource(R.drawable.logo_avatar);
417                     mLargeContactPhotoView.setImageResource(R.drawable.ic_avatar_bg);
418                 }
419             }
420         };
421         mLoadedNumber = primaryNumber;
422         BitmapWorkerTask.loadBitmap(cr, mLargeContactPhotoView, primaryNumber, runnable);
423     }
424 
425     /**
426      * Loads the contact photo associated with the given number and sets it in the views that
427      * correspond to a secondary number.
428      */
loadContactPhotoForSecondaryNumber(String secondaryNumber)429     private void loadContactPhotoForSecondaryNumber(String secondaryNumber) {
430         BitmapWorkerTask.BitmapRunnable runnable = new BitmapWorkerTask.BitmapRunnable() {
431             @Override
432             public void run() {
433                 if (mBitmap != null) {
434                     mLargeContactPhotoView.setImageBitmap(mBitmap);
435                 } else {
436                     mLargeContactPhotoView.setImageResource(R.drawable.logo_avatar);
437                 }
438             }
439         };
440 
441         Context context = getContext();
442         BitmapWorkerTask.loadBitmap(context.getContentResolver(), mLargeContactPhotoView,
443                 secondaryNumber, runnable);
444 
445         int scrimColor = context.getColor(R.color.phone_secondary_call_scrim);
446         mLargeContactPhotoView.setColorFilter(scrimColor);
447     }
448 
setStateText(CharSequence stateText)449     private void setStateText(CharSequence stateText) {
450         mStateTextView.setText(stateText);
451         mStateTextView.setVisibility(TextUtils.isEmpty(stateText) ? View.GONE : View.VISIBLE);
452     }
453 
454     /**
455      * If the phone is using bluetooth, then do nothing. If the phone is not using bluetooth:
456      * <p>
457      * <ol>
458      *     <li>If the phone supports bluetooth, use it.
459      *     <li>If the phone doesn't support bluetooth and support speaker, use speaker
460      *     <li>Otherwise, do nothing. Hopefully no phones won't have bt or speaker.
461      * </ol>
462      */
trySpeakerAudioRouteIfNecessary()463     private void trySpeakerAudioRouteIfNecessary() {
464         if (mUiCallManager == null) {
465             return;
466         }
467 
468         int supportedAudioRouteMask = mUiCallManager.getSupportedAudioRouteMask();
469         boolean supportsBluetooth = (supportedAudioRouteMask & CallAudioState.ROUTE_BLUETOOTH) != 0;
470         boolean supportsSpeaker = (supportedAudioRouteMask & CallAudioState.ROUTE_SPEAKER) != 0;
471         boolean isUsingBluetooth =
472                 mUiCallManager.getAudioRoute() == CallAudioState.ROUTE_BLUETOOTH;
473 
474         if (supportsBluetooth && !isUsingBluetooth) {
475             mUiCallManager.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH);
476         } else if (!supportsBluetooth && supportsSpeaker) {
477             mUiCallManager.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
478         }
479     }
480 
openDialpad(boolean animate)481     private void openDialpad(boolean animate) {
482         if (mToggleDialpadButton.isActivated()) {
483             return;
484         }
485         mToggleDialpadButton.setActivated(true);
486         // This array of of size 2 because getLocationOnScreen returns (x,y) coordinates.
487         int[] location = new int[2];
488         mToggleDialpadButton.getLocationOnScreen(location);
489 
490         // The dialpad should be aligned with the right edge of mToggleDialpadButton.
491         int startingMargin = location[1] + mToggleDialpadButton.getWidth();
492 
493         ViewGroup.MarginLayoutParams layoutParams =
494                 (ViewGroup.MarginLayoutParams) mDialpadContainer.getLayoutParams();
495 
496         if (layoutParams.getMarginStart() != startingMargin) {
497             layoutParams.setMarginStart(startingMargin);
498             mDialpadContainer.setLayoutParams(layoutParams);
499         }
500 
501         Animation anim = new DialpadAnimation(getContext(), false /* reverse */, animate);
502         mDialpadContainer.startAnimation(anim);
503     }
504 
closeDialpad()505     private void closeDialpad() {
506         if (!mToggleDialpadButton.isActivated()) {
507             return;
508         }
509         mToggleDialpadButton.setActivated(false);
510         Animation anim = new DialpadAnimation(getContext(), true /* reverse */);
511         mDialpadContainer.startAnimation(anim);
512     }
513 
514     private final View.OnTouchListener mDialpadTouchListener = new View.OnTouchListener() {
515 
516         @Override
517         public boolean onTouch(View v, MotionEvent event) {
518             Character digit = mDialpadButtonMap.get(v.getId());
519             if (digit == null) {
520                 Log.w(TAG, "Unknown dialpad button pressed.");
521                 return false;
522             }
523             if (event.getAction() == MotionEvent.ACTION_DOWN) {
524                 v.setPressed(true);
525                 mUiCallManager.playDtmfTone(mUiCallManager.getPrimaryCall(), digit);
526                 return true;
527             } else if (event.getAction() == MotionEvent.ACTION_UP) {
528                 v.setPressed(false);
529                 v.performClick();
530                 mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall());
531                 return true;
532             }
533 
534             return false;
535         }
536     };
537 
538     private final View.OnKeyListener mDialpadKeyListener = new View.OnKeyListener() {
539         @Override
540         public boolean onKey(View v, int keyCode, KeyEvent event) {
541             Character digit = mDialpadButtonMap.get(v.getId());
542             if (digit == null) {
543                 Log.w(TAG, "Unknown dialpad button pressed.");
544                 return false;
545             }
546 
547             if (event.getKeyCode() != KeyEvent.KEYCODE_DPAD_CENTER) {
548                 return false;
549             }
550 
551             if (event.getAction() == KeyEvent.ACTION_DOWN) {
552                 v.setPressed(true);
553                 mUiCallManager.playDtmfTone(mUiCallManager.getPrimaryCall(), digit);
554                 return true;
555             } else if (event.getAction() == KeyEvent.ACTION_UP) {
556                 v.setPressed(false);
557                 mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall());
558                 return true;
559             }
560 
561             return false;
562         }
563     };
564 
565     private final Runnable mUpdateDurationRunnable = new Runnable() {
566         @Override
567         public void run() {
568             UiCall primaryCall = mUiCallManager.getPrimaryCall();
569             if (primaryCall.getState() != Call.STATE_ACTIVE) {
570                 return;
571             }
572             String callInfoText = TelecomUtils.getCallInfoText(getContext(),
573                     primaryCall, mCallInfoLabel);
574             setStateText(callInfoText);
575             mHandler.postDelayed(this /* runnable */, DateUtils.SECOND_IN_MILLIS);
576 
577         }
578     };
579 
580     private final Runnable mStopDtmfToneRunnable =
581             () -> mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall());
582 
583     private final class DialpadAnimation extends Animation {
584         private static final int DURATION = 300;
585         private static final float MAX_SCRIM_ALPHA = 0.6f;
586 
587         private final int mStartingTranslation;
588         private final int mScrimColor;
589         private final boolean mReverse;
590 
DialpadAnimation(Context context, boolean reverse)591         DialpadAnimation(Context context, boolean reverse) {
592             this(context, reverse, true);
593         }
594 
DialpadAnimation(Context context, boolean reverse, boolean animate)595         DialpadAnimation(Context context, boolean reverse, boolean animate) {
596             setDuration(animate ? DURATION : 0);
597             setInterpolator(new AccelerateDecelerateInterpolator());
598             mStartingTranslation = context.getResources().getDimensionPixelOffset(
599                     R.dimen.in_call_card_dialpad_translation_x);
600             mScrimColor = context.getColor(R.color.phone_theme);
601             mReverse = reverse;
602         }
603 
604         @Override
applyTransformation(float interpolatedTime, Transformation t)605         protected void applyTransformation(float interpolatedTime, Transformation t) {
606             if (mReverse) {
607                 interpolatedTime = 1f - interpolatedTime;
608             }
609             int translationX = (int) (mStartingTranslation * (1f - interpolatedTime));
610             mDialpadContainer.setTranslationX(translationX);
611             mDialpadContainer.setAlpha(interpolatedTime);
612             if (interpolatedTime == 0f) {
613                 mDialpadContainer.setVisibility(View.GONE);
614             } else {
615                 mDialpadContainer.setVisibility(View.VISIBLE);
616             }
617             float alpha = 255f * interpolatedTime * MAX_SCRIM_ALPHA;
618             mLargeContactPhotoView.setColorFilter(Color.argb((int) alpha, Color.red(mScrimColor),
619                     Color.green(mScrimColor), Color.blue(mScrimColor)));
620 
621             mSecondaryNameTextView.setAlpha(1f - interpolatedTime);
622             mSecondaryStateTextView.setAlpha(1f - interpolatedTime);
623         }
624     }
625 
626     private final CallListener mCallListener = new CallListener() {
627         @Override
628         public void onCallAdded(UiCall call) {
629             if (Log.isLoggable(TAG, Log.DEBUG)) {
630                 Log.d(TAG, "onCallAdded(); call: " + call);
631             }
632             updateCalls();
633             trySpeakerAudioRouteIfNecessary();
634         }
635 
636         @Override
637         public void onCallRemoved(UiCall call) {
638             if (Log.isLoggable(TAG, Log.DEBUG)) {
639                 Log.d(TAG, "onCallRemoved(); call: " + call);
640             }
641             mLastRemovedCall = call;
642             updateCalls();
643         }
644 
645         @Override
646         public void onAudioStateChanged(boolean isMuted, int audioRoute,
647                 int supportedAudioRouteMask) {
648             if (Log.isLoggable(TAG, Log.DEBUG)) {
649                 Log.d(TAG, String.format("onAudioStateChanged(); isMuted: %b, audioRoute: %d, "
650                         + " supportedAudioRouteMask: %d", isMuted, audioRoute,
651                         supportedAudioRouteMask));
652             }
653             mMuteButton.setActivated(isMuted);
654             trySpeakerAudioRouteIfNecessary();
655         }
656 
657         @Override
658         public void onStateChanged(UiCall call, int state) {
659             if (Log.isLoggable(TAG, Log.DEBUG)) {
660                 Log.d(TAG, "onStateChanged(); call: " + call + ", state: " + state);
661             }
662             updateCalls();
663         }
664 
665         @Override
666         public void onCallUpdated(UiCall call) {
667             if (Log.isLoggable(TAG, Log.DEBUG)) {
668                 Log.d(TAG, "onCallUpdated(); call: " + call);
669             }
670             updateCalls();
671         }
672     };
673 }
674