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