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