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