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