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