diff options
author | Santos Cordon <santoscordon@google.com> | 2013-09-13 18:56:45 -0700 |
---|---|---|
committer | Santos Cordon <santoscordon@google.com> | 2013-09-13 23:46:31 -0700 |
commit | 49ff6571403695e81dbbd83e4f61790ce9c75f6d (patch) | |
tree | 6ae7141e11e5866b5dda8ce8cf84b20ea6b7068a /InCallUI | |
parent | b5346c68a29e128fed4d1e72b3501599ee9663bb (diff) |
Fix UI lag when calling a contact with a photo.
Related fixes unrelated to latency problem:
a. Update StatusBarNotifier with new changes to CallerInfoCache.
b. Outgoing JANK fix in notifier needed to know if the activity
was previously started, not just if it was finished. (b/10734874)
c. Consolidate places where we use the Intent.
Several improvements to speed up startUp time from most egregious to
least:
1. In InCallPresenter, statusBarNotifier wasn't unlistening to
incoming call changes which kept orphaned instances still listening and
calling into contactInfoCache. ContactInfoCache was subsequently calling
into many notifiers which did heavy image parcelling work.
- Clear incomingCallListeners on tear down.
2. StatusBarNotifier was getting called directly from InCallPresenter &
onIncomingCall() callbacks. onIncomingCall callback was unnecessary and
caused extra work.
- Fix is to stop listening to incoming calls. Status bar notifier gets
called directly by InCallPresenter in startAndFinish() so listening
to incoming was redundant.
3. Make ContactInfoCache listeners list a Set to avoid duplicate
entries and reduce callback execution.
bug:10712670
bug:10734874
Change-Id: Ic8d50b1d9ce336ffe3a5191abe1c9db32365eee6
Diffstat (limited to 'InCallUI')
6 files changed, 114 insertions, 98 deletions
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java index 71f577b74..8fb2eb4b4 100644 --- a/InCallUI/src/com/android/incallui/CallCardFragment.java +++ b/InCallUI/src/com/android/incallui/CallCardFragment.java @@ -134,9 +134,9 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr } @Override - public void setPrimaryImage(Bitmap image) { + public void setPrimaryImage(Drawable image) { if (image != null) { - setDrawableToImageView(mPhoto, new BitmapDrawable(getResources(), image)); + setDrawableToImageView(mPhoto, image); } } @@ -213,9 +213,9 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr } @Override - public void setSecondaryImage(Bitmap bitmap) { - if (bitmap != null) { - setDrawableToImageView(mSecondaryPhoto, new BitmapDrawable(getResources(), bitmap)); + public void setSecondaryImage(Drawable image) { + if (image != null) { + setDrawableToImageView(mSecondaryPhoto, image); } } diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java index 279925d50..b055a58a1 100644 --- a/InCallUI/src/com/android/incallui/CallCardPresenter.java +++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java @@ -242,14 +242,16 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> } @Override - public void onImageLoadComplete(int callId, Bitmap photo) { + public void onImageLoadComplete(int callId, ContactCacheEntry entry) { if (getUi() == null) { return; } - if (mPrimary != null && callId == mPrimary.getCallId()) { - getUi().setPrimaryImage(photo); - } else if (mSecondary != null && callId == mSecondary.getCallId()) { - getUi().setSecondaryImage(photo); + if (entry.photo != null) { + if (mPrimary != null && callId == mPrimary.getCallId()) { + getUi().setPrimaryImage(entry.photo); + } else if (mSecondary != null && callId == mSecondary.getCallId()) { + getUi().setSecondaryImage(entry.photo); + } } } }); @@ -429,12 +431,12 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> Drawable photo, boolean isConference); void setSecondary(boolean show, String name, boolean nameIsNumber, String label, Drawable photo, boolean isConference); - void setSecondaryImage(Bitmap bitmap); + void setSecondaryImage(Drawable image); void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn, String gatewayLabel, String gatewayNumber); void setPrimaryCallElapsedTime(boolean show, String duration); void setPrimaryName(String name, boolean nameIsNumber); - void setPrimaryImage(Bitmap bitmap); + void setPrimaryImage(Drawable image); void setPrimaryPhoneNumber(String phoneNumber); void setPrimaryLabel(String label); } diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java index 42f4eb974..1b7e10d85 100644 --- a/InCallUI/src/com/android/incallui/CallList.java +++ b/InCallUI/src/com/android/incallui/CallList.java @@ -236,6 +236,10 @@ public class CallList { return result; } + public Call getCall(int callId) { + return mCallMap.get(callId); + } + public boolean existsLiveCall() { for (Call call : mCallMap.values()) { if (!isCallDead(call)) { diff --git a/InCallUI/src/com/android/incallui/ContactInfoCache.java b/InCallUI/src/com/android/incallui/ContactInfoCache.java index 9ad7c5415..788f4e4a9 100644 --- a/InCallUI/src/com/android/incallui/ContactInfoCache.java +++ b/InCallUI/src/com/android/incallui/ContactInfoCache.java @@ -34,11 +34,13 @@ 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.android.collect.Sets; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import java.util.HashMap; import java.util.List; +import java.util.Set; /** * Class responsible for querying Contact Information for Call objects. Can perform asynchronous @@ -54,8 +56,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete private final Context mContext; private final PhoneNumberService mPhoneNumberService; private final HashMap<Integer, ContactCacheEntry> mInfoMap = Maps.newHashMap(); - private final HashMap<Integer, List<ContactInfoCacheCallback>> mCallBacks = - Maps.newHashMap(); + private final HashMap<Integer, Set<ContactInfoCacheCallback>> mCallBacks = Maps.newHashMap(); private static ContactInfoCache sCache = null; @@ -115,7 +116,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete final int callId = identification.getCallId(); final ContactCacheEntry cacheEntry = mInfoMap.get(callId); - List<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId); + Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId); // If we have a previously obtained intermediate result return that now if (cacheEntry != null) { @@ -135,7 +136,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete } Log.d(TAG, "Contact lookup. In memory cache miss; searching provider."); // New lookup - callBacks = Lists.newArrayList(); + callBacks = Sets.newHashSet(); callBacks.add(callback); mCallBacks.put(callId, callBacks); @@ -402,7 +403,8 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete displayName = info.cnapName; info.name = info.cnapName; displayNumber = number; - Log.d(TAG, " ==> 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, @@ -432,8 +434,8 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete // AND a restricted presentation. However we leave it here in case of weird // network behavior displayName = getPresentationString(context, presentation); - Log.d(TAG, - " ==> valid name, but presentation not allowed!" + " displayName = " + displayName); + Log.d(TAG, " ==> valid name, but presentation not allowed!" + + " displayName = " + displayName); } else { displayName = info.name; displayNumber = number; @@ -453,7 +455,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete * Sends the updated information to call the callbacks for the entry. */ private void sendInfoNotifications(int callId, ContactCacheEntry entry) { - final List<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);; + final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId); if (callBacks != null) { for (ContactInfoCacheCallback callBack : callBacks) { callBack.onContactInfoComplete(callId, entry); @@ -462,21 +464,16 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete } private void sendImageNotifications(int callId, ContactCacheEntry entry) { - final List<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);; - if (callBacks != null) { + final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId); + if (callBacks != null && entry.photo != null) { for (ContactInfoCacheCallback callBack : callBacks) { - if (entry.photo == null) { - callBack.onImageLoadComplete(callId, null); - } else { - callBack.onImageLoadComplete(callId, ((BitmapDrawable) entry.photo) - .getBitmap()); - } + callBack.onImageLoadComplete(callId, entry); } } } private void clearCallbacks(int callId) { - mCallBacks.remove(callId);; + mCallBacks.remove(callId); } /** @@ -497,7 +494,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete */ public interface ContactInfoCacheCallback { public void onContactInfoComplete(int callId, ContactCacheEntry entry); - public void onImageLoadComplete(int callId, Bitmap photo); + public void onImageLoadComplete(int callId, ContactCacheEntry entry); } public static class ContactCacheEntry { diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java index 0f8d4071a..47b0871ae 100644 --- a/InCallUI/src/com/android/incallui/InCallPresenter.java +++ b/InCallUI/src/com/android/incallui/InCallPresenter.java @@ -50,9 +50,17 @@ public class InCallPresenter implements CallList.Listener { private Context mContext; private CallList mCallList; private InCallActivity mInCallActivity; - private boolean mServiceConnected = false; private InCallState mInCallState = InCallState.NO_CALLS; private ProximitySensor mProximitySensor; + private boolean mServiceConnected = false; + + /** + * Is true when the activity has been previously started. Some code needs to know not just if + * the activity is currently up, but if it had been previously shown in foreground for this + * in-call session (e.g., StatusBarNotifier). This gets reset when the session ends in the + * tear-down method. + */ + private boolean mActivityPreviouslyStarted = false; public static synchronized InCallPresenter getInstance() { if (sInCallPresenter == null) { @@ -78,7 +86,6 @@ public class InCallPresenter implements CallList.Listener { mStatusBarNotifier = new StatusBarNotifier(context, mContactInfoCache, mCallList); addListener(mStatusBarNotifier); - addIncomingCallListener(mStatusBarNotifier); mAudioModeProvider = audioModeProvider; @@ -218,6 +225,11 @@ public class InCallPresenter implements CallList.Listener { mIncomingCallListeners.add(listener); } + public void removeIncomingCallListener(IncomingCallListener listener) { + Preconditions.checkNotNull(listener); + mIncomingCallListeners.remove(listener); + } + public void addListener(InCallStateListener listener) { Preconditions.checkNotNull(listener); mListeners.add(listener); @@ -272,8 +284,12 @@ public class InCallPresenter implements CallList.Listener { !mInCallActivity.isFinishing()); } + public boolean isActivityPreviouslyStarted() { + return mActivityPreviouslyStarted; + } + /** - * Called when the activity goes out of the foreground. + * Called when the activity goes in/out of the foreground. */ public void onUiShowing(boolean showing) { // We need to update the notification bar when we leave the UI because that @@ -285,6 +301,10 @@ public class InCallPresenter implements CallList.Listener { if (mProximitySensor != null) { mProximitySensor.onInCallShowing(showing); } + + if (showing) { + mActivityPreviouslyStarted = true; + } } /** @@ -397,6 +417,7 @@ public class InCallPresenter implements CallList.Listener { Log.i(this, "attemptCleanup? " + shouldCleanup); if (shouldCleanup) { + mActivityPreviouslyStarted = false; // blow away stale contact info so that we get fresh data on // the next set of calls @@ -417,6 +438,7 @@ public class InCallPresenter implements CallList.Listener { mInCallActivity = null; mListeners.clear(); + mIncomingCallListeners.clear(); Log.d(this, "Finished InCallPresenter.CleanUp"); } @@ -426,7 +448,7 @@ public class InCallPresenter implements CallList.Listener { mContext.startActivity(getInCallIntent()); } - private Intent getInCallIntent() { + public Intent getInCallIntent() { final Intent intent = new Intent(Intent.ACTION_MAIN, null); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java index 83dba8c57..2692ab813 100644 --- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java +++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java @@ -36,11 +36,12 @@ import com.android.incallui.InCallApp.NotificationBroadcastReceiver; import com.android.incallui.InCallPresenter.InCallState; import com.android.services.telephony.common.Call; +import java.util.HashMap; + /** * This class adds Notifications to the status bar for the in-call experience. */ -public class StatusBarNotifier implements InCallPresenter.InCallStateListener, - InCallPresenter.IncomingCallListener { +public class StatusBarNotifier implements InCallPresenter.InCallStateListener { // notification types private static final int IN_CALL_NOTIFICATION = 1; @@ -49,7 +50,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, private final CallList mCallList; private final NotificationManager mNotificationManager; private boolean mIsShowingNotification = false; - private InCallState mInCallState = InCallState.NO_CALLS; + private int mCallState = Call.State.INVALID; private int mSavedIcon = 0; private int mSavedContent = 0; private Bitmap mSavedLargeIcon; @@ -72,45 +73,8 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, @Override public void onStateChange(InCallState state, CallList callList) { Log.d(this, "onStateChange"); - updateNotification(state, callList); - } - - @Override - 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); - // TODO(klp): InCallPresenter already calls updateNofication() when it wants to start - // the notification. We shouldn't do this twice. - // TODO(klp): This search doesn't happen for outgoing calls any more. It works because - // the call card makes a requests that are cached...but eventually this startup process - // needs to incorporate call searches for all new calls, not just incoming. - - // 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() { - private ContactCacheEntry mEntry; - - @Override - public void onContactInfoComplete(int callId, ContactCacheEntry entry) { - mEntry = entry; - buildAndSendNotification(InCallState.INCOMING, call, entry, false); - } - - @Override - public void onImageLoadComplete(int callId, Bitmap photo) { - if (mEntry != null) { - mEntry.photo = new BitmapDrawable(mContext.getResources(), photo); - buildAndSendNotification(InCallState.INCOMING, call, mEntry, false); - } - } - }); + updateNotification(state, callList); } /** @@ -165,7 +129,6 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, updateInCallNotification(true, state, callList); } - /** * Take down the in-call notification. * @see updateInCallNotification() @@ -202,22 +165,51 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, return; } - // Contact info should have already been done on incoming calls. - // TODO(klp): This also needs to be done for outgoing calls. - ContactCacheEntry entry = mContactInfoCache.getInfo(call.getCallId()); - if (entry == null) { - entry = ContactInfoCache.buildCacheEntryFromCall(mContext, call.getIdentification(), - state == InCallState.INCOMING); - } - buildAndSendNotification(state, call, entry, allowFullScreenIntent); + // 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(), call.getState() == Call.State.INCOMING, + new ContactInfoCacheCallback() { + private boolean mAllowFullScreenIntent = allowFullScreenIntent; + + @Override + public void onContactInfoComplete(int callId, ContactCacheEntry entry) { + Call call = CallList.getInstance().getCall(callId); + if (call != null) { + buildAndSendNotification(call, entry, mAllowFullScreenIntent); + } + + // Full screen intents are what bring up the in call screen. We only want + // to do this the first time we are called back. + mAllowFullScreenIntent = false; + } + + @Override + public void onImageLoadComplete(int callId, ContactCacheEntry entry) { + Call call = CallList.getInstance().getCall(callId); + if (call != null) { + buildAndSendNotification(call, entry, mAllowFullScreenIntent); + } + } }); } /** * Sets up the main Ui for the notification */ - private void buildAndSendNotification(InCallState state, Call call, - ContactCacheEntry contactInfo, boolean allowFullScreenIntent) { + private void buildAndSendNotification(Call originalCall, ContactCacheEntry contactInfo, + boolean allowFullScreenIntent) { + + // This can get called to update an existing notification after contact information has come + // back. However, it can happen much later. Before we continue, we need to make sure that + // the call being passed in is still the one we want to show in the notification. + final Call call = getCallToShow(CallList.getInstance()); + if (call.getCallId() != originalCall.getCallId()) { + return; + } + final int state = call.getState(); final boolean isConference = call.isConferenceCall(); final int iconResId = getIconToDisplay(call); final Bitmap largeIcon = getLargeIconToDisplay(contactInfo, isConference); @@ -250,15 +242,17 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, builder.setContentTitle(contentTitle); builder.setLargeIcon(largeIcon); - if (call.getState() == Call.State.ACTIVE) { + if (state == Call.State.ACTIVE) { builder.setUsesChronometer(true); builder.setWhen(call.getConnectTime()); } else { builder.setUsesChronometer(false); } - // Add special Content for calls that are ongoing - if (InCallState.INCALL == state || InCallState.OUTGOING == state) { + // Add hang up option for any active calls (active | onhold), outgoing calls (dialing). + if (state == Call.State.ACTIVE || + state == Call.State.DIALING || + state == Call.State.ONHOLD) { addHangupAction(builder); } @@ -277,7 +271,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, * we do not issue a new notification for the exact same data. */ private boolean checkForChangeAndSaveData(int icon, int content, Bitmap largeIcon, - String contentTitle, InCallState state, boolean showFullScreenIntent) { + String contentTitle, int state, boolean showFullScreenIntent) { // The two are different: // if new title is not null, it should be different from saved version OR @@ -288,7 +282,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, // any change means we are definitely updating boolean retval = (mSavedIcon != icon) || (mSavedContent != content) || - (mInCallState != state) || (mSavedLargeIcon != largeIcon) || + (mCallState != state) || (mSavedLargeIcon != largeIcon) || contentTitleChanged; // A full screen intent means that we have been asked to interrupt an activity, @@ -306,7 +300,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, mSavedIcon = icon; mSavedContent = content; - mInCallState = state; + mCallState = state; mSavedLargeIcon = largeIcon; mSavedContentTitle = contentTitle; @@ -501,7 +495,8 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, // comes up the user will see it flash on and off on an outgoing call. // This code ensures that we do not show the notification for outgoing calls before // the activity has started. - if (state == InCallState.OUTGOING && !InCallPresenter.getInstance().isActivityStarted()) { + if (state == InCallState.OUTGOING && + !InCallPresenter.getInstance().isActivityPreviouslyStarted()) { Log.v(this, "suppressing: activity not started."); shouldSuppress = true; } @@ -511,11 +506,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, private PendingIntent createLaunchPendingIntent() { - final Intent intent = new Intent(Intent.ACTION_MAIN, null); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_NO_USER_ACTION); - intent.setClass(mContext, InCallActivity.class); + final Intent intent = InCallPresenter.getInstance().getInCallIntent(); // PendingIntent that can be used to launch the InCallActivity. The // system fires off this intent if the user pulls down the windowshade |