• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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