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