Misc phone app code cleanup.
[android/platform/packages/apps/Phone.git] / src / com / android / phone / CallCard.java
1 /*
2  * Copyright (C) 2006 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 com.android.internal.telephony.Call;
20 import com.android.internal.telephony.CallerInfo;
21 import com.android.internal.telephony.CallerInfoAsyncQuery;
22 import com.android.internal.telephony.Connection;
23 import com.android.internal.telephony.Phone;
24
25 import android.content.ContentUris;
26 import android.content.Context;
27 import android.graphics.drawable.Drawable;
28 import android.net.Uri;
29 import android.pim.ContactsAsyncHelper;
30 import android.provider.Contacts.People;
31 import android.text.TextUtils;
32 import android.text.format.DateUtils;
33 import android.util.AttributeSet;
34 import android.util.Log;
35 import android.view.LayoutInflater;
36 import android.view.MotionEvent;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.widget.FrameLayout;
40 import android.widget.ImageView;
41 import android.widget.TextView;
42
43 import java.util.List;
44
45 /**
46  * "Call card" UI element: the in-call screen contains a tiled layout of call
47  * cards, each representing the state of a current "call" (ie. an active call,
48  * a call on hold, or an incoming call.)
49  */
50 public class CallCard extends FrameLayout
51         implements CallTime.OnTickListener, CallerInfoAsyncQuery.OnQueryCompleteListener,
52                 ContactsAsyncHelper.OnImageLoadCompleteListener{
53     private static final String LOG_TAG = "CallCard";
54     private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
55
56     /**
57      * Reference to the InCallScreen activity that owns us.  This may be
58      * null if we haven't been initialized yet *or* after the InCallScreen
59      * activity has been destroyed.
60      */
61     private InCallScreen mInCallScreen;
62
63     // Phone app instance
64     private PhoneApp mApplication;
65
66     // Top-level subviews of the CallCard
67     private ViewGroup mMainCallCard;
68     private ViewGroup mOtherCallOngoingInfoArea;
69     private ViewGroup mOtherCallOnHoldInfoArea;
70
71     // "Upper" and "lower" title widgets
72     private TextView mUpperTitle;
73     private ViewGroup mLowerTitleViewGroup;
74     private TextView mLowerTitle;
75     private ImageView mLowerTitleIcon;
76     private TextView mElapsedTime;
77
78     // Text colors, used with the lower title and "other call" info areas
79     private int mTextColorConnected;
80     private int mTextColorConnectedBluetooth;
81     private int mTextColorEnded;
82     private int mTextColorOnHold;
83
84     private ImageView mPhoto;
85     private TextView mName;
86     private TextView mPhoneNumber;
87     private TextView mLabel;
88
89     // "Other call" info area
90     private ImageView mOtherCallOngoingIcon;
91     private TextView mOtherCallOngoingName;
92     private TextView mOtherCallOngoingStatus;
93     private TextView mOtherCallOnHoldName;
94     private TextView mOtherCallOnHoldStatus;
95
96     // Menu button hint
97     private TextView mMenuButtonHint;
98
99     private CallTime mCallTime;
100
101     // Track the state for the photo.
102     private ContactsAsyncHelper.ImageTracker mPhotoTracker;
103
104     // A few hardwired constants used in our screen layout.
105     // TODO: These should all really come from resources, but that's
106     // nontrivial; see the javadoc for the ConfigurationHelper class.
107     // For now, let's at least keep them all here in one place
108     // rather than sprinkled througout this file.
109     //
110     static final int MAIN_CALLCARD_MIN_HEIGHT_LANDSCAPE = 200;
111     static final int CALLCARD_SIDE_MARGIN_LANDSCAPE = 50;
112     static final float TITLE_TEXT_SIZE_LANDSCAPE = 22F;  // scaled pixels
113
114     public CallCard(Context context, AttributeSet attrs) {
115         super(context, attrs);
116
117         if (DBG) log("CallCard constructor...");
118         if (DBG) log("- this = " + this);
119         if (DBG) log("- context " + context + ", attrs " + attrs);
120
121         // Inflate the contents of this CallCard, and add it (to ourself) as a child.
122         LayoutInflater inflater = LayoutInflater.from(context);
123         inflater.inflate(
124                 R.layout.call_card,  // resource
125                 this,                // root
126                 true);
127
128         mApplication = PhoneApp.getInstance();
129
130         mCallTime = new CallTime(this);
131
132         // create a new object to track the state for the photo.
133         mPhotoTracker = new ContactsAsyncHelper.ImageTracker();
134     }
135
136     void setInCallScreenInstance(InCallScreen inCallScreen) {
137         mInCallScreen = inCallScreen;
138     }
139
140     public void onTickForCallTimeElapsed(long timeElapsed) {
141         // While a call is in progress, update the elapsed time shown
142         // onscreen.
143         updateElapsedTimeWidget(timeElapsed);
144     }
145
146     /* package */
147     void stopTimer() {
148         mCallTime.cancelTimer();
149     }
150
151     @Override
152     protected void onFinishInflate() {
153         super.onFinishInflate();
154
155         if (DBG) log("CallCard onFinishInflate(this = " + this + ")...");
156
157         mMainCallCard = (ViewGroup) findViewById(R.id.mainCallCard);
158         mOtherCallOngoingInfoArea = (ViewGroup) findViewById(R.id.otherCallOngoingInfoArea);
159         mOtherCallOnHoldInfoArea = (ViewGroup) findViewById(R.id.otherCallOnHoldInfoArea);
160
161         // "Upper" and "lower" title widgets
162         mUpperTitle = (TextView) findViewById(R.id.upperTitle);
163         mLowerTitleViewGroup = (ViewGroup) findViewById(R.id.lowerTitleViewGroup);
164         mLowerTitle = (TextView) findViewById(R.id.lowerTitle);
165         mLowerTitleIcon = (ImageView) findViewById(R.id.lowerTitleIcon);
166         mElapsedTime = (TextView) findViewById(R.id.elapsedTime);
167
168         // Text colors
169         mTextColorConnected = getResources().getColor(R.color.incall_textConnected);
170         mTextColorConnectedBluetooth =
171                 getResources().getColor(R.color.incall_textConnectedBluetooth);
172         mTextColorEnded = getResources().getColor(R.color.incall_textEnded);
173         mTextColorOnHold = getResources().getColor(R.color.incall_textOnHold);
174
175         // "Caller info" area, including photo / name / phone numbers / etc
176         mPhoto = (ImageView) findViewById(R.id.photo);
177         mName = (TextView) findViewById(R.id.name);
178         mPhoneNumber = (TextView) findViewById(R.id.phoneNumber);
179         mLabel = (TextView) findViewById(R.id.label);
180
181         // "Other call" info area
182         mOtherCallOngoingIcon = (ImageView) findViewById(R.id.otherCallOngoingIcon);
183         mOtherCallOngoingName = (TextView) findViewById(R.id.otherCallOngoingName);
184         mOtherCallOngoingStatus = (TextView) findViewById(R.id.otherCallOngoingStatus);
185         mOtherCallOnHoldName = (TextView) findViewById(R.id.otherCallOnHoldName);
186         mOtherCallOnHoldStatus = (TextView) findViewById(R.id.otherCallOnHoldStatus);
187
188         // Menu Button hint
189         mMenuButtonHint = (TextView) findViewById(R.id.menuButtonHint);
190     }
191
192     /**
193      * Updates the state of all UI elements on the CallCard, based on the
194      * current state of the phone.
195      */
196     void updateState(Phone phone) {
197         if (DBG) log("updateState(" + phone + ")...");
198
199         // Update some internal state based on the current state of the phone.
200         // TODO: This code, and updateForegroundCall() / updateRingingCall(),
201         // can probably still be simplified some more.
202
203         Phone.State state = phone.getState();  // IDLE, RINGING, or OFFHOOK
204         if (state == Phone.State.RINGING) {
205             // A phone call is ringing *or* call waiting
206             // (ie. another call may also be active as well.)
207             updateRingingCall(phone);
208         } else if (state == Phone.State.OFFHOOK) {
209             // The phone is off hook. At least one call exists that is
210             // dialing, active, or holding, and no calls are ringing or waiting.
211             updateForegroundCall(phone);
212         } else {
213             // The phone state is IDLE!
214             //
215             // The most common reason for this is if a call just
216             // ended: the phone will be idle, but we *will* still
217             // have a call in the DISCONNECTED state:
218             Call fgCall = phone.getForegroundCall();
219             Call bgCall = phone.getBackgroundCall();
220             if ((fgCall.getState() == Call.State.DISCONNECTED)
221                 || (bgCall.getState() == Call.State.DISCONNECTED)) {
222                 // In this case, we want the main CallCard to display
223                 // the "Call ended" state.  The normal "foreground call"
224                 // code path handles that.
225                 updateForegroundCall(phone);
226             } else {
227                 // We don't have any DISCONNECTED calls, which means
228                 // that the phone is *truly* idle.
229                 //
230                 // It's very rare to be on the InCallScreen at all in this
231                 // state, but it can happen in some cases:
232                 // - A stray onPhoneStateChanged() event came in to the
233                 //   InCallScreen *after* it was dismissed.
234                 // - We're allowed to be on the InCallScreen because
235                 //   an MMI or USSD is running, but there's no actual "call"
236                 //   to display.
237                 // - We're displaying an error dialog to the user
238                 //   (explaining why the call failed), so we need to stay on
239                 //   the InCallScreen so that the dialog will be visible.
240                 //
241                 // In these cases, put the callcard into a sane but "blank" state:
242                 updateNoCall(phone);
243             }
244         }
245     }
246
247     /**
248      * Updates the UI for the state where the phone is in use, but not ringing.
249      */
250     private void updateForegroundCall(Phone phone) {
251         if (DBG) log("updateForegroundCall()...");
252
253         Call fgCall = phone.getForegroundCall();
254         Call bgCall = phone.getBackgroundCall();
255
256         if (fgCall.isIdle() && !fgCall.hasConnections()) {
257             if (DBG) log("updateForegroundCall: no active call, show holding call");
258             // TODO: make sure this case agrees with the latest UI spec.
259
260             // Display the background call in the main info area of the
261             // CallCard, since there is no foreground call.  Note that
262             // displayMainCallStatus() will notice if the call we passed in is on
263             // hold, and display the "on hold" indication.
264             fgCall = bgCall;
265
266             // And be sure to not display anything in the "on hold" box.
267             bgCall = null;
268         }
269
270         displayMainCallStatus(phone, fgCall);
271
272         if (phone.getPhoneName().equals("CDMA")) {
273             if (mApplication.cdmaPhoneCallState.getCurrentCallState()
274                     == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
275                 displayOnHoldCallStatus(phone, fgCall);
276             } else {
277                 //This is required so that even if a background call is not present
278                 // we need to clean up the background call area.
279                 displayOnHoldCallStatus(phone, bgCall);
280             }
281         } else {
282             displayOnHoldCallStatus(phone, bgCall);
283         }
284
285         displayOngoingCallStatus(phone, null);
286     }
287
288     /**
289      * Updates the UI for the "generic call" state, where the phone is in
290      * use but we don't know any specific details about the state of the
291      * call (like who you're talking to, or how many lines are in use.)
292      */
293     private void updateGenericCall(Phone phone) {
294         if (DBG) log("updateForegroundCall()...");
295
296         Call fgCall = phone.getForegroundCall();
297
298         // Display the special "generic" state in the main call area:
299         displayMainCallGeneric(phone, fgCall);
300
301         // And hide the "other call" info areas:
302         displayOnHoldCallStatus(phone, null);
303         displayOngoingCallStatus(phone, null);
304     }
305
306     /**
307      * Updates the UI for the state where an incoming call is ringing (or
308      * call waiting), regardless of whether the phone's already offhook.
309      */
310     private void updateRingingCall(Phone phone) {
311         if (DBG) log("updateRingingCall()...");
312
313         Call ringingCall = phone.getRingingCall();
314         Call fgCall = phone.getForegroundCall();
315         Call bgCall = phone.getBackgroundCall();
316
317         displayMainCallStatus(phone, ringingCall);
318         displayOnHoldCallStatus(phone, bgCall);
319         displayOngoingCallStatus(phone, fgCall);
320     }
321
322     /**
323      * Updates the UI for the state where the phone is not in use.
324      * This is analogous to updateForegroundCall() and updateRingingCall(),
325      * but for the (uncommon) case where the phone is
326      * totally idle.  (See comments in updateState() above.)
327      *
328      * This puts the callcard into a sane but "blank" state.
329      */
330     private void updateNoCall(Phone phone) {
331         if (DBG) log("updateNoCall()...");
332
333         displayMainCallStatus(phone, null);
334         displayOnHoldCallStatus(phone, null);
335         displayOngoingCallStatus(phone, null);
336     }
337
338     /**
339      * Updates the main block of caller info on the CallCard
340      * (ie. the stuff in the mainCallCard block) based on the specified Call.
341      */
342     private void displayMainCallStatus(Phone phone, Call call) {
343         if (DBG) log("displayMainCallStatus(phone " + phone
344                      + ", call " + call + ")...");
345
346         if (call == null) {
347             // There's no call to display, presumably because the phone is idle.
348             mMainCallCard.setVisibility(View.GONE);
349             return;
350         }
351         mMainCallCard.setVisibility(View.VISIBLE);
352
353         Call.State state = call.getState();
354         if (DBG) log("  - call.state: " + call.getState());
355
356         int callCardBackgroundResid = 0;
357
358         // Background frame resources are different between portrait/landscape.
359         // TODO: Don't do this manually.  Instead let the resource system do
360         // it: just move the *_land assets over to the res/drawable-land
361         // directory (but with the same filename as the corresponding
362         // portrait asset.)
363         boolean landscapeMode = InCallScreen.ConfigurationHelper.isLandscape();
364
365         // Background images are also different if Bluetooth is active.
366         final boolean bluetoothActive = mApplication.showBluetoothIndication();
367
368         switch (state) {
369             case ACTIVE:
370                 if (bluetoothActive) {
371                     callCardBackgroundResid =
372                             landscapeMode ? R.drawable.incall_frame_bluetooth_tall_land
373                             : R.drawable.incall_frame_bluetooth_tall_port;
374                 } else {
375                     callCardBackgroundResid =
376                             landscapeMode ? R.drawable.incall_frame_connected_tall_land
377                             : R.drawable.incall_frame_connected_tall_port;
378                 }
379
380                 // update timer field
381                 if (DBG) log("displayMainCallStatus: start periodicUpdateTimer");
382                 mCallTime.setActiveCallMode(call);
383                 mCallTime.reset();
384                 mCallTime.periodicUpdateTimer();
385
386                 break;
387
388             case HOLDING:
389                 callCardBackgroundResid =
390                         landscapeMode ? R.drawable.incall_frame_hold_tall_land
391                         : R.drawable.incall_frame_hold_tall_port;
392
393                 // update timer field
394                 mCallTime.cancelTimer();
395
396                 break;
397
398             case DISCONNECTED:
399                 callCardBackgroundResid =
400                         landscapeMode ? R.drawable.incall_frame_ended_tall_land
401                         : R.drawable.incall_frame_ended_tall_port;
402
403                 // Stop getting timer ticks from this call
404                 mCallTime.cancelTimer();
405
406                 break;
407
408             case DIALING:
409             case ALERTING:
410                 if (bluetoothActive) {
411                     callCardBackgroundResid =
412                             landscapeMode ? R.drawable.incall_frame_bluetooth_tall_land
413                             : R.drawable.incall_frame_bluetooth_tall_port;
414                 } else {
415                     callCardBackgroundResid =
416                             landscapeMode ? R.drawable.incall_frame_normal_tall_land
417                             : R.drawable.incall_frame_normal_tall_port;
418                 }
419
420                 // Stop getting timer ticks from a previous call
421                 mCallTime.cancelTimer();
422
423                 break;
424
425             case INCOMING:
426             case WAITING:
427                 if (bluetoothActive) {
428                     callCardBackgroundResid =
429                             landscapeMode ? R.drawable.incall_frame_bluetooth_tall_land
430                             : R.drawable.incall_frame_bluetooth_tall_port;
431                 } else {
432                     callCardBackgroundResid =
433                             landscapeMode ? R.drawable.incall_frame_normal_tall_land
434                             : R.drawable.incall_frame_normal_tall_port;
435                 }
436
437                 // Stop getting timer ticks from a previous call
438                 mCallTime.cancelTimer();
439
440                 break;
441
442             case IDLE:
443                 // The "main CallCard" should never be trying to display
444                 // an idle call!  In updateState(), if the phone is idle,
445                 // we call updateNoCall(), which means that we shouldn't
446                 // have passed a call into this method at all.
447                 Log.w(LOG_TAG, "displayMainCallStatus: IDLE call in the main call card!");
448
449                 // (It is possible, though, that we had a valid call which
450                 // became idle *after* the check in updateState() but
451                 // before we get here...  So continue the best we can,
452                 // with whatever (stale) info we can get from the
453                 // passed-in Call object.)
454
455                 break;
456
457             default:
458                 Log.w(LOG_TAG, "displayMainCallStatus: unexpected call state: " + state);
459                 break;
460         }
461
462         // Set the background frame color based on the state of the call.
463         setMainCallCardBackgroundResource(callCardBackgroundResid);
464         // (Text colors are set in updateCardTitleWidgets().)
465
466         updateCardTitleWidgets(phone, call);
467
468         if (PhoneUtils.isConferenceCall(call)) {
469             // Update onscreen info for a conference call.
470             updateDisplayForConference();
471         } else {
472             // Update onscreen info for a regular call (which presumably
473             // has only one connection.)
474             Connection conn = null;
475             if (phone.getPhoneName().equals("CDMA")) {
476                 conn = call.getLatestConnection();
477             } else { // GSM.
478                 conn = call.getEarliestConnection();
479             }
480
481             if (conn == null) {
482                 if (DBG) log("displayMainCallStatus: connection is null, using default values.");
483                 // if the connection is null, we run through the behaviour
484                 // we had in the past, which breaks down into trivial steps
485                 // with the current implementation of getCallerInfo and
486                 // updateDisplayForPerson.
487                 CallerInfo info = PhoneUtils.getCallerInfo(getContext(), null /* conn */);
488                 updateDisplayForPerson(info, Connection.PRESENTATION_ALLOWED, false, call);
489             } else {
490                 if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
491                 int presentation = conn.getNumberPresentation();
492
493                 // make sure that we only make a new query when the current
494                 // callerinfo differs from what we've been requested to display.
495                 boolean runQuery = true;
496                 Object o = conn.getUserData();
497                 if (o instanceof PhoneUtils.CallerInfoToken) {
498                     runQuery = mPhotoTracker.isDifferentImageRequest(
499                             ((PhoneUtils.CallerInfoToken) o).currentInfo);
500                 } else {
501                     runQuery = mPhotoTracker.isDifferentImageRequest(conn);
502                 }
503
504                 if (runQuery) {
505                     if (DBG) log("- displayMainCallStatus: starting CallerInfo query...");
506                     PhoneUtils.CallerInfoToken info =
507                             PhoneUtils.startGetCallerInfo(getContext(), conn, this, call);
508                     updateDisplayForPerson(info.currentInfo, presentation, !info.isFinal, call);
509                 } else {
510                     // No need to fire off a new query.  We do still need
511                     // to update the display, though (since we might have
512                     // previously been in the "conference call" state.)
513                     if (DBG) log("- displayMainCallStatus: using data we already have...");
514                     if (o instanceof CallerInfo) {
515                         CallerInfo ci = (CallerInfo) o;
516                         // Update CNAP information if Phone state change occurred
517                         ci.cnapName = conn.getCnapName();
518                         ci.numberPresentation = conn.getNumberPresentation();
519                         ci.namePresentation = conn.getCnapNamePresentation();
520                         if (DBG) log("- displayMainCallStatus: CNAP data from Connection: "
521                                 + "CNAP name=" + ci.cnapName
522                                 + ", Number/Name Presentation=" + ci.numberPresentation);
523                         if (DBG) log("   ==> Got CallerInfo; updating display: ci = " + ci);
524                         updateDisplayForPerson(ci, presentation, false, call);
525                     } else if (o instanceof PhoneUtils.CallerInfoToken){
526                         CallerInfo ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
527                         if (DBG) log("- displayMainCallStatus: CNAP data from Connection: "
528                                 + "CNAP name=" + ci.cnapName
529                                 + ", Number/Name Presentation=" + ci.numberPresentation);
530                         if (DBG) log("   ==> Got CallerInfoToken; updating display: ci = " + ci);
531                         updateDisplayForPerson(ci, presentation, true, call);
532                     } else {
533                         Log.w(LOG_TAG, "displayMainCallStatus: runQuery was false, "
534                               + "but we didn't have a cached CallerInfo object!  o = " + o);
535                         // TODO: any easy way to recover here (given that
536                         // the CallCard is probably displaying stale info
537                         // right now?)  Maybe force the CallCard into the
538                         // "Unknown" state?
539                     }
540                 }
541             }
542         }
543
544         // In some states we override the "photo" ImageView to be an
545         // indication of the current state, rather than displaying the
546         // regular photo as set above.
547         updatePhotoForCallState(call);
548     }
549
550     /**
551      * Version of displayMainCallStatus() that sets the main call area
552      * into the "generic" state.
553      * @see displayMainCallStatus
554      */
555     private void displayMainCallGeneric(Phone phone, Call call) {
556         if (DBG) log("displayMainCallGeneric(phone " + phone
557                      + ", call " + call + ")...");
558
559         mMainCallCard.setVisibility(View.VISIBLE);
560
561         // Background frame resources are different between portrait/landscape.
562         // TODO: Don't do this manually.  Instead let the resource system do
563         // it: just move the *_land assets over to the res/drawable-land
564         // directory (but with the same filename as the corresponding
565         // portrait asset.)
566         boolean landscapeMode = InCallScreen.ConfigurationHelper.isLandscape();
567
568         // Background images are also different if Bluetooth is active.
569         final boolean bluetoothActive = mApplication.showBluetoothIndication();
570
571         int callCardBackgroundResid = 0;
572         if (bluetoothActive) {
573             callCardBackgroundResid =
574                     landscapeMode ? R.drawable.incall_frame_bluetooth_tall_land
575                     : R.drawable.incall_frame_bluetooth_tall_port;
576         } else {
577             callCardBackgroundResid =
578                     landscapeMode ? R.drawable.incall_frame_connected_tall_land
579                     : R.drawable.incall_frame_connected_tall_port;
580         }
581
582         // Set the background frame color based on the state of the call.
583         setMainCallCardBackgroundResource(callCardBackgroundResid);
584         // (Text colors are set in updateCardTitleWidgets().)
585
586         // Update timer field:
587         // TODO(CDMA): Need to confirm that we can trust the time info
588         // from the passed-in Call object, even though the call is "generic".
589         if (DBG) log("displayMainCallStatus: start periodicUpdateTimer");
590         mCallTime.setActiveCallMode(call);
591         mCallTime.reset();
592         mCallTime.periodicUpdateTimer();
593
594         updateCardTitleWidgets(phone, call);
595         updateDisplayForGenericCall();
596     }
597
598     /**
599      * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
600      * refreshes the CallCard data when it called.
601      */
602     public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
603         if (DBG) log("onQueryComplete: token " + token + ", cookie " + cookie + ", ci " + ci);
604
605         if (cookie instanceof Call) {
606             // grab the call object and update the display for an individual call,
607             // as well as the successive call to update image via call state.
608             // If the object is a textview instead, we update it as we need to.
609             if (DBG) log("callerinfo query complete, updating ui from displayMainCallStatus()");
610             Call call = (Call) cookie;
611             Connection conn = call.getEarliestConnection();
612             PhoneUtils.CallerInfoToken cit =
613                    PhoneUtils.startGetCallerInfo(getContext(), conn, this, null);
614             int presentation = conn.getNumberPresentation();
615             if (DBG) log("- onQueryComplete: presentation=" + presentation
616                     + ", contactExists=" + ci.contactExists);
617             // Depending on whether there was a contact match or not, we want to pass in different
618             // CallerInfo (for CNAP). Therefore if ci.contactExists then use the ci passed in.
619             // Otherwise, regenerate the CIT from the Connection and use the CallerInfo from there.
620             if (ci.contactExists) {
621                 updateDisplayForPerson(ci, Connection.PRESENTATION_ALLOWED, false, call);
622             } else {
623                 updateDisplayForPerson(cit.currentInfo, presentation, false, call);
624             }
625             updatePhotoForCallState(call);
626
627         } else if (cookie instanceof TextView){
628             if (DBG) log("callerinfo query complete, updating ui from ongoing or onhold");
629             ((TextView) cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, mContext));
630         }
631     }
632
633     /**
634      * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface.
635      * make sure that the call state is reflected after the image is loaded.
636      */
637     public void onImageLoadComplete(int token, Object cookie, ImageView iView,
638             boolean imagePresent){
639         if (cookie != null) {
640             updatePhotoForCallState((Call) cookie);
641         }
642     }
643
644     /**
645      * Updates the "upper" and "lower" titles based on the current state of this call.
646      */
647     private void updateCardTitleWidgets(Phone phone, Call call) {
648         if (DBG) log("updateCardTitleWidgets(call " + call + ")...");
649         Call.State state = call.getState();
650
651         // TODO: Still need clearer spec on exactly how title *and* status get
652         // set in all states.  (Then, given that info, refactor the code
653         // here to be more clear about exactly which widgets on the card
654         // need to be set.)
655
656         // Normal "foreground" call card:
657         String cardTitle = getTitleForCallCard(call);
658
659         if (DBG) log("updateCardTitleWidgets: " + cardTitle);
660
661         // We display *either* the "upper title" or the "lower title", but
662         // never both.
663
664         if (state == Call.State.ACTIVE) {
665             final boolean bluetoothActive = mApplication.showBluetoothIndication();
666             int ongoingCallIcon = bluetoothActive ? R.drawable.ic_incall_ongoing_bluetooth
667                     : R.drawable.ic_incall_ongoing;
668             int textColor = bluetoothActive ? mTextColorConnectedBluetooth : mTextColorConnected;
669
670             if (mApplication.phone.getPhoneName().equals("CDMA")) {
671                // Check if the "Dialing" 3Way call needs to be displayed
672                // as the Foreground Call state still remains ACTIVE
673                if (mApplication.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
674                     // Use the "upper title":
675                     mUpperTitle.setText(cardTitle);
676                     mLowerTitleViewGroup.setVisibility(View.INVISIBLE);
677                } else {
678                     // Use the "lower title" (in green).
679                     mLowerTitleViewGroup.setVisibility(View.VISIBLE);
680                     mLowerTitle.setText(cardTitle);
681                     mLowerTitleIcon.setImageResource(ongoingCallIcon);
682                     mLowerTitle.setTextColor(textColor);
683                     mElapsedTime.setTextColor(textColor);
684                     mUpperTitle.setText("");
685                }
686             } else { // GSM
687                 // Use the "lower title" (in green).
688                 mLowerTitleViewGroup.setVisibility(View.VISIBLE);
689                 mLowerTitleIcon.setImageResource(ongoingCallIcon);
690                 mLowerTitle.setText(cardTitle);
691                 mLowerTitle.setTextColor(textColor);
692                 mElapsedTime.setTextColor(textColor);
693                 setUpperTitle("");
694             }
695         } else if (state == Call.State.DISCONNECTED) {
696             // Use the "lower title" (in red).
697             // TODO: We may not *always* want to use the lower title for
698             // the DISCONNECTED state.  "Error" states like BUSY or
699             // CONGESTION (see getCallFailedString()) should probably go
700             // in the upper title, for example.  In fact, the lower title
701             // should probably be used *only* for the normal "Call ended"
702             // case.
703             mLowerTitleViewGroup.setVisibility(View.VISIBLE);
704             mLowerTitleIcon.setImageResource(R.drawable.ic_incall_end);
705             mLowerTitle.setText(cardTitle);
706             mLowerTitle.setTextColor(mTextColorEnded);
707             mElapsedTime.setTextColor(mTextColorEnded);
708             setUpperTitle("");
709         } else {
710             // All other states (DIALING, INCOMING, etc.) use the "upper title":
711             setUpperTitle(cardTitle, state);
712             mLowerTitleViewGroup.setVisibility(View.INVISIBLE);
713         }
714
715         // Draw the onscreen "elapsed time" indication EXCEPT if we're in
716         // the "Call ended" state.  (In that case, don't touch the
717         // mElapsedTime widget, so we continue to see the elapsed time of
718         // the call that just ended.)
719         if (call.getState() == Call.State.DISCONNECTED) {
720             // "Call ended" state -- don't touch the onscreen elapsed time.
721         } else {
722             long duration = CallTime.getCallDuration(call);  // msec
723             updateElapsedTimeWidget(duration / 1000);
724             // Also see onTickForCallTimeElapsed(), which updates this
725             // widget once per second while the call is active.
726         }
727     }
728
729     /**
730      * Updates mElapsedTime based on the specified number of seconds.
731      * A timeElapsed value of zero means to not show an elapsed time at all.
732      */
733     private void updateElapsedTimeWidget(long timeElapsed) {
734         // if (DBG) log("updateElapsedTimeWidget: " + timeElapsed);
735         if (timeElapsed == 0) {
736             mElapsedTime.setText("");
737         } else {
738             mElapsedTime.setText(DateUtils.formatElapsedTime(timeElapsed));
739         }
740     }
741
742     /**
743      * Returns the "card title" displayed at the top of a foreground
744      * ("active") CallCard to indicate the current state of this call, like
745      * "Dialing" or "In call" or "On hold".  A null return value means that
746      * there's no title string for this state.
747      */
748     private String getTitleForCallCard(Call call) {
749         String retVal = null;
750         Call.State state = call.getState();
751         Context context = getContext();
752         int resId;
753
754         if (DBG) log("- getTitleForCallCard(Call " + call + ")...");
755
756         switch (state) {
757             case IDLE:
758                 break;
759
760             case ACTIVE:
761                 // Title is "Call in progress".  (Note this appears in the
762                 // "lower title" area of the CallCard.)
763                 if (mApplication.phone.getPhoneName().equals("CDMA")) {
764                     if (mApplication.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
765                         retVal = context.getString(R.string.card_title_dialing);
766                     } else {
767                         retVal = context.getString(R.string.card_title_in_progress);
768                     }
769                 } else { //GSM
770                     retVal = context.getString(R.string.card_title_in_progress);
771                 }
772                 break;
773
774             case HOLDING:
775                 retVal = context.getString(R.string.card_title_on_hold);
776                 // TODO: if this is a conference call on hold,
777                 // maybe have a special title here too?
778                 break;
779
780             case DIALING:
781             case ALERTING:
782                 retVal = context.getString(R.string.card_title_dialing);
783                 break;
784
785             case INCOMING:
786             case WAITING:
787                 retVal = context.getString(R.string.card_title_incoming_call);
788                 break;
789
790             case DISCONNECTED:
791                 retVal = getCallFailedString(call);
792                 break;
793         }
794
795         if (DBG) log("  ==> result: " + retVal);
796         return retVal;
797     }
798
799     /**
800      * Updates the "on hold" box in the "other call" info area
801      * (ie. the stuff in the otherCallOnHoldInfo block)
802      * based on the specified Call.
803      * Or, clear out the "on hold" box if the specified call
804      * is null or idle.
805      */
806     private void displayOnHoldCallStatus(Phone phone, Call call) {
807         if (DBG) log("displayOnHoldCallStatus(call =" + call + ")...");
808         if (call == null) {
809             mOtherCallOnHoldInfoArea.setVisibility(View.GONE);
810             return;
811         }
812
813         String name = null;
814         Call.State state = call.getState();
815         switch (state) {
816             case HOLDING:
817                 // Ok, there actually is a background call on hold.
818                 // Display the "on hold" box.
819
820                 // First, see if we need to query.
821                 if (PhoneUtils.isConferenceCall(call)) {
822                     if (DBG) log("==> conference call.");
823                     name = getContext().getString(R.string.confCall);
824                 } else {
825                     // perform query and update the name temporarily
826                     // make sure we hand the textview we want updated to the
827                     // callback function.
828                     if (DBG) log("==> NOT a conf call; call startGetCallerInfo...");
829                     PhoneUtils.CallerInfoToken info = PhoneUtils.startGetCallerInfo(
830                             getContext(), call, this, mOtherCallOnHoldName);
831                     name = PhoneUtils.getCompactNameFromCallerInfo(info.currentInfo, getContext());
832                 }
833
834                 mOtherCallOnHoldName.setText(name);
835
836                 // The call here is always "on hold", so use the orange "hold" frame
837                 // and orange text color:
838                 setOnHoldInfoAreaBackgroundResource(R.drawable.incall_frame_hold_short);
839                 mOtherCallOnHoldName.setTextColor(mTextColorOnHold);
840                 mOtherCallOnHoldStatus.setTextColor(mTextColorOnHold);
841
842                 mOtherCallOnHoldInfoArea.setVisibility(View.VISIBLE);
843
844                 break;
845
846             case ACTIVE:
847                 // CDMA: This is because in CDMA when the user originates the second call,
848                 // although the Foreground call state is still ACTIVE in reality the network
849                 // put the first call on hold.
850                 if (mApplication.phone.getPhoneName().equals("CDMA")) {
851                     List<Connection> connections = call.getConnections();
852                     if (connections.size() > 2) {
853                         // This means that current Mobile Originated call is the not the first 3-Way
854                         // call the user is making, which in turn tells the PhoneApp that we no
855                         // longer know which previous caller/party had dropped out before the user
856                         // made this call.
857                         name = getContext().getString(R.string.card_title_in_call);
858                     } else {
859                         // This means that the current Mobile Originated call IS the first 3-Way
860                         // and hence we display the first callers/party's info here.
861                         Connection conn = call.getEarliestConnection();
862                         PhoneUtils.CallerInfoToken info = PhoneUtils.startGetCallerInfo(
863                                 getContext(), conn, this, mOtherCallOnHoldName);
864
865                         name = PhoneUtils.getCompactNameFromCallerInfo(info.currentInfo,
866                                 getContext());
867                     }
868
869                     mOtherCallOnHoldName.setText(name);
870
871                     // The call here is either in Callwaiting or 3way, use the orange "hold" frame
872                     // and orange text color:
873                     setOnHoldInfoAreaBackgroundResource(R.drawable.incall_frame_hold_short);
874                     mOtherCallOnHoldName.setTextColor(mTextColorOnHold);
875                     mOtherCallOnHoldStatus.setTextColor(mTextColorOnHold);
876                     mOtherCallOnHoldInfoArea.setVisibility(View.VISIBLE);
877                 }
878                 break;
879
880             default:
881                 // There's actually no call on hold.  (Presumably this call's
882                 // state is IDLE, since any other state is meaningless for the
883                 // background call.)
884                 mOtherCallOnHoldInfoArea.setVisibility(View.GONE);
885                 break;
886         }
887     }
888
889     /**
890      * Updates the "Ongoing call" box in the "other call" info area
891      * (ie. the stuff in the otherCallOngoingInfo block)
892      * based on the specified Call.
893      * Or, clear out the "ongoing call" box if the specified call
894      * is null or idle.
895      */
896     private void displayOngoingCallStatus(Phone phone, Call call) {
897         if (DBG) log("displayOngoingCallStatus(call =" + call + ")...");
898         if (call == null) {
899             mOtherCallOngoingInfoArea.setVisibility(View.GONE);
900             return;
901         }
902
903         Call.State state = call.getState();
904         switch (state) {
905             case ACTIVE:
906             case DIALING:
907             case ALERTING:
908                 // Ok, there actually is an ongoing call.
909                 // Display the "ongoing call" box.
910                 String name;
911
912                 // First, see if we need to query.
913                 if (call.isGeneric()) {
914                     name = getContext().getString(R.string.card_title_in_call);
915                 } else if (PhoneUtils.isConferenceCall(call)) {
916                     name = getContext().getString(R.string.confCall);
917                 } else {
918                     // perform query and update the name temporarily
919                     // make sure we hand the textview we want updated to the
920                     // callback function.
921                     PhoneUtils.CallerInfoToken info = PhoneUtils.startGetCallerInfo(
922                             getContext(), call, this, mOtherCallOngoingName);
923                     name = PhoneUtils.getCompactNameFromCallerInfo(info.currentInfo, getContext());
924                 }
925
926                 mOtherCallOngoingName.setText(name);
927
928                 // This is an "ongoing" call: we normally use the green
929                 // background frame and text color, but we use blue
930                 // instead if bluetooth is in use.
931                 boolean bluetoothActive = mApplication.showBluetoothIndication();
932
933                 int ongoingCallBackground =
934                         bluetoothActive ? R.drawable.incall_frame_bluetooth_short
935                         : R.drawable.incall_frame_connected_short;
936                 setOngoingInfoAreaBackgroundResource(ongoingCallBackground);
937
938                 int ongoingCallIcon = bluetoothActive ? R.drawable.ic_incall_ongoing_bluetooth
939                         : R.drawable.ic_incall_ongoing;
940                 mOtherCallOngoingIcon.setImageResource(ongoingCallIcon);
941
942                 int textColor = bluetoothActive ? mTextColorConnectedBluetooth
943                         : mTextColorConnected;
944                 mOtherCallOngoingName.setTextColor(textColor);
945                 mOtherCallOngoingStatus.setTextColor(textColor);
946
947                 mOtherCallOngoingInfoArea.setVisibility(View.VISIBLE);
948
949                 break;
950
951             default:
952                 // There's actually no ongoing call.  (Presumably this call's
953                 // state is IDLE, since any other state is meaningless for the
954                 // foreground call.)
955                 mOtherCallOngoingInfoArea.setVisibility(View.GONE);
956                 break;
957         }
958     }
959
960
961     private String getCallFailedString(Call call) {
962         Connection c = call.getEarliestConnection();
963         int resID;
964
965         if (c == null) {
966             if (DBG) log("getCallFailedString: connection is null, using default values.");
967             // if this connection is null, just assume that the
968             // default case occurs.
969             resID = R.string.card_title_call_ended;
970         } else {
971
972             Connection.DisconnectCause cause = c.getDisconnectCause();
973
974             // TODO: The card *title* should probably be "Call ended" in all
975             // cases, but if the DisconnectCause was an error condition we should
976             // probably also display the specific failure reason somewhere...
977
978             switch (cause) {
979                 case BUSY:
980                     resID = R.string.callFailed_userBusy;
981                     break;
982
983                 case CONGESTION:
984                     resID = R.string.callFailed_congestion;
985                     break;
986
987                 case LOST_SIGNAL:
988                     resID = R.string.callFailed_noSignal;
989                     break;
990
991                 case LIMIT_EXCEEDED:
992                     resID = R.string.callFailed_limitExceeded;
993                     break;
994
995                 case POWER_OFF:
996                     resID = R.string.callFailed_powerOff;
997                     break;
998
999                 case ICC_ERROR:
1000                     resID = R.string.callFailed_simError;
1001                     break;
1002
1003                 case OUT_OF_SERVICE:
1004                     resID = R.string.callFailed_outOfService;
1005                     break;
1006
1007                 default:
1008                     resID = R.string.card_title_call_ended;
1009                     break;
1010             }
1011         }
1012         return getContext().getString(resID);
1013     }
1014
1015     /**
1016      * Updates the name / photo / number / label fields on the CallCard
1017      * based on the specified CallerInfo.
1018      *
1019      * If the current call is a conference call, use
1020      * updateDisplayForConference() instead.
1021      *
1022      * If the phone is in the "generic call" state, use
1023      * updateDisplayForGenericCall() instead.
1024      */
1025     private void updateDisplayForPerson(CallerInfo info,
1026                                         int presentation,
1027                                         boolean isTemporary,
1028                                         Call call) {
1029         if (DBG) log("updateDisplayForPerson(" + info + ")...");
1030
1031         // inform the state machine that we are displaying a photo.
1032         mPhotoTracker.setPhotoRequest(info);
1033         mPhotoTracker.setPhotoState(ContactsAsyncHelper.ImageTracker.DISPLAY_IMAGE);
1034
1035         String name;
1036         String displayNumber = null;
1037         String label = null;
1038         Uri personUri = null;
1039
1040         if (info != null) {
1041             // It appears that there is a small change in behaviour with the
1042             // PhoneUtils' startGetCallerInfo whereby if we query with an
1043             // empty number, we will get a valid CallerInfo object, but with
1044             // fields that are all null, and the isTemporary boolean input
1045             // parameter as true.
1046
1047             // In the past, we would see a NULL callerinfo object, but this
1048             // ends up causing null pointer exceptions elsewhere down the
1049             // line in other cases, so we need to make this fix instead. It
1050             // appears that this was the ONLY call to PhoneUtils
1051             // .getCallerInfo() that relied on a NULL CallerInfo to indicate
1052             // an unknown contact.
1053
1054             if (TextUtils.isEmpty(info.name)) {
1055                 if (TextUtils.isEmpty(info.phoneNumber)) {
1056                     name =  getPresentationString(presentation);
1057                 } else if (presentation != Connection.PRESENTATION_ALLOWED) {
1058                     // This case should never happen since the network should never send a phone #
1059                     // AND a restricted presentation. However we leave it here in case of weird
1060                     // network behavior
1061                     name = getPresentationString(presentation);
1062                 } else if (!TextUtils.isEmpty(info.cnapName)) {
1063                     name = info.cnapName;
1064                     info.name = info.cnapName;
1065                     displayNumber = info.phoneNumber;
1066                 } else {
1067                     name = info.phoneNumber;
1068                 }
1069             } else {
1070                 if (presentation != Connection.PRESENTATION_ALLOWED) {
1071                     // This case should never happen since the network should never send a name
1072                     // AND a restricted presentation. However we leave it here in case of weird
1073                     // network behavior
1074                     name = getPresentationString(presentation);
1075                 } else {
1076                     name = info.name;
1077                     displayNumber = info.phoneNumber;
1078                     label = info.phoneLabel;
1079                 }
1080             }
1081             personUri = ContentUris.withAppendedId(People.CONTENT_URI, info.person_id);
1082         } else {
1083             name =  getPresentationString(presentation);
1084         }
1085
1086         if (call.isGeneric()) {
1087             mName.setText(R.string.card_title_in_call);
1088         } else {
1089             mName.setText(name);
1090         }
1091         mName.setVisibility(View.VISIBLE);
1092
1093         // Update mPhoto
1094         // if the temporary flag is set, we know we'll be getting another call after
1095         // the CallerInfo has been correctly updated.  So, we can skip the image
1096         // loading until then.
1097
1098         // If the photoResource is filled in for the CallerInfo, (like with the
1099         // Emergency Number case), then we can just set the photo image without
1100         // requesting for an image load. Please refer to CallerInfoAsyncQuery.java
1101         // for cases where CallerInfo.photoResource may be set.  We can also avoid
1102         // the image load step if the image data is cached.
1103         if (isTemporary && (info == null || !info.isCachedPhotoCurrent)) {
1104             mPhoto.setVisibility(View.INVISIBLE);
1105         } else if (info != null && info.photoResource != 0){
1106             showImage(mPhoto, info.photoResource);
1107         } else if (!showCachedImage(mPhoto, info)) {
1108             // Load the image with a callback to update the image state.
1109             // Use a placeholder image value of -1 to indicate no image.
1110             ContactsAsyncHelper.updateImageViewWithContactPhotoAsync(info, 0, this, call,
1111                     getContext(), mPhoto, personUri, -1);
1112         }
1113         if (displayNumber != null && !call.isGeneric()) {
1114             mPhoneNumber.setText(displayNumber);
1115             mPhoneNumber.setVisibility(View.VISIBLE);
1116         } else {
1117             mPhoneNumber.setVisibility(View.GONE);
1118         }
1119
1120         if (label != null) {
1121             mLabel.setText(label);
1122             mLabel.setVisibility(View.VISIBLE);
1123         } else {
1124             mLabel.setVisibility(View.GONE);
1125         }
1126     }
1127
1128
1129     private String getPresentationString(int presentation) {
1130         String name = getContext().getString(R.string.unknown);
1131         if (presentation == Connection.PRESENTATION_RESTRICTED) {
1132             name = getContext().getString(R.string.private_num);
1133         } else if (presentation == Connection.PRESENTATION_PAYPHONE) {
1134             name = getContext().getString(R.string.payphone);
1135         }
1136         return name;
1137     }
1138
1139     /**
1140      * Updates the name / photo / number / label fields
1141      * for the special "conference call" state.
1142      *
1143      * If the current call has only a single connection, use
1144      * updateDisplayForPerson() instead.
1145      */
1146     private void updateDisplayForConference() {
1147         if (DBG) log("updateDisplayForConference()...");
1148
1149         if (mApplication.phone.getPhoneName().equals("CDMA")) {
1150             // This state corresponds to both 3-Way merged call and
1151             // Call Waiting accepted call.
1152             // Display only the "dialing" icon and no caller information cause in CDMA
1153             // as in this state the user does not really know which caller party he is talking to.
1154             showImage(mPhoto, R.drawable.picture_dialing);
1155             mName.setText(R.string.card_title_in_call);
1156         } else {
1157             // Display the "conference call" image in the photo slot,
1158             // with no other information.
1159             showImage(mPhoto, R.drawable.picture_conference);
1160             mName.setText(R.string.card_title_conf_call);
1161         }
1162
1163         mName.setVisibility(View.VISIBLE);
1164
1165         // TODO: For a conference call, the "phone number" slot is specced
1166         // to contain a summary of who's on the call, like "Bill Foldes
1167         // and Hazel Nutt" or "Bill Foldes and 2 others".
1168         // But for now, just hide it:
1169         mPhoneNumber.setVisibility(View.GONE);
1170
1171         mLabel.setVisibility(View.GONE);
1172
1173         // TODO: consider also showing names / numbers / photos of some of the
1174         // people on the conference here, so you can see that info without
1175         // having to click "Manage conference".  We probably have enough
1176         // space to show info for 2 people, at least.
1177         //
1178         // To do this, our caller would pass us the activeConnections
1179         // list, and we'd call PhoneUtils.getCallerInfo() separately for
1180         // each connection.
1181     }
1182
1183     /**
1184      * Updates the name / photo / number / label fields
1185      * for the special "generic call" state.
1186      * @see updateDisplayForPerson
1187      * @see updateDisplayForConference
1188      */
1189     private void updateDisplayForGenericCall() {
1190         if (DBG) log("updateDisplayForGenericCall()...");
1191
1192         // Display a generic "in-call" image in the photo slot, with no
1193         // other information.
1194
1195         showImage(mPhoto, R.drawable.picture_dialing);
1196
1197         mName.setVisibility(View.GONE);
1198         mPhoneNumber.setVisibility(View.GONE);
1199         mLabel.setVisibility(View.GONE);
1200     }
1201
1202     /**
1203      * Updates the CallCard "photo" IFF the specified Call is in a state
1204      * that needs a special photo (like "busy" or "dialing".)
1205      *
1206      * If the current call does not require a special image in the "photo"
1207      * slot onscreen, don't do anything, since presumably the photo image
1208      * has already been set (to the photo of the person we're talking, or
1209      * the generic "picture_unknown" image, or the "conference call"
1210      * image.)
1211      */
1212     private void updatePhotoForCallState(Call call) {
1213         if (DBG) log("updatePhotoForCallState(" + call + ")...");
1214         int photoImageResource = 0;
1215
1216         // Check for the (relatively few) telephony states that need a
1217         // special image in the "photo" slot.
1218         Call.State state = call.getState();
1219         switch (state) {
1220             case DISCONNECTED:
1221                 // Display the special "busy" photo for BUSY or CONGESTION.
1222                 // Otherwise (presumably the normal "call ended" state)
1223                 // leave the photo alone.
1224                 Connection c = call.getEarliestConnection();
1225                 // if the connection is null, we assume the default case,
1226                 // otherwise update the image resource normally.
1227                 if (c != null) {
1228                     Connection.DisconnectCause cause = c.getDisconnectCause();
1229                     if ((cause == Connection.DisconnectCause.BUSY)
1230                         || (cause == Connection.DisconnectCause.CONGESTION)) {
1231                         photoImageResource = R.drawable.picture_busy;
1232                     }
1233                 } else if (DBG) {
1234                     log("updatePhotoForCallState: connection is null, ignoring.");
1235                 }
1236
1237                 // TODO: add special images for any other DisconnectCauses?
1238                 break;
1239
1240             case DIALING:
1241             case ALERTING:
1242                 photoImageResource = R.drawable.picture_dialing;
1243                 break;
1244
1245             default:
1246                 // Leave the photo alone in all other states.
1247                 // If this call is an individual call, and the image is currently
1248                 // displaying a state, (rather than a photo), we'll need to update
1249                 // the image.
1250                 // This is for the case where we've been displaying the state and
1251                 // now we need to restore the photo.  This can happen because we
1252                 // only query the CallerInfo once, and limit the number of times
1253                 // the image is loaded. (So a state image may overwrite the photo
1254                 // and we would otherwise have no way of displaying the photo when
1255                 // the state goes away.)
1256
1257                 // if the photoResource field is filled-in in the Connection's
1258                 // caller info, then we can just use that instead of requesting
1259                 // for a photo load.
1260
1261                 // look for the photoResource if it is available.
1262                 CallerInfo ci = null;
1263                 {
1264                     Connection conn = null;
1265                     if (mApplication.phone.getPhoneName().equals("CDMA")) {
1266                         conn = call.getLatestConnection();
1267                     } else { // GSM.
1268                         conn = call.getEarliestConnection();
1269                     }
1270
1271                     if (conn != null) {
1272                         Object o = conn.getUserData();
1273                         if (o instanceof CallerInfo) {
1274                             ci = (CallerInfo) o;
1275                         } else if (o instanceof PhoneUtils.CallerInfoToken) {
1276                             ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
1277                         }
1278                     }
1279                 }
1280
1281                 if (ci != null) {
1282                     photoImageResource = ci.photoResource;
1283                 }
1284
1285                 // If no photoResource found, check to see if this is a conference call. If
1286                 // it is not a conference call:
1287                 //   1. Try to show the cached image
1288                 //   2. If the image is not cached, check to see if a load request has been
1289                 //      made already.
1290                 //   3. If the load request has not been made [DISPLAY_DEFAULT], start the
1291                 //      request and note that it has started by updating photo state with
1292                 //      [DISPLAY_IMAGE].
1293                 // Load requests started in (3) use a placeholder image of -1 to hide the
1294                 // image by default.  Please refer to CallerInfoAsyncQuery.java for cases
1295                 // where CallerInfo.photoResource may be set.
1296                 if (photoImageResource == 0) {
1297                     if (!PhoneUtils.isConferenceCall(call)) {
1298                         if (!showCachedImage(mPhoto, ci) && (mPhotoTracker.getPhotoState() ==
1299                                 ContactsAsyncHelper.ImageTracker.DISPLAY_DEFAULT)) {
1300                             ContactsAsyncHelper.updateImageViewWithContactPhotoAsync(ci,
1301                                     getContext(), mPhoto, mPhotoTracker.getPhotoUri(), -1);
1302                             mPhotoTracker.setPhotoState(
1303                                     ContactsAsyncHelper.ImageTracker.DISPLAY_IMAGE);
1304                         }
1305                     }
1306                 } else {
1307                     showImage(mPhoto, photoImageResource);
1308                     mPhotoTracker.setPhotoState(ContactsAsyncHelper.ImageTracker.DISPLAY_IMAGE);
1309                     return;
1310                 }
1311                 break;
1312         }
1313
1314         if (photoImageResource != 0) {
1315             if (DBG) log("- overrriding photo image: " + photoImageResource);
1316             showImage(mPhoto, photoImageResource);
1317             // Track the image state.
1318             mPhotoTracker.setPhotoState(ContactsAsyncHelper.ImageTracker.DISPLAY_DEFAULT);
1319         }
1320     }
1321
1322     /**
1323      * Try to display the cached image from the callerinfo object.
1324      *
1325      *  @return true if we were able to find the image in the cache, false otherwise.
1326      */
1327     private static final boolean showCachedImage (ImageView view, CallerInfo ci) {
1328         if ((ci != null) && ci.isCachedPhotoCurrent) {
1329             if (ci.cachedPhoto != null) {
1330                 showImage(view, ci.cachedPhoto);
1331             } else {
1332                 showImage(view, R.drawable.picture_unknown);
1333             }
1334             return true;
1335         }
1336         return false;
1337     }
1338
1339     /** Helper function to display the resource in the imageview AND ensure its visibility.*/
1340     private static final void showImage(ImageView view, int resource) {
1341         view.setImageResource(resource);
1342         view.setVisibility(View.VISIBLE);
1343     }
1344
1345     /** Helper function to display the drawable in the imageview AND ensure its visibility.*/
1346     private static final void showImage(ImageView view, Drawable drawable) {
1347         view.setImageDrawable(drawable);
1348         view.setVisibility(View.VISIBLE);
1349     }
1350
1351     /**
1352      * Intercepts (and discards) any touch events to the CallCard.
1353      */
1354     @Override
1355     public boolean dispatchTouchEvent(MotionEvent ev) {
1356         // if (DBG) log("CALLCARD: dispatchTouchEvent(): ev = " + ev);
1357
1358         // We *never* let touch events get thru to the UI inside the
1359         // CallCard, since there's nothing touchable there.
1360         return true;
1361     }
1362
1363     /**
1364      * Sets the background drawable of the main call card.
1365      */
1366     private void setMainCallCardBackgroundResource(int resid) {
1367         mMainCallCard.setBackgroundResource(resid);
1368     }
1369
1370     /**
1371      * Sets the background drawable of the "ongoing call" info area.
1372      */
1373     private void setOngoingInfoAreaBackgroundResource(int resid) {
1374         mOtherCallOngoingInfoArea.setBackgroundResource(resid);
1375     }
1376
1377     /**
1378      * Sets the background drawable of the "call on hold" info area.
1379      */
1380     private void setOnHoldInfoAreaBackgroundResource(int resid) {
1381         mOtherCallOnHoldInfoArea.setBackgroundResource(resid);
1382     }
1383
1384     /**
1385      * Returns the "Menu button hint" TextView (which is manipulated
1386      * directly by the InCallScreen.)
1387      * @see InCallScreen.updateMenuButtonHint()
1388      */
1389     /* package */ TextView getMenuButtonHint() {
1390         return mMenuButtonHint;
1391     }
1392
1393     /**
1394      * Updates anything about our View hierarchy or internal state
1395      * that needs to be different in landscape mode.
1396      *
1397      * @see InCallScreen.applyConfigurationToLayout()
1398      */
1399     /* package */ void updateForLandscapeMode() {
1400         if (DBG) log("updateForLandscapeMode()...");
1401
1402         // The main CallCard's minimum height is smaller in landscape mode
1403         // than in portrait mode.
1404         mMainCallCard.setMinimumHeight(MAIN_CALLCARD_MIN_HEIGHT_LANDSCAPE);
1405
1406         // Add some left and right margin to the top-level elements, since
1407         // there's no need to use the full width of the screen (which is
1408         // much wider in landscape mode.)
1409         setSideMargins(mMainCallCard, CALLCARD_SIDE_MARGIN_LANDSCAPE);
1410         setSideMargins(mOtherCallOngoingInfoArea, CALLCARD_SIDE_MARGIN_LANDSCAPE);
1411         setSideMargins(mOtherCallOnHoldInfoArea, CALLCARD_SIDE_MARGIN_LANDSCAPE);
1412
1413         // A couple of TextViews are slightly smaller in landscape mode.
1414         mUpperTitle.setTextSize(TITLE_TEXT_SIZE_LANDSCAPE);
1415     }
1416
1417     /**
1418      * Sets the left and right margins of the specified ViewGroup (whose
1419      * LayoutParams object which must inherit from
1420      * ViewGroup.MarginLayoutParams.)
1421      *
1422      * TODO: Is there already a convenience method like this somewhere?
1423      */
1424     private void setSideMargins(ViewGroup vg, int margin) {
1425         ViewGroup.MarginLayoutParams lp =
1426                 (ViewGroup.MarginLayoutParams) vg.getLayoutParams();
1427         // Equivalent to setting android:layout_marginLeft/Right in XML
1428         lp.leftMargin = margin;
1429         lp.rightMargin = margin;
1430         vg.setLayoutParams(lp);
1431     }
1432
1433     /**
1434      * Sets the CallCard "upper title" to a plain string, with no icon.
1435      */
1436     private void setUpperTitle(String title) {
1437         mUpperTitle.setText(title);
1438         mUpperTitle.setCompoundDrawables(null, null, null, null);
1439     }
1440
1441     /**
1442      * Sets the CallCard "upper title".  Also, depending on the passed-in
1443      * Call state, possibly display an icon along with the title.
1444      */
1445     private void setUpperTitle(String title, Call.State state) {
1446         mUpperTitle.setText(title);
1447
1448         int bluetoothIconId = 0;
1449         if (((state == Call.State.INCOMING) || (state == Call.State.WAITING))
1450                 && mApplication.showBluetoothIndication()) {
1451             // Display the special bluetooth icon also, if this is an incoming
1452             // call and the audio will be routed to bluetooth.
1453             bluetoothIconId = R.drawable.ic_incoming_call_bluetooth;
1454         }
1455
1456         mUpperTitle.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0);
1457         if (bluetoothIconId != 0) mUpperTitle.setCompoundDrawablePadding(5);
1458     }
1459
1460
1461     // Debugging / testing code
1462
1463     private void log(String msg) {
1464         Log.d(LOG_TAG, msg);
1465     }
1466
1467     private static void logErr(String msg) {
1468         Log.e(LOG_TAG, msg);
1469     }
1470 }