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