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