1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
|
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.incallui;
import android.content.ContentUris;
import android.content.Context;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.services.telephony.common.Call;
/**
* Presenter for the Call Card Fragment.
* This class listens for changes to InCallState and passes it along to the fragment.
*/
public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
implements InCallStateListener, CallerInfoAsyncQuery.OnQueryCompleteListener {
private Context mContext;
@Override
public void onUiReady(CallCardUi ui) {
super.onUiReady(ui);
}
public void setContext(Context context) {
mContext = context;
}
@Override
public void onStateChange(InCallState state, CallList callList) {
final CallCardUi ui = getUi();
Call primary = null;
Call secondary = null;
if (state == InCallState.INCOMING) {
primary = callList.getIncomingCall();
} else if (state == InCallState.INCALL) {
primary = callList.getActiveCall();
if (primary != null) {
secondary = callList.getBackgroundCall();
} else {
primary = callList.getBackgroundCall();
secondary = callList.getSecondBackgroundCall();
}
}
Logger.d(this, "Primary call: " + primary);
Logger.d(this, "Secondary call: " + secondary);
// Set primary call data
final CallerInfo primaryCallInfo = CallerInfoUtils.getCallerInfoForCall(mContext, primary,
null, this);
updateDisplayByCallerInfo(primary, primaryCallInfo, primary.getNumberPresentation(), true);
// Set secondary call data
if (secondary != null) {
ui.setSecondaryCallInfo(true, secondary.getNumber());
} else {
ui.setSecondaryCallInfo(false, null);
}
}
public interface CallCardUi extends Ui {
// TODO(klp): Consider passing in the Call object directly in these methods.
void setVisible(boolean on);
void setNumber(String number);
void setNumberLabel(String label);
void setName(String name);
void setName(String name, boolean isNumber);
void setSecondaryCallInfo(boolean show, String number);
}
@Override
public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
if (cookie instanceof Call) {
final Call call = (Call) cookie;
if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) {
updateDisplayByCallerInfo(call, ci, Call.PRESENTATION_ALLOWED, true);
} else {
// If the contact doesn't exist, we can still use information from the
// returned caller info (geodescription, etc).
updateDisplayByCallerInfo(call, ci, call.getNumberPresentation(), true);
}
// Todo (klp): updatePhotoForCallState(call);
}
}
/**
* Based on the given caller info, determine a suitable name, phone number and label
* to be passed to the CallCardUI.
*
* If the current call is a conference call, use
* updateDisplayForConference() instead.
*/
private void updateDisplayByCallerInfo(Call call, CallerInfo info, int presentation,
boolean isPrimary) {
//Todo (klp): Either enable or get rid of this
// inform the state machine that we are displaying a photo.
//mPhotoTracker.setPhotoRequest(info);
//mPhotoTracker.setPhotoState(ContactsAsyncHelper.ImageTracker.DISPLAY_IMAGE);
// The actual strings we're going to display onscreen:
String displayName;
String displayNumber = null;
String label = null;
Uri personUri = null;
// Gather missing info unless the call is generic, in which case we wouldn't use
// the gathered information anyway.
if (info != null) {
// It appears that there is a small change in behaviour with the
// PhoneUtils' startGetCallerInfo whereby if we query with an
// empty number, we will get a valid CallerInfo object, but with
// fields that are all null, and the isTemporary boolean input
// parameter as true.
// In the past, we would see a NULL callerinfo object, but this
// ends up causing null pointer exceptions elsewhere down the
// line in other cases, so we need to make this fix instead. It
// appears that this was the ONLY call to PhoneUtils
// .getCallerInfo() that relied on a NULL CallerInfo to indicate
// an unknown contact.
// Currently, infi.phoneNumber may actually be a SIP address, and
// if so, it might sometimes include the "sip:" prefix. That
// prefix isn't really useful to the user, though, so strip it off
// if present. (For any other URI scheme, though, leave the
// prefix alone.)
// TODO: It would be cleaner for CallerInfo to explicitly support
// SIP addresses instead of overloading the "phoneNumber" field.
// Then we could remove this hack, and instead ask the CallerInfo
// for a "user visible" form of the SIP address.
String number = info.phoneNumber;
if ((number != null) && number.startsWith("sip:")) {
number = number.substring(4);
}
if (TextUtils.isEmpty(info.name)) {
// No valid "name" in the CallerInfo, so fall back to
// something else.
// (Typically, we promote the phone number up to the "name" slot
// onscreen, and possibly display a descriptive string in the
// "number" slot.)
if (TextUtils.isEmpty(number)) {
// No name *or* number! Display a generic "unknown" string
// (or potentially some other default based on the presentation.)
displayName = getPresentationString(presentation);
Logger.d(this, " ==> no name *or* number! displayName = " + displayName);
} else if (presentation != Call.PRESENTATION_ALLOWED) {
// This case should never happen since the network should never send a phone #
// AND a restricted presentation. However we leave it here in case of weird
// network behavior
displayName = getPresentationString(presentation);
Logger.d(this, " ==> presentation not allowed! displayName = " + displayName);
} else if (!TextUtils.isEmpty(info.cnapName)) {
// No name, but we do have a valid CNAP name, so use that.
displayName = info.cnapName;
info.name = info.cnapName;
displayNumber = number;
Logger.d(this, " ==> cnapName available: displayName '"
+ displayName + "', displayNumber '" + displayNumber + "'");
} else {
// No name; all we have is a number. This is the typical
// case when an incoming call doesn't match any contact,
// or if you manually dial an outgoing number using the
// dialpad.
// Promote the phone number up to the "name" slot:
displayName = number;
// ...and use the "number" slot for a geographical description
// string if available (but only for incoming calls.)
if ((call != null) && (call.getState() == Call.State.INCOMING)) {
// TODO (CallerInfoAsyncQuery cleanup): Fix the CallerInfo
// query to only do the geoDescription lookup in the first
// place for incoming calls.
displayNumber = info.geoDescription; // may be null
Logger.d(this, "Geodescrption: " + info.geoDescription);
}
Logger.d(this, " ==> no name; falling back to number: displayName '"
+ displayName + "', displayNumber '" + displayNumber + "'");
}
} else {
// We do have a valid "name" in the CallerInfo. Display that
// in the "name" slot, and the phone number in the "number" slot.
if (presentation != Call.PRESENTATION_ALLOWED) {
// This case should never happen since the network should never send a name
// AND a restricted presentation. However we leave it here in case of weird
// network behavior
displayName = getPresentationString(presentation);
Logger.d(this, " ==> valid name, but presentation not allowed!"
+ " displayName = " + displayName);
} else {
displayName = info.name;
displayNumber = number;
label = info.phoneLabel;
Logger.d(this, " ==> name is present in CallerInfo: displayName '"
+ displayName + "', displayNumber '" + displayNumber + "'");
}
}
personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, info.person_id);
Logger.d(this, "- got personUri: '" + personUri
+ "', based on info.person_id: " + info.person_id);
} else {
displayName = getPresentationString(presentation);
}
// TODO (klp): Update secondary user call info as well.
if (isPrimary) {
updateInfoUiForPrimary(displayName, displayNumber, label);
}
// TODO (klp): Update other fields - photo, sip label, etc.
}
/**
* Updates the info portion of the call card with passed in values for the primary user.
*/
private void updateInfoUiForPrimary(String displayName, String displayNumber, String label) {
final CallCardUi ui = getUi();
ui.setName(displayName);
ui.setNumber(displayNumber);
ui.setNumberLabel(label);
}
public String getPresentationString(int presentation) {
String name = mContext.getString(R.string.unknown);
if (presentation == Call.PRESENTATION_RESTRICTED) {
name = mContext.getString(R.string.private_num);
} else if (presentation == Call.PRESENTATION_PAYPHONE) {
name = mContext.getString(R.string.payphone);
}
return name;
}
}
|