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

Commit 9e31e900 authored by Grace Jia's avatar Grace Jia
Browse files

Hold only notification info in VoipCallMonitor.

Currently we're holding the instances of NotificationStatusBar in
VoipCallMonitor to track the notification of ongoing voip call. This
cause leaks of notification. Fix this by only track the necessary
information of the notification.

Bug: 280787082
Test: atest VoipCallMonitorTest
Change-Id: I151ececc22466bc07e083cd83d895dbdd88b8e12
parent d27148d0
Loading
Loading
Loading
Loading
+49 −40
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;

import com.android.internal.annotations.VisibleForTesting;
@@ -52,11 +51,11 @@ import java.util.concurrent.CompletableFuture;

public class VoipCallMonitor extends CallsManagerListenerBase {

    private final List<Call> mPendingCalls;
    private final List<Call> mNotificationPendingCalls;
    // Same notification may be passed as different object in onNotificationPosted and
    // onNotificationRemoved. Use its string as key to cache ongoing notifications.
    private final Map<String, Call> mNotifications;
    private final Map<PhoneAccountHandle, Set<Call>> mPhoneAccountHandleListMap;
    private final Map<NotificationInfo, Call> mNotificationInfoToCallMap;
    private final Map<PhoneAccountHandle, Set<Call>> mAccountHandleToCallMap;
    private ActivityManagerInternal mActivityManagerInternal;
    private final Map<PhoneAccountHandle, ServiceConnection> mServices;
    private NotificationListenerService mNotificationListener;
@@ -64,7 +63,7 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
    private final HandlerThread mHandlerThread;
    private final Handler mHandler;
    private final Context mContext;
    private List<StatusBarNotification> mPendingSBN;
    private List<NotificationInfo> mCachedNotifications;
    private TelecomSystem.SyncRoot mSyncRoot;

    public VoipCallMonitor(Context context, TelecomSystem.SyncRoot lock) {
@@ -73,11 +72,11 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
        mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        mPendingCalls = new ArrayList<>();
        mPendingSBN = new ArrayList<>();
        mNotifications = new HashMap<>();
        mNotificationPendingCalls = new ArrayList<>();
        mCachedNotifications = new ArrayList<>();
        mNotificationInfoToCallMap = new HashMap<>();
        mServices = new HashMap<>();
        mPhoneAccountHandleListMap = new HashMap<>();
        mAccountHandleToCallMap = new HashMap<>();
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);

        mNotificationListener = new NotificationListenerService() {
@@ -85,19 +84,21 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
            public void onNotificationPosted(StatusBarNotification sbn) {
                synchronized (mLock) {
                    if (sbn.getNotification().isStyle(Notification.CallStyle.class)) {
                        NotificationInfo info = new NotificationInfo(sbn.getPackageName(),
                                sbn.getUser());
                        boolean sbnMatched = false;
                        for (Call call : mPendingCalls) {
                            if (notificationMatchedCall(sbn, call)) {
                                mPendingCalls.remove(call);
                                mNotifications.put(sbn.toString(), call);
                        for (Call call : mNotificationPendingCalls) {
                            if (info.matchesCall(call)) {
                                mNotificationPendingCalls.remove(call);
                                mNotificationInfoToCallMap.put(info, call);
                                sbnMatched = true;
                                break;
                            }
                        }
                        if (!sbnMatched) {
                            // notification may posted before we started to monitor the call, cache
                            // notification may post before we started to monitor the call, cache
                            // this notification and try to match it later with new added call.
                            mPendingSBN.add(sbn);
                            mCachedNotifications.add(info);
                        }
                    }
                }
@@ -106,13 +107,16 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
            @Override
            public void onNotificationRemoved(StatusBarNotification sbn) {
                synchronized (mLock) {
                    mPendingSBN.remove(sbn);
                    if (mNotifications.isEmpty()) {
                    NotificationInfo info = new NotificationInfo(sbn.getPackageName(),
                            sbn.getUser());
                    mCachedNotifications.remove(info);
                    if (mNotificationInfoToCallMap.isEmpty()) {
                        return;
                    }
                    Call call = mNotifications.getOrDefault(sbn.toString(), null);
                    Call call = mNotificationInfoToCallMap.getOrDefault(info, null);
                    if (call != null) {
                        mNotifications.remove(sbn.toString(), call);
                        // TODO: fix potential bug for multiple calls of same voip app.
                        mNotificationInfoToCallMap.remove(info, call);
                        stopFGSDelegation(call);
                    }
                }
@@ -147,7 +151,7 @@ public class VoipCallMonitor extends CallsManagerListenerBase {

        synchronized (mLock) {
            PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
            Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
            Set<Call> callList = mAccountHandleToCallMap.computeIfAbsent(phoneAccountHandle,
                    k -> new HashSet<>());
            callList.add(call);

@@ -169,7 +173,7 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
        synchronized (mLock) {
            stopMonitorWorks(call);
            PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
            Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
            Set<Call> callList = mAccountHandleToCallMap.computeIfAbsent(phoneAccountHandle,
                    k -> new HashSet<>());
            callList.remove(call);

@@ -218,13 +222,13 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
        synchronized (mLock) {
            Log.i(this, "stopFGSDelegation of call %s", call);
            PhoneAccountHandle handle = call.getTargetPhoneAccount();
            Set<Call> calls = mPhoneAccountHandleListMap.get(handle);
            Set<Call> calls = mAccountHandleToCallMap.get(handle);
            if (calls != null) {
                for (Call c : calls) {
                    stopMonitorWorks(c);
                }
            }
            mPhoneAccountHandleListMap.remove(handle);
            mAccountHandleToCallMap.remove(handle);

            if (mActivityManagerInternal != null) {
                ServiceConnection fgsConnection = mServices.get(handle);
@@ -247,26 +251,26 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
    private void startMonitorNotification(Call call) {
        synchronized (mLock) {
            boolean sbnMatched = false;
            for (StatusBarNotification sbn : mPendingSBN) {
                if (notificationMatchedCall(sbn, call)) {
                    mPendingSBN.remove(sbn);
                    mNotifications.put(sbn.toString(), call);
            for (NotificationInfo info : mCachedNotifications) {
                if (info.matchesCall(call)) {
                    mCachedNotifications.remove(info);
                    mNotificationInfoToCallMap.put(info, call);
                    sbnMatched = true;
                    break;
                }
            }
            if (!sbnMatched) {
                // Only continue to
                mPendingCalls.add(call);
                mNotificationPendingCalls.add(call);
                CompletableFuture<Void> future = new CompletableFuture<>();
                mHandler.postDelayed(() -> future.complete(null), 5000L);
                future.thenComposeAsync(
                        (x) -> {
                            if (mPendingCalls.contains(call)) {
                            if (mNotificationPendingCalls.contains(call)) {
                                Log.i(this, "Notification for voip-call %s haven't "
                                        + "posted in time, stop delegation.", call.getId());
                                stopFGSDelegation(call);
                                mPendingCalls.remove(call);
                                mNotificationPendingCalls.remove(call);
                                return null;
                            }
                            return null;
@@ -276,7 +280,7 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
    }

    private void stopMonitorNotification(Call call) {
        mPendingCalls.remove(call);
        mNotificationPendingCalls.remove(call);
    }

    @VisibleForTesting
@@ -289,15 +293,20 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
        mNotificationListener = listener;
    }

    private boolean notificationMatchedCall(StatusBarNotification sbn, Call call) {
        String packageName = sbn.getPackageName();
        UserHandle userHandle = sbn.getUser();
        PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
    private class NotificationInfo {
        private String mPackageName;
        private UserHandle mUserHandle;

        NotificationInfo(String packageName, UserHandle userHandle) {
            mPackageName = packageName;
            mUserHandle = userHandle;
        }

        return packageName != null &&
                packageName.equals(call.getTargetPhoneAccount()
                        .getComponentName().getPackageName())
                && userHandle != null
                && userHandle.equals(accountHandle.getUserHandle());
        boolean matchesCall(Call call) {
            PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
            return mPackageName != null && mPackageName.equals(
                   accountHandle.getComponentName().getPackageName())
                    && mUserHandle != null && mUserHandle.equals(accountHandle.getUserHandle());
        }
    }
}