summaryrefslogtreecommitdiff
path: root/InCallUI/src
diff options
context:
space:
mode:
Diffstat (limited to 'InCallUI/src')
-rw-r--r--InCallUI/src/com/android/incallui/Call.java121
-rw-r--r--InCallUI/src/com/android/incallui/CallCardFragment.java15
-rw-r--r--InCallUI/src/com/android/incallui/CallCardPresenter.java6
-rw-r--r--InCallUI/src/com/android/incallui/CallList.java29
-rw-r--r--InCallUI/src/com/android/incallui/CallerInfo.java2
-rw-r--r--InCallUI/src/com/android/incallui/ExternalCallList.java105
-rw-r--r--InCallUI/src/com/android/incallui/ExternalCallNotifier.java406
-rw-r--r--InCallUI/src/com/android/incallui/InCallActivity.java4
-rw-r--r--InCallUI/src/com/android/incallui/InCallPresenter.java68
-rw-r--r--InCallUI/src/com/android/incallui/InCallServiceImpl.java2
-rw-r--r--InCallUI/src/com/android/incallui/LatencyReport.java145
-rw-r--r--InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java8
-rw-r--r--InCallUI/src/com/android/incallui/StatusBarNotifier.java45
-rw-r--r--InCallUI/src/com/android/incallui/spam/SpamCallListListener.java117
14 files changed, 1036 insertions, 37 deletions
diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java
index 447c34c88..1ad37e01a 100644
--- a/InCallUI/src/com/android/incallui/Call.java
+++ b/InCallUI/src/com/android/incallui/Call.java
@@ -21,6 +21,7 @@ import android.hardware.camera2.CameraCharacteristics;
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
+import android.support.annotation.IntDef;
import android.telecom.Call.Details;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
@@ -33,12 +34,16 @@ 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;
import com.android.dialer.util.IntentUtil;
import com.android.incallui.util.TelecomCallUtil;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -49,6 +54,19 @@ import java.util.Objects;
*/
@NeededForTesting
public class Call {
+
+ /**
+ * Specifies whether a number is in the call history or not.
+ * {@link #CALL_HISTORY_STATUS_UNKNOWN} means there is no result.
+ */
+ @IntDef({CALL_HISTORY_STATUS_UNKNOWN, CALL_HISTORY_STATUS_PRESENT,
+ CALL_HISTORY_STATUS_NOT_PRESENT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CallHistoryStatus {}
+ public static final int CALL_HISTORY_STATUS_UNKNOWN = 0;
+ public static final int CALL_HISTORY_STATUS_PRESENT = 1;
+ public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2;
+
/* Defines different states of this call */
public static class State {
public static final int INVALID = 0;
@@ -357,7 +375,8 @@ public class Call {
}
};
- private android.telecom.Call mTelecomCall;
+ private final android.telecom.Call mTelecomCall;
+ private final LatencyReport mLatencyReport;
private boolean mIsEmergencyCall;
private Uri mHandle;
private final String mId;
@@ -379,6 +398,8 @@ public class Call {
private String mLastForwardedNumber;
private String mCallSubject;
private PhoneAccountHandle mPhoneAccountHandle;
+ @CallHistoryStatus private int mCallHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN;
+ private boolean mIsSpam;
/**
* Indicates whether the phone account associated with this call supports specifying a call
@@ -388,7 +409,7 @@ public class Call {
private long mTimeAddedMs;
- private LogState mLogState = new LogState();
+ private final LogState mLogState = new LogState();
/**
* Used only to create mock calls for testing
@@ -396,17 +417,37 @@ public class Call {
@NeededForTesting
Call(int state) {
mTelecomCall = null;
+ mLatencyReport = new LatencyReport();
mId = ID_PREFIX + Integer.toString(sIdCounter++);
setState(state);
}
- public Call(android.telecom.Call telecomCall) {
+ /**
+ * Creates a new instance of a {@link Call}. Registers a callback for
+ * {@link android.telecom.Call} events.
+ */
+ public Call(android.telecom.Call telecomCall, LatencyReport latencyReport) {
+ this(telecomCall, latencyReport, 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, LatencyReport latencyReport,
+ boolean registerCallback) {
mTelecomCall = telecomCall;
+ mLatencyReport = latencyReport;
mId = ID_PREFIX + Integer.toString(sIdCounter++);
- updateFromTelecomCall();
+ updateFromTelecomCall(registerCallback);
- mTelecomCall.registerCallback(mTelecomCallCallback);
+ if (registerCallback) {
+ mTelecomCall.registerCallback(mTelecomCallCallback);
+ }
mTimeAddedMs = System.currentTimeMillis();
}
@@ -426,7 +467,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 +477,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 +486,7 @@ public class Call {
maybeCancelVideoUpgrade(mTelecomCall.getDetails().getVideoState());
}
- if (mTelecomCall.getVideoCall() != null) {
+ if (registerCallback && mTelecomCall.getVideoCall() != null) {
if (mVideoCallCallback == null) {
mVideoCallCallback = new InCallVideoCallCallback(this);
}
@@ -883,9 +925,46 @@ public class Call {
}
/**
+ * 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 +982,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(),
@@ -919,4 +999,25 @@ public class Call {
public String toSimpleString() {
return super.toString();
}
+
+ public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
+ mCallHistoryStatus = callHistoryStatus;
+ }
+
+ @CallHistoryStatus
+ public int getCallHistoryStatus() {
+ return mCallHistoryStatus;
+ }
+
+ public void setSpam(boolean isSpam) {
+ mIsSpam = isSpam;
+ }
+
+ public boolean isSpam() {
+ return mIsSpam;
+ }
+
+ public LatencyReport getLatencyReport() {
+ return mLatencyReport;
+ }
}
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index 39dd5eae2..506feb572 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -131,6 +131,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
private TextView mCallTypeLabel;
private ImageView mHdAudioIcon;
private ImageView mForwardIcon;
+ private ImageView mSpamIcon;
private View mCallNumberAndLabel;
private TextView mElapsedTime;
private Drawable mPrimaryPhotoDrawable;
@@ -281,6 +282,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
mHdAudioIcon = (ImageView) view.findViewById(R.id.hdAudioIcon);
mForwardIcon = (ImageView) view.findViewById(R.id.forwardIcon);
+ mSpamIcon = (ImageView) view.findViewById(R.id.spamIcon);
mCallNumberAndLabel = view.findViewById(R.id.labelAndNumber);
mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
@@ -1216,6 +1218,19 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
mForwardIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
}
+ /**
+ * Changes the visibility of the spam icon.
+ *
+ * @param visible {@code true} if the UI should show the spam icon.
+ */
+ @Override
+ public void showSpamIndicator(boolean visible) {
+ if (visible) {
+ mSpamIcon.setVisibility(View.VISIBLE);
+ mNumberLabel.setText(R.string.label_spam_caller);
+ mPhoneNumber.setVisibility(View.GONE);
+ }
+ }
/**
* Changes the visibility of the "manage conference call" button.
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index d0762fdd0..a7d4dd483 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -458,6 +458,10 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
getUi().showHdAudioIndicator(showHdAudioIndicator);
}
+ private void maybeShowSpamIconAndLabel() {
+ getUi().showSpamIndicator(mPrimary.isSpam());
+ }
+
/**
* Only show the conference call button if we can manage the conference.
*/
@@ -826,6 +830,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
boolean isEmergencyCall = mPrimary.isEmergencyCall();
mEmergencyCallListener.onCallUpdated((BaseFragment) ui, isEmergencyCall);
}
+ maybeShowSpamIconAndLabel();
}
private void updateSecondaryDisplayInfo() {
@@ -1163,6 +1168,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
void setProgressSpinnerVisible(boolean visible);
void showHdAudioIndicator(boolean visible);
void showForwardIndicator(boolean visible);
+ void showSpamIndicator(boolean visible);
void showManageConferenceCallButton(boolean visible);
boolean isManageConferenceVisible();
boolean isCallSubjectVisible();
diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java
index d0f3c1000..48870f68a 100644
--- a/InCallUI/src/com/android/incallui/CallList.java
+++ b/InCallUI/src/com/android/incallui/CallList.java
@@ -23,8 +23,8 @@ import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import com.android.contacts.common.testing.NeededForTesting;
-import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
import com.android.dialer.logging.Logger;
+import com.android.dialer.service.ExtendedCallInfoService;
import com.android.incallui.util.TelecomCallUtil;
import com.google.common.base.Preconditions;
@@ -69,7 +69,7 @@ public class CallList {
.newHashMap();
private final Set<Call> mPendingDisconnectCalls = Collections.newSetFromMap(
new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
- private FilteredNumberAsyncQueryHandler mFilteredQueryHandler;
+ private ExtendedCallInfoService mExtendedCallInfoService;
/**
* Static singleton accessor method.
@@ -86,14 +86,25 @@ public class CallList {
CallList() {
}
- public void onCallAdded(final android.telecom.Call telecomCall) {
+ public void onCallAdded(final android.telecom.Call telecomCall, LatencyReport latencyReport) {
Trace.beginSection("onCallAdded");
- final Call call = new Call(telecomCall);
+ final Call call = new Call(telecomCall, latencyReport);
Log.d(this, "onCallAdded: callState=" + call.getState());
if (call.getState() == Call.State.INCOMING ||
call.getState() == Call.State.CALL_WAITING) {
onIncoming(call, call.getCannedSmsResponses());
+ if (mExtendedCallInfoService != null) {
+ String number = TelecomCallUtil.getNumber(telecomCall);
+ mExtendedCallInfoService.getExtendedCallInfo(number, null,
+ new ExtendedCallInfoService.Listener() {
+ @Override
+ public void onComplete(boolean isSpam) {
+ call.setSpam(isSpam);
+ onUpdate(call);
+ }
+ });
+ }
} else {
onUpdate(call);
}
@@ -615,8 +626,14 @@ public class CallList {
}
};
- public void setFilteredNumberQueryHandler(FilteredNumberAsyncQueryHandler handler) {
- mFilteredQueryHandler = handler;
+ public void setExtendedCallInfoService(ExtendedCallInfoService service) {
+ mExtendedCallInfoService = service;
+ }
+
+ public void onInCallUiShown(boolean forFullScreenIntent) {
+ for (Call call : mCallById.values()) {
+ call.getLatencyReport().onInCallUiShown(forFullScreenIntent);
+ }
}
/**
diff --git a/InCallUI/src/com/android/incallui/CallerInfo.java b/InCallUI/src/com/android/incallui/CallerInfo.java
index f270678e0..f3d0e0763 100644
--- a/InCallUI/src/com/android/incallui/CallerInfo.java
+++ b/InCallUI/src/com/android/incallui/CallerInfo.java
@@ -322,7 +322,7 @@ public class CallerInfo {
info.userType = ContactsUtils.determineUserType(directoryId, contactId);
info.nameAlternative = ContactInfoHelper.lookUpDisplayNameAlternative(
- context, info.lookupKeyOrNull, info.userType);
+ context, info.lookupKeyOrNull, info.userType, directoryId);
}
cursor.close();
}
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<Call> mExternalCalls = new ArraySet<>();
+ private final Set<ExternalCallListener> mExternalCallListeners = Collections.newSetFromMap(
+ new ConcurrentHashMap<ExternalCallListener, Boolean>(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..639a46da0
--- /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<Call, NotificationInfo> 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(),
+ new LatencyReport(), 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/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index eca79f8a7..5ae231ae1 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -77,6 +77,7 @@ public class InCallActivity extends TransactionSafeActivity implements FragmentD
public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text";
public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call";
+ public static final String FOR_FULL_SCREEN_INTENT = "InCallActivity.for_full_screen_intent";
private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
private static final String TAG_CONFERENCE_FRAGMENT = "tag_conference_manager_fragment";
@@ -301,6 +302,9 @@ public class InCallActivity extends TransactionSafeActivity implements FragmentD
if (mShowPostCharWaitDialogOnResume) {
showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars);
}
+
+ CallList.getInstance().onInCallUiShown(
+ getIntent().getBooleanExtra(FOR_FULL_SCREEN_INTENT, false));
}
// onPause is guaranteed to be called when the InCallActivity goes
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 0109d7ee6..fac539311 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -27,6 +27,7 @@ import android.database.ContentObserver;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Handler;
+import android.os.SystemClock;
import android.provider.CallLog;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
@@ -41,6 +42,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;
@@ -55,6 +57,7 @@ import com.android.dialer.filterednumber.FilteredNumbersUtil;
import com.android.dialer.logging.InteractionEvent;
import com.android.dialer.logging.Logger;
import com.android.dialer.util.TelecomUtil;
+import com.android.incallui.spam.SpamCallListListener;
import com.android.incallui.util.TelecomCallUtil;
import com.android.incalluibind.ObjectFactory;
@@ -109,9 +112,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;
@@ -120,6 +125,7 @@ public class InCallPresenter implements CallList.Listener,
private InCallCameraManager mInCallCameraManager = null;
private AnswerPresenter mAnswerPresenter = new AnswerPresenter();
private FilteredNumberAsyncQueryHandler mFilteredQueryHandler;
+ private CallList.Listener mSpamCallListListener;
/**
* Whether or not we are currently bound and waiting for Telecom to send us a new call.
@@ -299,8 +305,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 +326,7 @@ public class InCallPresenter implements CallList.Listener,
mContactInfoCache = contactInfoCache;
mStatusBarNotifier = statusBarNotifier;
+ mExternalCallNotifier = externalCallNotifier;
addListener(mStatusBarNotifier);
mAudioModeProvider = audioModeProvider;
@@ -329,6 +338,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;
@@ -337,13 +348,18 @@ public class InCallPresenter implements CallList.Listener,
// will kick off an update and the whole process can start.
mCallList.addListener(this);
+ // Create spam call list listener and add it to the list of listeners
+ mSpamCallListListener = new SpamCallListListener(context);
+ mCallList.addListener(mSpamCallListListener);
+
VideoPauseController.getInstance().setUp(this);
InCallVideoCallCallbackNotifier.getInstance().addSessionModificationListener(this);
mFilteredQueryHandler = new FilteredNumberAsyncQueryHandler(context.getContentResolver());
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- mCallList.setFilteredNumberQueryHandler(mFilteredQueryHandler);
+ mCallList.setExtendedCallInfoService(
+ com.android.dialerbind.ObjectFactory.newExtendedCallInfoService(context));
Log.d(this, "Finished InCallPresenter.setUp");
}
@@ -498,10 +514,17 @@ public class InCallPresenter implements CallList.Listener,
}
public void onCallAdded(final android.telecom.Call call) {
+ LatencyReport latencyReport = new LatencyReport(call);
if (shouldAttemptBlocking(call)) {
- maybeBlockCall(call);
+ maybeBlockCall(call, latencyReport);
} else {
- mCallList.onCallAdded(call);
+ latencyReport.onCallBlockingDone();
+ if (call.getDetails()
+ .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
+ mExternalCallList.onCallAdded(call);
+ } else {
+ mCallList.onCallAdded(call, latencyReport);
+ }
}
// Since a call has been added we are no longer waiting for Telecom to send us a call.
@@ -531,7 +554,8 @@ public class InCallPresenter implements CallList.Listener,
* checking whether a function is blocked does not return in a reasonable time, we proceed
* with adding the call anyways.
*/
- private void maybeBlockCall(final android.telecom.Call call) {
+ private void maybeBlockCall(final android.telecom.Call call,
+ final LatencyReport latencyReport) {
final String countryIso = GeoUtil.getCurrentCountryIso(mContext);
final String number = TelecomCallUtil.getNumber(call);
final long timeAdded = System.currentTimeMillis();
@@ -547,7 +571,8 @@ public class InCallPresenter implements CallList.Listener,
final Runnable runnable = new Runnable() {
public void run() {
hasTimedOut.set(true);
- mCallList.onCallAdded(call);
+ latencyReport.onCallBlockingDone();
+ mCallList.onCallAdded(call, latencyReport);
}
};
handler.postDelayed(runnable, BLOCK_QUERY_TIMEOUT_MS);
@@ -560,7 +585,8 @@ public class InCallPresenter implements CallList.Listener,
}
if (id == null) {
if (!hasTimedOut.get()) {
- mCallList.onCallAdded(call);
+ latencyReport.onCallBlockingDone();
+ mCallList.onCallAdded(call, latencyReport);
}
} else {
Log.i(this, "Rejecting incoming call from blocked number");
@@ -584,14 +610,21 @@ public class InCallPresenter implements CallList.Listener,
Log.d(this, "checkForBlockedCall: invalid number, skipping block checking");
if (!hasTimedOut.get()) {
handler.removeCallbacks(runnable);
- mCallList.onCallAdded(call);
+
+ latencyReport.onCallBlockingDone();
+ mCallList.onCallAdded(call, latencyReport);
}
}
}
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,10 +1539,14 @@ public class InCallPresenter implements CallList.Listener,
if (mStatusBarNotifier != null) {
removeListener(mStatusBarNotifier);
}
+ if (mExternalCallNotifier != null && mExternalCallList != null) {
+ mExternalCallList.removeExternalCallListener(mExternalCallNotifier);
+ }
mStatusBarNotifier = null;
if (mCallList != null) {
mCallList.removeListener(this);
+ mCallList.removeListener(mSpamCallListListener);
}
mCallList = null;
@@ -1759,7 +1796,14 @@ public class InCallPresenter implements CallList.Listener,
if (call == null) {
return getColorsFromPhoneAccountHandle(mPendingPhoneAccountHandle);
} else {
- return getColorsFromPhoneAccountHandle(call.getAccountHandle());
+ if (call.isSpam()) {
+ Resources resources = mContext.getResources();
+ return new InCallUIMaterialColorMapUtils(
+ resources).calculatePrimaryAndSecondaryColor(
+ resources.getColor(R.color.incall_call_spam_background_color));
+ } else {
+ return getColorsFromPhoneAccountHandle(call.getAccountHandle());
+ }
}
}
@@ -1808,6 +1852,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/LatencyReport.java b/InCallUI/src/com/android/incallui/LatencyReport.java
new file mode 100644
index 000000000..655372a8f
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/LatencyReport.java
@@ -0,0 +1,145 @@
+/*
+ * 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 android.os.Bundle;
+import android.os.SystemClock;
+
+import com.android.incalluibind.ObjectFactory;
+
+/**
+ * Tracks latency information for a call.
+ */
+public class LatencyReport {
+ // The following are hidden constants from android.telecom.TelecomManager.
+ private static final String EXTRA_CALL_CREATED_TIME_MILLIS =
+ "android.telecom.extra.CALL_CREATED_TIME_MILLIS";
+ private static final String EXTRA_CALL_TELECOM_ROUTING_START_TIME_MILLIS =
+ "android.telecom.extra.CALL_TELECOM_ROUTING_START_TIME_MILLIS";
+ private static final String EXTRA_CALL_TELECOM_ROUTING_END_TIME_MILLIS =
+ "android.telecom.extra.CALL_TELECOM_ROUTING_END_TIME_MILLIS";
+
+ public static final long INVALID_TIME = -1;
+
+ private final boolean mWasIncoming;
+
+ // Time elapsed since boot when the call was created by the connection service.
+ private final long mCreatedTimeMillis;
+
+ // Time elapsed since boot when telecom began processing the call.
+ private final long mTelecomRoutingStartTimeMillis;
+
+ // Time elapsed since boot when telecom finished processing the call. This includes things like
+ // looking up contact info and call blocking but before showing any UI.
+ private final long mTelecomRoutingEndTimeMillis;
+
+ // Time elapsed since boot when the call was added to the InCallUi.
+ private final long mCallAddedTimeMillis;
+
+ // Time elapsed since boot when the call was added and call blocking evaluation was completed.
+ private long mCallBlockingTimeMillis = INVALID_TIME;
+
+ // Time elapsed since boot when the call notification was shown.
+ private long mCallNotificationTimeMillis = INVALID_TIME;
+
+ // Time elapsed since boot when the InCallUI was shown.
+ private long mInCallUiShownTimeMillis = INVALID_TIME;
+
+ // Whether the call was shown to the user as a heads up notification instead of a full screen
+ // UI.
+ private boolean mDidDisplayHeadsUpNotification;
+
+ public LatencyReport() {
+ mWasIncoming = false;
+ mCreatedTimeMillis = INVALID_TIME;
+ mTelecomRoutingStartTimeMillis = INVALID_TIME;
+ mTelecomRoutingEndTimeMillis = INVALID_TIME;
+ mCallAddedTimeMillis = SystemClock.elapsedRealtime();
+ }
+
+ public LatencyReport(android.telecom.Call telecomCall) {
+ mWasIncoming = telecomCall.getState() == android.telecom.Call.STATE_RINGING;
+ Bundle extras = telecomCall.getDetails().getIntentExtras();
+ if (extras == null) {
+ mCreatedTimeMillis = INVALID_TIME;
+ mTelecomRoutingStartTimeMillis = INVALID_TIME;
+ mTelecomRoutingEndTimeMillis = INVALID_TIME;
+ } else {
+ mCreatedTimeMillis = extras.getLong(EXTRA_CALL_CREATED_TIME_MILLIS, INVALID_TIME);
+ mTelecomRoutingStartTimeMillis = extras.getLong(
+ EXTRA_CALL_TELECOM_ROUTING_START_TIME_MILLIS, INVALID_TIME);
+ mTelecomRoutingEndTimeMillis = extras.getLong(
+ EXTRA_CALL_TELECOM_ROUTING_END_TIME_MILLIS, INVALID_TIME);
+ }
+ mCallAddedTimeMillis = SystemClock.elapsedRealtime();
+ }
+
+ public boolean getWasIncoming() {
+ return mWasIncoming;
+ }
+
+ public long getCreatedTimeMillis() {
+ return mCreatedTimeMillis;
+ }
+
+ public long getTelecomRoutingStartTimeMillis() {
+ return mTelecomRoutingStartTimeMillis;
+ }
+
+ public long getTelecomRoutingEndTimeMillis() {
+ return mTelecomRoutingEndTimeMillis;
+ }
+
+ public long getCallAddedTimeMillis() {
+ return mCallAddedTimeMillis;
+ }
+
+ public long getCallBlockingTimeMillis() {
+ return mCallBlockingTimeMillis;
+ }
+
+ public void onCallBlockingDone() {
+ if (mCallBlockingTimeMillis == INVALID_TIME) {
+ mCallBlockingTimeMillis = SystemClock.elapsedRealtime();
+ }
+ }
+
+ public long getCallNotificationTimeMillis() {
+ return mCallNotificationTimeMillis;
+ }
+
+ public void onNotificationShown() {
+ if (mCallNotificationTimeMillis == INVALID_TIME) {
+ mCallNotificationTimeMillis = SystemClock.elapsedRealtime();
+ }
+ }
+
+ public long getInCallUiShownTimeMillis() {
+ return mInCallUiShownTimeMillis;
+ }
+
+ public void onInCallUiShown(boolean forFullScreenIntent) {
+ if (mInCallUiShownTimeMillis == INVALID_TIME) {
+ mInCallUiShownTimeMillis = SystemClock.elapsedRealtime();
+ mDidDisplayHeadsUpNotification = mWasIncoming && !forFullScreenIntent;
+ }
+ }
+
+ public boolean getDidDisplayHeadsUpNotification() {
+ return mDidDisplayHeadsUpNotification;
+ }
+}
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/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
index a61620317..7132ff9e0 100644
--- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java
+++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
@@ -33,7 +33,9 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.media.AudioAttributes;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
@@ -52,6 +54,7 @@ import com.android.contacts.common.testing.NeededForTesting;
import com.android.contacts.common.util.BitmapUtil;
import com.android.contacts.common.util.ContactDisplayUtils;
import com.android.dialer.R;
+import com.android.dialer.service.ExtendedCallInfoService;
import com.android.incallui.ContactInfoCache.ContactCacheEntry;
import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
import com.android.incallui.InCallPresenter.InCallState;
@@ -76,6 +79,9 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
// Notification for incoming calls. This is interruptive and will show up as a HUN.
private static final int NOTIFICATION_INCOMING_CALL = 2;
+ private static final int PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN = 0;
+ private static final int PENDING_INTENT_REQUEST_CODE_FULL_SCREEN = 1;
+
private static final long[] VIBRATE_PATTERN = new long[] {0, 1000, 1000};
private final Context mContext;
@@ -239,6 +245,10 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
}
final int callState = call.getState();
+ // Dont' show as spam if the number is in local contact.
+ if (contactInfo.contactLookupResult == Call.LogState.LOOKUP_LOCAL_CONTACT) {
+ call.setSpam(false);
+ }
// Check if data has changed; if nothing is different, don't issue another notification.
final int iconResId = getIconToDisplay(call);
@@ -286,13 +296,13 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
builder.setPublicVersion(publicBuilder.build());
// Set up the main intent to send the user to the in-call screen
- final PendingIntent inCallPendingIntent = createLaunchPendingIntent();
- builder.setContentIntent(inCallPendingIntent);
+ builder.setContentIntent(createLaunchPendingIntent(false /* isFullScreen */));
// Set the intent as a full screen intent as well if a call is incoming
if (notificationType == NOTIFICATION_INCOMING_CALL
&& !InCallPresenter.getInstance().isShowingInCallUi()) {
- configureFullScreenIntent(builder, inCallPendingIntent, call);
+ configureFullScreenIntent(
+ builder, createLaunchPendingIntent(true /* isFullScreen */), call);
// Set the notification category for incoming calls
builder.setCategory(Notification.CATEGORY_CALL);
}
@@ -339,8 +349,10 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
+ mCurrentNotification);
mNotificationManager.cancel(mCurrentNotification);
}
+
Log.i(this, "Displaying notification for " + notificationType);
mNotificationManager.notify(notificationType, notification);
+ call.getLatencyReport().onNotificationShown();
mCurrentNotification = notificationType;
}
@@ -464,6 +476,10 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) {
largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap();
}
+ if (call.isSpam()) {
+ Drawable drawable = mContext.getResources().getDrawable(R.drawable.blocked_contact);
+ largeIcon = CallCardFragment.drawableToBitmap(drawable);
+ }
return largeIcon;
}
@@ -525,7 +541,11 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
if (call.hasProperty(Details.PROPERTY_WIFI)) {
resId = R.string.notification_incoming_call_wifi;
} else {
- resId = R.string.notification_incoming_call;
+ if (call.isSpam()) {
+ resId = R.string.notification_incoming_spam_call;
+ } else {
+ resId = R.string.notification_incoming_call;
+ }
}
} else if (call.getState() == Call.State.ONHOLD) {
resId = R.string.notification_on_hold;
@@ -707,19 +727,24 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
return builder;
}
- private PendingIntent createLaunchPendingIntent() {
-
- final Intent intent = InCallPresenter.getInstance().getInCallIntent(
+ private PendingIntent createLaunchPendingIntent(boolean isFullScreen) {
+ Intent intent = InCallPresenter.getInstance().getInCallIntent(
false /* showDialpad */, false /* newOutgoingCall */);
+ int requestCode = PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN;
+ if (isFullScreen) {
+ intent.putExtra(InCallActivity.FOR_FULL_SCREEN_INTENT, true);
+ // Use a unique request code so that the pending intent isn't clobbered by the
+ // non-full screen pending intent.
+ requestCode = PENDING_INTENT_REQUEST_CODE_FULL_SCREEN;
+ }
+
// PendingIntent that can be used to launch the InCallActivity. The
// system fires off this intent if the user pulls down the windowshade
// and clicks the notification's expanded view. It's also used to
// launch the InCallActivity immediately when when there's an incoming
// call (see the "fullScreenIntent" field below).
- PendingIntent inCallPendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
-
- return inCallPendingIntent;
+ return PendingIntent.getActivity(mContext, requestCode, intent, 0);
}
/**
diff --git a/InCallUI/src/com/android/incallui/spam/SpamCallListListener.java b/InCallUI/src/com/android/incallui/spam/SpamCallListListener.java
new file mode 100644
index 000000000..b97f4d099
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/spam/SpamCallListListener.java
@@ -0,0 +1,117 @@
+/*
+ * 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.spam;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import android.content.Context;
+import android.telecom.DisconnectCause;
+import android.text.TextUtils;
+
+import com.android.dialer.calllog.CallLogAsyncTaskUtil;
+import com.android.incallui.Call;
+import com.android.incallui.CallList;
+import com.android.incallui.Log;
+
+public class SpamCallListListener implements CallList.Listener {
+ private static final String TAG = "SpamCallListListener";
+
+ private final Context mContext;
+
+ public SpamCallListListener(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void onIncomingCall(final Call call) {
+ String number = call.getNumber();
+ if (TextUtils.isEmpty(number)) {
+ return;
+ }
+ CallLogAsyncTaskUtil.getNumberInCallHistory(mContext, number,
+ new CallLogAsyncTaskUtil.OnGetNumberInCallHistoryListener() {
+ @Override
+ public void onComplete(boolean inCallHistory) {
+ call.setCallHistoryStatus(inCallHistory ?
+ Call.CALL_HISTORY_STATUS_PRESENT
+ : Call.CALL_HISTORY_STATUS_NOT_PRESENT);
+ }
+ });
+ }
+
+ @Override
+ public void onUpgradeToVideo(Call call) {}
+
+ @Override
+ public void onCallListChange(CallList callList) {}
+
+ @Override
+ public void onDisconnect(Call call) {
+ if (shouldShowAfterCallNotification(call)) {
+ showNotification(call.getNumber());
+ }
+ }
+
+ /**
+ * Posts the intent for displaying the after call spam notification to the user.
+ */
+ @VisibleForTesting
+ /* package */ void showNotification(String number) {
+ //TODO(mhashmi): build and show notifications here
+ }
+
+ /**
+ * Determines if the after call notification should be shown for the specified call.
+ */
+ private boolean shouldShowAfterCallNotification(Call call) {
+ String number = call.getNumber();
+ if (TextUtils.isEmpty(number)) {
+ return false;
+ }
+
+ Call.LogState logState = call.getLogState();
+ if (!logState.isIncoming) {
+ return false;
+ }
+
+ if (logState.duration <= 0) {
+ return false;
+ }
+
+ if (logState.contactLookupResult != Call.LogState.LOOKUP_NOT_FOUND
+ && logState.contactLookupResult != Call.LogState.LOOKUP_UNKNOWN) {
+ return false;
+ }
+
+ int callHistoryStatus = call.getCallHistoryStatus();
+ if (callHistoryStatus == Call.CALL_HISTORY_STATUS_PRESENT) {
+ return false;
+ } else if (callHistoryStatus == Call.CALL_HISTORY_STATUS_UNKNOWN) {
+ Log.i(TAG, "Call history status is unknown, returning false");
+ return false;
+ }
+
+ // Check if call disconnected because of either user hanging up
+ int disconnectCause = call.getDisconnectCause().getCode();
+ if (disconnectCause != DisconnectCause.LOCAL && disconnectCause != DisconnectCause.REMOTE) {
+ return false;
+ }
+
+ Log.i(TAG, "shouldShowAfterCallNotification, returning true");
+ return true;
+ }
+} \ No newline at end of file