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