CallCardFragment.java revision 1df52df7a0248814fbd4575103059a8b427f5d9a
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.app.Activity;
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.ViewGroup;
30import android.view.ViewStub;
31import android.widget.ImageView;
32import android.widget.TextView;
33
34import com.android.services.telephony.common.Call;
35
36/**
37 * Fragment for call card.
38 */
39public class CallCardFragment extends BaseFragment<CallCardPresenter>
40        implements CallCardPresenter.CallCardUi {
41
42    // Primary caller info
43    private TextView mPhoneNumber;
44    private TextView mNumberLabel;
45    private TextView mName;
46    private TextView mCallStateLabel;
47    private ImageView mPhoto;
48    private TextView mElapsedTime;
49
50    // Secondary caller info
51    private ViewStub mSecondaryCallInfo;
52    private TextView mSecondaryCallName;
53    private ImageView mSecondaryPhoto;
54
55    // Cached DisplayMetrics density.
56    private float mDensity;
57
58    @Override
59    CallCardPresenter createPresenter() {
60        return new CallCardPresenter();
61    }
62
63    @Override
64    public View onCreateView(LayoutInflater inflater, ViewGroup container,
65            Bundle savedInstanceState) {
66        mDensity = getResources().getDisplayMetrics().density;
67
68        return inflater.inflate(R.layout.call_card, container, false);
69    }
70
71
72    @Override
73    public void onViewCreated(View view, Bundle savedInstanceState) {
74        mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
75        mName = (TextView) view.findViewById(R.id.name);
76        mNumberLabel = (TextView) view.findViewById(R.id.label);
77        mSecondaryCallInfo = (ViewStub) view.findViewById(R.id.secondary_call_info);
78        mPhoto = (ImageView) view.findViewById(R.id.photo);
79        mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
80        mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
81
82        // This method call will begin the callbacks on CallCardUi. We need to ensure
83        // everything needed for the callbacks is set up before this is called.
84        getPresenter().onUiReady(this);
85    }
86
87    @Override
88    public void onDestroyView() {
89        super.onDestroyView();
90        getPresenter().onUiUnready(this);
91    }
92
93    @Override
94    public void setVisible(boolean on) {
95        if (on) {
96            getView().setVisibility(View.VISIBLE);
97        } else {
98            getView().setVisibility(View.INVISIBLE);
99        }
100    }
101
102    @Override
103    public void setPrimary(String number, String name, boolean nameIsNumber, String label,
104            Drawable photo, boolean isConference) {
105
106        if (isConference) {
107            name = getView().getResources().getString(R.string.card_title_conf_call);
108            photo = getView().getResources().getDrawable(R.drawable.picture_conference);
109        }
110
111        // Set the number
112        if (TextUtils.isEmpty(number)) {
113            mPhoneNumber.setText("");
114            mPhoneNumber.setVisibility(View.GONE);
115        } else {
116            mPhoneNumber.setText(number);
117            mPhoneNumber.setVisibility(View.VISIBLE);
118            mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
119        }
120
121        // Set direction of the name field
122
123        // set the name field.
124        if (TextUtils.isEmpty(name)) {
125            mName.setText("");
126        } else {
127            mName.setText(name);
128
129            int nameDirection = View.TEXT_DIRECTION_INHERIT;
130            if (nameIsNumber) {
131                nameDirection = View.TEXT_DIRECTION_LTR;
132            }
133            mName.setTextDirection(nameDirection);
134        }
135
136        // Set the label (Mobile, Work, etc)
137        if (!TextUtils.isEmpty(label)) {
138            mNumberLabel.setText(label);
139            mNumberLabel.setVisibility(View.VISIBLE);
140        } else {
141            mNumberLabel.setVisibility(View.GONE);
142        }
143
144        setDrawableToImageView(mPhoto, photo);
145    }
146
147    @Override
148    public void setSecondary(boolean show, String name, String label, Drawable photo) {
149
150        if (show) {
151            showAndInitializeSecondaryCallInfo();
152
153            mSecondaryCallName.setText(name);
154            setDrawableToImageView(mSecondaryPhoto, photo);
155        } else {
156            mSecondaryCallInfo.setVisibility(View.GONE);
157        }
158    }
159
160    @Override
161    public void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn) {
162        String callStateLabel = null;
163
164        // States other than disconnected not yet supported
165        callStateLabel = getCallStateLabelFromState(state, cause);
166
167        Logger.v(this, "setCallState ", callStateLabel);
168        Logger.v(this, "DisconnectCause ", cause);
169        Logger.v(this, "bluetooth on ", bluetoothOn);
170
171        if (!TextUtils.isEmpty(callStateLabel)) {
172            mCallStateLabel.setVisibility(View.VISIBLE);
173            mCallStateLabel.setText(callStateLabel);
174
175            if (Call.State.INCOMING == state) {
176                setBluetoothOn(bluetoothOn);
177            }
178        } else {
179            mCallStateLabel.setVisibility(View.GONE);
180            // Gravity is aligned left when receiving an incoming call in landscape.
181            // In that rare case, the gravity needs to be reset to the right.
182            // Also, setText("") is used since there is a delay in making the view GONE,
183            // so the user will otherwise see the text jump to the right side before disappearing.
184            if(mCallStateLabel.getGravity() != Gravity.END) {
185                mCallStateLabel.setText("");
186                mCallStateLabel.setGravity(Gravity.END);
187            }
188        }
189    }
190
191    @Override
192    public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) {
193        if (show) {
194            if (mElapsedTime.getVisibility() != View.VISIBLE) {
195                AnimationUtils.Fade.show(mElapsedTime);
196            }
197            mElapsedTime.setText(callTimeElapsed);
198        } else {
199            // hide() animation has no effect if it is already hidden.
200            AnimationUtils.Fade.hide(mElapsedTime, View.INVISIBLE);
201        }
202    }
203
204    private void setDrawableToImageView(ImageView view, Drawable photo) {
205        if (photo == null) {
206            photo = view.getResources().getDrawable(R.drawable.picture_unknown);
207        }
208
209        final Drawable current = view.getDrawable();
210        if (current == null) {
211            view.setImageDrawable(photo);
212            AnimationUtils.Fade.show(view);
213        } else {
214            AnimationUtils.startCrossFade(view, current, photo);
215            mPhoto.setVisibility(View.VISIBLE);
216        }
217    }
218
219    private void setBluetoothOn(boolean onOff) {
220        // Also, display a special icon (alongside the "Incoming call"
221        // label) if there's an incoming call and audio will be routed
222        // to bluetooth when you answer it.
223        final int bluetoothIconId = R.drawable.ic_incoming_call_bluetooth;
224
225        if (onOff) {
226            mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0);
227            mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5));
228        } else {
229            // Clear out any icons
230            mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
231        }
232    }
233
234    /**
235     * Gets the call state label based on the state of the call and
236     * cause of disconnect
237     */
238    private String getCallStateLabelFromState(int state, Call.DisconnectCause cause) {
239        final Context context = getView().getContext();
240        String callStateLabel = null;  // Label to display as part of the call banner
241
242        if (Call.State.IDLE == state) {
243            // "Call state" is meaningless in this state.
244
245        } else if (Call.State.ACTIVE == state) {
246            // We normally don't show a "call state label" at all in
247            // this state (but see below for some special cases).
248
249        } else if (Call.State.ONHOLD == state) {
250            callStateLabel = context.getString(R.string.card_title_on_hold);
251
252        } else if (Call.State.DIALING == state) {
253            callStateLabel = context.getString(R.string.card_title_dialing);
254
255        } else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) {
256            callStateLabel = context.getString(R.string.card_title_incoming_call);
257
258        // TODO(klp): Add a disconnecting state
259        //} else if (Call.State.DISCONNECTING) {
260                // While in the DISCONNECTING state we display a "Hanging up"
261                // message in order to make the UI feel more responsive.  (In
262                // GSM it's normal to see a delay of a couple of seconds while
263                // negotiating the disconnect with the network, so the "Hanging
264                // up" state at least lets the user know that we're doing
265                // something.  This state is currently not used with CDMA.)
266                //callStateLabel = context.getString(R.string.card_title_hanging_up);
267
268        } else if (Call.State.DISCONNECTED == state) {
269            callStateLabel = getCallFailedString(cause);
270
271        } else {
272            Logger.wtf(this, "updateCallStateWidgets: unexpected call: " + state);
273        }
274
275        return callStateLabel;
276    }
277
278    /**
279     * Maps the disconnect cause to a resource string.
280     */
281    private String getCallFailedString(Call.DisconnectCause cause) {
282        int resID = R.string.card_title_call_ended;
283
284        // TODO: The card *title* should probably be "Call ended" in all
285        // cases, but if the DisconnectCause was an error condition we should
286        // probably also display the specific failure reason somewhere...
287
288        switch (cause) {
289            case BUSY:
290                resID = R.string.callFailed_userBusy;
291                break;
292
293            case CONGESTION:
294                resID = R.string.callFailed_congestion;
295                break;
296
297            case TIMED_OUT:
298                resID = R.string.callFailed_timedOut;
299                break;
300
301            case SERVER_UNREACHABLE:
302                resID = R.string.callFailed_server_unreachable;
303                break;
304
305            case NUMBER_UNREACHABLE:
306                resID = R.string.callFailed_number_unreachable;
307                break;
308
309            case INVALID_CREDENTIALS:
310                resID = R.string.callFailed_invalid_credentials;
311                break;
312
313            case SERVER_ERROR:
314                resID = R.string.callFailed_server_error;
315                break;
316
317            case OUT_OF_NETWORK:
318                resID = R.string.callFailed_out_of_network;
319                break;
320
321            case LOST_SIGNAL:
322            case CDMA_DROP:
323                resID = R.string.callFailed_noSignal;
324                break;
325
326            case LIMIT_EXCEEDED:
327                resID = R.string.callFailed_limitExceeded;
328                break;
329
330            case POWER_OFF:
331                resID = R.string.callFailed_powerOff;
332                break;
333
334            case ICC_ERROR:
335                resID = R.string.callFailed_simError;
336                break;
337
338            case OUT_OF_SERVICE:
339                resID = R.string.callFailed_outOfService;
340                break;
341
342            case INVALID_NUMBER:
343            case UNOBTAINABLE_NUMBER:
344                resID = R.string.callFailed_unobtainable_number;
345                break;
346
347            default:
348                resID = R.string.card_title_call_ended;
349                break;
350        }
351        return this.getView().getContext().getString(resID);
352    }
353
354    private void showAndInitializeSecondaryCallInfo() {
355        mSecondaryCallInfo.setVisibility(View.VISIBLE);
356
357        // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccesible
358        // until mSecondaryCallInfo is inflated in the call above.
359        if (mSecondaryCallName == null) {
360            mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName);
361        }
362        if (mSecondaryPhoto == null) {
363            mSecondaryPhoto = (ImageView) getView().findViewById(R.id.secondaryCallPhoto);
364        }
365    }
366}
367