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

Commit 1feb0f28 authored by Thomas Stuart's avatar Thomas Stuart
Browse files

VoipCallMonitor updates; stopFGS when notifs are removed

VoipCallMonitor was failing to revoke foreground service delegation if
the user dismissed an ongoing call notification for an active call.

This change ensures FGS is revoked if an application does not have a
notification indicating an ongoing call is active.

Fixes: 286090019
Test: 2 new unit tests + manual testing
	- verified an app can update a notif from incoming call to
          an ongoing call and maintain FGS
Change-Id: Iabc60e9616d4cfaf78cad7d10c601d9593543ee0
parent 34800e09
Loading
Loading
Loading
Loading
+63 −10
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.telecom.PhoneAccountHandle;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.telecom.Call;

import com.android.server.telecom.CallsManagerListenerBase;
import com.android.server.telecom.LogUtils;
import com.android.server.telecom.LoggedHandlerExecutor;
@@ -46,6 +47,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

@@ -89,13 +91,21 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
                        boolean sbnMatched = false;
                        for (Call call : mNotificationPendingCalls) {
                            if (info.matchesCall(call)) {
                                Log.i(this, "onNotificationPosted: found a pending "
                                                + "callId=[%s] for the call notification w/ "
                                                + "id=[%s]",
                                        call.getId(), sbn.getId());
                                mNotificationPendingCalls.remove(call);
                                mNotificationInfoToCallMap.put(info, call);
                                sbnMatched = true;
                                break;
                            }
                        }
                        if (!sbnMatched) {
                        if (!sbnMatched &&
                                !mCachedNotifications.contains(info) /* don't re-add if update */) {
                            Log.i(this, "onNotificationPosted: could not find a"
                                            + "call for the call notification w/ id=[%s]",
                                    sbn.getId());
                            // notification may post before we started to monitor the call, cache
                            // this notification and try to match it later with new added call.
                            mCachedNotifications.add(info);
@@ -154,7 +164,6 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
            Set<Call> callList = mAccountHandleToCallMap.computeIfAbsent(phoneAccountHandle,
                    k -> new HashSet<>());
            callList.add(call);

            CompletableFuture.completedFuture(null).thenComposeAsync(
                    (x) -> {
                        startFGSDelegation(call.getCallingPackageIdentity().mCallingPackagePid,
@@ -223,11 +232,15 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
            Log.i(this, "stopFGSDelegation of call %s", call);
            PhoneAccountHandle handle = call.getTargetPhoneAccount();
            Set<Call> calls = mAccountHandleToCallMap.get(handle);

            // Every call for the package that is losing foreground service delegation should be
            // removed from tracking maps/contains in this class
            if (calls != null) {
                for (Call c : calls) {
                    stopMonitorWorks(c);
                    stopMonitorWorks(c); // remove the call from tacking in this class
                }
            }

            mAccountHandleToCallMap.remove(handle);

            if (mActivityManagerInternal != null) {
@@ -253,6 +266,8 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
            boolean sbnMatched = false;
            for (NotificationInfo info : mCachedNotifications) {
                if (info.matchesCall(call)) {
                    Log.i(this, "startMonitorNotification: found a cached call "
                            + "notification for call=[%s]", call);
                    mCachedNotifications.remove(info);
                    mNotificationInfoToCallMap.put(info, call);
                    sbnMatched = true;
@@ -261,6 +276,8 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
            }
            if (!sbnMatched) {
                // Only continue to
                Log.i(this, "startMonitorNotification: could not find a call"
                        + " notification for the call=[%s];", call);
                mNotificationPendingCalls.add(call);
                CompletableFuture<Void> future = new CompletableFuture<>();
                mHandler.postDelayed(() -> future.complete(null), 5000L);
@@ -288,12 +305,7 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
        mActivityManagerInternal = ami;
    }

    @VisibleForTesting
    public void setNotificationListenerService(NotificationListenerService listener) {
        mNotificationListener = listener;
    }

    private class NotificationInfo {
    private static class NotificationInfo extends Object {
        private String mPackageName;
        private UserHandle mUserHandle;

@@ -308,5 +320,46 @@ public class VoipCallMonitor extends CallsManagerListenerBase {
                    accountHandle.getComponentName().getPackageName())
                    && mUserHandle != null && mUserHandle.equals(accountHandle.getUserHandle());
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof NotificationInfo)) {
                return false;
            }
            NotificationInfo that = (NotificationInfo) obj;
            return Objects.equals(this.mPackageName, that.mPackageName)
                    && Objects.equals(this.mUserHandle, that.mUserHandle);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mPackageName, mUserHandle);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("{ NotificationInfo: [mPackageName: ")
                    .append(mPackageName)
                    .append("], [mUserHandle=")
                    .append(mUserHandle)
                    .append("]  }");
            return sb.toString();
        }
    }

    @VisibleForTesting
    public void postNotification(StatusBarNotification statusBarNotification) {
        mNotificationListener.onNotificationPosted(statusBarNotification);
    }

    @VisibleForTesting
    public void removeNotification(StatusBarNotification statusBarNotification) {
        mNotificationListener.onNotificationRemoved(statusBarNotification);
    }

    @VisibleForTesting
    public Set<Call> getCallsForHandle(PhoneAccountHandle handle){
        return mAccountHandleToCallMap.get(handle);
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -28,6 +28,13 @@
        android:text="@string/get_call_id"
    />

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

    <Button
        android:id="@+id/answer_button"
        android:layout_width="wrap_content"
+1 −0
Original line number Diff line number Diff line
@@ -39,5 +39,6 @@
    <!-- extra functionality -->
    <string name="start_stream">start streaming</string>
    <string name="crash_app">throw exception</string>
    <string name="update_notification"> update notification to ongoing call style</string>

</resources>
 No newline at end of file
+8 −3
Original line number Diff line number Diff line
@@ -21,11 +21,9 @@ import static android.telecom.CallAttributes.DIRECTION_INCOMING;
import static android.telecom.CallAttributes.DIRECTION_OUTGOING;

import android.app.Activity;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaPlayer;
import android.net.StringNetworkSpecifier;
import android.net.Uri;
import android.os.Bundle;
import android.os.OutcomeReceiver;
@@ -174,8 +172,15 @@ public class InCallActivity extends Activity {
                        "Intentionally throwing RuntimeException from InCallActivity");
            }
        });
    }

        findViewById(R.id.updateCallStyleNotification).setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Utils.updateCallStyleNotification_toOngoingCall(getApplicationContext());
                    }
                });
    }

    @Override
    protected void onStop() {
+27 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.telecom.transactionalVoipApp;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
import android.content.ComponentName;
@@ -38,9 +39,11 @@ import java.util.List;

public class Utils {
    public static final String TAG = "TransactionalAppUtils";
    public static final String CALLER_NAME = "Sundar Pichai";
    public static final String sEXTRAS_KEY = "ExtrasKey";
    public static final String sCALL_DIRECTION_KEY = "CallDirectionKey";
    public static final String CHANNEL_ID = "TelecomVoipAppChannelId";
    public static final int CALL_NOTIFICATION_ID = 123456;
    private static final int SAMPLING_RATE_HZ = 44100;

    public static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE = new PhoneAccountHandle(
@@ -70,7 +73,7 @@ public class Utils {
                .setContentTitle("Incoming call")
                .setSmallIcon(R.drawable.ic_android_black_24dp)
                .setStyle(Notification.CallStyle.forIncomingCall(
                        new Person.Builder().setName("Tom Stu").setImportant(true).build(),
                        new Person.Builder().setName(CALLER_NAME).setImportant(true).build(),
                        pendingAnswer, pendingReject)
                )
                .setFullScreenIntent(pendingAnswer, true)
@@ -79,6 +82,29 @@ public class Utils {
        return callStyleNotification;
    }


    public static void updateCallStyleNotification_toOngoingCall(Context context) {
        PendingIntent ongoingCall = PendingIntent.getActivity(context, 0,
                new Intent(""), PendingIntent.FLAG_IMMUTABLE);

        Notification callStyleNotification = new Notification.Builder(context,
                CHANNEL_ID)
                .setContentText("active call in the TransactionalTestApp")
                .setContentTitle("Ongoing call")
                .setSmallIcon(R.drawable.ic_android_black_24dp)
                .setStyle(Notification.CallStyle.forOngoingCall(
                        new Person.Builder().setName(CALLER_NAME).setImportant(true).build(),
                        ongoingCall)
                )
                .setFullScreenIntent(ongoingCall, true)
                .build();

        NotificationManager notificationManager =
                context.getSystemService(NotificationManager.class);

        notificationManager.notify(CALL_NOTIFICATION_ID, callStyleNotification);
    }

    public static MediaPlayer createMediaPlayer(Context context) {
        int audioToPlay = (Math.random() > 0.5f) ?
                com.android.server.telecom.transactionalVoipApp.R.raw.sample_audio :
Loading