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