Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit de7ac140 authored by Thomas Stuart's avatar Thomas Stuart
Browse files

cleanup a crashed or kill apps calls

The telecom dev rel mentioned flows were not echoing the endpoints
whenever an application was swiped up or killed.  When I was trying to
reproduce this issue I noticed the client VoIP application was losing
it's CallControl reference even though the call still existed in the
platform.

Telecom should be cleaning up a clients calls if the app crashes via
a RuntimeException or is killed off by the ActivityManager.

The fix is to set a IBinder.DeathRecipient each time a
TransactionalServiceWrapper is created. This cleans up all the calls
every time the binder dies.

Fixes: 285045481
Test: manual;
    - crash test app (w/ calls)  via RuntimeException
    - kill test app (w/ calls) via adb shell am force-stop packageName
Change-Id: Id6158d77cbaf3dd60557ca5e3aef7858d35868f3
parent 4f03e4d1
Loading
Loading
Loading
Loading
+13 −9
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.telecom;

import android.telecom.Log;
import android.telecom.PhoneAccountHandle;

import com.android.internal.telecom.ICallEventCallback;
@@ -28,8 +29,8 @@ import java.util.Map;
 * more calls.
 */
public class TransactionalServiceRepository {

    private static final Map<PhoneAccountHandle, TransactionalServiceWrapper> lookupTable =
    private static final String TAG = TransactionalServiceRepository.class.getSimpleName();
    private static final Map<PhoneAccountHandle, TransactionalServiceWrapper> mServiceLookupTable =
            new HashMap<>();

    public TransactionalServiceRepository() {
@@ -38,12 +39,15 @@ public class TransactionalServiceRepository {
    public TransactionalServiceWrapper addNewCallForTransactionalServiceWrapper
            (PhoneAccountHandle phoneAccountHandle, ICallEventCallback callEventCallback,
                    CallsManager callsManager, Call call) {

        TransactionalServiceWrapper service = null;
        TransactionalServiceWrapper service;
        // Only create a new TransactionalServiceWrapper if this is the first call for a package.
        // Otherwise, get the existing TSW and add the new call to the service.
        if (!hasExistingServiceWrapper(phoneAccountHandle)) {
            Log.d(TAG, "creating a new TSW; handle=[%s]", phoneAccountHandle);
            service = new TransactionalServiceWrapper(callEventCallback,
                    callsManager, phoneAccountHandle, call, this);
        } else {
            Log.d(TAG, "add a new call to an existing TSW; handle=[%s]", phoneAccountHandle);
            service = getTransactionalServiceWrapper(phoneAccountHandle);
            if (service == null) {
                throw new IllegalStateException("service is null");
@@ -52,25 +56,25 @@ public class TransactionalServiceRepository {
            }
        }

        lookupTable.put(phoneAccountHandle, service);
        mServiceLookupTable.put(phoneAccountHandle, service);

        return service;
    }

    public TransactionalServiceWrapper getTransactionalServiceWrapper(PhoneAccountHandle pah) {
        return lookupTable.get(pah);
        return mServiceLookupTable.get(pah);
    }

    public boolean hasExistingServiceWrapper(PhoneAccountHandle pah) {
        return lookupTable.containsKey(pah);
        return mServiceLookupTable.containsKey(pah);
    }

    public boolean removeServiceWrapper(PhoneAccountHandle pah) {
        Log.i(TAG, "removeServiceWrapper: for phoneAccountHandle=[%s]", pah);
        if (!hasExistingServiceWrapper(pah)) {
            return false;
        }
        lookupTable.remove(pah);
        mServiceLookupTable.remove(pah);
        return true;
    }

}
+32 −27
Original line number Diff line number Diff line
@@ -51,17 +51,17 @@ import com.android.server.telecom.voip.VoipCallTransaction;
import com.android.server.telecom.voip.VoipCallTransactionResult;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Implements {@link android.telecom.CallEventCallback} and {@link android.telecom.CallControl}
 * on a per-client basis which is tied to a {@link PhoneAccountHandle}
 */
public class TransactionalServiceWrapper implements
        ConnectionServiceFocusManager.ConnectionServiceFocus, IBinder.DeathRecipient {
        ConnectionServiceFocusManager.ConnectionServiceFocus {
    private static final String TAG = TransactionalServiceWrapper.class.getSimpleName();

    // CallControl : Client (ex. voip app) --> Telecom
@@ -84,13 +84,25 @@ public class TransactionalServiceWrapper implements
    private final TransactionalServiceRepository mRepository;
    private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
    // init when constructor is called
    private final Hashtable<String, Call> mTrackedCalls = new Hashtable<>();
    private final ConcurrentHashMap<String, Call> mTrackedCalls = new ConcurrentHashMap<>();
    private final TelecomSystem.SyncRoot mLock;
    private final String mPackageName;
    // needs to be non-final for testing
    private TransactionManager mTransactionManager;
    private CallStreamingController mStreamingController;


    // Each TransactionalServiceWrapper should have their own Binder.DeathRecipient to clean up
    // any calls in the event the application crashes or is force stopped.
    private final IBinder.DeathRecipient mAppDeathListener = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.i(TAG, "binderDied: for package=[%s]; cleaning calls", mPackageName);
            cleanupTransactionalServiceWrapper();
            mICallEventCallback.asBinder().unlinkToDeath(this, 0);
        }
    };

    public TransactionalServiceWrapper(ICallEventCallback callEventCallback,
            CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call,
            TransactionalServiceRepository repo) {
@@ -105,6 +117,7 @@ public class TransactionalServiceWrapper implements
        mTransactionManager = TransactionManager.getInstance();
        mStreamingController = mCallsManager.getCallStreamingController();
        mLock = mCallsManager.getLock();
        setDeathRecipient(callEventCallback);
    }

    @VisibleForTesting
@@ -128,12 +141,6 @@ public class TransactionalServiceWrapper implements
        }
    }

    public Call getCallById(String callId) {
        synchronized (mLock) {
            return mTrackedCalls.get(callId);
        }
    }

    @VisibleForTesting
    public boolean untrackCall(Call call) {
        Call removedCall = null;
@@ -158,24 +165,13 @@ public class TransactionalServiceWrapper implements
        return callCount;
    }

    @Override
    public void binderDied() {
        // remove all tacked calls from CallsManager && frameworks side
        for (String id : mTrackedCalls.keySet()) {
            Call call = mTrackedCalls.get(id);
            mCallsManager.markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR));
            mCallsManager.removeCall(call);
            // remove calls from Frameworks side
            if (mICallEventCallback != null) {
                try {
                    mICallEventCallback.removeCallFromTransactionalServiceWrapper(call.getId());
                } catch (RemoteException e) {
                    // pass
                }
    public void cleanupTransactionalServiceWrapper() {
        for (Call call : mTrackedCalls.values()) {
            mCallsManager.markCallAsDisconnected(call,
                    new DisconnectCause(DisconnectCause.ERROR, "process died"));
            mCallsManager.removeCall(call); // This will clear mTrackedCalls && ClientTWS
        }
    }
        mTrackedCalls.clear();
    }

    /***
     *********************************************************************************************
@@ -556,10 +552,10 @@ public class TransactionalServiceWrapper implements
            try {
                // remove the call from frameworks wrapper (client side)
                mICallEventCallback.removeCallFromTransactionalServiceWrapper(call.getId());
                // remove the call from this class/wrapper (server side)
                untrackCall(call);
            } catch (RemoteException e) {
            }
            // remove the call from this class/wrapper (server side)
            untrackCall(call);
        }
    }

@@ -605,6 +601,15 @@ public class TransactionalServiceWrapper implements
        return new SerialTransaction(transactions, mLock);
    }

    private void setDeathRecipient(ICallEventCallback callEventCallback) {
        try {
            callEventCallback.asBinder().linkToDeath(mAppDeathListener, 0);
        } catch (Exception e) {
            Log.w(TAG, "setDeathRecipient: hit exception=[%s] trying to link binder to death",
                    e.toString());
        }
    }

    /***
     *********************************************************************************************
     **                    FocusManager                                                       **
+6 −0
Original line number Diff line number Diff line
@@ -58,6 +58,12 @@
        android:layout_height="wrap_content"
        android:text="@string/start_stream"/>

    <Button
        android:id="@+id/crash_app"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/crash_app"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
+1 −0
Original line number Diff line number Diff line
@@ -38,5 +38,6 @@
    <string name="request_bluetooth_endpoint">Bluetooth</string>
    <!-- extra functionality -->
    <string name="start_stream">start streaming</string>
    <string name="crash_app">throw exception</string>

</resources>
 No newline at end of file
+24 −1
Original line number Diff line number Diff line
@@ -164,10 +164,28 @@ public class InCallActivity extends Activity {
                }
            }
        });

        findViewById(R.id.crash_app).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // To test edge cases, it may be useful to crash the app. To do this, throwing a
                // RuntimeException is sufficient.
                throw new RuntimeException(
                        "Intentionally throwing RuntimeException from InCallActivity");
            }
        });
    }


    @Override
    protected void onStop() {
        Log.i(TAG, "onStop: InCallActivity has stopped");
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        Log.i(TAG, "onDestroy: InCallActivity has been destroyed");
        disconnectAndStopAudio();
        super.onDestroy();
    }
@@ -205,8 +223,13 @@ public class InCallActivity extends Activity {
            sb.append("Error Getting Id");
        }
        sb.append("]");
        try {
            view.setText(sb.toString());
        }
        catch (Exception e){
            // ignore updating the ui
        }
    }

    private void addCall() {
        mVoipCall = new MyVoipCall("123");
Loading