CallCardFragment.java revision 391bafb1527260cb67ca50327e7443c5d86fed6d
1/*
2 * Copyright (C) 2013 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
17package com.android.incallui;
18
19import android.content.Context;
20import android.graphics.drawable.Drawable;
21import android.os.Bundle;
22import android.telephony.DisconnectCause;
23import android.text.TextUtils;
24import android.view.Gravity;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.View.OnClickListener;
28import android.view.ViewGroup;
29import android.view.ViewStub;
30import android.view.accessibility.AccessibilityEvent;
31import android.widget.Button;
32import android.widget.ImageView;
33import android.widget.TextView;
34
35import com.android.contacts.common.util.ViewUtil;
36
37import java.util.List;
38
39/**
40 * Fragment for call card.
41 */
42public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPresenter.CallCardUi>
43        implements CallCardPresenter.CallCardUi {
44
45    // Primary caller info
46    private TextView mPhoneNumber;
47    private TextView mNumberLabel;
48    private TextView mPrimaryName;
49    private TextView mCallStateLabel;
50    private TextView mCallTypeLabel;
51    private ImageView mPhoto;
52    private TextView mElapsedTime;
53
54    // Secondary caller info
55    private ViewStub mSecondaryCallInfo;
56    private TextView mSecondaryCallName;
57    private ImageView mSecondaryPhoto;
58    private View mSecondaryPhotoOverlay;
59
60    private View mEndCallButton;
61
62    // Cached DisplayMetrics density.
63    private float mDensity;
64
65    @Override
66    CallCardPresenter.CallCardUi getUi() {
67        return this;
68    }
69
70    @Override
71    CallCardPresenter createPresenter() {
72        return new CallCardPresenter();
73    }
74
75    @Override
76    public void onCreate(Bundle savedInstanceState) {
77        super.onCreate(savedInstanceState);
78    }
79
80
81    @Override
82    public void onActivityCreated(Bundle savedInstanceState) {
83        super.onActivityCreated(savedInstanceState);
84
85        final CallList calls = CallList.getInstance();
86        final Call call = calls.getFirstCall();
87        getPresenter().init(getActivity(), call);
88    }
89
90    @Override
91    public View onCreateView(LayoutInflater inflater, ViewGroup container,
92            Bundle savedInstanceState) {
93        super.onCreateView(inflater, container, savedInstanceState);
94
95        mDensity = getResources().getDisplayMetrics().density;
96
97        return inflater.inflate(R.layout.call_card, container, false);
98    }
99
100    @Override
101    public void onViewCreated(View view, Bundle savedInstanceState) {
102        super.onViewCreated(view, savedInstanceState);
103
104        mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
105        mPrimaryName = (TextView) view.findViewById(R.id.name);
106        mNumberLabel = (TextView) view.findViewById(R.id.label);
107        mSecondaryCallInfo = (ViewStub) view.findViewById(R.id.secondary_call_info);
108        mPhoto = (ImageView) view.findViewById(R.id.photo);
109        mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
110        mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
111        mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
112
113        mEndCallButton = view.findViewById(R.id.endButton);
114        mEndCallButton.setOnClickListener(new View.OnClickListener() {
115            @Override
116            public void onClick(View v) {
117                getPresenter().endCallClicked();
118            }
119        });
120        ViewUtil.setupFloatingActionButton(mEndCallButton, getResources());
121    }
122
123    @Override
124    public void setVisible(boolean on) {
125        if (on) {
126            getView().setVisibility(View.VISIBLE);
127        } else {
128            getView().setVisibility(View.INVISIBLE);
129        }
130    }
131
132    public void setShowConnectionHandoff(boolean showConnectionHandoff) {
133        Log.v(this, "setShowConnectionHandoff: " + showConnectionHandoff);
134    }
135
136    @Override
137    public void setPrimaryName(String name, boolean nameIsNumber) {
138        if (TextUtils.isEmpty(name)) {
139            mPrimaryName.setText("");
140        } else {
141            mPrimaryName.setText(name);
142
143            // Set direction of the name field
144            int nameDirection = View.TEXT_DIRECTION_INHERIT;
145            if (nameIsNumber) {
146                nameDirection = View.TEXT_DIRECTION_LTR;
147            }
148            mPrimaryName.setTextDirection(nameDirection);
149        }
150    }
151
152    @Override
153    public void setPrimaryImage(Drawable image) {
154        if (image != null) {
155            setDrawableToImageView(mPhoto, image);
156        }
157    }
158
159    @Override
160    public void setPrimaryPhoneNumber(String number) {
161        // Set the number
162        if (TextUtils.isEmpty(number)) {
163            mPhoneNumber.setText("");
164            mPhoneNumber.setVisibility(View.GONE);
165        } else {
166            mPhoneNumber.setText(number);
167            mPhoneNumber.setVisibility(View.VISIBLE);
168            mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
169        }
170    }
171
172    @Override
173    public void setPrimaryLabel(String label) {
174        if (!TextUtils.isEmpty(label)) {
175            mNumberLabel.setText(label);
176            mNumberLabel.setVisibility(View.VISIBLE);
177        } else {
178            mNumberLabel.setVisibility(View.GONE);
179        }
180
181    }
182
183    @Override
184    public void setPrimary(String number, String name, boolean nameIsNumber, String label,
185            Drawable photo, boolean isConference, boolean isGeneric, boolean isSipCall) {
186        Log.d(this, "Setting primary call");
187
188        if (isConference) {
189            name = getConferenceString(isGeneric);
190            photo = getConferencePhoto(isGeneric);
191            nameIsNumber = false;
192        }
193
194        setPrimaryPhoneNumber(number);
195
196        // set the name field.
197        setPrimaryName(name, nameIsNumber);
198
199        // Set the label (Mobile, Work, etc)
200        setPrimaryLabel(label);
201
202        showInternetCallLabel(isSipCall);
203
204        setDrawableToImageView(mPhoto, photo);
205    }
206
207    @Override
208    public void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
209            Drawable photo, boolean isConference, boolean isGeneric) {
210
211        if (show) {
212            if (isConference) {
213                name = getConferenceString(isGeneric);
214                photo = getConferencePhoto(isGeneric);
215                nameIsNumber = false;
216            }
217
218            showAndInitializeSecondaryCallInfo();
219            mSecondaryCallName.setText(name);
220
221            int nameDirection = View.TEXT_DIRECTION_INHERIT;
222            if (nameIsNumber) {
223                nameDirection = View.TEXT_DIRECTION_LTR;
224            }
225            mSecondaryCallName.setTextDirection(nameDirection);
226
227            setDrawableToImageView(mSecondaryPhoto, photo);
228        } else {
229            mSecondaryCallInfo.setVisibility(View.GONE);
230        }
231    }
232
233    @Override
234    public void setSecondaryImage(Drawable image) {
235        if (image != null) {
236            setDrawableToImageView(mSecondaryPhoto, image);
237        }
238    }
239
240    @Override
241    public void setCallState(int state, int cause, boolean bluetoothOn,
242            String gatewayLabel, String gatewayNumber, String wifiConnection) {
243        String callStateLabel = null;
244
245        if (Call.State.isDialing(state) && !TextUtils.isEmpty(gatewayLabel)) {
246            // Provider info: (e.g. "Calling via <gatewayLabel>")
247            callStateLabel = gatewayLabel;
248        } else {
249            callStateLabel = getCallStateLabelFromState(state, cause);
250        }
251
252        Log.v(this, "setCallState " + callStateLabel);
253        Log.v(this, "DisconnectCause " + DisconnectCause.toString(cause));
254        Log.v(this, "bluetooth on " + bluetoothOn);
255        Log.v(this, "gateway " + gatewayLabel + gatewayNumber);
256
257        // Update the call state label.
258        if (!TextUtils.isEmpty(callStateLabel)) {
259            mCallStateLabel.setText(callStateLabel);
260            mCallStateLabel.setVisibility(View.VISIBLE);
261        } else {
262            mCallStateLabel.setVisibility(View.GONE);
263        }
264
265        if (Call.State.INCOMING == state) {
266            setBluetoothOn(bluetoothOn);
267        }
268    }
269
270    private void showInternetCallLabel(boolean show) {
271        if (show) {
272            final String label = getView().getContext().getString(
273                    R.string.incall_call_type_label_sip);
274            mCallTypeLabel.setVisibility(View.VISIBLE);
275            mCallTypeLabel.setText(label);
276        } else {
277            mCallTypeLabel.setVisibility(View.GONE);
278        }
279    }
280
281    @Override
282    public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) {
283        if (show) {
284            if (mElapsedTime.getVisibility() != View.VISIBLE) {
285                AnimationUtils.Fade.show(mElapsedTime);
286            }
287            mElapsedTime.setText(callTimeElapsed);
288        } else {
289            // hide() animation has no effect if it is already hidden.
290            AnimationUtils.Fade.hide(mElapsedTime, View.INVISIBLE);
291        }
292    }
293
294    private void setDrawableToImageView(ImageView view, Drawable photo) {
295        if (photo == null) {
296            photo = view.getResources().getDrawable(R.drawable.picture_unknown);
297        }
298
299        final Drawable current = view.getDrawable();
300        if (current == null) {
301            view.setImageDrawable(photo);
302            AnimationUtils.Fade.show(view);
303        } else {
304            AnimationUtils.startCrossFade(view, current, photo);
305            view.setVisibility(View.VISIBLE);
306        }
307    }
308
309    private String getConferenceString(boolean isGeneric) {
310        Log.v(this, "isGenericString: " + isGeneric);
311        final int resId = isGeneric ? R.string.card_title_in_call : R.string.card_title_conf_call;
312        return getView().getResources().getString(resId);
313    }
314
315    private Drawable getConferencePhoto(boolean isGeneric) {
316        Log.v(this, "isGenericPhoto: " + isGeneric);
317        final int resId = isGeneric ? R.drawable.picture_dialing : R.drawable.picture_conference;
318        return getView().getResources().getDrawable(resId);
319    }
320
321    private void setBluetoothOn(boolean onOff) {
322        // Also, display a special icon (alongside the "Incoming call"
323        // label) if there's an incoming call and audio will be routed
324        // to bluetooth when you answer it.
325        final int bluetoothIconId = R.drawable.ic_in_call_bt_dk;
326
327        if (onOff) {
328            mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0);
329            mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5));
330        } else {
331            // Clear out any icons
332            mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
333        }
334    }
335
336    /**
337     * Gets the call state label based on the state of the call and
338     * cause of disconnect
339     */
340    private String getCallStateLabelFromState(int state, int cause) {
341        final Context context = getView().getContext();
342        String callStateLabel = null;  // Label to display as part of the call banner
343
344        if (Call.State.IDLE == state) {
345            // "Call state" is meaningless in this state.
346
347        } else if (Call.State.ACTIVE == state) {
348            // We normally don't show a "call state label" at all in
349            // this state (but see below for some special cases).
350
351        } else if (Call.State.ONHOLD == state) {
352            callStateLabel = context.getString(R.string.card_title_on_hold);
353        } else if (Call.State.DIALING == state) {
354            callStateLabel = context.getString(R.string.card_title_dialing);
355        } else if (Call.State.REDIALING == state) {
356            callStateLabel = context.getString(R.string.card_title_redialing);
357        } else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) {
358            callStateLabel = context.getString(R.string.card_title_incoming_call);
359
360        } else if (Call.State.DISCONNECTING == state) {
361            // While in the DISCONNECTING state we display a "Hanging up"
362            // message in order to make the UI feel more responsive.  (In
363            // GSM it's normal to see a delay of a couple of seconds while
364            // negotiating the disconnect with the network, so the "Hanging
365            // up" state at least lets the user know that we're doing
366            // something.  This state is currently not used with CDMA.)
367            callStateLabel = context.getString(R.string.card_title_hanging_up);
368
369        } else if (Call.State.DISCONNECTED == state) {
370            callStateLabel = getCallFailedString(cause);
371
372        } else {
373            Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state);
374        }
375
376        return callStateLabel;
377    }
378
379    /**
380     * Maps the disconnect cause to a resource string.
381     *
382     * @param cause disconnect cause as defined in {@link DisconnectCause}
383     */
384    private String getCallFailedString(int cause) {
385        int resID = R.string.card_title_call_ended;
386
387        // TODO: The card *title* should probably be "Call ended" in all
388        // cases, but if the DisconnectCause was an error condition we should
389        // probably also display the specific failure reason somewhere...
390
391        switch (cause) {
392            case DisconnectCause.BUSY:
393                resID = R.string.callFailed_userBusy;
394                break;
395
396            case DisconnectCause.CONGESTION:
397                resID = R.string.callFailed_congestion;
398                break;
399
400            case DisconnectCause.TIMED_OUT:
401                resID = R.string.callFailed_timedOut;
402                break;
403
404            case DisconnectCause.SERVER_UNREACHABLE:
405                resID = R.string.callFailed_server_unreachable;
406                break;
407
408            case DisconnectCause.NUMBER_UNREACHABLE:
409                resID = R.string.callFailed_number_unreachable;
410                break;
411
412            case DisconnectCause.INVALID_CREDENTIALS:
413                resID = R.string.callFailed_invalid_credentials;
414                break;
415
416            case DisconnectCause.SERVER_ERROR:
417                resID = R.string.callFailed_server_error;
418                break;
419
420            case DisconnectCause.OUT_OF_NETWORK:
421                resID = R.string.callFailed_out_of_network;
422                break;
423
424            case DisconnectCause.LOST_SIGNAL:
425            case DisconnectCause.CDMA_DROP:
426                resID = R.string.callFailed_noSignal;
427                break;
428
429            case DisconnectCause.LIMIT_EXCEEDED:
430                resID = R.string.callFailed_limitExceeded;
431                break;
432
433            case DisconnectCause.POWER_OFF:
434                resID = R.string.callFailed_powerOff;
435                break;
436
437            case DisconnectCause.ICC_ERROR:
438                resID = R.string.callFailed_simError;
439                break;
440
441            case DisconnectCause.OUT_OF_SERVICE:
442                resID = R.string.callFailed_outOfService;
443                break;
444
445            case DisconnectCause.INVALID_NUMBER:
446            case DisconnectCause.UNOBTAINABLE_NUMBER:
447                resID = R.string.callFailed_unobtainable_number;
448                break;
449
450            default:
451                resID = R.string.card_title_call_ended;
452                break;
453        }
454        return this.getView().getContext().getString(resID);
455    }
456
457    private void showAndInitializeSecondaryCallInfo() {
458        mSecondaryCallInfo.setVisibility(View.VISIBLE);
459
460        // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccesible
461        // until mSecondaryCallInfo is inflated in the call above.
462        if (mSecondaryCallName == null) {
463            mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName);
464        }
465        if (mSecondaryPhoto == null) {
466            mSecondaryPhoto = (ImageView) getView().findViewById(R.id.secondaryCallPhoto);
467        }
468
469        if (mSecondaryPhotoOverlay == null) {
470            mSecondaryPhotoOverlay = getView().findViewById(R.id.dim_effect_for_secondary_photo);
471            mSecondaryPhotoOverlay.setOnClickListener(new OnClickListener() {
472                @Override
473                public void onClick(View v) {
474                    getPresenter().secondaryPhotoClicked();
475                }
476            });
477            mSecondaryPhotoOverlay.setOnTouchListener(new SmallerHitTargetTouchListener());
478        }
479    }
480
481    public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
482        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
483            dispatchPopulateAccessibilityEvent(event, mPrimaryName);
484            dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
485            return;
486        }
487        dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
488        dispatchPopulateAccessibilityEvent(event, mPrimaryName);
489        dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
490        dispatchPopulateAccessibilityEvent(event, mCallTypeLabel);
491        dispatchPopulateAccessibilityEvent(event, mSecondaryCallName);
492
493        return;
494    }
495
496    public void setEndCallButtonEnabled(boolean enabled) {
497        mEndCallButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
498    }
499
500    private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
501        if (view == null) return;
502        final List<CharSequence> eventText = event.getText();
503        int size = eventText.size();
504        view.dispatchPopulateAccessibilityEvent(event);
505        // if no text added write null to keep relative position
506        if (size == eventText.size()) {
507            eventText.add(null);
508        }
509    }
510}
511