From ad944e7cf20303adddd960d8870b47fffcdcc8df Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Fri, 1 Apr 2016 10:23:42 -0700 Subject: DO NOT MERGE Add support for multi-endpoint. - Add new "ExternalCallNotifier" which tracks external calls that Telecom informs Incall about. - Refactored some common code from StatusBarNotifier that is used in ExternalCallNotifier into NotificationUtil so it can be used in both places. - Modified CallList to track and store external calls seperately from regular ones. - Added support for triggering a call pull in the NotificationBroadcastReceiver. Bug: 27458894 Change-Id: I505c8b5f7aad273ebdaaeae2431564d10b23770b (cherry picked from commit d8eb77f116ba21a4f40fd59d608b2e8bbdf2e09b) --- InCallUI/res/values/strings.xml | 11 + InCallUI/src/com/android/incallui/Call.java | 72 +++- .../src/com/android/incallui/ExternalCallList.java | 105 ++++++ .../com/android/incallui/ExternalCallNotifier.java | 406 +++++++++++++++++++++ .../src/com/android/incallui/InCallPresenter.java | 31 +- .../com/android/incallui/InCallServiceImpl.java | 2 + .../incallui/NotificationBroadcastReceiver.java | 8 + .../com/android/incallui/InCallPresenterTest.java | 6 +- 8 files changed, 629 insertions(+), 12 deletions(-) create mode 100644 InCallUI/src/com/android/incallui/ExternalCallList.java create mode 100644 InCallUI/src/com/android/incallui/ExternalCallNotifier.java (limited to 'InCallUI') diff --git a/InCallUI/res/values/strings.xml b/InCallUI/res/values/strings.xml index 7701cdb2d..84eb14c0a 100644 --- a/InCallUI/res/values/strings.xml +++ b/InCallUI/res/values/strings.xml @@ -218,6 +218,17 @@ The user will be able to send text messages using the phone number. [CHAR LIMIT=12] --> Message + + Ongoing call on another device + + Transfer Call To place a call, first turn off Airplane mode. diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java index 447c34c88..d552ecfe5 100644 --- a/InCallUI/src/com/android/incallui/Call.java +++ b/InCallUI/src/com/android/incallui/Call.java @@ -33,6 +33,8 @@ import android.telecom.VideoProfile; import android.text.TextUtils; import com.android.contacts.common.CallUtil; +import com.android.contacts.common.compat.CallSdkCompat; +import com.android.contacts.common.compat.CompatUtils; import com.android.contacts.common.compat.SdkVersionOverride; import com.android.contacts.common.compat.telecom.TelecomManagerCompat; import com.android.contacts.common.testing.NeededForTesting; @@ -400,13 +402,30 @@ public class Call { setState(state); } + /** + * Creates a new instance of a {@link Call}. Registers a callback for + * {@link android.telecom.Call} events. + */ public Call(android.telecom.Call telecomCall) { + this(telecomCall, true /* registerCallback */); + } + + /** + * Creates a new instance of a {@link Call}. Optionally registers a callback for + * {@link android.telecom.Call} events. + * + * Intended for use when creating a {@link Call} instance for use with the + * {@link ContactInfoCache}, where we do not want to register callbacks for the new call. + */ + public Call(android.telecom.Call telecomCall, boolean registerCallback) { mTelecomCall = telecomCall; mId = ID_PREFIX + Integer.toString(sIdCounter++); - updateFromTelecomCall(); + updateFromTelecomCall(registerCallback); - mTelecomCall.registerCallback(mTelecomCallCallback); + if (registerCallback) { + mTelecomCall.registerCallback(mTelecomCallCallback); + } mTimeAddedMs = System.currentTimeMillis(); } @@ -426,7 +445,8 @@ public class Call { private void update() { Trace.beginSection("Update"); int oldState = getState(); - updateFromTelecomCall(); + // We want to potentially register a video call callback here. + updateFromTelecomCall(true /* registerCallback */); if (oldState != getState() && getState() == Call.State.DISCONNECTED) { CallList.getInstance().onDisconnect(this); } else { @@ -435,7 +455,7 @@ public class Call { Trace.endSection(); } - private void updateFromTelecomCall() { + private void updateFromTelecomCall(boolean registerCallback) { Log.d(this, "updateFromTelecomCall: " + mTelecomCall.toString()); final int translatedState = translateState(mTelecomCall.getState()); if (mState != State.BLOCKED) { @@ -444,7 +464,7 @@ public class Call { maybeCancelVideoUpgrade(mTelecomCall.getDetails().getVideoState()); } - if (mTelecomCall.getVideoCall() != null) { + if (registerCallback && mTelecomCall.getVideoCall() != null) { if (mVideoCallCallback == null) { mVideoCallCallback = new InCallVideoCallCallback(this); } @@ -882,10 +902,47 @@ public class Call { return mLogState; } + /** + * Determines if the call is an external call. + * + * An external call is one which does not exist locally for the + * {@link android.telecom.ConnectionService} it is associated with. + * + * External calls are only supported in N and higher. + * + * @return {@code true} if the call is an external call, {@code false} otherwise. + */ + public boolean isExternalCall() { + return CompatUtils.isNCompatible() && + hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL); + } + + /** + * Determines if the external call is pullable. + * + * An external call is one which does not exist locally for the + * {@link android.telecom.ConnectionService} it is associated with. An external call may be + * "pullable", which means that the user can request it be transferred to the current device. + * + * External calls are only supported in N and higher. + * + * @return {@code true} if the call is an external call, {@code false} otherwise. + */ + public boolean isPullableExternalCall() { + return CompatUtils.isNCompatible() && + (mTelecomCall.getDetails().getCallCapabilities() + & CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL) + == CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL; + } + /** * Logging utility methods */ public void logCallInitiationType() { + if (isExternalCall()) { + return; + } + if (getState() == State.INCOMING) { getLogState().callInitiationMethod = LogState.INITIATION_INCOMING; } else if (getIntentExtras() != null) { @@ -903,11 +960,12 @@ public class Call { return String.valueOf(mId); } - return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " + - "videoState:%s, mSessionModificationState:%d, VideoSettings:%s]", + return String.format(Locale.US, "[%s, %s, %s, %s, children:%s, parent:%s, " + + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, VideoSettings:%s]", mId, State.toString(getState()), Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()), + Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()), mChildCallIds, getParentId(), this.mTelecomCall.getConferenceableCalls(), diff --git a/InCallUI/src/com/android/incallui/ExternalCallList.java b/InCallUI/src/com/android/incallui/ExternalCallList.java new file mode 100644 index 000000000..06e0bb975 --- /dev/null +++ b/InCallUI/src/com/android/incallui/ExternalCallList.java @@ -0,0 +1,105 @@ +package com.android.incallui; + +import com.google.common.base.Preconditions; + +import com.android.contacts.common.compat.CallSdkCompat; + +import android.os.Handler; +import android.os.Looper; +import android.telecom.Call; +import android.util.ArraySet; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Tracks the external calls known to the InCall UI. + * + * External calls are those with {@link android.telecom.Call.Details#PROPERTY_IS_EXTERNAL_CALL}. + */ +public class ExternalCallList { + + public interface ExternalCallListener { + void onExternalCallAdded(Call call); + void onExternalCallRemoved(Call call); + void onExternalCallUpdated(Call call); + } + + /** + * Handles {@link android.telecom.Call.Callback} callbacks. + */ + private final Call.Callback mTelecomCallCallback = new Call.Callback() { + @Override + public void onDetailsChanged(Call call, Call.Details details) { + notifyExternalCallUpdated(call); + } + }; + + private final Set mExternalCalls = new ArraySet<>(); + private final Set mExternalCallListeners = Collections.newSetFromMap( + new ConcurrentHashMap(8, 0.9f, 1)); + + /** + * Begins tracking an external call and notifies listeners of the new call. + */ + public void onCallAdded(Call telecomCall) { + Preconditions.checkArgument(telecomCall.getDetails() + .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)); + mExternalCalls.add(telecomCall); + telecomCall.registerCallback(mTelecomCallCallback, new Handler(Looper.getMainLooper())); + notifyExternalCallAdded(telecomCall); + } + + /** + * Stops tracking an external call and notifies listeners of the removal of the call. + */ + public void onCallRemoved(Call telecomCall) { + Preconditions.checkArgument(mExternalCalls.contains(telecomCall)); + mExternalCalls.remove(telecomCall); + telecomCall.unregisterCallback(mTelecomCallCallback); + notifyExternalCallRemoved(telecomCall); + } + + /** + * Adds a new listener to external call events. + */ + public void addExternalCallListener(ExternalCallListener listener) { + mExternalCallListeners.add(Preconditions.checkNotNull(listener)); + } + + /** + * Removes a listener to external call events. + */ + public void removeExternalCallListener(ExternalCallListener listener) { + Preconditions.checkArgument(mExternalCallListeners.contains(listener)); + mExternalCallListeners.remove(Preconditions.checkNotNull(listener)); + } + + /** + * Notifies listeners of the addition of a new external call. + */ + private void notifyExternalCallAdded(Call call) { + for (ExternalCallListener listener : mExternalCallListeners) { + listener.onExternalCallAdded(call); + } + } + + /** + * Notifies listeners of the removal of an external call. + */ + private void notifyExternalCallRemoved(Call call) { + for (ExternalCallListener listener : mExternalCallListeners) { + listener.onExternalCallRemoved(call); + } + } + + /** + * Notifies listeners of changes to an external call. + */ + private void notifyExternalCallUpdated(Call call) { + for (ExternalCallListener listener : mExternalCallListeners) { + listener.onExternalCallUpdated(call); + } + } +} diff --git a/InCallUI/src/com/android/incallui/ExternalCallNotifier.java b/InCallUI/src/com/android/incallui/ExternalCallNotifier.java new file mode 100644 index 000000000..40a2e02bf --- /dev/null +++ b/InCallUI/src/com/android/incallui/ExternalCallNotifier.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2016 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 com.google.common.base.Preconditions; + +import com.android.contacts.common.ContactsUtils; +import com.android.contacts.common.compat.CallSdkCompat; +import com.android.contacts.common.preference.ContactsPreferences; +import com.android.contacts.common.util.BitmapUtil; +import com.android.contacts.common.util.ContactDisplayUtils; +import com.android.dialer.R; +import com.android.incallui.util.TelecomCallUtil; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.support.annotation.Nullable; +import android.telecom.Call; +import android.telecom.PhoneAccount; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; +import android.text.TextUtils; +import android.util.ArrayMap; + +import java.util.Map; + +/** + * Handles the display of notifications for "external calls". + * + * External calls are a representation of a call which is in progress on the user's other device + * (e.g. another phone, or a watch). + */ +public class ExternalCallNotifier implements ExternalCallList.ExternalCallListener { + + /** + * Tag used with the notification manager to uniquely identify external call notifications. + */ + private static final String NOTIFICATION_TAG = "EXTERNAL_CALL"; + + /** + * Represents a call and associated cached notification data. + */ + private static class NotificationInfo { + private final Call mCall; + private final int mNotificationId; + @Nullable private String mContentTitle; + @Nullable private Bitmap mLargeIcon; + @Nullable private String mPersonReference; + + public NotificationInfo(Call call, int notificationId) { + Preconditions.checkNotNull(call); + mCall = call; + mNotificationId = notificationId; + } + + public Call getCall() { + return mCall; + } + + public int getNotificationId() { + return mNotificationId; + } + + public @Nullable String getContentTitle() { + return mContentTitle; + } + + public @Nullable Bitmap getLargeIcon() { + return mLargeIcon; + } + + public @Nullable String getPersonReference() { + return mPersonReference; + } + + public void setContentTitle(@Nullable String contentTitle) { + mContentTitle = contentTitle; + } + + public void setLargeIcon(@Nullable Bitmap largeIcon) { + mLargeIcon = largeIcon; + } + + public void setPersonReference(@Nullable String personReference) { + mPersonReference = personReference; + } + } + + private final Context mContext; + private final ContactInfoCache mContactInfoCache; + private Map mNotifications = new ArrayMap<>(); + private int mNextUniqueNotificationId; + private ContactsPreferences mContactsPreferences; + + /** + * Initializes a new instance of the external call notifier. + */ + public ExternalCallNotifier(Context context, ContactInfoCache contactInfoCache) { + mContext = Preconditions.checkNotNull(context); + mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext); + mContactInfoCache = Preconditions.checkNotNull(contactInfoCache); + } + + /** + * Handles the addition of a new external call by showing a new notification. + * Triggered by {@link CallList#onCallAdded(android.telecom.Call)}. + */ + @Override + public void onExternalCallAdded(android.telecom.Call call) { + Log.i(this, "onExternalCallAdded " + call); + Preconditions.checkArgument(!mNotifications.containsKey(call)); + NotificationInfo info = new NotificationInfo(call, mNextUniqueNotificationId++); + mNotifications.put(call, info); + + showNotifcation(info); + } + + /** + * Handles the removal of an external call by hiding its associated notification. + * Triggered by {@link CallList#onCallRemoved(android.telecom.Call)}. + */ + @Override + public void onExternalCallRemoved(android.telecom.Call call) { + Log.i(this, "onExternalCallRemoved " + call); + + dismissNotification(call); + } + + /** + * Handles updates to an external call. + */ + @Override + public void onExternalCallUpdated(Call call) { + Preconditions.checkArgument(mNotifications.containsKey(call)); + postNotification(mNotifications.get(call)); + } + + /** + * Initiates a call pull given a notification ID. + * + * @param notificationId The notification ID associated with the external call which is to be + * pulled. + */ + public void pullExternalCall(int notificationId) { + for (NotificationInfo info : mNotifications.values()) { + if (info.getNotificationId() == notificationId) { + CallSdkCompat.pullExternalCall(info.getCall()); + return; + } + } + } + + /** + * Shows a notification for a new external call. Performs a contact cache lookup to find any + * associated photo and information for the call. + */ + private void showNotifcation(final NotificationInfo info) { + // 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. + com.android.incallui.Call incallCall = new com.android.incallui.Call(info.getCall(), + false /* registerCallback */); + + mContactInfoCache.findInfo(incallCall, false /* isIncoming */, + new ContactInfoCache.ContactInfoCacheCallback() { + @Override + public void onContactInfoComplete(String callId, + ContactInfoCache.ContactCacheEntry entry) { + + // Ensure notification still exists as the external call could have been + // removed during async contact info lookup. + if (mNotifications.containsKey(info.getCall())) { + saveContactInfo(info, entry); + } + } + + @Override + public void onImageLoadComplete(String callId, + ContactInfoCache.ContactCacheEntry entry) { + + // Ensure notification still exists as the external call could have been + // removed during async contact info lookup. + if (mNotifications.containsKey(info.getCall())) { + savePhoto(info, entry); + } + } + + @Override + public void onContactInteractionsInfoComplete(String callId, + ContactInfoCache.ContactCacheEntry entry) { + } + }); + } + + /** + * Dismisses a notification for an external call. + */ + private void dismissNotification(Call call) { + Preconditions.checkArgument(mNotifications.containsKey(call)); + + NotificationManager notificationManager = + (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancel(NOTIFICATION_TAG, mNotifications.get(call).getNotificationId()); + + mNotifications.remove(call); + } + + /** + * Attempts to build a large icon to use for the notification based on the contact info and + * post the updated notification to the notification manager. + */ + private void savePhoto(NotificationInfo info, ContactInfoCache.ContactCacheEntry entry) { + Bitmap largeIcon = getLargeIconToDisplay(mContext, entry, info.getCall()); + if (largeIcon != null) { + largeIcon = getRoundedIcon(mContext, largeIcon); + } + info.setLargeIcon(largeIcon); + postNotification(info); + } + + /** + * Builds and stores the contact information the notification will display and posts the updated + * notification to the notification manager. + */ + private void saveContactInfo(NotificationInfo info, ContactInfoCache.ContactCacheEntry entry) { + info.setContentTitle(getContentTitle(mContext, mContactsPreferences, + entry, info.getCall())); + info.setPersonReference(getPersonReference(entry, info.getCall())); + postNotification(info); + } + + /** + * Rebuild an existing or show a new notification given {@link NotificationInfo}. + */ + private void postNotification(NotificationInfo info) { + Log.i(this, "postNotification : " + info.getContentTitle()); + Notification.Builder builder = new Notification.Builder(mContext); + // Set notification as ongoing since calls are long-running versus a point-in-time notice. + builder.setOngoing(true); + // Make the notification prioritized over the other normal notifications. + builder.setPriority(Notification.PRIORITY_HIGH); + // Set the content ("Ongoing call on another device") + builder.setContentText(mContext.getString(R.string.notification_external_call)); + builder.setSmallIcon(R.drawable.ic_call_white_24dp); + builder.setContentTitle(info.getContentTitle()); + builder.setLargeIcon(info.getLargeIcon()); + builder.setColor(mContext.getResources().getColor(R.color.dialer_theme_color)); + builder.addPerson(info.getPersonReference()); + + // Where the external call supports being transferred to the local device, add an action + // to the notification to initiate the call pull process. + if ((info.getCall().getDetails().getCallCapabilities() + & CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL) + == CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL) { + + Intent intent = new Intent( + NotificationBroadcastReceiver.ACTION_PULL_EXTERNAL_CALL, null, mContext, + NotificationBroadcastReceiver.class); + intent.putExtra(NotificationBroadcastReceiver.EXTRA_NOTIFICATION_ID, + info.getNotificationId()); + + builder.addAction(new Notification.Action.Builder(R.drawable.ic_call_white_24dp, + mContext.getText(R.string.notification_transfer_call), + PendingIntent.getBroadcast(mContext, 0, intent, 0)).build()); + } + + /** + * This builder is used for the notification shown when the device is locked and the user + * has set their notification settings to 'hide sensitive content' + * {@see Notification.Builder#setPublicVersion}. + */ + Notification.Builder publicBuilder = new Notification.Builder(mContext); + publicBuilder.setSmallIcon(R.drawable.ic_call_white_24dp); + publicBuilder.setColor(mContext.getResources().getColor(R.color.dialer_theme_color)); + + builder.setPublicVersion(publicBuilder.build()); + Notification notification = builder.build(); + + NotificationManager notificationManager = + (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(NOTIFICATION_TAG, info.getNotificationId(), notification); + } + + /** + * Finds a large icon to display in a notification for a call. For conference calls, a + * conference call icon is used, otherwise if contact info is specified, the user's contact + * photo or avatar is used. + * + * @param context The context. + * @param contactInfo The contact cache info. + * @param call The call. + * @return The large icon to use for the notification. + */ + private @Nullable Bitmap getLargeIconToDisplay(Context context, + ContactInfoCache.ContactCacheEntry contactInfo, android.telecom.Call call) { + + Bitmap largeIcon = null; + if (call.getDetails().hasProperty(android.telecom.Call.Details.PROPERTY_CONFERENCE) && + !call.getDetails() + .hasProperty(android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE)) { + + largeIcon = BitmapFactory.decodeResource(context.getResources(), + R.drawable.img_conference); + } + if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) { + largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap(); + } + return largeIcon; + } + + /** + * Given a bitmap, returns a rounded version of the icon suitable for display in a notification. + * + * @param context The context. + * @param bitmap The bitmap to round. + * @return The rounded bitmap. + */ + private @Nullable Bitmap getRoundedIcon(Context context, @Nullable Bitmap bitmap) { + if (bitmap == null) { + return null; + } + final int height = (int) context.getResources().getDimension( + android.R.dimen.notification_large_icon_height); + final int width = (int) context.getResources().getDimension( + android.R.dimen.notification_large_icon_width); + return BitmapUtil.getRoundedBitmap(bitmap, width, height); + } + + /** + * Builds a notification content title for a call. If the call is a conference call, it is + * identified as such. Otherwise an attempt is made to show an associated contact name or + * phone number. + * + * @param context The context. + * @param contactsPreferences Contacts preferences, used to determine the preferred formatting + * for contact names. + * @param contactInfo The contact info which was looked up in the contact cache. + * @param call The call to generate a title for. + * @return The content title. + */ + private @Nullable String getContentTitle(Context context, + @Nullable ContactsPreferences contactsPreferences, + ContactInfoCache.ContactCacheEntry contactInfo, android.telecom.Call call) { + + if (call.getDetails().hasProperty(android.telecom.Call.Details.PROPERTY_CONFERENCE) && + !call.getDetails() + .hasProperty(android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE)) { + + return context.getResources().getString(R.string.card_title_conf_call); + } + + String preferredName = ContactDisplayUtils.getPreferredDisplayName(contactInfo.namePrimary, + contactInfo.nameAlternative, contactsPreferences); + if (TextUtils.isEmpty(preferredName)) { + return TextUtils.isEmpty(contactInfo.number) ? null : BidiFormatter.getInstance() + .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR); + } + return preferredName; + } + + /** + * Gets a "person reference" for a notification, used by the system to determine whether the + * notification should be allowed past notification interruption filters. + * + * @param contactInfo The contact info from cache. + * @param call The call. + * @return the person reference. + */ + private String getPersonReference(ContactInfoCache.ContactCacheEntry contactInfo, + Call call) { + + String number = TelecomCallUtil.getNumber(call); + // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed. + // So, do not pass {@link Contacts#CONTENT_LOOKUP_URI} to NotificationManager to avoid + // NotificationManager using it. + if (contactInfo.lookupUri != null && contactInfo.userType != ContactsUtils.USER_TYPE_WORK) { + return contactInfo.lookupUri.toString(); + } else if (!TextUtils.isEmpty(number)) { + return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null).toString(); + } + return ""; + } +} diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java index 0109d7ee6..38507eef9 100644 --- a/InCallUI/src/com/android/incallui/InCallPresenter.java +++ b/InCallUI/src/com/android/incallui/InCallPresenter.java @@ -41,6 +41,7 @@ import android.view.Window; import android.view.WindowManager; import com.android.contacts.common.GeoUtil; +import com.android.contacts.common.compat.CallSdkCompat; import com.android.contacts.common.compat.CompatUtils; import com.android.contacts.common.compat.telecom.TelecomManagerCompat; import com.android.contacts.common.interactions.TouchPointManager; @@ -109,9 +110,11 @@ public class InCallPresenter implements CallList.Listener, private AudioModeProvider mAudioModeProvider; private StatusBarNotifier mStatusBarNotifier; + private ExternalCallNotifier mExternalCallNotifier; private ContactInfoCache mContactInfoCache; private Context mContext; private CallList mCallList; + private ExternalCallList mExternalCallList; private InCallActivity mInCallActivity; private InCallState mInCallState = InCallState.NO_CALLS; private ProximitySensor mProximitySensor; @@ -299,8 +302,10 @@ public class InCallPresenter implements CallList.Listener, public void setUp(Context context, CallList callList, + ExternalCallList externalCallList, AudioModeProvider audioModeProvider, StatusBarNotifier statusBarNotifier, + ExternalCallNotifier externalCallNotifier, ContactInfoCache contactInfoCache, ProximitySensor proximitySensor) { if (mServiceConnected) { @@ -318,6 +323,7 @@ public class InCallPresenter implements CallList.Listener, mContactInfoCache = contactInfoCache; mStatusBarNotifier = statusBarNotifier; + mExternalCallNotifier = externalCallNotifier; addListener(mStatusBarNotifier); mAudioModeProvider = audioModeProvider; @@ -329,6 +335,8 @@ public class InCallPresenter implements CallList.Listener, addInCallUiListener(mAnswerPresenter); mCallList = callList; + mExternalCallList = externalCallList; + externalCallList.addExternalCallListener(mExternalCallNotifier); // This only gets called by the service so this is okay. mServiceConnected = true; @@ -501,7 +509,12 @@ public class InCallPresenter implements CallList.Listener, if (shouldAttemptBlocking(call)) { maybeBlockCall(call); } else { - mCallList.onCallAdded(call); + if (call.getDetails() + .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) { + mExternalCallList.onCallAdded(call); + } else { + mCallList.onCallAdded(call); + } } // Since a call has been added we are no longer waiting for Telecom to send us a call. @@ -590,8 +603,13 @@ public class InCallPresenter implements CallList.Listener, } public void onCallRemoved(android.telecom.Call call) { - mCallList.onCallRemoved(call); - call.unregisterCallback(mCallCallback); + if (call.getDetails() + .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) { + mExternalCallList.onCallRemoved(call); + } else { + mCallList.onCallRemoved(call); + call.unregisterCallback(mCallCallback); + } } public void onCanAddCallChanged(boolean canAddCall) { @@ -1506,6 +1524,9 @@ public class InCallPresenter implements CallList.Listener, if (mStatusBarNotifier != null) { removeListener(mStatusBarNotifier); } + if (mExternalCallNotifier != null && mExternalCallList != null) { + mExternalCallList.removeExternalCallListener(mExternalCallNotifier); + } mStatusBarNotifier = null; if (mCallList != null) { @@ -1808,6 +1829,10 @@ public class InCallPresenter implements CallList.Listener, return mAnswerPresenter; } + ExternalCallNotifier getExternalCallNotifier() { + return mExternalCallNotifier; + } + /** * Private constructor. Must use getInstance() to get this singleton. */ diff --git a/InCallUI/src/com/android/incallui/InCallServiceImpl.java b/InCallUI/src/com/android/incallui/InCallServiceImpl.java index 86936973e..1414bc51d 100644 --- a/InCallUI/src/com/android/incallui/InCallServiceImpl.java +++ b/InCallUI/src/com/android/incallui/InCallServiceImpl.java @@ -64,8 +64,10 @@ public class InCallServiceImpl extends InCallService { InCallPresenter.getInstance().setUp( getApplicationContext(), CallList.getInstance(), + new ExternalCallList(), AudioModeProvider.getInstance(), new StatusBarNotifier(context, contactInfoCache), + new ExternalCallNotifier(context, contactInfoCache), contactInfoCache, new ProximitySensor( context, diff --git a/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java b/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java index 2543b783d..27f71159d 100644 --- a/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java +++ b/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java @@ -45,6 +45,10 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { "com.android.incallui.ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST"; public static final String ACTION_DECLINE_VIDEO_UPGRADE_REQUEST = "com.android.incallui.ACTION_DECLINE_VIDEO_UPGRADE_REQUEST"; + public static final String ACTION_PULL_EXTERNAL_CALL = + "com.android.incallui.ACTION_PULL_EXTERNAL_CALL"; + public static final String EXTRA_NOTIFICATION_ID = + "com.android.incallui.extra.EXTRA_NOTIFICATION_ID"; @Override public void onReceive(Context context, Intent intent) { @@ -68,6 +72,10 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { VideoProfile.STATE_BIDIRECTIONAL, context); } else if (action.equals(ACTION_DECLINE_VIDEO_UPGRADE_REQUEST)) { InCallPresenter.getInstance().declineUpgradeRequest(context); + } else if (action.equals(ACTION_PULL_EXTERNAL_CALL)) { + int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); + InCallPresenter.getInstance().getExternalCallNotifier() + .pullExternalCall(notificationId); } } diff --git a/InCallUI/tests/src/com/android/incallui/InCallPresenterTest.java b/InCallUI/tests/src/com/android/incallui/InCallPresenterTest.java index ed8d6223c..f0f08ab68 100644 --- a/InCallUI/tests/src/com/android/incallui/InCallPresenterTest.java +++ b/InCallUI/tests/src/com/android/incallui/InCallPresenterTest.java @@ -39,6 +39,7 @@ public class InCallPresenterTest extends InstrumentationTestCase { @Mock private InCallActivity mInCallActivity; @Mock private AudioModeProvider mAudioModeProvider; @Mock private StatusBarNotifier mStatusBarNotifier; + @Mock private ExternalCallNotifier mExternalCallNotifier; @Mock private ContactInfoCache mContactInfoCache; @Mock private ProximitySensor mProximitySensor; @@ -57,8 +58,9 @@ public class InCallPresenterTest extends InstrumentationTestCase { when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); mInCallPresenter = InCallPresenter.getInstance(); - mInCallPresenter.setUp(mContext, mCallList.getCallList(), mAudioModeProvider, - mStatusBarNotifier, mContactInfoCache, mProximitySensor); + mInCallPresenter.setUp(mContext, mCallList.getCallList(), new ExternalCallList(), + mAudioModeProvider, mStatusBarNotifier, mExternalCallNotifier, mContactInfoCache, + mProximitySensor); } @Override -- cgit v1.2.3