1 /* 2 * Copyright (C) 2009 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 17 package com.android.phone; 18 19 import android.content.Context; 20 import android.graphics.drawable.Drawable; 21 import android.os.SystemClock; 22 import android.util.AttributeSet; 23 import android.util.Log; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.view.animation.AlphaAnimation; 27 import android.view.animation.Animation; 28 import android.view.animation.Animation.AnimationListener; 29 import android.widget.Button; 30 import android.widget.FrameLayout; 31 import android.widget.ImageButton; 32 import android.widget.TextView; 33 import android.widget.ToggleButton; 34 35 import com.android.internal.telephony.Call; 36 import com.android.internal.telephony.Phone; 37 import com.android.internal.widget.SlidingTab; 38 39 40 /** 41 * In-call onscreen touch UI elements, used on some platforms. 42 * 43 * This widget is a fullscreen overlay, drawn on top of the 44 * non-touch-sensitive parts of the in-call UI (i.e. the call card). 45 */ 46 public class InCallTouchUi extends FrameLayout 47 implements View.OnClickListener, SlidingTab.OnTriggerListener { 48 private static final int IN_CALL_WIDGET_TRANSITION_TIME = 250; // in ms 49 private static final String LOG_TAG = "InCallTouchUi"; 50 private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2); 51 52 /** 53 * Reference to the InCallScreen activity that owns us. This may be 54 * null if we haven't been initialized yet *or* after the InCallScreen 55 * activity has been destroyed. 56 */ 57 private InCallScreen mInCallScreen; 58 59 // Phone app instance 60 private PhoneApp mApplication; 61 62 // UI containers / elements 63 private SlidingTab mIncomingCallWidget; // UI used for an incoming call 64 private View mInCallControls; // UI elements while on a regular call 65 // 66 private Button mAddButton; 67 private Button mMergeButton; 68 private Button mEndButton; 69 private Button mDialpadButton; 70 private ToggleButton mBluetoothButton; 71 private ToggleButton mMuteButton; 72 private ToggleButton mSpeakerButton; 73 // 74 private View mHoldButtonContainer; 75 private ImageButton mHoldButton; 76 private TextView mHoldButtonLabel; 77 private View mSwapButtonContainer; 78 private ImageButton mSwapButton; 79 private TextView mSwapButtonLabel; 80 private View mCdmaMergeButtonContainer; 81 private ImageButton mCdmaMergeButton; 82 // 83 private Drawable mHoldIcon; 84 private Drawable mUnholdIcon; 85 private Drawable mShowDialpadIcon; 86 private Drawable mHideDialpadIcon; 87 88 // Time of the most recent "answer" or "reject" action (see updateState()) 89 private long mLastIncomingCallActionTime; // in SystemClock.uptimeMillis() time base 90 91 // Overall enabledness of the "touch UI" features 92 private boolean mAllowIncomingCallTouchUi; 93 private boolean mAllowInCallTouchUi; 94 InCallTouchUi(Context context, AttributeSet attrs)95 public InCallTouchUi(Context context, AttributeSet attrs) { 96 super(context, attrs); 97 98 if (DBG) log("InCallTouchUi constructor..."); 99 if (DBG) log("- this = " + this); 100 if (DBG) log("- context " + context + ", attrs " + attrs); 101 102 // Inflate our contents, and add it (to ourself) as a child. 103 LayoutInflater inflater = LayoutInflater.from(context); 104 inflater.inflate( 105 R.layout.incall_touch_ui, // resource 106 this, // root 107 true); 108 109 mApplication = PhoneApp.getInstance(); 110 111 // The various touch UI features are enabled on a per-product 112 // basis. (These flags in config.xml may be overridden by 113 // product-specific overlay files.) 114 115 mAllowIncomingCallTouchUi = getResources().getBoolean(R.bool.allow_incoming_call_touch_ui); 116 if (DBG) log("- incoming call touch UI: " 117 + (mAllowIncomingCallTouchUi ? "ENABLED" : "DISABLED")); 118 mAllowInCallTouchUi = getResources().getBoolean(R.bool.allow_in_call_touch_ui); 119 if (DBG) log("- regular in-call touch UI: " 120 + (mAllowInCallTouchUi ? "ENABLED" : "DISABLED")); 121 } 122 setInCallScreenInstance(InCallScreen inCallScreen)123 void setInCallScreenInstance(InCallScreen inCallScreen) { 124 mInCallScreen = inCallScreen; 125 } 126 127 @Override onFinishInflate()128 protected void onFinishInflate() { 129 super.onFinishInflate(); 130 if (DBG) log("InCallTouchUi onFinishInflate(this = " + this + ")..."); 131 132 // Look up the various UI elements. 133 134 // "Dial-to-answer" widget for incoming calls. 135 mIncomingCallWidget = (SlidingTab) findViewById(R.id.incomingCallWidget); 136 mIncomingCallWidget.setLeftTabResources( 137 R.drawable.ic_jog_dial_answer, 138 com.android.internal.R.drawable.jog_tab_target_green, 139 com.android.internal.R.drawable.jog_tab_bar_left_answer, 140 com.android.internal.R.drawable.jog_tab_left_answer 141 ); 142 mIncomingCallWidget.setRightTabResources( 143 R.drawable.ic_jog_dial_decline, 144 com.android.internal.R.drawable.jog_tab_target_red, 145 com.android.internal.R.drawable.jog_tab_bar_right_decline, 146 com.android.internal.R.drawable.jog_tab_right_decline 147 ); 148 149 // For now, we only need to show two states: answer and decline. 150 mIncomingCallWidget.setLeftHintText(R.string.slide_to_answer_hint); 151 mIncomingCallWidget.setRightHintText(R.string.slide_to_decline_hint); 152 153 mIncomingCallWidget.setOnTriggerListener(this); 154 155 // Container for the UI elements shown while on a regular call. 156 mInCallControls = findViewById(R.id.inCallControls); 157 158 // Regular (single-tap) buttons, where we listen for click events: 159 // Main cluster of buttons: 160 mAddButton = (Button) mInCallControls.findViewById(R.id.addButton); 161 mAddButton.setOnClickListener(this); 162 mMergeButton = (Button) mInCallControls.findViewById(R.id.mergeButton); 163 mMergeButton.setOnClickListener(this); 164 mEndButton = (Button) mInCallControls.findViewById(R.id.endButton); 165 mEndButton.setOnClickListener(this); 166 mDialpadButton = (Button) mInCallControls.findViewById(R.id.dialpadButton); 167 mDialpadButton.setOnClickListener(this); 168 mBluetoothButton = (ToggleButton) mInCallControls.findViewById(R.id.bluetoothButton); 169 mBluetoothButton.setOnClickListener(this); 170 mMuteButton = (ToggleButton) mInCallControls.findViewById(R.id.muteButton); 171 mMuteButton.setOnClickListener(this); 172 mSpeakerButton = (ToggleButton) mInCallControls.findViewById(R.id.speakerButton); 173 mSpeakerButton.setOnClickListener(this); 174 175 // Upper corner buttons: 176 mHoldButtonContainer = mInCallControls.findViewById(R.id.holdButtonContainer); 177 mHoldButton = (ImageButton) mInCallControls.findViewById(R.id.holdButton); 178 mHoldButton.setOnClickListener(this); 179 mHoldButtonLabel = (TextView) mInCallControls.findViewById(R.id.holdButtonLabel); 180 // 181 mSwapButtonContainer = mInCallControls.findViewById(R.id.swapButtonContainer); 182 mSwapButton = (ImageButton) mInCallControls.findViewById(R.id.swapButton); 183 mSwapButton.setOnClickListener(this); 184 mSwapButtonLabel = (TextView) mInCallControls.findViewById(R.id.swapButtonLabel); 185 if (PhoneApp.getInstance().phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) { 186 // In CDMA we use a generalized text - "Manage call", as behavior on selecting 187 // this option depends entirely on what the current call state is. 188 mSwapButtonLabel.setText(R.string.onscreenManageCallsText); 189 } else { 190 mSwapButtonLabel.setText(R.string.onscreenSwapCallsText); 191 } 192 // 193 mCdmaMergeButtonContainer = mInCallControls.findViewById(R.id.cdmaMergeButtonContainer); 194 mCdmaMergeButton = (ImageButton) mInCallControls.findViewById(R.id.cdmaMergeButton); 195 mCdmaMergeButton.setOnClickListener(this); 196 197 // Icons we need to change dynamically. (Most other icons are specified 198 // directly in incall_touch_ui.xml.) 199 mHoldIcon = getResources().getDrawable(R.drawable.ic_in_call_touch_round_hold); 200 mUnholdIcon = getResources().getDrawable(R.drawable.ic_in_call_touch_round_unhold); 201 mShowDialpadIcon = getResources().getDrawable(R.drawable.ic_in_call_touch_dialpad); 202 mHideDialpadIcon = getResources().getDrawable(R.drawable.ic_in_call_touch_dialpad_close); 203 } 204 205 /** 206 * Updates the visibility and/or state of our UI elements, based on 207 * the current state of the phone. 208 */ updateState(Phone phone)209 void updateState(Phone phone) { 210 if (DBG) log("updateState(" + phone + ")..."); 211 212 if (mInCallScreen == null) { 213 log("- updateState: mInCallScreen has been destroyed; bailing out..."); 214 return; 215 } 216 217 Phone.State state = phone.getState(); // IDLE, RINGING, or OFFHOOK 218 if (DBG) log("- updateState: phone state is " + state); 219 220 boolean showIncomingCallControls = false; 221 boolean showInCallControls = false; 222 223 if (state == Phone.State.RINGING) { 224 // A phone call is ringing *or* call waiting. 225 if (mAllowIncomingCallTouchUi) { 226 // Watch out: even if the phone state is RINGING, it's 227 // possible for the ringing call to be in the DISCONNECTING 228 // state. (This typically happens immediately after the user 229 // rejects an incoming call, and in that case we *don't* show 230 // the incoming call controls.) 231 final Call ringingCall = phone.getRingingCall(); 232 if (ringingCall.getState().isAlive()) { 233 if (DBG) log("- updateState: RINGING! Showing incoming call controls..."); 234 showIncomingCallControls = true; 235 } 236 237 // Ugly hack to cover up slow response from the radio: 238 // if we attempted to answer or reject an incoming call 239 // within the last 500 msec, *don't* show the incoming call 240 // UI even if the phone is still in the RINGING state. 241 long now = SystemClock.uptimeMillis(); 242 if (now < mLastIncomingCallActionTime + 500) { 243 log("updateState: Too soon after last action; not drawing!"); 244 showIncomingCallControls = false; 245 } 246 247 // TODO: UI design issue: if the device is NOT currently 248 // locked, we probably don't need to make the user 249 // double-tap the "incoming call" buttons. (The device 250 // presumably isn't in a pocket or purse, so we don't need 251 // to worry about false touches while it's ringing.) 252 // But OTOH having "inconsistent" buttons might just make 253 // it *more* confusing. 254 } 255 } else { 256 if (mAllowInCallTouchUi) { 257 // Ok, the in-call touch UI is available on this platform, 258 // so make it visible (with some exceptions): 259 if (mInCallScreen.okToShowInCallTouchUi()) { 260 showInCallControls = true; 261 } else { 262 if (DBG) log("- updateState: NOT OK to show touch UI; disabling..."); 263 } 264 } 265 } 266 267 if (showInCallControls) { 268 updateInCallControls(phone); 269 } 270 271 if (showIncomingCallControls && showInCallControls) { 272 throw new IllegalStateException( 273 "'Incoming' and 'in-call' touch controls visible at the same time!"); 274 } 275 276 if (showIncomingCallControls) { 277 showIncomingCallWidget(); 278 } else { 279 hideIncomingCallWidget(); 280 } 281 282 mInCallControls.setVisibility(showInCallControls ? View.VISIBLE : View.GONE); 283 284 // TODO: As an optimization, also consider setting the visibility 285 // of the overall InCallTouchUi widget to GONE if *nothing at all* 286 // is visible right now. 287 } 288 289 // View.OnClickListener implementation onClick(View view)290 public void onClick(View view) { 291 int id = view.getId(); 292 if (DBG) log("onClick(View " + view + ", id " + id + ")..."); 293 294 switch (id) { 295 case R.id.addButton: 296 case R.id.mergeButton: 297 case R.id.endButton: 298 case R.id.dialpadButton: 299 case R.id.bluetoothButton: 300 case R.id.muteButton: 301 case R.id.speakerButton: 302 case R.id.holdButton: 303 case R.id.swapButton: 304 case R.id.cdmaMergeButton: 305 // Clicks on the regular onscreen buttons get forwarded 306 // straight to the InCallScreen. 307 mInCallScreen.handleOnscreenButtonClick(id); 308 break; 309 310 default: 311 Log.w(LOG_TAG, "onClick: unexpected click: View " + view + ", id " + id); 312 break; 313 } 314 } 315 316 /** 317 * Updates the enabledness and "checked" state of the buttons on the 318 * "inCallControls" panel, based on the current telephony state. 319 */ updateInCallControls(Phone phone)320 void updateInCallControls(Phone phone) { 321 int phoneType = phone.getPhoneType(); 322 // Note we do NOT need to worry here about cases where the entire 323 // in-call touch UI is disabled, like during an OTA call or if the 324 // dtmf dialpad is up. (That's handled by updateState(), which 325 // calls InCallScreen.okToShowInCallTouchUi().) 326 // 327 // If we get here, it *is* OK to show the in-call touch UI, so we 328 // now need to update the enabledness and/or "checked" state of 329 // each individual button. 330 // 331 332 // The InCallControlState object tells us the enabledness and/or 333 // state of the various onscreen buttons: 334 InCallControlState inCallControlState = mInCallScreen.getUpdatedInCallControlState(); 335 336 // "Add" or "Merge": 337 // These two buttons occupy the same space onscreen, so only 338 // one of them should be available at a given moment. 339 if (inCallControlState.canAddCall) { 340 mAddButton.setVisibility(View.VISIBLE); 341 mAddButton.setEnabled(true); 342 mMergeButton.setVisibility(View.GONE); 343 } else if (inCallControlState.canMerge) { 344 if (phoneType == Phone.PHONE_TYPE_CDMA) { 345 // In CDMA "Add" option is always given to the user and the 346 // "Merge" option is provided as a button on the top left corner of the screen, 347 // we always set the mMergeButton to GONE 348 mMergeButton.setVisibility(View.GONE); 349 } else if (phoneType == Phone.PHONE_TYPE_GSM) { 350 mMergeButton.setVisibility(View.VISIBLE); 351 mMergeButton.setEnabled(true); 352 mAddButton.setVisibility(View.GONE); 353 } else { 354 throw new IllegalStateException("Unexpected phone type: " + phoneType); 355 } 356 } else { 357 // Neither "Add" nor "Merge" is available. (This happens in 358 // some transient states, like while dialing an outgoing call, 359 // and in other rare cases like if you have both lines in use 360 // *and* there are already 5 people on the conference call.) 361 // Since the common case here is "while dialing", we show the 362 // "Add" button in a disabled state so that there won't be any 363 // jarring change in the UI when the call finally connects. 364 mAddButton.setVisibility(View.VISIBLE); 365 mAddButton.setEnabled(false); 366 mMergeButton.setVisibility(View.GONE); 367 } 368 if (inCallControlState.canAddCall && inCallControlState.canMerge) { 369 if (phoneType == Phone.PHONE_TYPE_GSM) { 370 // Uh oh, the InCallControlState thinks that "Add" *and* "Merge" 371 // should both be available right now. This *should* never 372 // happen with GSM, but if it's possible on any 373 // future devices we may need to re-layout Add and Merge so 374 // they can both be visible at the same time... 375 Log.w(LOG_TAG, "updateInCallControls: Add *and* Merge enabled," + 376 " but can't show both!"); 377 } else if (phoneType == Phone.PHONE_TYPE_CDMA) { 378 // In CDMA "Add" option is always given to the user and the hence 379 // in this case both "Add" and "Merge" options would be available to user 380 if (DBG) log("updateInCallControls: CDMA: Add and Merge both enabled"); 381 } else { 382 throw new IllegalStateException("Unexpected phone type: " + phoneType); 383 } 384 } 385 386 // "End call": this button has no state and it's always enabled. 387 mEndButton.setEnabled(true); 388 389 // "Dialpad": Enabled only when it's OK to use the dialpad in the 390 // first place. 391 mDialpadButton.setEnabled(inCallControlState.dialpadEnabled); 392 // 393 if (inCallControlState.dialpadVisible) { 394 // Show the "hide dialpad" state. 395 mDialpadButton.setText(R.string.onscreenHideDialpadText); 396 mDialpadButton.setCompoundDrawablesWithIntrinsicBounds( 397 null, mHideDialpadIcon, null, null); 398 } else { 399 // Show the "show dialpad" state. 400 mDialpadButton.setText(R.string.onscreenShowDialpadText); 401 mDialpadButton.setCompoundDrawablesWithIntrinsicBounds( 402 null, mShowDialpadIcon, null, null); 403 } 404 405 // "Bluetooth" 406 mBluetoothButton.setEnabled(inCallControlState.bluetoothEnabled); 407 mBluetoothButton.setChecked(inCallControlState.bluetoothIndicatorOn); 408 409 // "Mute" 410 mMuteButton.setEnabled(inCallControlState.canMute); 411 mMuteButton.setChecked(inCallControlState.muteIndicatorOn); 412 413 // "Speaker" 414 mSpeakerButton.setEnabled(inCallControlState.speakerEnabled); 415 mSpeakerButton.setChecked(inCallControlState.speakerOn); 416 417 // "Hold" 418 // (Note "Hold" and "Swap" are never both available at 419 // the same time. That's why it's OK for them to both be in the 420 // same position onscreen.) 421 // This button is totally hidden (rather than just disabled) 422 // when the operation isn't available. 423 mHoldButtonContainer.setVisibility( 424 inCallControlState.canHold ? View.VISIBLE : View.GONE); 425 if (inCallControlState.canHold) { 426 // The Hold button icon and label (either "Hold" or "Unhold") 427 // depend on the current Hold state. 428 if (inCallControlState.onHold) { 429 mHoldButton.setImageDrawable(mUnholdIcon); 430 mHoldButtonLabel.setText(R.string.onscreenUnholdText); 431 } else { 432 mHoldButton.setImageDrawable(mHoldIcon); 433 mHoldButtonLabel.setText(R.string.onscreenHoldText); 434 } 435 } 436 437 // "Swap" 438 // This button is totally hidden (rather than just disabled) 439 // when the operation isn't available. 440 mSwapButtonContainer.setVisibility( 441 inCallControlState.canSwap ? View.VISIBLE : View.GONE); 442 443 if (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) { 444 // "Merge" 445 // This button is totally hidden (rather than just disabled) 446 // when the operation isn't available. 447 mCdmaMergeButtonContainer.setVisibility( 448 inCallControlState.canMerge ? View.VISIBLE : View.GONE); 449 } 450 451 if (inCallControlState.canSwap && inCallControlState.canHold) { 452 // Uh oh, the InCallControlState thinks that Swap *and* Hold 453 // should both be available. This *should* never happen with 454 // either GSM or CDMA, but if it's possible on any future 455 // devices we may need to re-layout Hold and Swap so they can 456 // both be visible at the same time... 457 Log.w(LOG_TAG, "updateInCallControls: Hold *and* Swap enabled, but can't show both!"); 458 } 459 460 if (phoneType == Phone.PHONE_TYPE_CDMA) { 461 if (inCallControlState.canSwap && inCallControlState.canMerge) { 462 // Uh oh, the InCallControlState thinks that Swap *and* Merge 463 // should both be available. This *should* never happen with 464 // CDMA, but if it's possible on any future 465 // devices we may need to re-layout Merge and Swap so they can 466 // both be visible at the same time... 467 Log.w(LOG_TAG, "updateInCallControls: Merge *and* Swap" + 468 "enabled, but can't show both!"); 469 } 470 } 471 472 // One final special case: if the dialpad is visible, that trumps 473 // *any* of the upper corner buttons: 474 if (inCallControlState.dialpadVisible) { 475 mHoldButtonContainer.setVisibility(View.GONE); 476 mSwapButtonContainer.setVisibility(View.GONE); 477 } 478 } 479 480 // 481 // InCallScreen API 482 // 483 484 /** 485 * @return true if the onscreen touch UI is enabled (for regular 486 * "ongoing call" states) on the current device. 487 */ isTouchUiEnabled()488 /* package */ boolean isTouchUiEnabled() { 489 return mAllowInCallTouchUi; 490 } 491 492 // 493 // SlidingTab.OnTriggerListener implementation 494 // 495 496 /** 497 * Handles "Answer" and "Reject" actions for an incoming call. 498 * We get this callback from the SlidingTab 499 * when the user triggers an action. 500 * 501 * To answer or reject the incoming call, we call 502 * InCallScreen.handleOnscreenButtonClick() and pass one of the 503 * special "virtual button" IDs: 504 * - R.id.answerButton to answer the call 505 * or 506 * - R.id.rejectButton to reject the call. 507 */ onTrigger(View v, int whichHandle)508 public void onTrigger(View v, int whichHandle) { 509 log("onDialTrigger(whichHandle = " + whichHandle + ")..."); 510 511 switch (whichHandle) { 512 case SlidingTab.OnTriggerListener.LEFT_HANDLE: 513 if (DBG) log("LEFT_HANDLE: answer!"); 514 515 hideIncomingCallWidget(); 516 517 // ...and also prevent it from reappearing right away. 518 // (This covers up a slow response from the radio; see updateState().) 519 mLastIncomingCallActionTime = SystemClock.uptimeMillis(); 520 521 // Do the appropriate action. 522 if (mInCallScreen != null) { 523 // Send this to the InCallScreen as a virtual "button click" event: 524 mInCallScreen.handleOnscreenButtonClick(R.id.answerButton); 525 } else { 526 Log.e(LOG_TAG, "answer trigger: mInCallScreen is null"); 527 } 528 break; 529 530 case SlidingTab.OnTriggerListener.RIGHT_HANDLE: 531 if (DBG) log("RIGHT_HANDLE: reject!"); 532 533 hideIncomingCallWidget(); 534 535 // ...and also prevent it from reappearing right away. 536 // (This covers up a slow response from the radio; see updateState().) 537 mLastIncomingCallActionTime = SystemClock.uptimeMillis(); 538 539 // Do the appropriate action. 540 if (mInCallScreen != null) { 541 // Send this to the InCallScreen as a virtual "button click" event: 542 mInCallScreen.handleOnscreenButtonClick(R.id.rejectButton); 543 } else { 544 Log.e(LOG_TAG, "reject trigger: mInCallScreen is null"); 545 } 546 break; 547 548 default: 549 Log.e(LOG_TAG, "onDialTrigger: unexpected whichHandle value: " + whichHandle); 550 break; 551 } 552 553 // Regardless of what action the user did, be sure to clear out 554 // the hint text we were displaying while the user was dragging. 555 mInCallScreen.updateSlidingTabHint(0, 0); 556 } 557 558 /** 559 * Apply an animation to hide the incoming call widget. 560 */ hideIncomingCallWidget()561 private void hideIncomingCallWidget() { 562 if (mIncomingCallWidget.getVisibility() != View.VISIBLE 563 || mIncomingCallWidget.getAnimation() != null) { 564 // Widget is already hidden or in the process of being hidden 565 return; 566 } 567 // Hide the incoming call screen with a transition 568 AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f); 569 anim.setDuration(IN_CALL_WIDGET_TRANSITION_TIME); 570 anim.setAnimationListener(new AnimationListener() { 571 572 public void onAnimationStart(Animation animation) { 573 574 } 575 576 public void onAnimationRepeat(Animation animation) { 577 578 } 579 580 public void onAnimationEnd(Animation animation) { 581 // hide the incoming call UI. 582 mIncomingCallWidget.clearAnimation(); 583 mIncomingCallWidget.setVisibility(View.GONE); 584 } 585 }); 586 mIncomingCallWidget.startAnimation(anim); 587 } 588 589 /** 590 * Shows the incoming call widget and cancels any animation that may be fading it out. 591 */ showIncomingCallWidget()592 private void showIncomingCallWidget() { 593 Animation anim = mIncomingCallWidget.getAnimation(); 594 if (anim != null) { 595 anim.reset(); 596 mIncomingCallWidget.clearAnimation(); 597 } 598 mIncomingCallWidget.setVisibility(View.VISIBLE); 599 } 600 601 /** 602 * Handles state changes of the SlidingTabSelector widget. While the user 603 * is dragging one of the handles, we display an onscreen hint; see 604 * CallCard.getRotateWidgetHint(). 605 */ onGrabbedStateChange(View v, int grabbedState)606 public void onGrabbedStateChange(View v, int grabbedState) { 607 if (mInCallScreen != null) { 608 // Look up the hint based on which handle is currently grabbed. 609 // (Note we don't simply pass grabbedState thru to the InCallScreen, 610 // since *this* class is the only place that knows that the left 611 // handle means "Answer" and the right handle means "Decline".) 612 int hintTextResId, hintColorResId; 613 switch (grabbedState) { 614 case SlidingTab.OnTriggerListener.NO_HANDLE: 615 hintTextResId = 0; 616 hintColorResId = 0; 617 break; 618 case SlidingTab.OnTriggerListener.LEFT_HANDLE: 619 // TODO: Use different variants of "Slide to answer" in some cases 620 // depending on the phone state, like slide_to_answer_and_hold 621 // for a call waiting call, or slide_to_answer_and_end_active or 622 // slide_to_answer_and_end_onhold for the 2-lines-in-use case. 623 // (Note these are GSM-only cases, though.) 624 hintTextResId = R.string.slide_to_answer; 625 hintColorResId = R.color.incall_textConnected; // green 626 break; 627 case SlidingTab.OnTriggerListener.RIGHT_HANDLE: 628 hintTextResId = R.string.slide_to_decline; 629 hintColorResId = R.color.incall_textEnded; // red 630 break; 631 default: 632 Log.e(LOG_TAG, "onGrabbedStateChange: unexpected grabbedState: " + grabbedState); 633 hintTextResId = 0; 634 hintColorResId = 0; 635 break; 636 } 637 638 // Tell the InCallScreen to update the CallCard and force the 639 // screen to redraw. 640 mInCallScreen.updateSlidingTabHint(hintTextResId, hintColorResId); 641 } 642 } 643 644 645 // Debugging / testing code 646 log(String msg)647 private void log(String msg) { 648 Log.d(LOG_TAG, msg); 649 } 650 } 651