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