summaryrefslogtreecommitdiff
path: root/InCallUI/src/com/android/incallui
diff options
context:
space:
mode:
authorChiao Cheng <chiaocheng@google.com>2013-08-22 18:36:24 -0700
committerChiao Cheng <chiaocheng@google.com>2013-08-28 12:09:20 -0700
commit42373eb59cbef15ec61ebb5c919031f293291a53 (patch)
tree7af3883675986a4783d2931ebcfaaabceec5727f /InCallUI/src/com/android/incallui
parent6002b83345c66319a8839912b326a0f84d7a1d9a (diff)
Major fixes for in call to work with reverse number lookup.
- Separated caller info data into CallIdentification and switch callbacks to use it where call state is un-necessary. - Changed mCallList.update() method to be onIncoming(). - Catch all exceptions from service methods so errors do not vanish. - Fixed bind failure cases which led to DeadObjectException. - Changed local contact lookup to only occur for incoming calls. - Fixed CallCardPresenter to start contact search upon isntantiation instead of waiting for next call update. - Convert ContactInfoCache to singleton to avoid race condition where it's not initialized. - Handle cases where primary call may be null when we find a contact. - Fixed race conditions in CallButtonPresenter where audio mode is being set before ui is ready. - Fixed race condition in AnswerPresenter where state change was being called before ui is ready. - Changes to CallCardPresenter to support lookup for conference calls. Bug: 10413515 Bug: 10390984 Change-Id: I9fc4f2f35e8f5aad33c301b3c5c93132634cb63c
Diffstat (limited to 'InCallUI/src/com/android/incallui')
-rw-r--r--InCallUI/src/com/android/incallui/AnswerFragment.java31
-rw-r--r--InCallUI/src/com/android/incallui/AnswerPresenter.java87
-rw-r--r--InCallUI/src/com/android/incallui/BaseFragment.java4
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonPresenter.java9
-rw-r--r--InCallUI/src/com/android/incallui/CallCardFragment.java86
-rw-r--r--InCallUI/src/com/android/incallui/CallCardPresenter.java210
-rw-r--r--InCallUI/src/com/android/incallui/CallHandlerService.java83
-rw-r--r--InCallUI/src/com/android/incallui/CallList.java90
-rw-r--r--InCallUI/src/com/android/incallui/CallerInfoUtils.java43
-rw-r--r--InCallUI/src/com/android/incallui/ContactInfoCache.java324
-rw-r--r--InCallUI/src/com/android/incallui/InCallActivity.java8
-rw-r--r--InCallUI/src/com/android/incallui/InCallPresenter.java29
-rw-r--r--InCallUI/src/com/android/incallui/StatusBarNotifier.java53
13 files changed, 650 insertions, 407 deletions
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java
index 077eaac42..40462ce1f 100644
--- a/InCallUI/src/com/android/incallui/AnswerFragment.java
+++ b/InCallUI/src/com/android/incallui/AnswerFragment.java
@@ -16,8 +16,6 @@
package com.android.incallui;
-import com.google.common.base.Preconditions;
-
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
@@ -28,6 +26,8 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
+import com.google.common.base.Preconditions;
+
import java.util.ArrayList;
/**
@@ -39,10 +39,9 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
/**
* The popup showing the list of canned responses.
*
- * This is an AlertDialog containing a ListView showing the possible
- * choices. This may be null if the InCallScreen hasn't ever called
- * showRespondViaSmsPopup() yet, or if the popup was visible once but
- * then got dismissed.
+ * This is an AlertDialog containing a ListView showing the possible choices. This may be null
+ * if the InCallScreen hasn't ever called showRespondViaSmsPopup() yet, or if the popup was
+ * visible once but then got dismissed.
*/
private Dialog mCannedResponsePopup = null;
@@ -101,9 +100,8 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
lv.setAdapter(mTextResponsesAdapter);
lv.setOnItemClickListener(new RespondViaSmsItemClickListener());
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setCancelable(true)
- .setView(lv);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()).setCancelable(
+ true).setView(lv);
mCannedResponsePopup = builder.create();
mCannedResponsePopup.show();
}
@@ -111,9 +109,8 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
/**
* Dismiss currently visible popups.
*
- * This is safe to call even if the popup is already dismissed, and
- * even if you never called showRespondViaSmsPopup() in the first
- * place.
+ * This is safe to call even if the popup is already dismissed, and even if you never called
+ * showRespondViaSmsPopup() in the first place.
*/
@Override
public void dismissPopup() {
@@ -127,9 +124,7 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
public void configureMessageDialogue(ArrayList<String> textResponses) {
textResponses.add(getResources().getString(R.string.respond_via_sms_custom_message));
mTextResponsesAdapter = new ArrayAdapter<String>(getActivity(),
- android.R.layout.simple_list_item_1,
- android.R.id.text1,
- textResponses);
+ android.R.layout.simple_list_item_1, android.R.id.text1, textResponses);
}
@Override
@@ -151,14 +146,14 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
* OnItemClickListener for the "Respond via SMS" popup.
*/
public class RespondViaSmsItemClickListener implements AdapterView.OnItemClickListener {
+
/**
* Handles the user selecting an item from the popup.
*/
@Override
public void onItemClick(AdapterView<?> parent, // The ListView
- View view, // The TextView that was clicked
- int position,
- long id) {
+ View view, // The TextView that was clicked
+ int position, long id) {
Log.d(this, "RespondViaSmsItemClickListener.onItemClick(" + position + ")...");
final String message = (String) parent.getItemAtPosition(position);
Log.v(this, "- message: '" + message + "'");
diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java
index 78ee0f1a3..1c970d7db 100644
--- a/InCallUI/src/com/android/incallui/AnswerPresenter.java
+++ b/InCallUI/src/com/android/incallui/AnswerPresenter.java
@@ -16,8 +16,6 @@
package com.android.incallui;
-import com.android.incallui.InCallPresenter.InCallState;
-import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.services.telephony.common.Call;
import java.util.ArrayList;
@@ -26,53 +24,94 @@ import java.util.ArrayList;
* Presenter for the Incoming call widget.
*/
public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
- implements InCallStateListener {
+ implements CallList.CallUpdateListener, CallList.Listener {
- private Call mCall;
- private ArrayList<String> mTextResponses;
+ private static final String TAG = AnswerPresenter.class.getSimpleName();
+
+ private int mCallId = Call.INVALID_CALL_ID;
@Override
public void onUiReady(AnswerUi ui) {
super.onUiReady(ui);
+
+ final CallList calls = CallList.getInstance();
+ final Call call = calls.getIncomingCall();
+ // TODO: change so that answer presenter never starts up if it's not incoming.
+ if (call != null) {
+ processIncomingCall(call);
+
+ // Listen for call updates for the current call.
+ calls.addCallUpdateListener(mCallId, this);
+
+ // Listen for incoming calls.
+ calls.addListener(this);
+ }
}
@Override
- public void onStateChange(InCallState state, CallList callList) {
- if (state == InCallState.INCOMING) {
- getUi().showAnswerUi(true);
- mCall = callList.getIncomingCall();
- mTextResponses = callList.getTextResponses(mCall);
- if (mTextResponses != null) {
- getUi().showTextButton(true);
- getUi().configureMessageDialogue(mTextResponses);
- } else {
- getUi().showTextButton(false);
+ public void onCallListChange(CallList callList) {
+ // no-op
+ }
+
+ @Override
+ public void onIncomingCall(Call call) {
+ // TODO: Ui is being destroyed when the fragment detaches. Need clean up step to stop
+ // getting updates here.
+ if (getUi() != null) {
+ if (call.getCallId() != mCallId) {
+ // A new call is coming in.
+ processIncomingCall(call);
}
- Log.d(this, "Showing incoming with: " + mCall);
+ }
+ }
+
+ private void processIncomingCall(Call call) {
+ mCallId = call.getCallId();
+ Log.d(TAG, "Showing incoming for call id: " + mCallId);
+ final ArrayList<String> textMsgs = CallList.getInstance().getTextResponses(
+ call.getCallId());
+ getUi().showAnswerUi(true);
+
+ if (textMsgs != null) {
+ getUi().showTextButton(true);
+ getUi().configureMessageDialogue(textMsgs);
} else {
+ getUi().showTextButton(false);
+ }
+ }
+
+
+ @Override
+ public void onCallStateChanged(Call call) {
+ Log.d(TAG, "onCallStateChange() " + call);
+ if (call.getState() != Call.State.INCOMING) {
+ // Stop listening for updates.
+ CallList.getInstance().removeCallUpdateListener(mCallId, this);
+ CallList.getInstance().removeListener(this);
+
getUi().showAnswerUi(false);
- mCall = null;
+ mCallId = Call.INVALID_CALL_ID;
}
}
public void onAnswer() {
- if (mCall == null) {
+ if (mCallId == Call.INVALID_CALL_ID) {
return;
}
- Log.d(this, "onAnswer " + mCall.getCallId());
+ Log.d(this, "onAnswer " + mCallId);
- CallCommandClient.getInstance().answerCall(mCall.getCallId());
+ CallCommandClient.getInstance().answerCall(mCallId);
}
public void onDecline() {
- if (mCall == null) {
+ if (mCallId == Call.INVALID_CALL_ID) {
return;
}
- Log.d(this, "onDecline " + mCall.getCallId());
+ Log.d(this, "onDecline " + mCallId);
- CallCommandClient.getInstance().rejectCall(mCall.getCallId(), false, null);
+ CallCommandClient.getInstance().rejectCall(mCallId, false, null);
}
public void onText() {
@@ -82,7 +121,7 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
public void rejectCallWithMessage(String message) {
Log.d(this, "sendTextToDefaultActivity()...");
- CallCommandClient.getInstance().rejectCall(mCall.getCallId(), true, message);
+ CallCommandClient.getInstance().rejectCall(mCallId, true, message);
getUi().dismissPopup();
}
diff --git a/InCallUI/src/com/android/incallui/BaseFragment.java b/InCallUI/src/com/android/incallui/BaseFragment.java
index 0f3d6b4ae..a348ce49a 100644
--- a/InCallUI/src/com/android/incallui/BaseFragment.java
+++ b/InCallUI/src/com/android/incallui/BaseFragment.java
@@ -47,8 +47,8 @@ public abstract class BaseFragment<T extends Presenter<U>, U extends Ui> extends
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
mPresenter.onUiReady(getUi());
}
}
diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
index 3a2d719e9..9db383fdd 100644
--- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
@@ -39,6 +39,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
@Override
public void onUiReady(CallButtonUi ui) {
super.onUiReady(ui);
+
if (mAudioModeProvider != null) {
mAudioModeProvider.addListener(this);
}
@@ -66,12 +67,16 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
@Override
public void onAudioMode(int mode) {
- getUi().setAudio(mode);
+ if (getUi() != null) {
+ getUi().setAudio(mode);
+ }
}
@Override
public void onSupportedAudioMode(int mask) {
- getUi().setSupportedAudio(mask);
+ if (getUi() != null) {
+ getUi().setSupportedAudio(mask);
+ }
}
public int getAudioMode() {
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index b77dc4cde..d9712d1e0 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -42,7 +42,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
// Primary caller info
private TextView mPhoneNumber;
private TextView mNumberLabel;
- private TextView mName;
+ private TextView mPrimaryName;
private TextView mCallStateLabel;
private ImageView mPhoto;
private TextView mElapsedTime;
@@ -72,7 +72,10 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getPresenter().init(getActivity(), ServiceFactory.newPhoneNumberService(getActivity()));
+ final CallList calls = CallList.getInstance();
+ final Call call = calls.getFirstCall();
+ getPresenter().init(getActivity(), ServiceFactory.newPhoneNumberService(getActivity()),
+ call);
}
@Override
@@ -90,7 +93,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
super.onViewCreated(view, savedInstanceState);
mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
- mName = (TextView) view.findViewById(R.id.name);
+ mPrimaryName = (TextView) view.findViewById(R.id.name);
mNumberLabel = (TextView) view.findViewById(R.id.label);
mSecondaryCallInfo = (ViewStub) view.findViewById(R.id.secondary_call_info);
mPhoto = (ImageView) view.findViewById(R.id.photo);
@@ -117,27 +120,30 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
}
@Override
- public void setName(String name) {
- mName.setText(name);
+ public void setPrimaryName(String name, boolean nameIsNumber) {
+ if (TextUtils.isEmpty(name)) {
+ mPrimaryName.setText("");
+ } else {
+ mPrimaryName.setText(name);
+
+ // Set direction of the name field
+ int nameDirection = View.TEXT_DIRECTION_INHERIT;
+ if (nameIsNumber) {
+ nameDirection = View.TEXT_DIRECTION_LTR;
+ }
+ mPrimaryName.setTextDirection(nameDirection);
+ }
}
@Override
- public void setImage(Bitmap image) {
+ public void setPrimaryImage(Bitmap image) {
if (image != null) {
setDrawableToImageView(mPhoto, new BitmapDrawable(getResources(), image));
}
}
@Override
- public void setPrimary(String number, String name, boolean nameIsNumber, String label,
- Drawable photo, boolean isConference, String gatewayLabel, String gatewayNumber) {
- Log.d(this, "Setting primary call [" + gatewayLabel + "][" + gatewayNumber + "]");
-
- if (isConference) {
- name = getView().getResources().getString(R.string.card_title_conf_call);
- photo = getView().getResources().getDrawable(R.drawable.picture_conference);
- }
-
+ public void setPrimaryPhoneNumber(String number) {
// Set the number
if (TextUtils.isEmpty(number)) {
mPhoneNumber.setText("");
@@ -147,8 +153,21 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
mPhoneNumber.setVisibility(View.VISIBLE);
mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
}
+ }
- // Set any gateway information
+ @Override
+ public void setPrimaryLabel(String label) {
+ if (!TextUtils.isEmpty(label)) {
+ mNumberLabel.setText(label);
+ mNumberLabel.setVisibility(View.VISIBLE);
+ } else {
+ mNumberLabel.setVisibility(View.GONE);
+ }
+
+ }
+
+ @Override
+ public void setPrimaryGateway(String gatewayLabel, String gatewayNumber) {
if (!TextUtils.isEmpty(gatewayLabel) && !TextUtils.isEmpty(gatewayNumber)) {
mProviderLabel.setText(gatewayLabel);
mProviderNumber.setText(gatewayNumber);
@@ -156,29 +175,28 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
} else {
mProviderInfo.setVisibility(View.GONE);
}
+ }
- // Set direction of the name field
-
- // set the name field.
- if (TextUtils.isEmpty(name)) {
- mName.setText("");
- } else {
- mName.setText(name);
+ @Override
+ public void setPrimary(String number, String name, boolean nameIsNumber, String label,
+ Drawable photo, boolean isConference, String gatewayLabel, String gatewayNumber) {
+ Log.d(this, "Setting primary call [" + gatewayLabel + "][" + gatewayNumber + "]");
- int nameDirection = View.TEXT_DIRECTION_INHERIT;
- if (nameIsNumber) {
- nameDirection = View.TEXT_DIRECTION_LTR;
- }
- mName.setTextDirection(nameDirection);
+ if (isConference) {
+ name = getView().getResources().getString(R.string.card_title_conf_call);
+ photo = getView().getResources().getDrawable(R.drawable.picture_conference);
}
+ setPrimaryPhoneNumber(number);
+
+ // Set any gateway information
+ setPrimaryGateway(gatewayLabel, gatewayNumber);
+
+ // set the name field.
+ setPrimaryName(name, nameIsNumber);
+
// Set the label (Mobile, Work, etc)
- if (!TextUtils.isEmpty(label)) {
- mNumberLabel.setText(label);
- mNumberLabel.setVisibility(View.VISIBLE);
- } else {
- mNumberLabel.setVisibility(View.GONE);
- }
+ setPrimaryLabel(label);
setDrawableToImageView(mPhoto, photo);
}
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index cc2e3bf54..c06806926 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -33,6 +33,8 @@ import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.service.PhoneNumberService;
import com.android.services.telephony.common.AudioMode;
import com.android.services.telephony.common.Call;
+import com.android.services.telephony.common.CallIdentification;
+import com.google.common.base.Preconditions;
/**
* Presenter for the Call Card Fragment.
@@ -40,14 +42,13 @@ import com.android.services.telephony.common.Call;
* This class listens for changes to InCallState and passes it along to the fragment.
*/
public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
- implements InCallStateListener, AudioModeListener, ContactInfoCacheCallback {
+ implements InCallStateListener, AudioModeListener {
private static final String TAG = CallCardPresenter.class.getSimpleName();
private static final long CALL_TIME_UPDATE_INTERVAL = 1000; // in milliseconds
private PhoneNumberService mPhoneNumberService;
private AudioModeProvider mAudioModeProvider;
- private ContactInfoCache mContactInfoCache;
private Call mPrimary;
private Call mSecondary;
private ContactCacheEntry mPrimaryContactInfo;
@@ -65,15 +66,33 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
});
}
- public void init(Context context, PhoneNumberService phoneNumberService) {
+
+ public void init(Context context, PhoneNumberService phoneNumberService, Call call) {
+ mContext = Preconditions.checkNotNull(context);
+ mPhoneNumberService = Preconditions.checkNotNull(phoneNumberService);
+ Preconditions.checkNotNull(call);
mContext = context;
mPhoneNumberService = phoneNumberService;
+ final CallIdentification identification = call.getIdentification();
+
+ // TODO(klp): Logic to determine which ui field get what data resides in contactInfoCache.
+ // It needs to be moved so it can be re-used.
+ mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, identification,
+ call.getState() == Call.State.INCOMING);
+
+ // start processing lookups right away.
+ startContactInfoSearch(identification, true, false, call.getState() == Call.State.INCOMING);
}
@Override
public void onUiReady(CallCardUi ui) {
super.onUiReady(ui);
+ // Contact search may have completed before ui is ready.
+ if (mPrimaryContactInfo != null) {
+ updatePrimaryDisplayInfo(mPrimaryContactInfo, false);
+ }
+
if (mAudioModeProvider != null) {
mAudioModeProvider.addListener(this);
}
@@ -93,7 +112,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
@Override
public void onStateChange(InCallState state, CallList callList) {
- Log.d(TAG, "onStateChange()");
+ Log.d(TAG, "onStateChange() " + state);
final CallCardUi ui = getUi();
if (ui == null) {
return;
@@ -118,15 +137,35 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
Log.d(this, "Primary call: " + primary);
Log.d(this, "Secondary call: " + secondary);
+ if (primary != null) {
+ if (mPrimary == null || mPrimary.getCallId() != primary.getCallId()) {
+ // primary call has changed
+ mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext,
+ primary.getIdentification(), primary.getState() == Call.State.INCOMING);
+ updatePrimaryDisplayInfo(mPrimaryContactInfo, isConference(primary));
+ startContactInfoSearch(primary.getIdentification(), true,
+ primary.isConferenceCall(), primary.getState() == Call.State.INCOMING);
+ }
+ }
+
+ if (secondary == null) {
+ // Secondary call may have ended. Update the ui.
+ mSecondaryContactInfo = null;
+ updateSecondaryDisplayInfo();
+ } else {
+ if (mSecondary == null || mSecondary.getCallId() != secondary.getCallId()) {
+ // secondary call has changed
+ mSecondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext,
+ secondary.getIdentification(), secondary.getState() == Call.State.INCOMING);
+ updateSecondaryDisplayInfo();
+ startContactInfoSearch(secondary.getIdentification(), false,
+ secondary.isConferenceCall(), secondary.getState() == Call.State.INCOMING);
+ }
+ }
+
mPrimary = primary;
mSecondary = secondary;
- // Query for contact data. This will call back on onContactInfoComplete at least once
- // synchronously, and potentially a second time asynchronously if it needs to make
- // a full query for the data.
- // It is in that callback that we set the values into the Ui.
- startContactInfoSearch();
-
// Start/Stop the call time update timer
if (mPrimary != null && mPrimary.getState() == Call.State.ACTIVE) {
Log.d(this, "Starting the calltime timer");
@@ -175,27 +214,62 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
}
}
-
- public void setContactInfoCache(ContactInfoCache cache) {
- mContactInfoCache = cache;
- startContactInfoSearch();
- }
-
/**
* Starts a query for more contact data for the save primary and secondary calls.
*/
- private void startContactInfoSearch() {
- if (mPrimary != null && mContactInfoCache != null) {
- mContactInfoCache.findInfo(mPrimary, this);
+ private void startContactInfoSearch(final CallIdentification identification,
+ final boolean isPrimary, final boolean isConference, boolean isIncoming) {
+
+ final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
+
+ // See if local contact lookup was already made by the status bar (for incoming calls)
+ final ContactCacheEntry entry = cache.getInfo(identification.getCallId());
+
+ // TODO: un-stable... must use number field to check if a contact was found
+ // because the contactinfocache pre-massages the data into the ui fields.
+ // Need to do massaging outside of contactinfocache.
+ if (entry == null || entry.number == null) {
+ // TODO(klp): currently we can't distinguish between...
+ // 1) a lookup occurred but failed to find a local contact.
+ // 2) a lookup has not occurred.
+ // We need to track it so we can avoid an un-necessary lookup here.
+ Log.d(TAG, "Local contact cache does not contain the contact. Searching provider.");
+ cache.findInfo(identification, isIncoming, new ContactInfoCacheCallback() {
+ @Override
+ public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
+ // TODO: un-stable... must use label field to check if a contact was found
+ // because the contactinfocache pre-massages the data into the ui fields.
+ // Need to do massaging outside of contactinfocache.
+ if (entry.label == null) {
+ // Name not found. Try lookup.
+ Log.d(TAG, "Local contact not found, performing reverse lookup.");
+ lookupPhoneNumber(identification.getNumber());
+ } else {
+ Log.d(TAG, "Found contact in provider: " + entry);
+ updateContactEntry(entry, isPrimary, isConference);
+ }
+ }
+ });
} else {
- mPrimaryContactInfo = null;
- updatePrimaryDisplayInfo();
+ Log.d(TAG, "Found contact in cache: " + entry);
+ updateContactEntry(entry, isPrimary, isConference);
+ }
+ }
+
+ private boolean isConference(Call call) {
+ if (call == null) {
+ return false;
}
+ return call.isConferenceCall();
+ }
- if (mSecondary != null && mContactInfoCache != null) {
- mContactInfoCache.findInfo(mSecondary, this);
+ private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary,
+ boolean isConference) {
+ if (isPrimary) {
+ mPrimaryContactInfo = entry;
+ updatePrimaryDisplayInfo(entry, isConference);
} else {
- mSecondaryContactInfo = null;
+ mSecondaryContactInfo = entry;
updateSecondaryDisplayInfo();
}
}
@@ -238,38 +312,25 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
return retval;
}
- /**
- * Callback received when Contact info data query completes.
- */
- @Override
- public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
- if (mPrimary != null && mPrimary.getCallId() == callId) {
- mPrimaryContactInfo = entry;
- updatePrimaryDisplayInfo();
- lookupPhoneNumber(mPrimary.getNumber());
- }
- if (mSecondary != null && mSecondary.getCallId() == callId) {
- mSecondaryContactInfo = entry;
- updateSecondaryDisplayInfo();
- // TODO(klp): investigate reverse lookup for secondary call.
- }
-
- }
-
- private void updatePrimaryDisplayInfo() {
+ private void updatePrimaryDisplayInfo(ContactCacheEntry entry, boolean isConference) {
+ Log.d(TAG, "Update primary display " + entry);
final CallCardUi ui = getUi();
if (ui == null) {
+ // TODO: May also occur if search result comes back after ui is destroyed. Look into
+ // removing that case completely.
+ Log.d(TAG, "updatePrimaryDisplayInfo called but ui is null!");
return;
}
- if (mPrimaryContactInfo != null) {
- final String name = getNameForCall(mPrimaryContactInfo);
- final String number = getNumberForCall(mPrimaryContactInfo);
- final boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
+ if (entry != null) {
+ final String name = getNameForCall(entry);
+ final String number = getNumberForCall(entry);
+ final boolean nameIsNumber = name != null && name.equals(entry.number);
final String gatewayLabel = getGatewayLabel();
final String gatewayNumber = getGatewayNumber();
- ui.setPrimary(number, name, nameIsNumber, mPrimaryContactInfo.label,
- mPrimaryContactInfo.photo, mPrimary.isConferenceCall(), gatewayLabel,
+
+ ui.setPrimary(number, name, nameIsNumber, entry.label,
+ entry.photo, isConference, gatewayLabel,
gatewayNumber);
} else {
// reset to nothing (like at end of call)
@@ -278,6 +339,23 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
}
+ private void updateSecondaryDisplayInfo() {
+
+ final CallCardUi ui = getUi();
+ if (ui == null) {
+ return;
+ }
+
+ if (mSecondaryContactInfo != null) {
+ Log.d(TAG, "updateSecondaryDisplayInfo() " + mSecondaryContactInfo);
+ ui.setSecondary(true, getNameForCall(mSecondaryContactInfo),
+ mSecondaryContactInfo.label, mSecondaryContactInfo.photo);
+ } else {
+ // reset to nothing so that it starts off blank next time we use it.
+ ui.setSecondary(false, null, null, null);
+ }
+ }
+
public void lookupPhoneNumber(String phoneNumber) {
if (mPhoneNumberService != null) {
mPhoneNumberService.getPhoneNumberInfo(phoneNumber,
@@ -291,7 +369,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
// TODO(klp): Ui is sometimes null due to something being shutdown.
if (getUi() != null) {
if (info.getName() != null) {
- getUi().setName(info.getName());
+ getUi().setPrimaryName(info.getName(), false);
}
if (info.getImageUrl() != null) {
@@ -333,6 +411,12 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
private boolean hasOutgoingGatewayCall() {
// We only display the gateway information while DIALING so return false for any othe
// call state.
+ // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
+ // is also called after a contact search completes (call is not present yet). Split the
+ // UI update so it can receive independent updates.
+ if (mPrimary == null) {
+ return false;
+ }
return (mPrimary.getState() == Call.State.DIALING &&
!TextUtils.isEmpty(mPrimary.getGatewayNumber()) &&
!TextUtils.isEmpty(mPrimary.getGatewayPackage()));
@@ -352,7 +436,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
protected void onPostExecute(Bitmap bitmap) {
// TODO(klp): same as above, figure out why it's null.
if (getUi() != null) {
- getUi().setImage(bitmap);
+ getUi().setPrimaryImage(bitmap);
}
}
@@ -382,22 +466,6 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
return contactInfo.number;
}
- private void updateSecondaryDisplayInfo() {
- final CallCardUi ui = getUi();
- if (ui == null) {
- return;
- }
-
- if (mSecondaryContactInfo != null) {
- final String name = getNameForCall(mSecondaryContactInfo);
- ui.setSecondary(true, getNameForCall(mSecondaryContactInfo),
- mSecondaryContactInfo.label, mSecondaryContactInfo.photo);
- } else {
- // reset to nothing so that it starts off blank next time we use it.
- ui.setSecondary(false, null, null, null);
- }
- }
-
public void setAudioModeProvider(AudioModeProvider audioModeProvider) {
mAudioModeProvider = audioModeProvider;
mAudioModeProvider.addListener(this);
@@ -409,8 +477,12 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
Drawable photo, boolean isConference, String gatewayLabel, String gatewayNumber);
void setSecondary(boolean show, String name, String label, Drawable photo);
void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn);
+
void setPrimaryCallElapsedTime(boolean show, String duration);
- void setName(String name);
- void setImage(Bitmap bitmap);
+ void setPrimaryName(String name, boolean nameIsNumber);
+ void setPrimaryImage(Bitmap bitmap);
+ void setPrimaryPhoneNumber(String phoneNumber);
+ void setPrimaryLabel(String label);
+ void setPrimaryGateway(String label, String number);
}
}
diff --git a/InCallUI/src/com/android/incallui/CallHandlerService.java b/InCallUI/src/com/android/incallui/CallHandlerService.java
index 150935075..76237e106 100644
--- a/InCallUI/src/com/android/incallui/CallHandlerService.java
+++ b/InCallUI/src/com/android/incallui/CallHandlerService.java
@@ -36,6 +36,8 @@ import java.util.Map;
*/
public class CallHandlerService extends Service {
+ private final static String TAG = CallHandlerService.class.getSimpleName();
+
private static final int ON_UPDATE_CALL = 1;
private static final int ON_UPDATE_MULTI_CALL = 2;
private static final int ON_UPDATE_CALL_WITH_TEXT_RESPONSES = 3;
@@ -56,8 +58,8 @@ public class CallHandlerService extends Service {
Log.d(this, "onCreate started");
super.onCreate();
- mCallList = new CallList();
mMainHandler = new MainHandler();
+ mCallList = CallList.getInstance();
mAudioModeProvider = new AudioModeProvider();
mInCallPresenter = InCallPresenter.getInstance();
mInCallPresenter.setUp(getApplicationContext(), mCallList, mAudioModeProvider);
@@ -105,54 +107,73 @@ public class CallHandlerService extends Service {
@Override
public void setCallCommandService(ICallCommandService service) {
- Log.d(CallHandlerService.this, "onConnected: " + service.toString());
- CallCommandClient.getInstance().setService(service);
+ try {
+ Log.d(CallHandlerService.this, "onConnected: " + service.toString());
+ CallCommandClient.getInstance().setService(service);
+ } catch (Exception e) {
+ Log.e(TAG, "Error processing setCallCommandservice() call", e);
+ }
}
@Override
public void onDisconnect(Call call) {
- Log.d(CallHandlerService.this, "onDisconnected: " + call);
- mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_DISCONNECT_CALL, 0, 0, call));
+ try {
+ Log.d(CallHandlerService.this, "onDisconnected: " + call);
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_DISCONNECT_CALL, call));
+ } catch (Exception e) {
+ Log.e(TAG, "Error processing onDisconnect() call.", e);
+ }
}
@Override
public void onIncoming(Call call, List<String> textResponses) {
- Log.d(CallHandlerService.this, "onIncomingCall: " + call);
-
- // TODO(klp): Add text responses to the call object.
- Map.Entry<Call, List<String> > incomingCall = new AbstractMap.SimpleEntry<Call,
- List<String> >(call, textResponses);
- mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_CALL_WITH_TEXT_RESPONSES,
- 0, 0, incomingCall));
+ try {
+ Log.d(CallHandlerService.this, "onIncomingCall: " + call);
+
+ // TODO(klp): Add text responses to the call object.
+ Map.Entry<Call, List<String>> incomingCall
+ = new AbstractMap.SimpleEntry<Call, List<String>>(call, textResponses);
+ Log.d("TEST", mMainHandler.toString());
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(
+ ON_UPDATE_CALL_WITH_TEXT_RESPONSES, incomingCall));
+ } catch (Exception e) {
+ Log.e(TAG, "Error processing onIncoming() call.", e);
+ }
}
@Override
- public void onUpdate(List<Call> calls, boolean fullUpdate) {
- Log.d(CallHandlerService.this, "onUpdate " + calls.toString());
-
- if (Log.VERBOSE) {
- for (Call c : calls) {
- Log.v(this, "Call: " + c);
- }
+ public void onUpdate(List<Call> calls) {
+ try {
+ Log.d(CallHandlerService.this, "onUpdate " + calls.toString());
+
+ // TODO(klp): Add use of fullUpdate to message
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_MULTI_CALL, calls));
+ } catch (Exception e) {
+ Log.e(TAG, "Error processing onUpdate() call.", e);
}
-
- // TODO(klp): Add use of fullUpdate to message
- mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_MULTI_CALL, 0, 0, calls));
}
@Override
public void onAudioModeChange(int mode) {
- Log.d(CallHandlerService.this, "onAudioModeChange : " + AudioMode.toString(mode));
- mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_AUDIO_MODE, mode, 0, null));
+ try {
+ Log.d(CallHandlerService.this, "onAudioModeChange : " + AudioMode.toString(mode));
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_AUDIO_MODE, mode, 0, null));
+ } catch (Exception e) {
+ Log.e(TAG, "Error processing onAudioModeChange() call.", e);
+ }
}
@Override
public void onSupportedAudioModeChange(int modeMask) {
- Log.d(CallHandlerService.this, "onSupportedAudioModeChange : " +
- AudioMode.toString(modeMask));
-
- mMainHandler.sendMessage(
- mMainHandler.obtainMessage(ON_SUPPORTED_AUDIO_MODE, modeMask, 0, null));
+ try {
+ Log.d(CallHandlerService.this, "onSupportedAudioModeChange : " + AudioMode.toString(
+ modeMask));
+
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_SUPPORTED_AUDIO_MODE,
+ modeMask, 0, null));
+ } catch (Exception e) {
+ Log.e(TAG, "Error processing onSupportedAudioModeChange() call.", e);
+ }
}
};
@@ -188,7 +209,9 @@ public class CallHandlerService extends Service {
mCallList.onUpdate((List<Call>) msg.obj);
break;
case ON_UPDATE_CALL_WITH_TEXT_RESPONSES:
- mCallList.onUpdate((AbstractMap.SimpleEntry<Call, List<String> >) msg.obj);
+ AbstractMap.SimpleEntry<Call, List<String>> entry
+ = (AbstractMap.SimpleEntry<Call, List<String>>) msg.obj;
+ mCallList.onIncoming(entry.getKey(), entry.getValue());
break;
case ON_DISCONNECT_CALL:
mCallList.onDisconnect((Call) msg.obj);
diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java
index 7890b997a..4ce04d35a 100644
--- a/InCallUI/src/com/android/incallui/CallList.java
+++ b/InCallUI/src/com/android/incallui/CallList.java
@@ -16,6 +16,7 @@
package com.android.incallui;
+import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
import com.google.common.base.Preconditions;
@@ -25,7 +26,6 @@ import android.os.Message;
import com.android.services.telephony.common.Call;
-import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -42,27 +42,27 @@ public class CallList {
private static final int EVENT_DISCONNECTED_TIMEOUT = 1;
- private static CallList sInstance;
+ private static CallList sInstance = new CallList();
private final HashMap<Integer, Call> mCallMap = Maps.newHashMap();
private final HashMap<Integer, ArrayList<String>> mCallTextReponsesMap =
Maps.newHashMap();
private final Set<Listener> mListeners = Sets.newArraySet();
+ private final HashMap<Integer, List<CallUpdateListener>> mCallUpdateListenerMap = Maps
+ .newHashMap();
+
/**
* Static singleton accessor method.
*/
- /*public static synchronized CallList getInstance() {
- if (sInstance == null) {
- sInstance = new CallList();
- }
+ public static CallList getInstance() {
return sInstance;
- }*/
+ }
/**
* Private constructor. Instance should only be acquired through getInstance().
*/
- public CallList() {
+ private CallList() {
}
/**
@@ -89,13 +89,15 @@ public class CallList {
/**
* Called when a single call has changed.
*/
- public void onUpdate(AbstractMap.SimpleEntry<Call, List<String> > incomingCall) {
- Log.d(this, "onUpdate - " + incomingCall.getKey());
+ public void onIncoming(Call call, List<String> textMessages) {
+ Log.d(this, "onIncoming - " + call);
- updateCallInMap(incomingCall.getKey());
- updateCallTextMap(incomingCall.getKey(), incomingCall.getValue());
+ updateCallInMap(call);
+ updateCallTextMap(call, textMessages);
- notifyListenersOfChange();
+ for (Listener listener : mListeners) {
+ listener.onIncomingCall(call);
+ }
}
/**
@@ -110,11 +112,50 @@ public class CallList {
updateCallInMap(call);
updateCallTextMap(call, null);
+
+ notifyCallUpdateListeners(call);
}
notifyListenersOfChange();
}
+ public void notifyCallUpdateListeners(Call call) {
+ final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getCallId());
+ if (listeners != null) {
+ for (CallUpdateListener listener : listeners) {
+ listener.onCallStateChanged(call);
+ }
+ }
+ }
+
+ /**
+ * Add a call update listener for a call id.
+ *
+ * @param callId The call id to get updates for.
+ * @param listener The listener to add.
+ */
+ public void addCallUpdateListener(int callId, CallUpdateListener listener) {
+ List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(callId);
+ if (listeners == null) {
+ listeners = Lists.newArrayList();
+ mCallUpdateListenerMap.put(callId, listeners);
+ }
+ listeners.add(listener);
+ }
+
+ /**
+ * Remove a call update listener for a call id.
+ *
+ * @param callId The call id to remove the listener for.
+ * @param listener The listener to remove.
+ */
+ public void removeCallUpdateListener(int callId, CallUpdateListener listener) {
+ List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(callId);
+ if (listeners != null) {
+ listeners.remove(listener);
+ }
+ }
+
public void addListener(Listener listener) {
Preconditions.checkNotNull(listener);
@@ -179,6 +220,19 @@ public class CallList {
return call;
}
+
+ public Call getFirstCall() {
+ // TODO: should we switch to a simple list and pull the first one?
+ Call result = getIncomingCall();
+ if (result == null) {
+ result = getFirstCallWithState(Call.State.DIALING);
+ }
+ if (result == null) {
+ result = getFirstCallWithState(Call.State.ACTIVE);
+ }
+ return result;
+ }
+
public boolean existsLiveCall() {
for (Call call : mCallMap.values()) {
if (!isCallDead(call)) {
@@ -188,8 +242,8 @@ public class CallList {
return false;
}
- public ArrayList<String> getTextResponses(Call call) {
- return mCallTextReponsesMap.get(call.getCallId());
+ public ArrayList<String> getTextResponses(int callId) {
+ return mCallTextReponsesMap.get(callId);
}
/**
@@ -327,5 +381,11 @@ public class CallList {
*/
public interface Listener {
public void onCallListChange(CallList callList);
+ public void onIncomingCall(Call call);
+ }
+
+ public interface CallUpdateListener {
+ // TODO: refactor and limit arg to be call state. Caller info is not needed.
+ public void onCallStateChanged(Call call);
}
}
diff --git a/InCallUI/src/com/android/incallui/CallerInfoUtils.java b/InCallUI/src/com/android/incallui/CallerInfoUtils.java
index 690b9b7b7..8d1fc9f03 100644
--- a/InCallUI/src/com/android/incallui/CallerInfoUtils.java
+++ b/InCallUI/src/com/android/incallui/CallerInfoUtils.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.text.TextUtils;
import com.android.services.telephony.common.Call;
+import com.android.services.telephony.common.CallIdentification;
import java.util.Arrays;
@@ -28,40 +29,36 @@ public class CallerInfoUtils {
* more information is returned to the OnQueryCompleteListener (which contains
* information about the phone number label, user's name, etc).
*/
- public static CallerInfo getCallerInfoForCall(Context context, Call call,
+ public static CallerInfo getCallerInfoForCall(Context context, CallIdentification call,
CallerInfoAsyncQuery.OnQueryCompleteListener listener) {
- CallerInfo info = new CallerInfo();
+ CallerInfo info = buildCallerInfo(context, call);
String number = call.getNumber();
+ // TODO: Have phoneapp send a Uri when it knows the contact that triggered this call.
+
+ if (info.numberPresentation == Call.PRESENTATION_ALLOWED) {
+ // Start the query with the number provided from the call.
+ Log.d(TAG, "==> Actually starting CallerInfoAsyncQuery.startQuery()...");
+ CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, number, listener, call);
+ }
+ return info;
+ }
+
+ public static CallerInfo buildCallerInfo(Context context, CallIdentification identification) {
+ CallerInfo info = new CallerInfo();
+
// Store CNAP information retrieved from the Connection (we want to do this
// here regardless of whether the number is empty or not).
- info.cnapName = call.getCnapName();
+ info.cnapName = identification.getCnapName();
info.name = info.cnapName;
- info.numberPresentation = call.getNumberPresentation();
- info.namePresentation = call.getCnapNamePresentation();
-
- // TODO: Have phoneapp send a Uri when it knows the contact that triggered this call.
+ info.numberPresentation = identification.getNumberPresentation();
+ info.namePresentation = identification.getCnapNamePresentation();
+ String number = identification.getNumber();
if (!TextUtils.isEmpty(number)) {
number = modifyForSpecialCnapCases(context, info, number, info.numberPresentation);
info.phoneNumber = number;
-
- // For scenarios where we may receive a valid number from the network but a
- // restricted/unavailable presentation, we do not want to perform a contact query,
- // so just return the existing caller info.
- if (info.numberPresentation != Call.PRESENTATION_ALLOWED) {
- return info;
- } else {
- // Start the query with the number provided from the call.
- Log.d(TAG, "==> Actually starting CallerInfoAsyncQuery.startQuery()...");
- CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, number, listener, call);
- }
- } else {
- // The number is null or empty (Blocked caller id or empty). Just return the
- // caller info object as is, without starting a query.
- return info;
}
-
return info;
}
diff --git a/InCallUI/src/com/android/incallui/ContactInfoCache.java b/InCallUI/src/com/android/incallui/ContactInfoCache.java
index e62900ea9..a80d91c82 100644
--- a/InCallUI/src/com/android/incallui/ContactInfoCache.java
+++ b/InCallUI/src/com/android/incallui/ContactInfoCache.java
@@ -16,10 +16,6 @@
package com.android.incallui;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import com.google.common.base.Preconditions;
-
import android.content.ContentUris;
import android.content.Context;
import android.graphics.Bitmap;
@@ -31,7 +27,14 @@ import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import com.android.services.telephony.common.Call;
+import com.android.services.telephony.common.CallIdentification;
+import com.android.services.telephony.common.MoreStrings;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -42,73 +45,94 @@ import java.util.Map;
* queries.
* This class always gets called from the UI thread so it does not need thread protection.
*/
-public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteListener,
- ContactsAsyncHelper.OnImageLoadCompleteListener {
+public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadCompleteListener {
+ private static final String TAG = ContactInfoCache.class.getSimpleName();
private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0;
private final Context mContext;
- private final Map<Integer, SearchEntry> mInfoMap = Maps.newHashMap();
+ private final HashMap<Integer, ContactCacheEntry> mInfoMap = Maps.newHashMap();
+ private final HashMap<Integer, List<ContactInfoCacheCallback>> mCallBacks = Maps.newHashMap();
- public ContactInfoCache(Context context) {
+ private static ContactInfoCache sCache = null;
+
+ public static synchronized ContactInfoCache getInstance(Context mContext) {
+ if (sCache == null) {
+ sCache = new ContactInfoCache(mContext);
+ }
+ return sCache;
+ }
+
+ private ContactInfoCache(Context context) {
mContext = context;
}
+ public ContactCacheEntry getInfo(int callId) {
+ return mInfoMap.get(callId);
+ }
+
+ public static ContactCacheEntry buildCacheEntryFromCall(Context context,
+ CallIdentification identification, boolean isIncoming) {
+ final ContactCacheEntry entry = new ContactCacheEntry();
+
+ // TODO: get rid of caller info.
+ final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, identification);
+ ContactInfoCache.populateCacheEntry(context, info, entry,
+ identification.getNumberPresentation(), isIncoming);
+ return entry;
+ }
+
/**
* Requests contact data for the Call object passed in.
* Returns the data through callback. If callback is null, no response is made, however the
* query is still performed and cached.
*
- * @param call The call to look up.
+ * @param identification The call identification
* @param callback The function to call back when the call is found. Can be null.
*/
- public void findInfo(Call call, ContactInfoCacheCallback callback) {
+ public void findInfo(final CallIdentification identification, final boolean isIncoming,
+ ContactInfoCacheCallback callback) {
Preconditions.checkState(Looper.getMainLooper().getThread() == Thread.currentThread());
Preconditions.checkNotNull(callback);
- Preconditions.checkNotNull(call);
-
- final SearchEntry entry;
+ final int callId = identification.getCallId();
// If the entry already exists, add callback
- if (mInfoMap.containsKey(call.getCallId())) {
- entry = mInfoMap.get(call.getCallId());
-
- // If this entry is still pending, the callback will also get called when it returns.
- if (!entry.finished) {
- entry.addCallback(callback);
- }
+ List<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
+ if (callBacks == null) {
+
+ // New lookup
+ callBacks = Lists.newArrayList();
+ callBacks.add(callback);
+ mCallBacks.put(callId, callBacks);
+
+ /**
+ * Performs a query for caller information.
+ * Save any immediate data we get from the query. An asynchronous query may also be made
+ * for any data that we do not already have. Some queries, such as those for voicemail and
+ * emergency call information, will not perform an additional asynchronous query.
+ */
+ CallerInfoUtils.getCallerInfoForCall(mContext, identification,
+ new CallerInfoAsyncQuery.OnQueryCompleteListener() {
+ @Override
+ public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
+ int presentationMode = identification.getNumberPresentation();
+ if (ci.contactExists || ci.isEmergencyNumber() || ci
+ .isVoiceMailNumber()) {
+ presentationMode = Call.PRESENTATION_ALLOWED;
+ }
+
+ // This starts the photo load.
+ final ContactCacheEntry cacheEntry = buildEntry(mContext,
+ identification.getCallId(), ci, presentationMode, isIncoming,
+ ContactInfoCache.this);
+
+ // Add the contact info to the cache.
+ mInfoMap.put(callId, cacheEntry);
+ sendNotification(identification.getCallId(), cacheEntry);
+ }
+ });
} else {
- entry = new SearchEntry(call, callback);
- mInfoMap.put(call.getCallId(), entry);
- startQuery(entry);
- }
-
- // Call back with the information we have
- callback.onContactInfoComplete(entry.call.getCallId(), entry.info);
- }
-
- /**
- * Callback method for asynchronous caller information query.
- */
- @Override
- public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
- if (cookie instanceof Call) {
- final Call call = (Call) cookie;
-
- if (!mInfoMap.containsKey(call.getCallId())) {
- return;
- }
-
- final SearchEntry entry = mInfoMap.get(call.getCallId());
-
- int presentationMode = call.getNumberPresentation();
- if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) {
- presentationMode = Call.PRESENTATION_ALLOWED;
- }
-
- // start photo query
-
- updateCallerInfo(entry, ci, presentationMode);
+ callBacks.add(callback);
}
}
@@ -124,30 +148,30 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis
// TODO (klp): What is this, and why does it need the write_contacts permission?
// CallerInfoUtils.sendViewNotificationAsync(mContext, mLoadingPersonUri);
- final Call call = (Call) cookie;
+ final int callId = (Integer) cookie;
- if (!mInfoMap.containsKey(call.getCallId())) {
+ if (!mInfoMap.containsKey(callId)) {
Log.e(this, "Image Load received for empty search entry.");
return;
}
- final SearchEntry entry = mInfoMap.get(call.getCallId());
+ final ContactCacheEntry entry = mInfoMap.get(callId);
Log.d(this, "setting photo for entry: ", entry);
// TODO (klp): Handle conference calls
if (photo != null) {
Log.v(this, "direct drawable: ", photo);
- entry.info.photo = photo;
+ entry.photo = photo;
} else if (photoIcon != null) {
Log.v(this, "photo icon: ", photoIcon);
- entry.info.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
+ entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
} else {
Log.v(this, "unknown photo");
- entry.info.photo = null;
+ entry.photo = null;
}
- sendNotification(entry);
+ sendNotification(callId, entry);
}
/**
@@ -155,33 +179,63 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis
*/
public void clearCache() {
mInfoMap.clear();
+ mCallBacks.clear();
}
- /**
- * Performs a query for caller information.
- * Save any immediate data we get from the query. An asynchronous query may also be made
- * for any data that we do not already have. Some queries, such as those for voicemail and
- * emergency call information, will not perform an additional asynchronous query.
- */
- private void startQuery(SearchEntry entry) {
- final CallerInfo ci = CallerInfoUtils.getCallerInfoForCall(mContext, entry.call, this);
+ private static ContactCacheEntry buildEntry(Context context, int callId,
+ CallerInfo info, int presentation, boolean isIncoming,
+ ContactsAsyncHelper.OnImageLoadCompleteListener imageLoadListener) {
+ // The actual strings we're going to display onscreen:
+ Drawable photo = null;
+
+ final ContactCacheEntry cce = new ContactCacheEntry();
+ populateCacheEntry(context, info, cce, presentation, isIncoming);
+
+ // This will only be true for emergency numbers
+ if (info.photoResource != 0) {
+ photo = context.getResources().getDrawable(info.photoResource);
+ } else if (info.isCachedPhotoCurrent) {
+ if (info.cachedPhoto != null) {
+ photo = info.cachedPhoto;
+ } else {
+ photo = context.getResources().getDrawable(R.drawable.picture_unknown);
+ }
+ } else {
+ Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, info.person_id);
+ Log.d(TAG, "- got personUri: '" + personUri + "', based on info.person_id: " +
+ info.person_id);
- updateCallerInfo(entry, ci, entry.call.getNumberPresentation());
+ if (personUri == null) {
+ Log.v(TAG, "personUri is null. Just use unknown picture.");
+ photo = context.getResources().getDrawable(R.drawable.picture_unknown);
+ } else {
+ Log.d(TAG, "startObtainPhotoAsync");
+ // Load the image with a callback to update the image state.
+ // When the load is finished, onImageLoadComplete() will be called.
+ ContactsAsyncHelper.startObtainPhotoAsync(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
+ context, personUri, imageLoadListener, callId);
+
+ // If the image load is too slow, we show a default avatar icon afterward.
+ // If it is fast enough, this message will be canceled on onImageLoadComplete().
+ // TODO (klp): Figure out if this handler is still needed.
+ // mHandler.removeMessages(MESSAGE_SHOW_UNKNOWN_PHOTO);
+ // mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_UNKNOWN_PHOTO, MESSAGE_DELAY);
+ }
+ }
+
+ cce.photo = photo;
+ return cce;
}
- private void updateCallerInfo(SearchEntry entry, CallerInfo info, int presentation) {
- // The actual strings we're going to display onscreen:
+ /**
+ * Populate a cache entry from a caller identification (which got converted into a caller info).
+ */
+ public static void populateCacheEntry(Context context, CallerInfo info, ContactCacheEntry cce,
+ int presentation, boolean isIncoming) {
+ Preconditions.checkNotNull(info);
String displayName;
String displayNumber = null;
String label = null;
- Uri personUri = null;
- Drawable photo = null;
-
- final Call call = entry.call;
-
- // 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
@@ -219,21 +273,20 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis
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);
- Log.d(this, " ==> no name *or* number! displayName = " + displayName);
+ displayName = getPresentationString(context, presentation);
+ Log.d(TAG, " ==> 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);
- Log.d(this, " ==> presentation not allowed! displayName = " + displayName);
+ displayName = getPresentationString(context, presentation);
+ Log.d(TAG, " ==> 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;
- Log.d(this, " ==> cnapName available: displayName '"
- + displayName + "', displayNumber '" + displayNumber + "'");
+ Log.d(TAG, " ==> 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,
@@ -245,16 +298,16 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis
// ...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)) {
+ if (isIncoming) {
// 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
- Log.d(this, "Geodescrption: " + info.geoDescription);
+ Log.d(TAG, "Geodescrption: " + info.geoDescription);
}
- Log.d(this, " ==> no name; falling back to number: displayName '"
- + displayName + "', displayNumber '" + displayNumber + "'");
+ Log.d(TAG,
+ " ==> no name; falling back to number: displayName '" + displayName + "', displayNumber '" + displayNumber + "'");
}
} else {
// We do have a valid "name" in the CallerInfo. Display that
@@ -263,79 +316,44 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis
// 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);
- Log.d(this, " ==> valid name, but presentation not allowed!"
- + " displayName = " + displayName);
+ displayName = getPresentationString(context, presentation);
+ Log.d(TAG,
+ " ==> valid name, but presentation not allowed!" + " displayName = " + displayName);
} else {
displayName = info.name;
displayNumber = number;
label = info.phoneLabel;
- Log.d(this, " ==> name is present in CallerInfo: displayName '"
- + displayName + "', displayNumber '" + displayNumber + "'");
+ Log.d(TAG, " ==> name is present in CallerInfo: displayName '" + displayName
+ + "', displayNumber '" + displayNumber + "'");
}
}
- personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, info.person_id);
- Log.d(this, "- got personUri: '" + personUri
- + "', based on info.person_id: " + info.person_id);
- } else {
- displayName = getPresentationString(presentation);
- }
- // This will only be true for emergency numbers
- if (info.photoResource != 0) {
- photo = mContext.getResources().getDrawable(info.photoResource);
- } else if (info.isCachedPhotoCurrent) {
- if (info.cachedPhoto != null) {
- photo = info.cachedPhoto;
- } else {
- photo = mContext.getResources().getDrawable(R.drawable.picture_unknown);
- }
- } else {
- if (personUri == null) {
- Log.v(this, "personUri is null. Just use unknown picture.");
- photo = mContext.getResources().getDrawable(R.drawable.picture_unknown);
- } else {
- Log.d(this, "startObtainPhotoAsync");
- // Load the image with a callback to update the image state.
- // When the load is finished, onImageLoadComplete() will be called.
- ContactsAsyncHelper.startObtainPhotoAsync(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
- mContext, personUri, this, entry.call);
-
- // If the image load is too slow, we show a default avatar icon afterward.
- // If it is fast enough, this message will be canceled on onImageLoadComplete().
- // TODO (klp): Figure out if this handler is still needed.
- // mHandler.removeMessages(MESSAGE_SHOW_UNKNOWN_PHOTO);
- // mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_UNKNOWN_PHOTO, MESSAGE_DELAY);
- }
- }
-
- final ContactCacheEntry cce = entry.info;
cce.name = displayName;
cce.number = displayNumber;
cce.label = label;
- cce.photo = photo;
-
- sendNotification(entry);
}
/**
* Sends the updated information to call the callbacks for the entry.
*/
- private void sendNotification(SearchEntry entry) {
- for (int i = 0; i < entry.callbacks.size(); i++) {
- entry.callbacks.get(i).onContactInfoComplete(entry.call.getCallId(), entry.info);
+ private void sendNotification(int callId, ContactCacheEntry entry) {
+ List<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
+ if (callBacks != null) {
+ for (ContactInfoCacheCallback callBack : callBacks) {
+ callBack.onContactInfoComplete(callId, entry);
+ }
}
}
/**
* Gets name strings based on some special presentation modes.
*/
- private String getPresentationString(int presentation) {
- String name = mContext.getString(R.string.unknown);
+ private static String getPresentationString(Context context, int presentation) {
+ String name = context.getString(R.string.unknown);
if (presentation == Call.PRESENTATION_RESTRICTED) {
- name = mContext.getString(R.string.private_num);
+ name = context.getString(R.string.private_num);
} else if (presentation == Call.PRESENTATION_PAYPHONE) {
- name = mContext.getString(R.string.payphone);
+ name = context.getString(R.string.payphone);
}
return name;
}
@@ -352,31 +370,15 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis
public String number;
public String label;
public Drawable photo;
- }
-
- private static class SearchEntry {
- public Call call;
- public boolean finished;
- public final ContactCacheEntry info;
- public final List<ContactInfoCacheCallback> callbacks = Lists.newArrayList();
-
- public SearchEntry(Call call, ContactInfoCacheCallback callback) {
- this.call = call;
-
- info = new ContactCacheEntry();
- finished = false;
- callbacks.add(callback);
- }
-
- public void addCallback(ContactInfoCacheCallback cb) {
- if (!callbacks.contains(cb)) {
- callbacks.add(cb);
- }
- }
- public void finish() {
- callbacks.clear();
- finished = true;
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", MoreStrings.toSafeString(name))
+ .add("number", MoreStrings.toSafeString(number))
+ .add("label", label)
+ .add("photo", photo)
+ .toString();
}
}
}
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index ec5002f73..5915ecfb4 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -292,14 +292,11 @@ public class InCallActivity extends Activity {
mainPresenter.getAudioModeProvider());
mCallButtonFragment.getPresenter().setProximitySensor(
mainPresenter.getProximitySensor());
- mCallCardFragment.getPresenter().setAudioModeProvider(
- mainPresenter.getAudioModeProvider());
- mCallCardFragment.getPresenter().setContactInfoCache(
- mainPresenter.getContactInfoCache());
+ final CallCardPresenter presenter = mCallCardFragment.getPresenter();
+ presenter.setAudioModeProvider(mainPresenter.getAudioModeProvider());
mainPresenter.addListener(mCallButtonFragment.getPresenter());
mainPresenter.addListener(mCallCardFragment.getPresenter());
- mainPresenter.addListener(mAnswerFragment.getPresenter());
// setting activity should be last thing in setup process
mainPresenter.setActivity(this);
@@ -311,7 +308,6 @@ public class InCallActivity extends Activity {
mainPresenter.removeListener(mCallButtonFragment.getPresenter());
mainPresenter.removeListener(mCallCardFragment.getPresenter());
- mainPresenter.removeListener(mAnswerFragment.getPresenter());
mainPresenter.setActivity(null);
}
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 8bb0973bf..3aa717c62 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -23,7 +23,9 @@ import android.content.Context;
import android.content.Intent;
import com.android.services.telephony.common.Call;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
import java.util.Set;
/**
@@ -40,6 +42,7 @@ public class InCallPresenter implements CallList.Listener {
private static InCallPresenter sInCallPresenter;
private final Set<InCallStateListener> mListeners = Sets.newHashSet();
+ private final ArrayList<IncomingCallListener> mIncomingCallListeners = Lists.newArrayList();
private AudioModeProvider mAudioModeProvider;
private StatusBarNotifier mStatusBarNotifier;
@@ -65,10 +68,11 @@ public class InCallPresenter implements CallList.Listener {
mCallList = callList;
mCallList.addListener(this);
- mContactInfoCache = new ContactInfoCache(context);
+ mContactInfoCache = ContactInfoCache.getInstance(context);
mStatusBarNotifier = new StatusBarNotifier(context, mContactInfoCache, mCallList);
addListener(mStatusBarNotifier);
+ addIncomingCallListener(mStatusBarNotifier);
mAudioModeProvider = audioModeProvider;
@@ -138,6 +142,20 @@ public class InCallPresenter implements CallList.Listener {
}
/**
+ * Called when there is a new incoming call.
+ *
+ * @param call
+ */
+ @Override
+ public void onIncomingCall(Call call) {
+ mInCallState = startOrFinishUi(InCallState.INCOMING);
+
+ for (IncomingCallListener listener : mIncomingCallListeners) {
+ listener.onIncomingCall(call);
+ }
+ }
+
+ /**
* Given the call list, return the state in which the in-call screen should be.
*/
public static InCallState getPotentialStateFromCallList(CallList callList) {
@@ -156,6 +174,11 @@ public class InCallPresenter implements CallList.Listener {
return newState;
}
+ public void addIncomingCallListener(IncomingCallListener listener) {
+ Preconditions.checkNotNull(listener);
+ mIncomingCallListeners.add(listener);
+ }
+
public void addListener(InCallStateListener listener) {
Preconditions.checkNotNull(listener);
mListeners.add(listener);
@@ -375,4 +398,8 @@ public class InCallPresenter implements CallList.Listener {
// TODO(klp): Enhance state to contain the call objects instead of passing CallList
public void onStateChange(InCallState state, CallList callList);
}
+
+ public interface IncomingCallListener {
+ public void onIncomingCall(Call call);
+ }
}
diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
index 466c7b214..f8289c90b 100644
--- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java
+++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
@@ -16,6 +16,7 @@
package com.android.incallui;
+import com.android.services.telephony.common.CallIdentification;
import com.google.common.base.Preconditions;
import android.app.Notification;
@@ -37,7 +38,7 @@ import com.android.services.telephony.common.Call;
* This class adds Notifications to the status bar for the in-call experience.
*/
public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
- ContactInfoCacheCallback {
+ InCallPresenter.IncomingCallListener {
// notification types
private static final int IN_CALL_NOTIFICATION = 1;
@@ -71,12 +72,25 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
updateNotification(state, callList);
}
- /**
- * Called after the Contact Info query has finished.
- */
@Override
- public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
- updateNotification(mInCallState, mCallList);
+ public void onIncomingCall(final Call call) {
+ final ContactCacheEntry entry = ContactInfoCache.buildCacheEntryFromCall(mContext,
+ call.getIdentification(), true);
+
+ // Initial update with no contact information.
+ buildAndSendNotification(InCallState.INCOMING, call, entry, false);
+
+ // we make a call to the contact info cache to query for supplemental data to what the
+ // call provides. This includes the contact name and photo.
+ // This callback will always get called immediately and synchronously with whatever data
+ // it has available, and may make a subsequent call later (same thread) if it had to
+ // call into the contacts provider for more data.
+ mContactInfoCache.findInfo(call.getIdentification(), true, new ContactInfoCacheCallback() {
+ @Override
+ public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
+ buildAndSendNotification(InCallState.INCOMING, call, entry, false);
+ }
+ });
}
/**
@@ -160,28 +174,24 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
Log.d(this, "updateInCallNotification(allowFullScreenIntent = "
+ allowFullScreenIntent + ")...");
- if (shouldSuppressNotification(state, callList)) {
+ final Call call = getCallToShow(callList);
+ if (shouldSuppressNotification(state, call)) {
cancelInCall();
return;
}
- final Call call = getCallToShow(callList);
if (call == null) {
Log.wtf(this, "No call for the notification!");
+ return;
}
- // we make a call to the contact info cache to query for supplemental data to what the
- // call provides. This includes the contact name and photo.
- // This callback will always get called immediately and synchronously with whatever data
- // it has available, and may make a subsequent call later (same thread) if it had to
- // call into the contacts provider for more data.
- mContactInfoCache.findInfo(call, new ContactInfoCacheCallback() {
- @Override
- public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
- buildAndSendNotification(state, call, entry, allowFullScreenIntent);
- }
- });
-
+ // Contact info should have already been done on incoming calls.
+ ContactCacheEntry entry = mContactInfoCache.getInfo(call.getCallId());
+ if (entry == null) {
+ entry = ContactInfoCache.buildCacheEntryFromCall(mContext, call.getIdentification(),
+ state == InCallState.INCOMING);
+ }
+ buildAndSendNotification(state, call, entry, allowFullScreenIntent);
}
/**
@@ -426,7 +436,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
/**
* Returns true if notification should not be shown in the current state.
*/
- private boolean shouldSuppressNotification(InCallState state, CallList callList) {
+ private boolean shouldSuppressNotification(InCallState state, Call call) {
// Suppress the in-call notification if the InCallScreen is the
// foreground activity, since it's already obvious that you're on a
// call. (The status bar icon is needed only if you navigate *away*
@@ -440,7 +450,6 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
// We can still be in the INCALL state when a call is disconnected (in order to show
// the "Call ended" screen. So check that we have an active connection too.
- final Call call = getCallToShow(callList);
if (call == null) {
shouldSuppress = true;
}