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