CallCardPresenter.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.graphics.drawable.Drawable;
20import android.text.TextUtils;
21import android.text.format.DateUtils;
22
23import com.android.incallui.AudioModeProvider.AudioModeListener;
24import com.android.incallui.ContactInfoCache.ContactCacheEntry;
25import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
26import com.android.incallui.InCallPresenter.InCallState;
27import com.android.incallui.InCallPresenter.InCallStateListener;
28
29import com.android.services.telephony.common.AudioMode;
30import com.android.services.telephony.common.Call;
31import com.android.services.telephony.common.Call.DisconnectCause;
32
33/**
34 * Presenter for the Call Card Fragment.
35 * This class listens for changes to InCallState and passes it along to the fragment.
36 */
37public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
38        implements InCallStateListener, AudioModeListener, ContactInfoCacheCallback {
39
40    private static final long CALL_TIME_UPDATE_INTERVAL = 1000; // in milliseconds
41
42    private AudioModeProvider mAudioModeProvider;
43    private ContactInfoCache mContactInfoCache;
44    private Call mPrimary;
45    private Call mSecondary;
46    private ContactCacheEntry mPrimaryContactInfo;
47    private ContactCacheEntry mSecondaryContactInfo;
48    private CallTimer mCallTimer;
49
50    public CallCardPresenter() {
51
52        // create the call timer
53        mCallTimer = new CallTimer(new Runnable() {
54            @Override
55            public void run() {
56                updateCallTime();
57            }
58        });
59    }
60
61    @Override
62    public void onUiReady(CallCardUi ui) {
63        super.onUiReady(ui);
64
65        if (mAudioModeProvider != null) {
66            mAudioModeProvider.addListener(this);
67        }
68    }
69
70    @Override
71    public void onUiUnready(CallCardUi ui) {
72        super.onUiUnready(ui);
73
74        if (mAudioModeProvider != null) {
75            mAudioModeProvider.removeListener(this);
76        }
77        mPrimary = null;
78        mPrimaryContactInfo = null;
79        mSecondaryContactInfo = null;
80    }
81
82    @Override
83    public void onStateChange(InCallState state, CallList callList) {
84        final CallCardUi ui = getUi();
85        if (ui == null) {
86            return;
87        }
88
89        Call primary = null;
90        Call secondary = null;
91
92        if (state == InCallState.INCOMING) {
93            primary = callList.getIncomingCall();
94        } else if (state == InCallState.OUTGOING) {
95            primary = callList.getOutgoingCall();
96
97            // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
98            // highest priority call to display as the secondary call.
99            secondary = getCallToDisplay(callList, null, true);
100        } else if (state == InCallState.INCALL) {
101            primary = getCallToDisplay(callList, null, false);
102            secondary = getCallToDisplay(callList, primary, true);
103        }
104
105        Logger.d(this, "Primary call: " + primary);
106        Logger.d(this, "Secondary call: " + secondary);
107
108        mPrimary = primary;
109        mSecondary = secondary;
110
111        // Query for contact data. This will call back on onContactInfoComplete at least once
112        // synchronously, and potentially a second time asynchronously if it needs to make
113        // a full query for the data.
114        // It is in that callback that we set the values into the Ui.
115        startContactInfoSearch();
116
117        // Start/Stop the call time update timer
118        if (mPrimary != null && mPrimary.getState() == Call.State.ACTIVE) {
119            Logger.d(this, "Starting the calltime timer");
120            mCallTimer.start(CALL_TIME_UPDATE_INTERVAL);
121        } else {
122            Logger.d(this, "Canceling the calltime timer");
123            mCallTimer.cancel();
124            ui.setPrimaryCallElapsedTime(false, null);
125        }
126
127        // Set the call state
128        if (mPrimary != null) {
129            final boolean bluetoothOn = mAudioModeProvider != null &&
130                    mAudioModeProvider.getAudioMode() == AudioMode.BLUETOOTH;
131            ui.setCallState(mPrimary.getState(), mPrimary.getDisconnectCause(), bluetoothOn);
132        } else {
133            ui.setCallState(Call.State.IDLE, Call.DisconnectCause.UNKNOWN, false);
134        }
135    }
136
137    @Override
138    public void onAudioMode(int mode) {
139        if (mPrimary != null && getUi() != null) {
140            final boolean bluetoothOn = (AudioMode.BLUETOOTH == mode);
141
142            getUi().setCallState(mPrimary.getState(), mPrimary.getDisconnectCause(), bluetoothOn);
143        }
144    }
145
146    @Override
147    public void onSupportedAudioMode(int mask) {
148    }
149
150    public void updateCallTime() {
151        final CallCardUi ui = getUi();
152
153        if (ui == null || mPrimary == null || mPrimary.getState() != Call.State.ACTIVE) {
154            if (ui != null) {
155                ui.setPrimaryCallElapsedTime(false, null);
156            }
157            mCallTimer.cancel();
158        } else {
159            final long callStart = mPrimary.getConnectTime();
160            final long duration = System.currentTimeMillis() - callStart;
161            ui.setPrimaryCallElapsedTime(true, DateUtils.formatElapsedTime(duration / 1000));
162        }
163    }
164
165
166    public void setContactInfoCache(ContactInfoCache cache) {
167        mContactInfoCache = cache;
168        startContactInfoSearch();
169    }
170
171    /**
172     * Starts a query for more contact data for the save primary and secondary calls.
173     */
174    private void startContactInfoSearch() {
175        if (mPrimary != null && mContactInfoCache != null) {
176            mContactInfoCache.findInfo(mPrimary, this);
177        } else {
178            mPrimaryContactInfo = null;
179            updatePrimaryDisplayInfo();
180        }
181
182        if (mSecondary != null && mContactInfoCache != null) {
183            mContactInfoCache.findInfo(mSecondary, this);
184        } else {
185            mSecondaryContactInfo = null;
186            updateSecondaryDisplayInfo();
187        }
188    }
189
190    /**
191     * Get the highest priority call to display.
192     * Goes through the calls and chooses which to return based on priority of which type of call
193     * to display to the user. Callers can use the "ignore" feature to get the second best call
194     * by passing a previously found primary call as ignore.
195     *
196     * @param ignore A call to ignore if found.
197     */
198    private Call getCallToDisplay(CallList callList, Call ignore, boolean skipDisconnected) {
199
200        // Active calls come second.  An active call always gets precedent.
201        Call retval = callList.getActiveCall();
202        if (retval != null && retval != ignore) {
203            return retval;
204        }
205
206        // Disconnected calls get primary position if there are no active calls
207        // to let user know quickly what call has disconnected. Disconnected
208        // calls are very short lived.
209        if (!skipDisconnected) {
210            retval = callList.getDisconnectedCall();
211            if (retval != null && retval != ignore) {
212                return retval;
213            }
214        }
215
216        // Then we go to background call (calls on hold)
217        retval = callList.getBackgroundCall();
218        if (retval != null && retval != ignore) {
219            return retval;
220        }
221
222        // Lastly, we go to a second background call.
223        retval = callList.getSecondBackgroundCall();
224
225        return retval;
226    }
227
228    /**
229     * Callback received when Contact info data query completes.
230     */
231    @Override
232    public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
233        if (mPrimary != null && mPrimary.getCallId() == callId) {
234            mPrimaryContactInfo = entry;
235            updatePrimaryDisplayInfo();
236        }
237        if (mSecondary != null && mSecondary.getCallId() == callId) {
238            mSecondaryContactInfo = entry;
239            updateSecondaryDisplayInfo();
240        }
241
242    }
243
244    private void updatePrimaryDisplayInfo() {
245        final CallCardUi ui = getUi();
246        if (ui == null) {
247            return;
248        }
249
250        if (mPrimaryContactInfo != null) {
251            final String name = getNameForCall(mPrimaryContactInfo);
252            final String number = getNumberForCall(mPrimaryContactInfo);
253            final boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
254            ui.setPrimary(number, name, nameIsNumber, mPrimaryContactInfo.label,
255                    mPrimaryContactInfo.photo, mPrimary.isConferenceCall());
256        } else {
257            // reset to nothing (like at end of call)
258            ui.setPrimary(null, null, false, null, null, false);
259        }
260
261    }
262
263    /**
264     * Gets the name to display for the call.
265     */
266    private static String getNameForCall(ContactCacheEntry contactInfo) {
267        if (TextUtils.isEmpty(contactInfo.name)) {
268            return contactInfo.number;
269        }
270        return contactInfo.name;
271    }
272
273    /**
274     * Gets the number to display for a call.
275     */
276    private static String getNumberForCall(ContactCacheEntry contactInfo) {
277        // If the name is empty, we use the number for the name...so dont show a second
278        // number in the number field
279        if (TextUtils.isEmpty(contactInfo.name)) {
280            return null;
281        }
282        return contactInfo.number;
283    }
284
285    private void updateSecondaryDisplayInfo() {
286        final CallCardUi ui = getUi();
287        if (ui == null) {
288            return;
289        }
290
291        if (mSecondaryContactInfo != null) {
292            final String name = getNameForCall(mSecondaryContactInfo);
293            ui.setSecondary(true, getNameForCall(mSecondaryContactInfo),
294                    mSecondaryContactInfo.label, mSecondaryContactInfo.photo);
295        } else {
296            // reset to nothing so that it starts off blank next time we use it.
297            ui.setSecondary(false, null, null, null);
298        }
299    }
300
301    public void setAudioModeProvider(AudioModeProvider audioModeProvider) {
302        mAudioModeProvider = audioModeProvider;
303        mAudioModeProvider.addListener(this);
304    }
305
306    public interface CallCardUi extends Ui {
307        void setVisible(boolean on);
308        void setPrimary(String number, String name, boolean nameIsNumber, String label,
309                Drawable photo, boolean isConference);
310        void setSecondary(boolean show, String name, String label, Drawable photo);
311        void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn);
312        void setPrimaryCallElapsedTime(boolean show, String duration);
313    }
314}
315