Fix incoming callback and binding issues.
- Created queues to handle cases where callbacks were getting dropped while
the service was connecting.
- Fixed DeadObjectExceptions when the in-call ui was being re-installed.
- Change to a single connection object to fix connection leak where every
re-bind caused connection to double.
- Split caller information from Call into CallIdentification object.
Bug: 10468815
Bug: 10457750
Change-Id: Icaef130db2f6893df3175eed07d0d483a3c4c455
diff --git a/common/src/com/android/services/telephony/common/Call.java b/common/src/com/android/services/telephony/common/Call.java
index 4c68913..f397f62 100644
--- a/common/src/com/android/services/telephony/common/Call.java
+++ b/common/src/com/android/services/telephony/common/Call.java
@@ -16,32 +16,25 @@
package com.android.services.telephony.common;
-import com.google.android.collect.Lists;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telephony.PhoneConstants;
import com.google.android.collect.Sets;
-import com.google.common.collect.ImmutableList;
+import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.primitives.Ints;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.text.format.DateUtils;
-
-import java.util.Arrays;
-import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.SortedSet;
-
-import com.android.internal.telephony.PhoneConstants;
+import java.util.TreeSet;
/**
* Class object used across CallHandlerService APIs.
* Describes a single call and its state.
*/
-final public class Call implements Parcelable {
+public final class Call implements Parcelable {
public static final int INVALID_CALL_ID = -1;
public static final int MAX_CONFERENCED_CALLS = 5;
@@ -159,23 +152,13 @@
public static int PRESENTATION_PAYPHONE = PhoneConstants.PRESENTATION_PAYPHONE;
// Unique identifier for the call
- private int mCallId = INVALID_CALL_ID;
+ private int mCallId;
- // The phone number on the other end of the connection
- private String mNumber = "";
+ private CallIdentification mIdentification;
// The current state of the call
private int mState = State.INVALID;
- // Number presentation received from the carrier
- private int mNumberPresentation = PRESENTATION_ALLOWED;
-
- // Name presentation mode received from the carrier
- private int mCnapNamePresentation = PRESENTATION_ALLOWED;
-
- // Name associated with the other end of the connection; from the carrier.
- private String mCnapName = "";
-
// Reason for disconnect. Valid when the call state is DISCONNECTED.
private DisconnectCause mDisconnectCause = DisconnectCause.UNKNOWN;
@@ -196,18 +179,35 @@
public Call(int callId) {
mCallId = callId;
+ mIdentification = new CallIdentification(mCallId);
+ }
+
+ public Call(Call call) {
+ mCallId = call.mCallId;
+ mIdentification = new CallIdentification(call.mIdentification);
+ mState = call.mState;
+ mDisconnectCause = call.mDisconnectCause;
+ mCapabilities = call.mCapabilities;
+ mConnectTime = call.mConnectTime;
+ mChildCallIds = new TreeSet<Integer>(call.mChildCallIds);
+ mGatewayNumber = call.mGatewayNumber;
+ mGatewayPackage = call.mGatewayPackage;
}
public int getCallId() {
return mCallId;
}
+ public CallIdentification getIdentification() {
+ return mIdentification;
+ }
+
public String getNumber() {
- return mNumber;
+ return mIdentification.getNumber();
}
public void setNumber(String number) {
- mNumber = number;
+ mIdentification.setNumber(number);
}
public int getState() {
@@ -219,27 +219,27 @@
}
public int getNumberPresentation() {
- return mNumberPresentation;
+ return mIdentification.getNumberPresentation();
}
public void setNumberPresentation(int presentation) {
- mNumberPresentation = presentation;
+ mIdentification.setNumberPresentation(presentation);
}
public int getCnapNamePresentation() {
- return mCnapNamePresentation;
+ return mIdentification.getCnapNamePresentation();
}
public void setCnapNamePresentation(int presentation) {
- mCnapNamePresentation = presentation;
+ mIdentification.setCnapNamePresentation(presentation);
}
public String getCnapName() {
- return mCnapName;
+ return mIdentification.getCnapName();
}
public void setCnapName(String cnapName) {
- mCnapName = cnapName;
+ mIdentification.setCnapName(cnapName);
}
public DisconnectCause getDisconnectCause() {
@@ -321,17 +321,14 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mCallId);
- dest.writeString(mNumber);
dest.writeInt(mState);
- dest.writeInt(mNumberPresentation);
- dest.writeInt(mCnapNamePresentation);
- dest.writeString(mCnapName);
dest.writeString(getDisconnectCause().toString());
dest.writeInt(getCapabilities());
dest.writeLong(getConnectTime());
dest.writeIntArray(Ints.toArray(mChildCallIds));
dest.writeString(getGatewayNumber());
dest.writeString(getGatewayPackage());
+ dest.writeParcelable(mIdentification, 0);
}
/**
@@ -339,17 +336,14 @@
*/
private Call(Parcel in) {
mCallId = in.readInt();
- mNumber = in.readString();
mState = in.readInt();
- mNumberPresentation = in.readInt();
- mCnapNamePresentation = in.readInt();
- mCnapName = in.readString();
mDisconnectCause = DisconnectCause.valueOf(in.readString());
mCapabilities = in.readInt();
mConnectTime = in.readLong();
mChildCallIds.addAll(Ints.asList(in.createIntArray()));
mGatewayNumber = in.readString();
mGatewayPackage = in.readString();
+ mIdentification = in.readParcelable(CallIdentification.class.getClassLoader());
}
@Override
@@ -376,38 +370,16 @@
@Override
public String toString() {
- return toString(true);
- }
-
- public String toString(boolean safe) {
- StringBuffer buffer = new StringBuffer();
- buffer.append("callId: ");
- buffer.append(mCallId);
- if (!safe) {
- buffer.append(", number: ");
- buffer.append(mNumber);
- buffer.append(", name: ");
- buffer.append(mCnapName);
- }
- buffer.append(", state: ");
- buffer.append(STATE_MAP.get(mState));
- buffer.append(", disconnect_cause: ");
- buffer.append(getDisconnectCause().toString());
- buffer.append(", capabilities: ");
- buffer.append(Integer.toHexString(getCapabilities()));
-
- final long duration = System.currentTimeMillis() - getConnectTime();
- buffer.append(", elapsedTime: ");
- buffer.append(DateUtils.formatElapsedTime(duration / 1000));
-
- buffer.append(", childCalls: ");
- buffer.append(mChildCallIds.toString());
- buffer.append(", gateway: ");
- if (!safe) {
- buffer.append(mGatewayNumber);
- }
- buffer.append(" [").append(mGatewayPackage).append("]");
-
- return buffer.toString();
+ return Objects.toStringHelper(this)
+ .add("mCallId", mCallId)
+ .add("mState", mState)
+ .add("mDisconnectCause", mDisconnectCause)
+ .add("mCapabilities", mCapabilities)
+ .add("mConnectTime", mConnectTime)
+ .add("mChildCallIds", mChildCallIds)
+ .add("mGatewayNumber", MoreStrings.toSafeString(mGatewayNumber))
+ .add("mGatewayPackage", mGatewayPackage)
+ .add("mIdentification", mIdentification)
+ .toString();
}
}
diff --git a/common/src/com/android/services/telephony/common/CallIdentification.java b/common/src/com/android/services/telephony/common/CallIdentification.java
new file mode 100644
index 0000000..4a9884c
--- /dev/null
+++ b/common/src/com/android/services/telephony/common/CallIdentification.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2013 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.services.telephony.common;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.google.common.base.Objects;
+
+/**
+ * Class object used across CallHandlerService APIs. Describes a single call and its state.
+ */
+public final class CallIdentification implements Parcelable {
+
+ // Unique identifier for the call
+ private int mCallId;
+
+ // The phone number on the other end of the connection
+ private String mNumber = "";
+
+ // Number presentation received from the carrier
+ private int mNumberPresentation = Call.PRESENTATION_ALLOWED;
+
+ // Name presentation mode received from the carrier
+ private int mCnapNamePresentation = Call.PRESENTATION_ALLOWED;
+
+ // Name associated with the other end of the connection; from the carrier.
+ private String mCnapName = "";
+
+ public CallIdentification(int callId) {
+ mCallId = callId;
+ }
+
+ public CallIdentification(CallIdentification identification) {
+ mCallId = identification.mCallId;
+ mNumber = identification.mNumber;
+ mNumberPresentation = identification.mNumberPresentation;
+ mCnapNamePresentation = identification.mCnapNamePresentation;
+ mCnapName = identification.mCnapName;
+ }
+
+ public int getCallId() {
+ return mCallId;
+ }
+
+ public String getNumber() {
+ return mNumber;
+ }
+
+ public void setNumber(String number) {
+ mNumber = number;
+ }
+
+ public int getNumberPresentation() {
+ return mNumberPresentation;
+ }
+
+ public void setNumberPresentation(int presentation) {
+ mNumberPresentation = presentation;
+ }
+
+ public int getCnapNamePresentation() {
+ return mCnapNamePresentation;
+ }
+
+ public void setCnapNamePresentation(int presentation) {
+ mCnapNamePresentation = presentation;
+ }
+
+ public String getCnapName() {
+ return mCnapName;
+ }
+
+ public void setCnapName(String cnapName) {
+ mCnapName = cnapName;
+ }
+
+ /**
+ * Parcelable implementation
+ */
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mCallId);
+ dest.writeString(mNumber);
+ dest.writeInt(mNumberPresentation);
+ dest.writeInt(mCnapNamePresentation);
+ dest.writeString(mCnapName);
+ }
+
+ /**
+ * Constructor for Parcelable implementation.
+ */
+ private CallIdentification(Parcel in) {
+ mCallId = in.readInt();
+ mNumber = in.readString();
+ mNumberPresentation = in.readInt();
+ mCnapNamePresentation = in.readInt();
+ mCnapName = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Creates Call objects for Parcelable implementation.
+ */
+ public static final Creator<CallIdentification> CREATOR = new Creator<CallIdentification>() {
+
+ @Override
+ public CallIdentification createFromParcel(Parcel in) {
+ return new CallIdentification(in);
+ }
+
+ @Override
+ public CallIdentification[] newArray(int size) {
+ return new CallIdentification[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("mCallId", mCallId)
+ .add("mNumber", MoreStrings.toSafeString(mNumber))
+ .add("mNumberPresentation", mNumberPresentation)
+ .add("mCnapName", MoreStrings.toSafeString(mCnapName))
+ .add("mCnapNamePresentation", mCnapNamePresentation)
+ .toString();
+ }
+}
diff --git a/common/src/com/android/services/telephony/common/ICallHandlerService.aidl b/common/src/com/android/services/telephony/common/ICallHandlerService.aidl
index df2e2de..cf2e0b2 100644
--- a/common/src/com/android/services/telephony/common/ICallHandlerService.aidl
+++ b/common/src/com/android/services/telephony/common/ICallHandlerService.aidl
@@ -44,7 +44,7 @@
/**
* Called when the state of a call changes.
*/
- void onUpdate(in List<Call> call, boolean fullUpdate);
+ void onUpdate(in List<Call> call);
/**
* Called when a call disconnects.
diff --git a/common/src/com/android/services/telephony/common/MoreStrings.java b/common/src/com/android/services/telephony/common/MoreStrings.java
new file mode 100644
index 0000000..5e6e4b4
--- /dev/null
+++ b/common/src/com/android/services/telephony/common/MoreStrings.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2013 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.services.telephony.common;
+
+/**
+ * Static utility methods for Strings.
+ */
+public class MoreStrings {
+
+ public static String toSafeString(String value) {
+ if (value == null) {
+ return null;
+ }
+
+ // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
+ // sanitized phone numbers.
+ final StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < value.length(); i++) {
+ final char c = value.charAt(i);
+ if (c == '-' || c == '@' || c == '.') {
+ builder.append(c);
+ } else {
+ builder.append('x');
+ }
+ }
+ return builder.toString();
+ }
+
+}
diff --git a/src/com/android/phone/BluetoothManager.java b/src/com/android/phone/BluetoothManager.java
index f5106c8..dd38632 100644
--- a/src/com/android/phone/BluetoothManager.java
+++ b/src/com/android/phone/BluetoothManager.java
@@ -408,14 +408,14 @@
}
@Override
- public void onIncoming(Call call, ArrayList<String> messages) {
+ public void onIncoming(Call call) {
// An incoming call can affect bluetooth indicator, so we update it whenever there is
// a change to any of the calls.
updateBluetoothIndication();
}
@Override
- public void onUpdate(List<Call> calls, boolean fullUpdate) {
+ public void onUpdate(List<Call> calls) {
updateBluetoothIndication();
}
diff --git a/src/com/android/phone/CallHandlerServiceProxy.java b/src/com/android/phone/CallHandlerServiceProxy.java
index 12e039e..75bb26c 100644
--- a/src/com/android/phone/CallHandlerServiceProxy.java
+++ b/src/com/android/phone/CallHandlerServiceProxy.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.os.AsyncResult;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -32,31 +31,51 @@
import com.android.services.telephony.common.AudioMode;
import com.android.services.telephony.common.Call;
import com.android.services.telephony.common.ICallHandlerService;
-import com.android.services.telephony.common.ICallCommandService;
+import com.google.common.collect.Lists;
-import java.util.ArrayList;
import java.util.List;
/**
* This class is responsible for passing through call state changes to the CallHandlerService.
*/
-public class CallHandlerServiceProxy extends Handler implements CallModeler.Listener,
- AudioModeListener {
+public class CallHandlerServiceProxy extends Handler
+ implements CallModeler.Listener, AudioModeListener {
private static final String TAG = CallHandlerServiceProxy.class.getSimpleName();
- private static final boolean DBG =
- (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt(
+ "ro.debuggable", 0) == 1);
+ public static final int RETRY_DELAY_MILLIS = 2000;
+ private static final int BIND_RETRY_MSG = 1;
+ private static final int MAX_RETRY_COUNT = 5;
private AudioRouter mAudioRouter;
private CallCommandService mCallCommandService;
private CallModeler mCallModeler;
private Context mContext;
- private ICallHandlerService mCallHandlerService;
- private ServiceConnection mConnection;
+ private ICallHandlerService mCallHandlerServiceGuarded; // Guarded by mServiceAndQueueLock
+ private List<Call> mIncomingCallQueueGuarded; // Guarded by mServiceAndQueueLock
+ private List<List<Call>> mUpdateCallQueueGuarded; // Guarded by mServiceAndQueueLock
+ private List<Call> mDisconnectCallQueueGuarded; // Guarded by mServiceAndQueueLock
+ private final Object mServiceAndQueueLock = new Object();
+ private int mBindRetryCount = 0;
+
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+
+ switch (msg.what) {
+ case BIND_RETRY_MSG:
+ setupServiceConnection();
+ break;
+ }
+ }
public CallHandlerServiceProxy(Context context, CallModeler callModeler,
CallCommandService callCommandService, AudioRouter audioRouter) {
+ if (DBG) {
+ Log.d(TAG, "init CallHandlerServiceProxy");
+ }
mContext = context;
mCallCommandService = callCommandService;
mCallModeler = callModeler;
@@ -64,155 +83,263 @@
mAudioRouter.addAudioModeListener(this);
mCallModeler.addListener(this);
+
+ setupServiceConnection();
}
@Override
public void onDisconnect(Call call) {
- if (mCallHandlerService != null) {
- try {
- if (DBG) Log.d(TAG, "onDisconnect: " + call);
- mCallHandlerService.onDisconnect(call);
- maybeUnbind();
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception handling onDisconnect ", e);
+ try {
+ synchronized (mServiceAndQueueLock) {
+ if (mCallHandlerServiceGuarded == null) {
+ if (DBG) {
+ Log.d(TAG, "CallHandlerService not connected. Enqueue disconnect");
+ }
+ enqueueDisconnect(call);
+ return;
+ }
}
+ if (DBG) {
+ Log.d(TAG, "onDisconnect: " + call);
+ }
+ mCallHandlerServiceGuarded.onDisconnect(call);
+ } catch (Exception e) {
+ Log.e(TAG, "Remote exception handling onDisconnect ", e);
}
}
@Override
- public void onIncoming(Call call, ArrayList<String> textResponses) {
- if (maybeBindToService() && mCallHandlerService != null) {
- try {
- if (DBG) Log.d(TAG, "onIncoming: " + call);
- mCallHandlerService.onIncoming(call, textResponses);
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception handling onUpdate", e);
+ public void onIncoming(Call call) {
+ try {
+ synchronized (mServiceAndQueueLock) {
+ if (mCallHandlerServiceGuarded == null) {
+ if (DBG) {
+ Log.d(TAG, "CallHandlerService not connected. Enqueue incoming.");
+ }
+ enqueueIncoming(call);
+ return;
+ }
}
+ if (DBG) {
+ Log.d(TAG, "onIncoming: " + call);
+ }
+ // TODO(klp): check RespondViaSmsManager.allowRespondViaSmsForCall()
+ // must refactor call method to accept proper call object.
+ mCallHandlerServiceGuarded.onIncoming(call,
+ RejectWithTextMessageManager.loadCannedResponses());
+ } catch (Exception e) {
+ Log.e(TAG, "Remote exception handling onUpdate", e);
}
}
@Override
- public void onUpdate(List<Call> calls, boolean fullUpdate) {
- if (maybeBindToService() && mCallHandlerService != null) {
- try {
- if (DBG) Log.d(TAG, "onUpdate: " + calls.toString());
- mCallHandlerService.onUpdate(calls, fullUpdate);
- maybeUnbind();
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception handling onUpdate", e);
+ public void onUpdate(List<Call> calls) {
+ try {
+ synchronized (mServiceAndQueueLock) {
+ if (mCallHandlerServiceGuarded == null) {
+ if (DBG) {
+ Log.d(TAG, "CallHandlerService not connected. Enqueue update.");
+ }
+ enqueueUpdate(calls);
+ return;
+ }
}
+
+ if (DBG) {
+ Log.d(TAG, "onUpdate: " + calls.toString());
+ }
+ mCallHandlerServiceGuarded.onUpdate(calls);
+ } catch (Exception e) {
+ Log.e(TAG, "Remote exception handling onUpdate", e);
}
}
@Override
public void onAudioModeChange(int previousMode, int newMode) {
- // Just do a simple log for now.
- Log.i(TAG, "Updating with new audio mode: " + AudioMode.toString(newMode) +
- " from " + AudioMode.toString(previousMode));
-
- if (mCallHandlerService != null) {
- try {
- if (DBG) Log.d(TAG, "onSupportAudioModeChange");
-
- mCallHandlerService.onAudioModeChange(newMode);
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception handling onAudioModeChange", e);
+ try {
+ synchronized (mServiceAndQueueLock) {
+ // TODO(klp): does this need to be enqueued?
+ if (mCallHandlerServiceGuarded == null) {
+ if (DBG) {
+ Log.d(TAG, "CallHandlerService not conneccted. Skipping "
+ + "onAudioModeChange().");
+ }
+ return;
+ }
}
+
+ // Just do a simple log for now.
+ Log.i(TAG, "Updating with new audio mode: " + AudioMode.toString(newMode) +
+ " from " + AudioMode.toString(previousMode));
+
+ if (DBG) {
+ Log.d(TAG, "onSupportAudioModeChange");
+ }
+
+ mCallHandlerServiceGuarded.onAudioModeChange(newMode);
+ } catch (Exception e) {
+ Log.e(TAG, "Remote exception handling onAudioModeChange", e);
}
}
@Override
public void onSupportedAudioModeChange(int modeMask) {
- if (mCallHandlerService != null) {
- try {
- if (DBG) Log.d(TAG, "onSupportAudioModeChange: " + AudioMode.toString(modeMask));
+ try {
+ synchronized (mServiceAndQueueLock) {
+ // TODO(klp): does this need to be enqueued?
+ if (mCallHandlerServiceGuarded == null) {
+ if (DBG) {
+ Log.d(TAG, "CallHandlerService not conneccted. Skipping"
+ + "onSupportedAudioModeChange().");
+ }
+ return;
+ }
+ }
- mCallHandlerService.onSupportedAudioModeChange(modeMask);
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception handling onAudioModeChange", e);
+ if (DBG) {
+ Log.d(TAG, "onSupportAudioModeChange: " + AudioMode.toString(modeMask));
+ }
+
+ mCallHandlerServiceGuarded.onSupportedAudioModeChange(modeMask);
+ } catch (Exception e) {
+ Log.e(TAG, "Remote exception handling onAudioModeChange", e);
+ }
+
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override public void onServiceConnected (ComponentName className, IBinder service){
+ if (DBG) {
+ Log.d(TAG, "Service Connected");
+ }
+ onCallHandlerServiceConnected(ICallHandlerService.Stub.asInterface(service));
+ }
+
+ @Override public void onServiceDisconnected (ComponentName className){
+ Log.i(TAG, "Disconnected from UI service.");
+ synchronized (mServiceAndQueueLock) {
+ mCallHandlerServiceGuarded = null;
+
+ // Technically, unbindService is un-necessary since the framework will schedule and
+ // restart the crashed service. But there is a exponential backoff for the restart.
+ // Unbind explicitly and setup again to avoid the backoff since it's important to
+ // always have an in call ui.
+ mContext.unbindService(mConnection);
+ setupServiceConnection();
}
}
}
+ ;
+
/**
* Sets up the connection with ICallHandlerService
*/
private void setupServiceConnection() {
- mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
+ synchronized (mServiceAndQueueLock) {
+ if (mCallHandlerServiceGuarded == null) {
+
+
+ final Intent serviceIntent = new Intent(ICallHandlerService.class.getName());
+ final ComponentName component = new ComponentName(mContext.getResources().getString(
+ R.string.incall_ui_default_package), mContext.getResources().getString(
+ R.string.incall_ui_default_class));
+ serviceIntent.setComponent(component);
+
if (DBG) {
- Log.d(TAG, "Service Connected");
+ Log.d(TAG, "binding to service " + serviceIntent);
}
- onCallHandlerServiceConnected(ICallHandlerService.Stub.asInterface(service));
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- Log.i(TAG, "Disconnected from UI service.");
- mCallHandlerService = null;
-
- // clean up our current binding.
- mContext.unbindService(mConnection);
- mConnection = null;
-
- // potentially attempt to rebind if there are still active calls.
- maybeBindToService();
- }
- };
-
- final Intent serviceIntent = new Intent(ICallHandlerService.class.getName());
- final ComponentName component = new ComponentName(
- mContext.getResources().getString(R.string.incall_ui_default_package),
- mContext.getResources().getString(R.string.incall_ui_default_class));
- serviceIntent.setComponent(component);
- if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
- Log.e(TAG, "Cound not bind to ICallHandlerService");
- }
- }
-
- /**
- * Checks To see if there are any calls left. If not, unbind the callhandler service.
- */
- private void maybeUnbind() {
- if (!mCallModeler.hasLiveCall()) {
- if (mConnection != null) {
- mContext.unbindService(mConnection);
- mConnection = null;
+ if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
+ // This happens when the in-call package is in the middle of being installed.
+ // Delay the retry.
+ mBindRetryCount++;
+ if (mBindRetryCount < MAX_RETRY_COUNT) {
+ Log.e(TAG, "bindService failed on " + serviceIntent + ". Retrying in " +
+ RETRY_DELAY_MILLIS + " ms.");
+ sendMessageDelayed(Message.obtain(this, BIND_RETRY_MSG),
+ RETRY_DELAY_MILLIS);
+ } else {
+ Log.wtf(TAG, "Tried to bind to in-call UI " + MAX_RETRY_COUNT + " times."
+ + " Giving up.");
+ }
+ }
}
}
}
/**
- * Checks to see if there are any active calls. If so, binds the call handler service.
- * @return true if already bound. False otherwise.
- */
- private boolean maybeBindToService() {
- if (mCallModeler.hasLiveCall()) {
- // mConnection is set to non-null once an attempt is made to connect.
- // We do not check against mCallHandlerService here because we could potentially
- // create multiple bindings to the UI.
- if (mConnection != null) {
- return true;
- }
- setupServiceConnection();
- }
- return false;
- }
-
- /**
* Called when the in-call UI service is connected. Send command interface to in-call.
*/
private void onCallHandlerServiceConnected(ICallHandlerService callHandlerService) {
- mCallHandlerService = callHandlerService;
+ synchronized (mServiceAndQueueLock) {
+ mCallHandlerServiceGuarded = callHandlerService;
+
+ // TODO(klp): combine queues into a single ordered queue.
+ processIncomingCallQueue();
+ processUpdateCallQueue();
+ processDisconnectQueue();
+ }
try {
- mCallHandlerService.setCallCommandService(mCallCommandService);
-
- // start with a full update
- onUpdate(mCallModeler.getFullList(), true);
+ mCallHandlerServiceGuarded.setCallCommandService(mCallCommandService);
} catch (RemoteException e) {
Log.e(TAG, "Remote exception calling CallHandlerService::setCallCommandService", e);
}
}
+
+
+ private void enqueueDisconnect(Call call) {
+ if (mDisconnectCallQueueGuarded == null) {
+ mDisconnectCallQueueGuarded = Lists.newArrayList();
+ }
+ mDisconnectCallQueueGuarded.add(new Call(call));
+ }
+
+ private void enqueueIncoming(Call call) {
+ if (mIncomingCallQueueGuarded == null) {
+ mIncomingCallQueueGuarded = Lists.newArrayList();
+ }
+ mIncomingCallQueueGuarded.add(new Call(call));
+ }
+
+ private void enqueueUpdate(List<Call> calls) {
+ if (mUpdateCallQueueGuarded == null) {
+ mUpdateCallQueueGuarded = Lists.newArrayList();
+ }
+ final List<Call> copy = Lists.newArrayList();
+ for (Call call : calls) {
+ copy.add(new Call(call));
+ }
+ mUpdateCallQueueGuarded.add(copy);
+ }
+
+ private void processDisconnectQueue() {
+ if (mDisconnectCallQueueGuarded != null) {
+ for (Call call : mDisconnectCallQueueGuarded) {
+ onDisconnect(call);
+ }
+ mDisconnectCallQueueGuarded.clear();
+ mDisconnectCallQueueGuarded = null;
+ }
+ }
+
+ private void processIncomingCallQueue() {
+ if (mIncomingCallQueueGuarded != null) {
+ for (Call call : mIncomingCallQueueGuarded) {
+ onIncoming(call);
+ }
+ mIncomingCallQueueGuarded.clear();
+ mIncomingCallQueueGuarded = null;
+ }
+ }
+
+ private void processUpdateCallQueue() {
+ if (mUpdateCallQueueGuarded != null) {
+ for (List<Call> calls : mUpdateCallQueueGuarded) {
+ onUpdate(calls);
+ }
+ mUpdateCallQueueGuarded.clear();
+ mUpdateCallQueueGuarded = null;
+ }
+ }
}
diff --git a/src/com/android/phone/CallModeler.java b/src/com/android/phone/CallModeler.java
index 94584a2..6b7a137 100644
--- a/src/com/android/phone/CallModeler.java
+++ b/src/com/android/phone/CallModeler.java
@@ -203,8 +203,7 @@
for (int i = 0; i < mListeners.size(); ++i) {
if (call != null) {
- mListeners.get(i).onIncoming(call,
- mRejectWithTextMessageManager.loadCannedResponses());
+ mListeners.get(i).onIncoming(call);
}
}
}
@@ -246,7 +245,7 @@
doUpdate(false, updatedCalls);
for (int i = 0; i < mListeners.size(); ++i) {
- mListeners.get(i).onUpdate(updatedCalls, false);
+ mListeners.get(i).onUpdate(updatedCalls);
}
}
@@ -680,8 +679,8 @@
*/
public interface Listener {
void onDisconnect(Call call);
- void onIncoming(Call call, ArrayList<String> textReponses);
- void onUpdate(List<Call> calls, boolean fullUpdate);
+ void onIncoming(Call call);
+ void onUpdate(List<Call> calls);
}
/**
diff --git a/src/com/android/phone/DTMFTonePlayer.java b/src/com/android/phone/DTMFTonePlayer.java
index 87c071a..d3ed53e 100644
--- a/src/com/android/phone/DTMFTonePlayer.java
+++ b/src/com/android/phone/DTMFTonePlayer.java
@@ -116,11 +116,11 @@
}
@Override
- public void onIncoming(Call call, ArrayList<String> textResponses) {
+ public void onIncoming(Call call) {
}
@Override
- public void onUpdate(List<Call> calls, boolean full) {
+ public void onUpdate(List<Call> calls) {
logD("Call updated");
checkCallState();
}
diff --git a/src/com/android/phone/RejectWithTextMessageManager.java b/src/com/android/phone/RejectWithTextMessageManager.java
index f032169..8324bd4 100644
--- a/src/com/android/phone/RejectWithTextMessageManager.java
+++ b/src/com/android/phone/RejectWithTextMessageManager.java
@@ -104,7 +104,7 @@
*
* @see com.android.phone.RejectWithTextMessageManager.Settings
*/
- public ArrayList<String> loadCannedResponses() {
+ public static ArrayList<String> loadCannedResponses() {
if (DBG) log("loadCannedResponses()...");
final SharedPreferences prefs = PhoneGlobals.getInstance().getSharedPreferences(