Merge "Import translations. DO NOT MERGE" into oc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4caa4a3..6579106 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -43,6 +43,7 @@
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
+ <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
<permission
android:name="android.permission.BROADCAST_CALLLOG_INFO"
@@ -209,6 +210,8 @@
<action android:name="com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS" />
<action android:name="com.android.server.telecom.ACTION_CALL_BACK_FROM_NOTIFICATION" />
<action android:name="com.android.server.telecom.ACTION_SEND_SMS_FROM_NOTIFICATION" />
+ <action android:name="com.android.server.telecom.ACTION_ANSWER_FROM_NOTIFICATION" />
+ <action android:name="com.android.server.telecom.ACTION_REJECT_FROM_NOTIFICATION" />
</intent-filter>
</receiver>
diff --git a/res/anim/on_going_call.xml b/res/anim/on_going_call.xml
new file mode 100644
index 0000000..ba3ed46
--- /dev/null
+++ b/res/anim/on_going_call.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="false">
+ <item
+ android:drawable="@drawable/ic_ongoing_phone_24px_01"
+ android:duration="200"/>
+ <item
+ android:drawable="@drawable/ic_ongoing_phone_24px_02"
+ android:duration="200"/>
+ <item
+ android:drawable="@drawable/ic_ongoing_phone_24px_03"
+ android:duration="200"/>
+ <item
+ android:drawable="@drawable/ic_ongoing_phone_24px_04"
+ android:duration="200"/>
+ <item
+ android:drawable="@drawable/ic_ongoing_phone_24px_05"
+ android:duration="200"/>
+ <item
+ android:drawable="@drawable/ic_ongoing_phone_24px_06"
+ android:duration="200"/>
+ <item
+ android:drawable="@drawable/ic_ongoing_phone_24px_07"
+ android:duration="200"/>
+ <item
+ android:drawable="@drawable/ic_ongoing_phone_24px_08"
+ android:duration="200"/>
+ <item
+ android:drawable="@drawable/ic_ongoing_phone_24px_09"
+ android:duration="200"/>
+</animation-list>
\ No newline at end of file
diff --git a/res/drawable-hdpi/ic_call_white_24dp.png b/res/drawable-hdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-hdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_close_dk.png b/res/drawable-hdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-hdpi/ic_close_dk.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_01.png b/res/drawable-hdpi/ic_ongoing_phone_24px_01.png
new file mode 100644
index 0000000..ae31e04
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_01.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_02.png b/res/drawable-hdpi/ic_ongoing_phone_24px_02.png
new file mode 100644
index 0000000..67b2b16
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_02.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_03.png b/res/drawable-hdpi/ic_ongoing_phone_24px_03.png
new file mode 100644
index 0000000..fa936cb
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_03.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_04.png b/res/drawable-hdpi/ic_ongoing_phone_24px_04.png
new file mode 100644
index 0000000..ef51379
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_04.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_05.png b/res/drawable-hdpi/ic_ongoing_phone_24px_05.png
new file mode 100644
index 0000000..3712d16
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_05.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_06.png b/res/drawable-hdpi/ic_ongoing_phone_24px_06.png
new file mode 100644
index 0000000..c6a4216
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_06.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_07.png b/res/drawable-hdpi/ic_ongoing_phone_24px_07.png
new file mode 100644
index 0000000..80ad50b
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_07.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_08.png b/res/drawable-hdpi/ic_ongoing_phone_24px_08.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_08.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_09.png b/res/drawable-hdpi/ic_ongoing_phone_24px_09.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_09.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_white_24dp.png b/res/drawable-mdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-mdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_close_dk.png b/res/drawable-mdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-mdpi/ic_close_dk.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_01.png b/res/drawable-mdpi/ic_ongoing_phone_24px_01.png
new file mode 100644
index 0000000..ae31e04
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_01.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_02.png b/res/drawable-mdpi/ic_ongoing_phone_24px_02.png
new file mode 100644
index 0000000..67b2b16
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_02.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_03.png b/res/drawable-mdpi/ic_ongoing_phone_24px_03.png
new file mode 100644
index 0000000..fa936cb
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_03.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_04.png b/res/drawable-mdpi/ic_ongoing_phone_24px_04.png
new file mode 100644
index 0000000..ef51379
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_04.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_05.png b/res/drawable-mdpi/ic_ongoing_phone_24px_05.png
new file mode 100644
index 0000000..3712d16
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_05.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_06.png b/res/drawable-mdpi/ic_ongoing_phone_24px_06.png
new file mode 100644
index 0000000..c6a4216
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_06.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_07.png b/res/drawable-mdpi/ic_ongoing_phone_24px_07.png
new file mode 100644
index 0000000..80ad50b
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_07.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_08.png b/res/drawable-mdpi/ic_ongoing_phone_24px_08.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_08.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_09.png b/res/drawable-mdpi/ic_ongoing_phone_24px_09.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_09.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_white_24dp.png b/res/drawable-xhdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_close_dk.png b/res/drawable-xhdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-xhdpi/ic_close_dk.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_01.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_01.png
new file mode 100644
index 0000000..ae31e04
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_01.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_02.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_02.png
new file mode 100644
index 0000000..67b2b16
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_02.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_03.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_03.png
new file mode 100644
index 0000000..fa936cb
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_03.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_04.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_04.png
new file mode 100644
index 0000000..ef51379
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_04.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_05.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_05.png
new file mode 100644
index 0000000..3712d16
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_05.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_06.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_06.png
new file mode 100644
index 0000000..c6a4216
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_06.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_07.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_07.png
new file mode 100644
index 0000000..80ad50b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_07.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_08.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_08.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_08.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_09.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_09.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_09.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_call_white_24dp.png b/res/drawable-xxhdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_close_dk.png b/res/drawable-xxhdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_close_dk.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_01.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_01.png
new file mode 100644
index 0000000..ae31e04
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_01.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_02.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_02.png
new file mode 100644
index 0000000..67b2b16
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_02.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_03.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_03.png
new file mode 100644
index 0000000..fa936cb
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_03.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_04.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_04.png
new file mode 100644
index 0000000..ef51379
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_04.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_05.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_05.png
new file mode 100644
index 0000000..3712d16
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_05.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_06.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_06.png
new file mode 100644
index 0000000..c6a4216
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_06.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_07.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_07.png
new file mode 100644
index 0000000..80ad50b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_07.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_08.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_08.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_08.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_09.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_09.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_09.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_call_white_24dp.png b/res/drawable-xxxhdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_close_dk.png b/res/drawable-xxxhdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_close_dk.png
Binary files differ
diff --git a/res/values/colors.xml b/res/values/colors.xml
index b0bcd3c..8ff0b8e 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -25,4 +25,7 @@
<color name="blocked_numbers_butter_bar_color">#f5f5f5</color>
<color name="blocked_numbers_title_text_color">#ba000000</color>
<color name="blocked_numbers_secondary_text_color">#89000000</color>
+
+ <color name="notification_action_answer">#097138</color>
+ <color name="notification_action_decline">#A52714</color>
</resources>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 5056266..2eeb96c 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -596,12 +596,30 @@
@Override
public String getDescription() {
- StringBuilder s = new StringBuilder("Call ");
+ StringBuilder s = new StringBuilder();
+ if (isSelfManaged()) {
+ s.append("SelfMgd Call");
+ } else if (isExternalCall()) {
+ s.append("External Call");
+ } else {
+ s.append("Call");
+ }
s.append(getId());
s.append(" [");
s.append(SimpleDateFormat.getDateTimeInstance().format(new Date(getCreationTimeMillis())));
s.append("]");
s.append(isIncoming() ? "(MT - incoming)" : "(MO - outgoing)");
+ s.append("\n\tVia PhoneAccount: ");
+ PhoneAccountHandle targetPhoneAccountHandle = getTargetPhoneAccount();
+ if (targetPhoneAccountHandle != null) {
+ s.append(targetPhoneAccountHandle);
+ s.append(" (");
+ s.append(getTargetPhoneAccountLabel());
+ s.append(")");
+ } else {
+ s.append("not set");
+ }
+
s.append("\n\tTo address: ");
s.append(Log.piiHandle(getHandle()));
s.append("\n");
@@ -858,11 +876,11 @@
}
}
- String getCallerDisplayName() {
+ public String getCallerDisplayName() {
return mCallerDisplayName;
}
- int getCallerDisplayNamePresentation() {
+ public int getCallerDisplayNamePresentation() {
return mCallerDisplayNamePresentation;
}
@@ -966,6 +984,20 @@
checkIfVideoCapable();
}
+ public CharSequence getTargetPhoneAccountLabel() {
+ if (getTargetPhoneAccount() == null) {
+ return null;
+ }
+ PhoneAccount phoneAccount = mCallsManager.getPhoneAccountRegistrar()
+ .getPhoneAccountUnchecked(getTargetPhoneAccount());
+
+ if (phoneAccount == null) {
+ return null;
+ }
+
+ return phoneAccount.getLabel();
+ }
+
@VisibleForTesting
public boolean isIncoming() {
return mCallDirection == CALL_DIRECTION_INCOMING;
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 82703e2..45cd488 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -66,6 +66,7 @@
import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
import com.android.server.telecom.callfiltering.IncomingCallFilter;
import com.android.server.telecom.components.ErrorDialogActivity;
+import com.android.server.telecom.ui.IncomingCallNotifier;
import java.util.ArrayList;
import java.util.Collection;
@@ -206,6 +207,7 @@
private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final MissedCallNotifier mMissedCallNotifier;
+ private IncomingCallNotifier mIncomingCallNotifier;
private final CallerInfoLookupHelper mCallerInfoLookupHelper;
private final DefaultDialerCache mDefaultDialerCache;
private final Timeouts.Adapter mTimeoutsAdapter;
@@ -330,6 +332,14 @@
}
}
+ public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
+ if (mIncomingCallNotifier != null) {
+ mListeners.remove(mIncomingCallNotifier);
+ }
+ mIncomingCallNotifier = incomingCallNotifier;
+ mListeners.add(mIncomingCallNotifier);
+ }
+
public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) {
if (mRespondViaSmsManager != null) {
mListeners.remove(mRespondViaSmsManager);
@@ -762,6 +772,27 @@
phoneAccountHandle);
if (phoneAccount != null) {
call.setIsSelfManaged(phoneAccount.isSelfManaged());
+ if (call.isSelfManaged()) {
+ // Self managed calls will always be voip audio mode.
+ call.setIsVoipAudioMode(true);
+ } else {
+ // Incoming call is not self-managed, so we need to set extras on it to indicate
+ // whether answering will cause a background self-managed call to drop.
+ if (hasSelfManagedCalls()) {
+ Bundle dropCallExtras = new Bundle();
+ dropCallExtras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+
+ // Include the name of the app which will drop the call.
+ Call foregroundCall = getForegroundCall();
+ if (foregroundCall != null) {
+ CharSequence droppedApp = foregroundCall.getTargetPhoneAccountLabel();
+ dropCallExtras.putCharSequence(
+ Connection.EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME, droppedApp);
+ Log.i(this, "Incoming managed call will drop %s call.", droppedApp);
+ }
+ call.putExtras(Call.SOURCE_CONNECTION_SERVICE, dropCallExtras);
+ }
+ }
}
if (extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)) {
if (phoneAccount != null &&
@@ -769,6 +800,13 @@
call.setRttStreams(true);
}
}
+ // If the extras specifies a video state, set it on the call if the PhoneAccount supports
+ // video.
+ if (extras.containsKey(TelecomManager.EXTRA_INCOMING_VIDEO_STATE) &&
+ phoneAccount != null && phoneAccount.hasCapabilities(
+ PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
+ call.setVideoState(extras.getInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE));
+ }
call.initAnalytics();
if (getForegroundCall() != null) {
@@ -897,6 +935,10 @@
// self-managed from the moment it is created.
if (account != null) {
call.setIsSelfManaged(account.isSelfManaged());
+ if (call.isSelfManaged()) {
+ // Self-managed calls will ALWAYS use voip audio mode.
+ call.setIsVoipAudioMode(true);
+ }
}
call.setInitiatingUser(initiatingUser);
@@ -1085,15 +1127,33 @@
final boolean requireCallCapableAccountByHandle = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_requireCallCapableAccountForHandle);
-
+ final boolean isOutgoingCallPermitted = isOutgoingCallPermitted(call,
+ call.getTargetPhoneAccount());
if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
// If the account has been set, proceed to place the outgoing call.
// Otherwise the connection will be initiated when the account is set by the user.
- if (call.isSelfManaged() && !isOutgoingCallPermitted(call,
- call.getTargetPhoneAccount())) {
-
+ if (call.isSelfManaged() && !isOutgoingCallPermitted) {
notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
+ } else if (!call.isSelfManaged() && hasSelfManagedCalls() && !call.isEmergencyCall()) {
+ Call activeCall = getActiveCall();
+ CharSequence errorMessage;
+ if (activeCall == null) {
+ // Realistically this shouldn't happen, but best to handle gracefully
+ errorMessage = mContext.getText(R.string.cant_call_due_to_ongoing_unknown_call);
+ } else {
+ errorMessage = mContext.getString(R.string.cant_call_due_to_ongoing_call,
+ activeCall.getTargetPhoneAccountLabel());
+ }
+ // Call is managed and there are ongoing self-managed calls.
+ markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR,
+ errorMessage, errorMessage, "Ongoing call in another app."));
+ markCallAsRemoved(call);
} else {
+ if (call.isEmergencyCall()) {
+ // Disconnect all self-managed calls to make priority for emergency call.
+ mCalls.stream().filter(c -> c.isSelfManaged()).forEach(c -> c.disconnect());
+ }
+
call.startCreateConnection(mPhoneAccountRegistrar);
}
} else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
@@ -1137,7 +1197,21 @@
(foregroundCall.isActive() ||
foregroundCall.getState() == CallState.DIALING ||
foregroundCall.getState() == CallState.PULLING)) {
- if (0 == (foregroundCall.getConnectionCapabilities()
+ if (!foregroundCall.getTargetPhoneAccount().equals(
+ call.getTargetPhoneAccount()) &&
+ ((call.isSelfManaged() != foregroundCall.isSelfManaged()) ||
+ call.isSelfManaged())) {
+ // The foreground call is from another connection service, and either:
+ // 1. FG call's managed state doesn't match that of the incoming call.
+ // E.g. Incoming is self-managed and FG is managed, or incoming is managed
+ // and foreground is self-managed.
+ // 2. The incoming call is self-managed.
+ // E.g. The incoming call is
+ Log.i(this, "Answering call from %s CS; disconnecting calls from %s CS.",
+ foregroundCall.isSelfManaged() ? "selfMg" : "mg",
+ call.isSelfManaged() ? "selfMg" : "mg");
+ disconnectOtherCalls(call.getTargetPhoneAccount());
+ } else if (0 == (foregroundCall.getConnectionCapabilities()
& Connection.CAPABILITY_HOLD)) {
// This call does not support hold. If it is from a different connection
// service, then disconnect it, otherwise allow the connection service to
@@ -1148,13 +1222,11 @@
} else {
Call heldCall = getHeldCall();
if (heldCall != null) {
- Log.v(this, "Disconnecting held call %s before holding active call.",
+ Log.i(this, "Disconnecting held call %s before holding active call.",
heldCall);
heldCall.disconnect();
}
- Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
- foregroundCall, call);
foregroundCall.hold();
}
// TODO: Wait until we get confirmation of the active call being
@@ -1298,6 +1370,19 @@
}
}
+ /**
+ * Disconnects calls for any other {@link PhoneAccountHandle} but the one specified.
+ * Note: As a protective measure, will NEVER disconnect an emergency call. Although that
+ * situation should never arise, its a good safeguard.
+ * @param phoneAccountHandle Calls owned by {@link PhoneAccountHandle}s other than this one will
+ * be disconnected.
+ */
+ private void disconnectOtherCalls(PhoneAccountHandle phoneAccountHandle) {
+ mCalls.stream()
+ .filter(c -> !c.isEmergencyCall() &&
+ !c.getTargetPhoneAccount().equals(phoneAccountHandle))
+ .forEach(c -> disconnectCall(c));
+ }
/**
* Instructs Telecom to put the specified call on hold. Intended to be invoked by the
@@ -1692,7 +1777,6 @@
return getFirstCallWithState(CallState.RINGING);
}
- @VisibleForTesting
public Call getActiveCall() {
return getFirstCallWithState(CallState.ACTIVE);
}
@@ -1845,6 +1929,14 @@
}
/**
+ * Retrieves the {@link IncomingCallNotifier}.
+ * @return The {@link IncomingCallNotifier}.
+ */
+ IncomingCallNotifier getIncomingCallNotifier() {
+ return mIncomingCallNotifier;
+ }
+
+ /**
* Reject an incoming call and manually add it to the Call Log.
* @param incomingCall Incoming call that has been rejected
*/
@@ -2088,10 +2180,19 @@
* @return {@code true} if there are other calls, {@code false} otherwise.
*/
public boolean hasCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
- return mCalls.stream().filter(call ->
+ return getNumCallsForOtherPhoneAccount(phoneAccountHandle) > 0;
+ }
+
+ /**
+ * Determines the number of calls present for PhoneAccounts other than the one specified.
+ * @param phoneAccountHandle The handle of the PhoneAccount.
+ * @return Number of calls owned by other PhoneAccounts.
+ */
+ public int getNumCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
+ return (int) mCalls.stream().filter(call ->
!phoneAccountHandle.equals(call.getTargetPhoneAccount()) &&
- call.getParentCall() == null &&
- !call.isExternalCall()).count() > 0;
+ call.getParentCall() == null &&
+ !call.isExternalCall()).count();
}
/**
@@ -2104,6 +2205,14 @@
}
/**
+ * Determines if there are any self-managed calls.
+ * @return {@code true} if there are self-managed calls, {@code false} otherwise.
+ */
+ public boolean hasSelfManagedCalls() {
+ return mCalls.stream().filter(call -> call.isSelfManaged()).count() > 0;
+ }
+
+ /**
* Determines if there are any ongoing managed calls.
* @return {@code true} if there are ongoing managed calls, {@code false} otherwise.
*/
@@ -2114,6 +2223,18 @@
LIVE_CALL_STATES) > 0;
}
+ /**
+ * Deteremines if the system incoming call UI should be shown.
+ * The system incoming call UI will be shown if the new incoming call is self-managed, and there
+ * are ongoing calls for another PhoneAccount.
+ * @param incomingCall The incoming call.
+ * @return {@code true} if the system incoming call UI should be shown, {@code false} otherwise.
+ */
+ public boolean shouldShowSystemIncomingCallUi(Call incomingCall) {
+ return incomingCall.isIncoming() && incomingCall.isSelfManaged() &&
+ hasCallsForOtherPhoneAccount(incomingCall.getTargetPhoneAccount());
+ }
+
private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
if (hasMaximumManagedLiveCalls(call)) {
// NOTE: If the amount of live calls changes beyond 1, this logic will probably
@@ -2390,8 +2511,7 @@
} else {
return !hasEmergencyCall() &&
!hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
- !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
- !hasManagedCalls();
+ !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle);
}
}
@@ -2420,7 +2540,8 @@
// are associated with the current PhoneAccountHandle.
return !hasEmergencyCall() &&
!hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
- !hasCallsForOtherPhoneAccount(phoneAccountHandle);
+ !hasCallsForOtherPhoneAccount(phoneAccountHandle) &&
+ !hasManagedCalls();
}
}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 37257af..60ce2eb 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -922,20 +922,17 @@
Log.addEvent(call, LogUtils.Events.START_CONNECTION,
Log.piiHandle(call.getHandle()));
- // For self-managed incoming calls, if there is another ongoing call Telecom is
- // responsible for showing a UI to ask the user if they'd like to answer this
- // new incoming call.
- boolean shouldShowIncomingCallUI = call.isSelfManaged() &&
- !mCallsManager.hasCallsForOtherPhoneAccount(
- call.getTargetPhoneAccount());
-
ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
.setAccountHandle(call.getTargetPhoneAccount())
.setAddress(call.getHandle())
.setExtras(extras)
.setVideoState(call.getVideoState())
.setTelecomCallId(callId)
- .setShouldShowIncomingCallUi(shouldShowIncomingCallUI)
+ // For self-managed incoming calls, if there is another ongoing call Telecom
+ // is responsible for showing a UI to ask the user if they'd like to answer
+ // this new incoming call.
+ .setShouldShowIncomingCallUi(
+ !mCallsManager.shouldShowSystemIncomingCallUi(call))
.setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
.setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
.build();
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 2dafece..f84e157 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -64,14 +64,30 @@
public final class InCallController extends CallsManagerListenerBase {
public class InCallServiceConnection {
+ /**
+ * Indicates that a call to {@link #connect(Call)} has succeeded and resulted in a
+ * connection to an InCallService.
+ */
+ public static final int CONNECTION_SUCCEEDED = 1;
+ /**
+ * Indicates that a call to {@link #connect(Call)} has failed because of a binding issue.
+ */
+ public static final int CONNECTION_FAILED = 2;
+ /**
+ * Indicates that a call to {@link #connect(Call)} has been skipped because the
+ * IncallService does not support the type of call..
+ */
+ public static final int CONNECTION_NOT_SUPPORTED = 3;
+
public class Listener {
public void onDisconnect(InCallServiceConnection conn) {}
}
protected Listener mListener;
- public boolean connect(Call call) { return false; }
+ public int connect(Call call) { return CONNECTION_FAILED; }
public void disconnect() {}
+ public boolean isConnected() { return false; }
public void setHasEmergency(boolean hasEmergency) {}
public void setListener(Listener l) {
mListener = l;
@@ -190,10 +206,17 @@
}
@Override
- public boolean connect(Call call) {
+ public int connect(Call call) {
if (mIsConnected) {
Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request.");
- return true;
+ return CONNECTION_SUCCEEDED;
+ }
+
+ if (call.isSelfManaged() && !mInCallServiceInfo.isSelfManagedCallsSupported()) {
+ Log.i(this, "Skipping binding to %s - doesn't support self-mgd calls",
+ mInCallServiceInfo);
+ mIsConnected = false;
+ return CONNECTION_NOT_SUPPORTED;
}
Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
@@ -220,7 +243,7 @@
mInCallServiceInfo.getType());
}
- return mIsConnected;
+ return mIsConnected ? CONNECTION_SUCCEEDED : CONNECTION_FAILED;
}
@Override
@@ -239,6 +262,11 @@
}
@Override
+ public boolean isConnected() {
+ return mIsConnected;
+ }
+
+ @Override
public void dump(IndentingPrintWriter pw) {
pw.append("BindingConnection [");
pw.append(mIsConnected ? "" : "not ").append("connected, ");
@@ -304,11 +332,13 @@
}
@Override
- public boolean connect(Call call) {
+ public int connect(Call call) {
mIsConnected = true;
if (mIsProxying) {
- if (mSubConnection.connect(call)) {
- return true;
+ int result = mSubConnection.connect(call);
+ mIsConnected = result == CONNECTION_SUCCEEDED;
+ if (result != CONNECTION_FAILED) {
+ return result;
}
// Could not connect to child, stop proxying.
mIsProxying = false;
@@ -371,7 +401,9 @@
@Override
public void dump(IndentingPrintWriter pw) {
- pw.println("Emergency ICS Connection");
+ pw.print("Emergency ICS Connection [");
+ pw.append(mIsProxying ? "" : "not ").append("proxying, ");
+ pw.append(mIsConnected ? "" : "not ").append("connected]\n");
pw.increaseIndent();
pw.print("Emergency: ");
super.dump(pw);
@@ -423,7 +455,8 @@
if (newConnection != mCurrentConnection) {
if (mIsConnected) {
mCurrentConnection.disconnect();
- newConnection.connect(null);
+ int result = newConnection.connect(null);
+ mIsConnected = result == CONNECTION_SUCCEEDED;
}
mCurrentConnection = newConnection;
}
@@ -431,18 +464,19 @@
}
@Override
- public boolean connect(Call call) {
+ public int connect(Call call) {
if (mIsConnected) {
Log.i(this, "already connected");
- return true;
+ return CONNECTION_SUCCEEDED;
} else {
- if (mCurrentConnection.connect(call)) {
- mIsConnected = true;
- return true;
+ int result = mCurrentConnection.connect(call);
+ if (result != CONNECTION_FAILED) {
+ mIsConnected = result == CONNECTION_SUCCEEDED;
+ return result;
}
}
- return false;
+ return CONNECTION_FAILED;
}
@Override
@@ -456,6 +490,11 @@
}
@Override
+ public boolean isConnected() {
+ return mIsConnected;
+ }
+
+ @Override
public void setHasEmergency(boolean hasEmergency) {
if (mDialerConnection != null) {
mDialerConnection.setHasEmergency(hasEmergency);
@@ -472,7 +511,8 @@
@Override
public void dump(IndentingPrintWriter pw) {
- pw.println("Car Swapping ICS");
+ pw.print("Car Swapping ICS [");
+ pw.append(mIsConnected ? "" : "not ").append("connected]\n");
pw.increaseIndent();
if (mDialerConnection != null) {
pw.print("Dialer: ");
@@ -502,21 +542,32 @@
}
@Override
- public boolean connect(Call call) {
+ public int connect(Call call) {
for (InCallServiceBindingConnection subConnection : mSubConnections) {
subConnection.connect(call);
}
- return true;
+ return CONNECTION_SUCCEEDED;
}
@Override
public void disconnect() {
for (InCallServiceBindingConnection subConnection : mSubConnections) {
- subConnection.disconnect();
+ if (subConnection.isConnected()) {
+ subConnection.disconnect();
+ }
}
}
@Override
+ public boolean isConnected() {
+ boolean connected = false;
+ for (InCallServiceBindingConnection subConnection : mSubConnections) {
+ connected = connected || subConnection.isConnected();
+ }
+ return connected;
+ }
+
+ @Override
public void dump(IndentingPrintWriter pw) {
pw.println("Non-UI Connections:");
pw.increaseIndent();
@@ -696,15 +747,21 @@
@Override
public void onCallAdded(Call call) {
- if (!isBoundToServices()) {
+ if (!isBoundAndConnectedToServices()) {
+ Log.i(this, "onCallAdded: %s; not bound or connected.", call);
+ // We are not bound, or we're not connected.
bindToServices(call);
} else {
+ // We are bound, and we are connected.
adjustServiceBindingsForEmergency();
Log.i(this, "onCallAdded: %s", call);
// Track the call if we don't already know about it.
addCall(call);
+ Log.i(this, "mInCallServiceConnection isConnected=%b",
+ mInCallServiceConnection.isConnected());
+
List<ComponentName> componentsUpdated = new ArrayList<>();
for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
InCallServiceInfo info = entry.getKey();
@@ -960,53 +1017,66 @@
* Unbinds an existing bound connection to the in-call app.
*/
private void unbindFromServices() {
- if (isBoundToServices()) {
- if (mInCallServiceConnection != null) {
- mInCallServiceConnection.disconnect();
- mInCallServiceConnection = null;
- }
- if (mNonUIInCallServiceConnections != null) {
- mNonUIInCallServiceConnections.disconnect();
- mNonUIInCallServiceConnections = null;
- }
+ if (mInCallServiceConnection != null) {
+ mInCallServiceConnection.disconnect();
+ mInCallServiceConnection = null;
+ }
+ if (mNonUIInCallServiceConnections != null) {
+ mNonUIInCallServiceConnections.disconnect();
+ mNonUIInCallServiceConnections = null;
}
}
/**
* Binds to all the UI-providing InCallService as well as system-implemented non-UI
- * InCallServices. Method-invoker must check {@link #isBoundToServices()} before invoking.
+ * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking.
*
* @param call The newly added call that triggered the binding to the in-call services.
*/
@VisibleForTesting
public void bindToServices(Call call) {
- InCallServiceConnection dialerInCall = null;
- InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
- Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
- if (defaultDialerComponentInfo != null &&
- !defaultDialerComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
- dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
- }
- Log.i(this, "defaultDialer: " + dialerInCall);
+ if (mInCallServiceConnection == null) {
+ InCallServiceConnection dialerInCall = null;
+ InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
+ Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
+ if (defaultDialerComponentInfo != null &&
+ !defaultDialerComponentInfo.getComponentName().equals(
+ mSystemInCallComponentName)) {
+ dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
+ }
+ Log.i(this, "defaultDialer: " + dialerInCall);
- InCallServiceInfo systemInCallInfo = getInCallServiceComponent(mSystemInCallComponentName,
- IN_CALL_SERVICE_TYPE_SYSTEM_UI);
- EmergencyInCallServiceConnection systemInCall =
- new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
- systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
+ InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
+ mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI);
+ EmergencyInCallServiceConnection systemInCall =
+ new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
+ systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
- InCallServiceConnection carModeInCall = null;
- InCallServiceInfo carModeComponentInfo = getCarModeComponent();
- if (carModeComponentInfo != null &&
- !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
- carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
+ InCallServiceConnection carModeInCall = null;
+ InCallServiceInfo carModeComponentInfo = getCarModeComponent();
+ if (carModeComponentInfo != null &&
+ !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
+ carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
+ }
+
+ mInCallServiceConnection =
+ new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
}
- mInCallServiceConnection =
- new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
- mInCallServiceConnection.connect(call);
+ // Actually try binding to the UI InCallService. If the response
+ if (mInCallServiceConnection.connect(call) ==
+ InCallServiceConnection.CONNECTION_SUCCEEDED) {
+ // Only connect to the non-ui InCallServices if we actually connected to the main UI
+ // one.
+ connectToNonUiInCallServices(call);
+ } else {
+ Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
+ }
+ }
+
+ private void connectToNonUiInCallServices(Call call) {
List<InCallServiceInfo> nonUIInCallComponents =
getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
@@ -1091,9 +1161,14 @@
boolean isSelfManageCallsSupported = serviceInfo.metaData != null &&
serviceInfo.metaData.getBoolean(
TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
- if (requestedType == 0 || requestedType == getInCallServiceType(entry.serviceInfo,
- packageManager)) {
+ int currentType = getInCallServiceType(entry.serviceInfo, packageManager);
+ if (requestedType == 0 || requestedType == currentType) {
+ if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
+ // We enforce the rule that self-managed calls are not supported by non-ui
+ // InCallServices.
+ isSelfManageCallsSupported = false;
+ }
retval.add(new InCallServiceInfo(
new ComponentName(serviceInfo.packageName, serviceInfo.name),
isExternalCallsSupported, isSelfManageCallsSupported, requestedType));
@@ -1313,8 +1388,11 @@
}
}
- private boolean isBoundToServices() {
- return mInCallServiceConnection != null;
+ /**
+ * @return true if we are bound to the UI InCallService and it is connected.
+ */
+ private boolean isBoundAndConnectedToServices() {
+ return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
}
/**
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 4fe6ed6..ea82d52 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -122,6 +122,14 @@
public void onSimCallManagerChanged(PhoneAccountRegistrar registrar) {}
}
+ /**
+ * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an
+ * app.
+ */
+ public interface AppLabelProxy {
+ CharSequence getAppLabel(String packageName);
+ }
+
private static final String FILE_NAME = "phone-account-registrar-state.xml";
@VisibleForTesting
public static final int EXPECTED_STATE_VERSION = 9;
@@ -135,6 +143,7 @@
private final UserManager mUserManager;
private final SubscriptionManager mSubscriptionManager;
private final DefaultDialerCache mDefaultDialerCache;
+ private final AppLabelProxy mAppLabelProxy;
private State mState;
private UserHandle mCurrentUserHandle;
private interface PhoneAccountRegistrarWriteLock {}
@@ -142,21 +151,15 @@
new PhoneAccountRegistrarWriteLock() {};
@VisibleForTesting
- public PhoneAccountRegistrar(Context context, DefaultDialerCache defaultDialerCache) {
- this(context, FILE_NAME, defaultDialerCache);
+ public PhoneAccountRegistrar(Context context, DefaultDialerCache defaultDialerCache,
+ AppLabelProxy appLabelProxy) {
+ this(context, FILE_NAME, defaultDialerCache, appLabelProxy);
}
@VisibleForTesting
public PhoneAccountRegistrar(Context context, String fileName,
- DefaultDialerCache defaultDialerCache) {
- // TODO: This file path is subject to change -- it is storing the phone account registry
- // state file in the path /data/system/users/0/, which is likely not correct in a
- // multi-user setting.
- /** UNCOMMENT_FOR_MOVE_TO_SYSTEM_SERVICE
- String filePath = Environment.getUserSystemDirectory(UserHandle.myUserId()).
- getAbsolutePath();
- mAtomicFile = new AtomicFile(new File(filePath, fileName));
- UNCOMMENT_FOR_MOVE_TO_SYSTEM_SERVICE */
+ DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
+
mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
mState = new State();
@@ -164,6 +167,7 @@
mUserManager = UserManager.get(context);
mDefaultDialerCache = defaultDialerCache;
mSubscriptionManager = SubscriptionManager.from(mContext);
+ mAppLabelProxy = appLabelProxy;
mCurrentUserHandle = Process.myUserHandle();
read();
}
@@ -640,6 +644,27 @@
Log.i(this, "New phone account registered: " + account);
}
+ // When registering a self-managed PhoneAccount we enforce the rule that the label that the
+ // app uses is also its phone account label. Also ensure it does not attempt to declare
+ // itself as a sim acct, call manager or call provider.
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+ // Turn off bits we don't want to be able to set (TelecomServiceImpl protects against
+ // this but we'll also prevent it from happening here, just to be safe).
+ int newCapabilities = account.getCapabilities() &
+ ~(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_CONNECTION_MANAGER |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+
+ // Ensure name is correct.
+ CharSequence newLabel = mAppLabelProxy.getAppLabel(
+ account.getAccountHandle().getComponentName().getPackageName());
+
+ account = account.toBuilder()
+ .setLabel(newLabel)
+ .setCapabilities(newCapabilities)
+ .build();
+ }
+
mState.accounts.add(account);
// Set defaults and replace based on the group Id.
maybeReplaceOldAccount(account);
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index 60bfe16..5df4451 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -34,6 +34,20 @@
public static final String ACTION_CLEAR_MISSED_CALLS =
"com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS";
+ /**
+ * The action used to answer the current incoming call displayed by
+ * {@link com.android.server.telecom.ui.IncomingCallNotifier}.
+ */
+ public static final String ACTION_ANSWER_FROM_NOTIFICATION =
+ "com.android.server.telecom.ACTION_ANSWER_FROM_NOTIFICATION";
+
+ /**
+ * The action used to reject the current incoming call displayed by
+ * {@link com.android.server.telecom.ui.IncomingCallNotifier}.
+ */
+ public static final String ACTION_REJECT_FROM_NOTIFICATION =
+ "com.android.server.telecom.ACTION_REJECT_FROM_NOTIFICATION";
+
public static final String EXTRA_USERHANDLE = "userhandle";
private final Context mContext;
@@ -47,39 +61,65 @@
public void processIntent(Intent intent) {
String action = intent.getAction();
- Log.v(this, "Action received: %s.", action);
- UserHandle userHandle = intent.getParcelableExtra(EXTRA_USERHANDLE);
- if (userHandle == null) {
- Log.d(this, "user handle can't be null, not processing the broadcast");
- return;
- }
+ if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action) ||
+ ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action) ||
+ ACTION_CLEAR_MISSED_CALLS.equals(action)) {
+ Log.v(this, "Action received: %s.", action);
+ UserHandle userHandle = intent.getParcelableExtra(EXTRA_USERHANDLE);
+ if (userHandle == null) {
+ Log.d(this, "user handle can't be null, not processing the broadcast");
+ return;
+ }
- MissedCallNotifier missedCallNotifier = mCallsManager.getMissedCallNotifier();
+ MissedCallNotifier missedCallNotifier = mCallsManager.getMissedCallNotifier();
- // Send an SMS from the missed call notification.
- if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action)) {
- // Close the notification shade and the notification itself.
- closeSystemDialogs(mContext);
- missedCallNotifier.clearMissedCalls(userHandle);
+ // Send an SMS from the missed call notification.
+ if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action)) {
+ // Close the notification shade and the notification itself.
+ closeSystemDialogs(mContext);
+ missedCallNotifier.clearMissedCalls(userHandle);
- Intent callIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
- callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivityAsUser(callIntent, userHandle);
+ Intent callIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
+ callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivityAsUser(callIntent, userHandle);
- // Call back recent caller from the missed call notification.
- } else if (ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
- // Close the notification shade and the notification itself.
- closeSystemDialogs(mContext);
- missedCallNotifier.clearMissedCalls(userHandle);
+ // Call back recent caller from the missed call notification.
+ } else if (ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
+ // Close the notification shade and the notification itself.
+ closeSystemDialogs(mContext);
+ missedCallNotifier.clearMissedCalls(userHandle);
- Intent callIntent = new Intent(Intent.ACTION_CALL, intent.getData());
- callIntent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- mContext.startActivityAsUser(callIntent, userHandle);
+ Intent callIntent = new Intent(Intent.ACTION_CALL, intent.getData());
+ callIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ mContext.startActivityAsUser(callIntent, userHandle);
- // Clear the missed call notification and call log entries.
- } else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
- missedCallNotifier.clearMissedCalls(userHandle);
+ // Clear the missed call notification and call log entries.
+ } else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
+ missedCallNotifier.clearMissedCalls(userHandle);
+ }
+ } else if (ACTION_ANSWER_FROM_NOTIFICATION.equals(action)) {
+ Log.startSession("TBIP.aAFM");
+ try {
+ // Answer the current ringing call.
+ Call incomingCall = mCallsManager.getIncomingCallNotifier().getIncomingCall();
+ if (incomingCall != null) {
+ mCallsManager.answerCall(incomingCall, incomingCall.getVideoState());
+ }
+ } finally {
+ Log.endSession();
+ }
+ } else if (ACTION_REJECT_FROM_NOTIFICATION.equals(action)) {
+ Log.startSession("TBIP.aRFM");
+ try {
+ // Reject the current ringing call.
+ Call incomingCall = mCallsManager.getIncomingCallNotifier().getIncomingCall();
+ if (incomingCall != null) {
+ mCallsManager.rejectCall(incomingCall, false /* isRejectWithMessage */, null);
+ }
+ } finally {
+ Log.endSession();
+ }
}
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 9cded98..94da28b 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -361,6 +361,10 @@
"cannot also be call capable, connection managers, or " +
"SIM accounts.");
}
+
+ // For self-managed CS, the phone account registrar will override the
+ // label the user has set for the phone account. This ensures the
+ // self-managed cs implementation can't spoof their app name.
}
if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
enforceRegisterSimSubscriptionPermission();
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index f91581a..9c34ef5 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -21,6 +21,8 @@
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.ui.IncomingCallNotifier;
+import com.android.server.telecom.ui.IncomingCallNotifier.IncomingCallNotifierFactory;
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
@@ -31,9 +33,12 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.UserHandle;
import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -97,6 +102,7 @@
private final SyncRoot mLock = new SyncRoot() { };
private final MissedCallNotifier mMissedCallNotifier;
+ private final IncomingCallNotifier mIncomingCallNotifier;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final CallsManager mCallsManager;
private final RespondViaSmsManager mRespondViaSmsManager;
@@ -183,7 +189,8 @@
Timeouts.Adapter timeoutsAdapter,
AsyncRingtonePlayer asyncRingtonePlayer,
PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
- InterruptionFilterProxy interruptionFilterProxy) {
+ InterruptionFilterProxy interruptionFilterProxy,
+ IncomingCallNotifier incomingCallNotifier) {
mContext = context.getApplicationContext();
LogUtils.initLogging(mContext);
DefaultDialerManagerAdapter defaultDialerAdapter =
@@ -193,7 +200,21 @@
defaultDialerAdapter, mLock);
Log.startSession("TS.init");
- mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache);
+ mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache,
+ new PhoneAccountRegistrar.AppLabelProxy() {
+ @Override
+ public CharSequence getAppLabel(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ return pm.getApplicationLabel(info);
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ Log.w(this, "Could not determine package name.");
+ }
+
+ return null;
+ }
+ });
mContactsAsyncHelper = new ContactsAsyncHelper(
new ContactsAsyncHelper.ContentResolverAdapter() {
@Override
@@ -236,6 +257,25 @@
interruptionFilterProxy,
emergencyCallHelper);
+ mIncomingCallNotifier = incomingCallNotifier;
+ incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
+ @Override
+ public boolean hasCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
+ return mCallsManager.hasCallsForOtherPhoneAccount(phoneAccountHandle);
+ }
+
+ @Override
+ public int getNumCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
+ return mCallsManager.getNumCallsForOtherPhoneAccount(phoneAccountHandle);
+ }
+
+ @Override
+ public Call getActiveCall() {
+ return mCallsManager.getActiveCall();
+ }
+ });
+ mCallsManager.setIncomingCallNotifier(mIncomingCallNotifier);
+
mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index a066b6c..3d28eae 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -16,7 +16,6 @@
package com.android.server.telecom.components;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
@@ -28,6 +27,7 @@
import android.os.ServiceManager;
import android.service.notification.ZenModeConfig;
import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
import com.android.internal.telephony.CallerInfoAsyncQuery;
import com.android.server.telecom.AsyncRingtonePlayer;
@@ -42,7 +42,6 @@
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.InterruptionFilterProxy;
import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.InCallWakeLockController;
@@ -50,6 +49,7 @@
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.TelecomWakeLock;
import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl;
/**
@@ -132,7 +132,7 @@
new InCallWakeLockControllerFactory() {
@Override
public InCallWakeLockController create(Context context,
- CallsManager callsManager) {
+ CallsManager callsManager) {
return new InCallWakeLockController(
new TelecomWakeLock(context,
PowerManager.FULL_WAKE_LOCK,
@@ -180,7 +180,8 @@
}
return null;
}
- }
+ },
+ new IncomingCallNotifier(context)
));
}
if (BluetoothAdapter.getDefaultAdapter() != null) {
diff --git a/src/com/android/server/telecom/ui/IncomingCallNotifier.java b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
new file mode 100644
index 0000000..cd34e71
--- /dev/null
+++ b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2017 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.server.telecom.ui;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CallerInfo;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Manages the display of an incoming call UX when a new ringing self-managed call is added, and
+ * there is an ongoing call in another {@link android.telecom.PhoneAccount}.
+ */
+public class IncomingCallNotifier extends CallsManagerListenerBase {
+
+ public interface IncomingCallNotifierFactory {
+ IncomingCallNotifier make(Context context, CallsManagerProxy mCallsManagerProxy);
+ }
+
+ /**
+ * Eliminates strict dependency between this class and CallsManager.
+ */
+ public interface CallsManagerProxy {
+ boolean hasCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle);
+ int getNumCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle);
+ Call getActiveCall();
+ }
+
+ // Notification for incoming calls. This is interruptive and will show up as a HUN.
+ @VisibleForTesting
+ public static final int NOTIFICATION_INCOMING_CALL = 1;
+ private static final String NOTIFICATION_TAG = IncomingCallNotifier.class.getSimpleName();
+
+
+ public final Call.ListenerBase mCallListener = new Call.ListenerBase() {
+ @Override
+ public void onCallerInfoChanged(Call call) {
+ if (mIncomingCall != call) {
+ return;
+ }
+ showIncomingCallNotification(mIncomingCall);
+ }
+ };
+
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+ private final Set<Call> mCalls = new ArraySet<>();
+ private CallsManagerProxy mCallsManagerProxy;
+
+ // The current incoming call we are displaying UX for.
+ private Call mIncomingCall;
+
+ public IncomingCallNotifier(Context context) {
+ mContext = context;
+ mNotificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ public void setCallsManagerProxy(CallsManagerProxy callsManagerProxy) {
+ mCallsManagerProxy = callsManagerProxy;
+ }
+
+ public Call getIncomingCall() {
+ return mIncomingCall;
+ }
+
+ @Override
+ public void onCallAdded(Call call) {
+ if (!mCalls.contains(call)) {
+ mCalls.add(call);
+ }
+
+ updateIncomingCall();
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ if (mCalls.contains(call)) {
+ mCalls.remove(call);
+ }
+
+ updateIncomingCall();
+ }
+
+ @Override
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+ updateIncomingCall();
+ }
+
+ /**
+ * Determines which call is the active ringing call at this time and triggers the display of the
+ * UI.
+ */
+ private void updateIncomingCall() {
+ Optional<Call> incomingCallOp = mCalls.stream()
+ .filter(call -> call.isSelfManaged() && call.isIncoming() &&
+ call.getState() == CallState.RINGING)
+ .findFirst();
+ Call incomingCall = incomingCallOp.orElse(null);
+ if (incomingCall != null && mCallsManagerProxy != null &&
+ !mCallsManagerProxy.hasCallsForOtherPhoneAccount(
+ incomingCallOp.get().getTargetPhoneAccount())) {
+ // If there is no calls in any other ConnectionService, we can rely on the
+ // third-party app to display its own incoming call UI.
+ incomingCall = null;
+ }
+
+ Log.i(this, "updateIncomingCall: foundIncomingcall = %s", incomingCall);
+
+ boolean hadIncomingCall = mIncomingCall != null;
+ boolean hasIncomingCall = incomingCall != null;
+ if (incomingCall != mIncomingCall) {
+ Call previousIncomingCall = mIncomingCall;
+ mIncomingCall = incomingCall;
+
+ if (hasIncomingCall && !hadIncomingCall) {
+ mIncomingCall.addListener(mCallListener);
+ showIncomingCallNotification(mIncomingCall);
+ } else if (hadIncomingCall && !hasIncomingCall) {
+ previousIncomingCall.removeListener(mCallListener);
+ hideIncomingCallNotification();
+ }
+ }
+ }
+
+ private void showIncomingCallNotification(Call call) {
+ Log.i(this, "showIncomingCallNotification showCall = %s", call);
+
+ Notification.Builder builder = getNotificationBuilder(call,
+ mCallsManagerProxy.getActiveCall());
+ mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_INCOMING_CALL, builder.build());
+ }
+
+ private void hideIncomingCallNotification() {
+ Log.i(this, "hideIncomingCallNotification");
+ mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_INCOMING_CALL);
+ }
+
+ private String getNotificationName(Call call) {
+ String name = "";
+ if (call.getCallerDisplayNamePresentation() == TelecomManager.PRESENTATION_ALLOWED) {
+ name = call.getCallerDisplayName();
+ }
+ if (TextUtils.isEmpty(name)) {
+ name = call.getName();
+ }
+
+ if (TextUtils.isEmpty(name)) {
+ name = call.getPhoneNumber();
+ }
+ return name;
+ }
+
+ private Notification.Builder getNotificationBuilder(Call incomingCall, Call ongoingCall) {
+ // Change the notification app name to "Android System" to sufficiently distinguish this
+ // from the phone app's name.
+ Bundle extras = new Bundle();
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, mContext.getString(
+ com.android.internal.R.string.android_system_label));
+
+ Intent answerIntent = new Intent(
+ TelecomBroadcastIntentProcessor.ACTION_ANSWER_FROM_NOTIFICATION, null, mContext,
+ TelecomBroadcastReceiver.class);
+ Intent rejectIntent = new Intent(
+ TelecomBroadcastIntentProcessor.ACTION_REJECT_FROM_NOTIFICATION, null, mContext,
+ TelecomBroadcastReceiver.class);
+
+ String nameOrNumber = getNotificationName(incomingCall);
+ CharSequence viaApp = incomingCall.getTargetPhoneAccountLabel();
+ boolean isIncomingVideo = VideoProfile.isVideo(incomingCall.getVideoState());
+ boolean isOngoingVideo = ongoingCall != null ?
+ VideoProfile.isVideo(ongoingCall.getVideoState()) : false;
+ int numOtherCalls = ongoingCall != null ?
+ mCallsManagerProxy.getNumCallsForOtherPhoneAccount(
+ incomingCall.getTargetPhoneAccount()) : 1;
+
+ // Build the "IncomingApp call from John Smith" message.
+ CharSequence incomingCallText;
+ if (isIncomingVideo) {
+ incomingCallText = mContext.getString(R.string.notification_incoming_video_call, viaApp,
+ nameOrNumber);
+ } else {
+ incomingCallText = mContext.getString(R.string.notification_incoming_call, viaApp,
+ nameOrNumber);
+ }
+
+ // Build the "Answering will end your OtherApp call" line.
+ CharSequence disconnectText;
+ if (ongoingCall != null && ongoingCall.isSelfManaged()) {
+ CharSequence ongoingApp = ongoingCall.getTargetPhoneAccountLabel();
+ // For an ongoing self-managed call, we use a message like:
+ // "Answering will end your OtherApp call".
+ if (numOtherCalls > 1) {
+ // Multiple ongoing calls in the other app, so don't bother specifing whether it is
+ // a video call or audio call.
+ disconnectText = mContext.getString(R.string.answering_ends_other_calls,
+ ongoingApp);
+ } else if (isOngoingVideo) {
+ disconnectText = mContext.getString(R.string.answering_ends_other_video_call,
+ ongoingApp);
+ } else {
+ disconnectText = mContext.getString(R.string.answering_ends_other_call, ongoingApp);
+ }
+ } else {
+ // For an ongoing managed call, we use a message like:
+ // "Answering will end your ongoing call".
+ if (numOtherCalls > 1) {
+ // Multiple ongoing manage calls, so don't bother specifing whether it is a video
+ // call or audio call.
+ disconnectText = mContext.getString(R.string.answering_ends_other_managed_calls);
+ } else if (isOngoingVideo) {
+ disconnectText = mContext.getString(
+ R.string.answering_ends_other_managed_video_call);
+ } else {
+ disconnectText = mContext.getString(R.string.answering_ends_other_managed_call);
+ }
+ }
+
+ final Notification.Builder builder = new Notification.Builder(mContext);
+ builder.setOngoing(true);
+ builder.setExtras(extras);
+ builder.setPriority(Notification.PRIORITY_HIGH);
+ builder.setCategory(Notification.CATEGORY_CALL);
+ builder.setContentTitle(incomingCallText);
+ builder.setContentText(disconnectText);
+ builder.setSmallIcon(R.drawable.ic_phone);
+ // Ensures this is a heads up notification. A heads-up notification is typically only shown
+ // if there is a fullscreen intent. However since this notification doesn't have that we
+ // will use this trick to get it to show as one anyways.
+ builder.setVibrate(new long[0]);
+ builder.setColor(mContext.getResources().getColor(R.color.theme_color));
+ builder.addAction(
+ R.anim.on_going_call,
+ getActionText(R.string.answer_incoming_call, R.color.notification_action_answer),
+ PendingIntent.getBroadcast(mContext, 0, answerIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT));
+ builder.addAction(
+ R.drawable.ic_close_dk,
+ getActionText(R.string.decline_incoming_call, R.color.notification_action_decline),
+ PendingIntent.getBroadcast(mContext, 0, rejectIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT));
+ return builder;
+ }
+
+ private CharSequence getActionText(int stringRes, int colorRes) {
+ CharSequence string = mContext.getText(stringRes);
+ if (string == null) {
+ return "";
+ }
+ Spannable spannable = new SpannableString(string);
+ spannable.setSpan(
+ new ForegroundColorSpan(mContext.getColor(colorRes)), 0, spannable.length(), 0);
+ return spannable;
+ }
+}
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index edb3d72..534b7f2 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -122,6 +122,7 @@
public static final int CALL_LOG_COLUMN_TYPE = 5;
private static final int MISSED_CALL_NOTIFICATION_ID = 1;
+ private static final String NOTIFICATION_TAG = MissedCallNotifierImpl.class.getSimpleName();
private final Context mContext;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
@@ -375,7 +376,7 @@
long token = Binder.clearCallingIdentity();
try {
mNotificationManager.notifyAsUser(
- null /* tag */, MISSED_CALL_NOTIFICATION_ID, notification, userHandle);
+ NOTIFICATION_TAG, MISSED_CALL_NOTIFICATION_ID, notification, userHandle);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -395,7 +396,8 @@
long token = Binder.clearCallingIdentity();
try {
- mNotificationManager.cancelAsUser(null, MISSED_CALL_NOTIFICATION_ID, userHandle);
+ mNotificationManager.cancelAsUser(NOTIFICATION_TAG, MISSED_CALL_NOTIFICATION_ID,
+ userHandle);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 994abbe..a57b158 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -179,7 +179,8 @@
<activity android:name="com.android.server.telecom.testapps.SelfManagedCallingActivity"
android:label="@string/selfManagedCallingActivityLabel"
- android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
+ android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+ android:theme="@android:style/Theme.Material.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index 144d6ec..e30ef42 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -24,63 +24,80 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/checkIfPermittedBeforeCallingButton" />
- <TableLayout
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="This app provides two sample implementations of the self-managed ConnectionService API. Use this UI to add simulated self-managed calls:" />
+
+ <RadioGroup
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Sample Source 1"
+ android:id="@+id/useAcct1Button"
+ android:background="@color/test_call_a_color"/>
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Sample Source 2"
+ android:id="@+id/useAcct2Button"
+ android:background="@color/test_call_b_color"/>
+ </RadioGroup>
+
+ <RadioGroup
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Video Call"
+ android:id="@+id/videoCallButton"/>
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Audio Call"
+ android:id="@+id/audioCallButton"/>
+ </RadioGroup>
+
+ <LinearLayout android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
- <TableRow android:layout_span="2">
- <RadioGroup>
- <RadioButton
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Acct 1"
- android:id="@+id/useAcct1Button"/>
- <RadioButton
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Acct 2"
- android:id="@+id/useAcct2Button"/>
- </RadioGroup>
- </TableRow>
- <TableRow android:layout_span="2">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Place call:" />
- </TableRow>
- <TableRow>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Number:" />
- <EditText
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/phoneNumber" />
- </TableRow>
- <TableRow>
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Outgoing Call"
- android:id="@+id/placeOutgoingCallButton" />
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Incoming Call"
- android:id="@+id/placeIncomingCallButton" />
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Incoming Call (Delay)"
- android:id="@+id/placeIncomingCallDelayButton" />
- </TableRow>
- <TableRow android:layout_span="2">
- <ListView
- android:id="@+id/callList"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:listSelector="@null"
- android:divider="@null" />
- </TableRow>
- </TableLayout>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Number:" />
+ <EditText
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/phoneNumber"
+ android:text="tel:555-1212"/>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Outgoing Call"
+ android:id="@+id/placeOutgoingCallButton" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Incoming Call"
+ android:id="@+id/placeIncomingCallButton" />
+ </LinearLayout>
+
+ <ListView
+ android:id="@+id/callList"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:listSelector="@null"
+ android:divider="@null" />
</LinearLayout>
\ No newline at end of file
diff --git a/testapps/res/values/colors.xml b/testapps/res/values/colors.xml
new file mode 100644
index 0000000..3939e78
--- /dev/null
+++ b/testapps/res/values/colors.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<resources>
+ <color name="test_call_a_color">#f2eebf</color>
+ <color name="test_call_b_color">#afc5e6</color>
+</resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index 1d6367e..4275079 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -43,6 +43,8 @@
public static String SELF_MANAGED_ACCOUNT_1 = "1";
public static String SELF_MANAGED_ACCOUNT_2 = "2";
+ public static String SELF_MANAGED_NAME_1 = "SuperCall";
+ public static String SELF_MANAGED_NAME_2 = "Mega Call";
private static SelfManagedCallList sInstance;
private static ComponentName COMPONENT_NAME = new ComponentName(
@@ -89,20 +91,23 @@
}
public void registerPhoneAccounts(Context context) {
- registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1, SELF_MANAGED_ADDRESS_1);
- registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2);
+ registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1, SELF_MANAGED_ADDRESS_1,
+ SELF_MANAGED_NAME_1);
+ registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2,
+ SELF_MANAGED_NAME_2);
}
- public void registerPhoneAccount(Context context, String id, Uri address) {
+ public void registerPhoneAccount(Context context, String id, Uri address, String name) {
PhoneAccountHandle handle = new PhoneAccountHandle(COMPONENT_NAME, id);
mPhoneAccounts.put(id, handle);
- PhoneAccount.Builder builder = PhoneAccount.builder(handle, id)
+ PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
.addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
.addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
.setAddress(address)
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
PhoneAccount.CAPABILITY_VIDEO_CALLING |
- PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING);
+ PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
+ .setShortDescription(name);
TelecomManager.from(context).registerPhoneAccount(builder.build());
}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index 29d3c5f..b46d5e1 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -26,6 +26,8 @@
import android.widget.BaseAdapter;
import android.widget.TextView;
+import com.android.server.telecom.testapps.R;
+
import java.util.List;
public class SelfManagedCallListAdapter extends BaseAdapter {
@@ -124,6 +126,12 @@
SelfManagedConnection connection = mConnections.get(position);
PhoneAccountHandle phoneAccountHandle = connection.getExtras().getParcelable(
SelfManagedConnection.EXTRA_PHONE_ACCOUNT_HANDLE);
+ if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1)) {
+ result.setBackgroundColor(result.getContext().getColor(R.color.test_call_a_color));
+ } else {
+ result.setBackgroundColor(result.getContext().getColor(R.color.test_call_b_color));
+ }
+
CallAudioState audioState = connection.getCallAudioState();
String audioRoute = "?";
if (audioState != null) {
@@ -148,9 +156,9 @@
String callType;
if (connection.isIncomingCall()) {
if (connection.isIncomingCallUiShowing()) {
- callType = "Incoming - Show Incoming UX";
+ callType = "Incoming(our ux) ";
} else {
- callType = "Incoming - DO NOT SHOW Incoming UX";
+ callType = "Incoming(sys ux) ";
}
} else {
callType = "Outgoing";
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index c8f6157..4dfa012 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -22,6 +22,7 @@
import android.telecom.ConnectionRequest;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@@ -46,9 +47,10 @@
private CheckBox mCheckIfPermittedBeforeCalling;
private Button mPlaceOutgoingCallButton;
private Button mPlaceIncomingCallButton;
- private Button mPlaceIncomingCallDelayButton;
private RadioButton mUseAcct1Button;
private RadioButton mUseAcct2Button;
+ private RadioButton mVideoCallButton;
+ private RadioButton mAudioCallButton;
private EditText mNumber;
private ListView mListView;
private SelfManagedCallListAdapter mListAdapter;
@@ -102,21 +104,10 @@
}
});
- mPlaceIncomingCallDelayButton = (Button) findViewById(R.id.placeIncomingCallDelayButton);
- mPlaceIncomingCallDelayButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Delay the incoming call so that we can turn off the screen and
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- placeIncomingCall();
- }
- });
mUseAcct1Button = (RadioButton) findViewById(R.id.useAcct1Button);
mUseAcct2Button = (RadioButton) findViewById(R.id.useAcct2Button);
+ mVideoCallButton = (RadioButton) findViewById(R.id.videoCallButton);
+ mAudioCallButton = (RadioButton) findViewById(R.id.audioCallButton);
mNumber = (EditText) findViewById(R.id.phoneNumber);
mListView = (ListView) findViewById(R.id.callList);
mCallList.setListener(mCallListListener);
@@ -150,6 +141,10 @@
Bundle extras = new Bundle();
extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
getSelectedPhoneAccountHandle());
+ if (mVideoCallButton.isChecked()) {
+ extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ VideoProfile.STATE_BIDIRECTIONAL);
+ }
tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
}
@@ -167,6 +162,10 @@
Bundle extras = new Bundle();
extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
Uri.parse(mNumber.getText().toString()));
+ if (mVideoCallButton.isChecked()) {
+ extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE,
+ VideoProfile.STATE_BIDIRECTIONAL);
+ }
tm.addNewIncomingCall(getSelectedPhoneAccountHandle(), extras);
}
}
\ No newline at end of file
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index 051948b..766efa5 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -27,6 +27,7 @@
import android.telecom.Connection;
import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
+import android.telecom.VideoProfile;
import com.android.server.telecom.testapps.R;
@@ -129,6 +130,36 @@
notificationManager.notify(CALL_NOTIFICATION, mCallId, builder.build());
}
+ @Override
+ public void onHold() {
+ setOnHold();
+ }
+
+ @Override
+ public void onUnhold() {
+ setActive();
+ }
+
+ @Override
+ public void onAnswer(int videoState) {
+ setConnectionActive();
+ }
+
+ @Override
+ public void onAnswer() {
+ onAnswer(VideoProfile.STATE_AUDIO_ONLY);
+ }
+
+ @Override
+ public void onReject() {
+ setConnectionDisconnected(DisconnectCause.REJECTED);
+ }
+
+ @Override
+ public void onDisconnect() {
+ setConnectionDisconnected(DisconnectCause.LOCAL);
+ }
+
public void setConnectionActive() {
mMediaPlayer.start();
setActive();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index e1a01ba..1d52a3b 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -23,8 +23,10 @@
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
import java.util.Objects;
+import java.util.Random;
/**
* Sample implementation of the self-managed {@link ConnectionService} API.
@@ -32,6 +34,8 @@
* See {@link android.telecom} for more information on self-managed {@link ConnectionService}s.
*/
public class SelfManagedConnectionService extends ConnectionService {
+ private static final String[] TEST_NAMES = {"Tom Smith", "Jane Appleseed", "Joseph Engleton",
+ "Claudia McPherson", "Chris P. Bacon", "Seymour Butz", "Hugh Mungus", "Anita Bath"};
private final SelfManagedCallList mCallList = SelfManagedCallList.getInstance();
@Override
@@ -60,13 +64,17 @@
mCallList.notifyCreateOutgoingConnectionFailed(request);
}
-
private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming) {
SelfManagedConnection connection = new SelfManagedConnection(mCallList,
getApplicationContext(), isIncoming);
connection.setListener(mCallList.getConnectionListener());
connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
+ connection.setAudioModeIsVoip(true);
+ connection.setVideoState(request.getVideoState());
+ Random random = new Random();
+ connection.setCallerDisplayName(TEST_NAMES[random.nextInt(TEST_NAMES.length)],
+ TelecomManager.PRESENTATION_ALLOWED);
connection.setExtras(request.getExtras());
if (isIncoming) {
connection.setIsIncomingCallUiShowing(request.shouldShowIncomingCallUi());
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
new file mode 100644
index 0000000..c842379
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 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.server.telecom.tests;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.os.Build;
+import android.telecom.VideoProfile;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.ui.IncomingCallNotifier;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for the {@link com.android.server.telecom.ui.IncomingCallNotifier} class.
+ */
+public class IncomingCallNotifierTest extends TelecomTestCase {
+
+ @Mock private IncomingCallNotifier.CallsManagerProxy mCallsManagerProxy;
+ @Mock private Call mAudioCall;
+ @Mock private Call mVideoCall;
+ @Mock private Call mRingingCall;
+ private IncomingCallNotifier mIncomingCallNotifier;
+ private NotificationManager mNotificationManager;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ ApplicationInfo info = new ApplicationInfo();
+ info.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ doReturn(info).when(mContext).getApplicationInfo();
+ doReturn(null).when(mContext).getTheme();
+ mNotificationManager = (NotificationManager) mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ mIncomingCallNotifier = new IncomingCallNotifier(mContext);
+ mIncomingCallNotifier.setCallsManagerProxy(mCallsManagerProxy);
+
+ when(mAudioCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
+ when(mAudioCall.getTargetPhoneAccountLabel()).thenReturn("Bar");
+ when(mVideoCall.getVideoState()).thenReturn(VideoProfile.STATE_BIDIRECTIONAL);
+ when(mVideoCall.getTargetPhoneAccountLabel()).thenReturn("Bar");
+ when(mRingingCall.isSelfManaged()).thenReturn(true);
+ when(mRingingCall.isIncoming()).thenReturn(true);
+ when(mRingingCall.getState()).thenReturn(CallState.RINGING);
+ when(mRingingCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
+ when(mRingingCall.getTargetPhoneAccountLabel()).thenReturn("Foo");
+ }
+
+ /**
+ * Add a call that isn't ringing.
+ */
+ @SmallTest
+ public void testSingleCall() {
+ mIncomingCallNotifier.onCallAdded(mAudioCall);
+ verify(mNotificationManager, never()).notify(
+ eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());
+ }
+
+ /**
+ * Add a ringing call when there is no other ongoing call.
+ */
+ @SmallTest
+ public void testIncomingDuringOngoingCall() {
+ when(mCallsManagerProxy.hasCallsForOtherPhoneAccount(any())).thenReturn(false);
+ mIncomingCallNotifier.onCallAdded(mRingingCall);
+ verify(mNotificationManager, never()).notify(
+ eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());
+ }
+
+ /**
+ * Add a ringing call with another call ongoing, not from a different phone account.
+ */
+ @SmallTest
+ public void testIncomingDuringOngoingCall2() {
+ when(mCallsManagerProxy.hasCallsForOtherPhoneAccount(any())).thenReturn(false);
+ when(mCallsManagerProxy.getNumCallsForOtherPhoneAccount(any())).thenReturn(0);
+ when(mCallsManagerProxy.getActiveCall()).thenReturn(mAudioCall);
+
+ mIncomingCallNotifier.onCallAdded(mAudioCall);
+ mIncomingCallNotifier.onCallAdded(mRingingCall);
+ verify(mNotificationManager, never()).notify(
+ eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());;
+ }
+
+ /**
+ * Remove ringing call with another call ongoing.
+ */
+ @SmallTest
+ public void testCallRemoved() {
+ when(mCallsManagerProxy.hasCallsForOtherPhoneAccount(any())).thenReturn(true);
+ when(mCallsManagerProxy.getNumCallsForOtherPhoneAccount(any())).thenReturn(1);
+ when(mCallsManagerProxy.getActiveCall()).thenReturn(mAudioCall);
+
+ mIncomingCallNotifier.onCallAdded(mAudioCall);
+ mIncomingCallNotifier.onCallAdded(mRingingCall);
+ verify(mNotificationManager).notify(
+ eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());
+ mIncomingCallNotifier.onCallRemoved(mRingingCall);
+ verify(mNotificationManager).cancel(eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL));
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index f2faf90..2898457 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -54,15 +54,18 @@
import java.util.Set;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
public class PhoneAccountRegistrarTest extends TelecomTestCase {
private static final int MAX_VERSION = Integer.MAX_VALUE;
private static final String FILE_NAME = "phone-account-registrar-test-1223.xml";
+ private static final String TEST_LABEL = "right";
private PhoneAccountRegistrar mRegistrar;
@Mock private TelecomManager mTelecomManager;
@Mock private DefaultDialerCache mDefaultDialerCache;
+ @Mock private PhoneAccountRegistrar.AppLabelProxy mAppLabelProxy;
@Override
public void setUp() throws Exception {
@@ -75,9 +78,11 @@
.delete();
when(mDefaultDialerCache.getDefaultDialerApplication(anyInt()))
.thenReturn("com.android.dialer");
+ when(mAppLabelProxy.getAppLabel(anyString()))
+ .thenReturn(TEST_LABEL);
mRegistrar = new PhoneAccountRegistrar(
mComponentContextFixture.getTestDouble().getApplicationContext(),
- FILE_NAME, mDefaultDialerCache);
+ FILE_NAME, mDefaultDialerCache, mAppLabelProxy);
}
@Override
@@ -523,6 +528,55 @@
.build());
}
+ /**
+ * Tests ability to register a self-managed PhoneAccount; verifies that the user defined label
+ * is overridden.
+ * @throws Exception
+ */
+ @MediumTest
+ public void testSelfManagedPhoneAccount() throws Exception {
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+
+ PhoneAccountHandle selfManagedHandle = makeQuickAccountHandle(
+ new ComponentName("self", "managed"), "selfie1");
+
+ PhoneAccount selfManagedAccount = new PhoneAccount.Builder(selfManagedHandle, "Wrong")
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+ .build();
+
+ mRegistrar.registerPhoneAccount(selfManagedAccount);
+
+ PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(selfManagedHandle);
+ assertEquals(TEST_LABEL, registeredAccount.getLabel());
+ }
+
+ /**
+ * Tests to ensure that when registering a self-managed PhoneAccount, it cannot also be defined
+ * as a call provider, connection manager, or sim subscription.
+ * @throws Exception
+ */
+ @MediumTest
+ public void testSelfManagedCapabilityOverride() throws Exception {
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+
+ PhoneAccountHandle selfManagedHandle = makeQuickAccountHandle(
+ new ComponentName("self", "managed"), "selfie1");
+
+ PhoneAccount selfManagedAccount = new PhoneAccount.Builder(selfManagedHandle, TEST_LABEL)
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+ PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_CONNECTION_MANAGER |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+ .build();
+
+ mRegistrar.registerPhoneAccount(selfManagedAccount);
+
+ PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(selfManagedHandle);
+ assertEquals(PhoneAccount.CAPABILITY_SELF_MANAGED, registeredAccount.getCapabilities());
+ }
+
private static ComponentName makeQuickConnectionServiceComponentName() {
return new ComponentName(
"com.android.server.telecom.tests",
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 1f3554d..b90ca97 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -84,6 +84,7 @@
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
import com.android.server.telecom.components.UserCallIntentProcessor;
+import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
import com.google.common.base.Predicate;
@@ -194,6 +195,7 @@
@Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
@Mock AsyncRingtonePlayer mAsyncRingtonePlayer;
@Mock InterruptionFilterProxy mInterruptionFilterProxy;
+ @Mock IncomingCallNotifier mIncomingCallNotifier;
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
@@ -389,6 +391,7 @@
mTimeoutsAdapter = mock(Timeouts.Adapter.class);
when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
.thenReturn(TEST_TIMEOUT / 5L);
+ mIncomingCallNotifier = mock(IncomingCallNotifier.class);
mTelecomSystem = new TelecomSystem(
mComponentContextFixture.getTestDouble(),
@@ -421,7 +424,8 @@
mTimeoutsAdapter,
mAsyncRingtonePlayer,
mPhoneNumberUtilsAdapter,
- mInterruptionFilterProxy);
+ mInterruptionFilterProxy,
+ mIncomingCallNotifier);
mComponentContextFixture.setTelecomManager(new TelecomManager(
mComponentContextFixture.getTestDouble(),