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

Commit 7b85cfb7 authored by Tyler Gunn's avatar Tyler Gunn Committed by Philip P. Moltmann
Browse files

Add stubs in InCallController to track microphone and camera use.

For microphone use we basically track when we bind and unbind to a
calling UX.  This is not perfect as we will probably also include
external calls.

For camera use, we track when the VideoProvider associated with a call
sets or unsets the camera.  This is a pretty reliable indicator of when
the camera is in use as setting the camera from the UX over the
VideoProvider causes the IMS stack to start up the camera, and nulling it
will close the camera.

Change-Id: Ic4fb90f50fa94ccb73e711021ff55bc83a1cb7ed
parent 3f107cad
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -173,6 +173,7 @@ public class CallsManager extends Call.ListenerBase
        void onConnectionTimeChanged(Call call);
        void onConferenceStateChanged(Call call, boolean isConference);
        void onCdmaConferenceSwap(Call call);
        void onSetCamera(Call call, String cameraId);
    }

    /** Interface used to define the action which is executed delay under some condition. */
@@ -1011,6 +1012,18 @@ public class CallsManager extends Call.ListenerBase
        }
    }

    /**
     * Handles a change to the currently active camera for a call by notifying listeners.
     * @param call The call.
     * @param cameraId The ID of the camera in use, or {@code null} if no camera is in use.
     */
    @Override
    public void onSetCamera(Call call, String cameraId) {
        for (CallsManagerListener listener : mListeners) {
            listener.onSetCamera(call, cameraId);
        }
    }

    public Collection<Call> getCalls() {
        return Collections.unmodifiableCollection(mCalls);
    }
+4 −0
Original line number Diff line number Diff line
@@ -104,4 +104,8 @@ public class CallsManagerListenerBase implements CallsManager.CallsManagerListen
    @Override
    public void onCdmaConferenceSwap(Call call) {
    }

    @Override
    public void onSetCamera(Call call, String cameraId) {
    }
}
+113 −2
Original line number Diff line number Diff line
@@ -218,7 +218,7 @@ public class InCallController extends CallsManagerListenerBase {
                Log.startSession("ICSBC.oSD", Log.getPackageAbbreviation(name));
                synchronized (mLock) {
                    try {
                        Log.d(this, "onDisconnected: %s", name);
                        Log.d(this, "onServiceDisconnected: %s", name);
                        mIsBound = false;
                        onDisconnected();
                    } finally {
@@ -339,6 +339,8 @@ public class InCallController extends CallsManagerListenerBase {
                            mInCallServiceInfo.getDisconnectTime()
                                    - mInCallServiceInfo.getBindingStartTime(), mIsNullBinding);
                }

                InCallController.this.onDisconnected(mInCallServiceInfo);
            } else {
                Log.i(InCallController.this, "ICSBC#disconnect: already disconnected; %s",
                        mInCallServiceInfo);
@@ -948,6 +950,17 @@ public class InCallController extends CallsManagerListenerBase {

    private final CarModeTracker mCarModeTracker;

    /**
     * The package name of the app which is showing the calling UX.
     */
    private String mCurrentUserInterfacePackageName = null;

    /**
     * {@code true} if InCallController is tracking a managed, not external call which is using the
     * microphone, {@code false} otherwise.
     */
    private boolean mIsCallUsingMicrophone = false;

    public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
            SystemStateHelper systemStateHelper,
            DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
@@ -1040,6 +1053,7 @@ public class InCallController extends CallsManagerListenerBase {
        }
        call.removeListener(mCallListener);
        mCallIdMapper.removeCall(call);
        maybeTrackMicrophoneUse(isMuted());
    }

    @Override
@@ -1115,6 +1129,7 @@ public class InCallController extends CallsManagerListenerBase {
            }
            Log.i(this, "External call removed from components: %s", componentsUpdated);
        }
        maybeTrackMicrophoneUse(isMuted());
    }

    @Override
@@ -1136,6 +1151,7 @@ public class InCallController extends CallsManagerListenerBase {
        if (!mInCallServices.isEmpty()) {
            Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
                    newCallAudioState);
            maybeTrackMicrophoneUse(newCallAudioState.isMuted());
            for (IInCallService inCallService : mInCallServices.values()) {
                try {
                    inCallService.onCallAudioStateChanged(newCallAudioState);
@@ -1200,6 +1216,25 @@ public class InCallController extends CallsManagerListenerBase {
        updateCall(call);
    }

    /**
     * Track changes to camera usage for a call.
     * @param call The call.
     * @param cameraId The id of the camera to use, or {@code null} if camera is off.
     */
    @Override
    public void onSetCamera(Call call, String cameraId) {
        Log.i(this, "onSetCamera callId=%s, cameraId=%s", call.getId(), cameraId);
        if (cameraId != null) {
            Log.i(this, "onSetCamera: %s is using the camera.",
                    mCurrentUserInterfacePackageName);
            // TODO: Call AppOpsManager#startOp(camera, mCurrentMicrophonePackage)
        } else {
            Log.i(this, "onSetCamera: %s is no longer using the camera.",
                    mCurrentUserInterfacePackageName);
            // TODO: Call AppOpsManager#endOp(camera, mCurrentMicrophonePackage)
        }
    }

    void bringToForeground(boolean showDialpad) {
        if (!mInCallServices.isEmpty()) {
            for (IInCallService inCallService : mInCallServices.values()) {
@@ -1585,6 +1620,11 @@ public class InCallController extends CallsManagerListenerBase {
    private boolean onConnected(InCallServiceInfo info, IBinder service) {
        Log.i(this, "onConnected to %s", info.getComponentName());

        if (info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
                || info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
                || info.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI) {
            trackCallingUserInterfaceStarted(info);
        }
        IInCallService inCallService = IInCallService.Stub.asInterface(service);
        mInCallServices.put(info, inCallService);

@@ -1652,7 +1692,11 @@ public class InCallController extends CallsManagerListenerBase {
     */
    private void onDisconnected(InCallServiceInfo disconnectedInfo) {
        Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());

        if (disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
                || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
                || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI) {
            trackCallingUserInterfaceStopped(disconnectedInfo);
        }
        mInCallServices.remove(disconnectedInfo);
    }

@@ -1719,6 +1763,7 @@ public class InCallController extends CallsManagerListenerBase {
            mCallIdMapper.addCall(call);
            call.addListener(mCallListener);
        }
        maybeTrackMicrophoneUse(isMuted());
    }

    /**
@@ -1898,6 +1943,72 @@ public class InCallController extends CallsManagerListenerBase {
        }
    }

    /**
     * Tracks start of microphone use on binding to the current calling UX.
     * @param info
     */
    private void trackCallingUserInterfaceStarted(InCallServiceInfo info) {
        String packageName = info.getComponentName().getPackageName();
        if (!Objects.equals(mCurrentUserInterfacePackageName, packageName)) {
            Log.i(this, "trackCallingUserInterfaceStarted: %s is now calling UX.", packageName);
            mCurrentUserInterfacePackageName = packageName;
            // TODO: Call AppOpsManager#startOp(microphone, mCurrentMicrophonePackage)
        }
        maybeTrackMicrophoneUse(isMuted());
    }

    /**
     * Tracks stop of microphone use on unbind from the current calling UX.
     * @param info
     */
    private void trackCallingUserInterfaceStopped(InCallServiceInfo info) {
        maybeTrackMicrophoneUse(isMuted());
        mCurrentUserInterfacePackageName = null;
        String packageName = info.getComponentName().getPackageName();
        Log.i(this, "trackCallingUserInterfaceStopped: %s is no longer calling UX", packageName);
        // TODO: Call AppOpsManager#endOp(microphone, mCurrentMicrophonePackage)
    }

    /**
     * As calls are added, removed and change between external and non-external status, track
     * whether the current active calling UX is using the microphone.  We assume if there is a
     * managed call present and the mic is not muted that the microphone is in use.
     */
    private void maybeTrackMicrophoneUse(boolean isMuted) {
        boolean wasTrackingManagedCall = mIsCallUsingMicrophone;
        mIsCallUsingMicrophone = isTrackingManagedCall() && !isMuted;
        if (wasTrackingManagedCall != mIsCallUsingMicrophone) {
            if (mIsCallUsingMicrophone) {
                Log.i(this, "maybeTrackMicrophoneUse: %s is using the microphone",
                        mCurrentUserInterfacePackageName);
                // TODO: Call AppOpsManager#startOp(microphone, mCurrentMicrophonePackage)
            } else {
                Log.i(this, "maybeTrackMicrophoneUse: %s stopped using the microphone",
                        mCurrentUserInterfacePackageName);
                // TODO: Call AppOpsManager#endOp(microphone, mCurrentMicrophonePackage)
            }
        }
    }

    /**
     * @return {@code true} if InCallController is tracking a managed call (i.e. not self managed
     * and not external).
     */
    private boolean isTrackingManagedCall() {
        return mCallIdMapper.getCalls().stream().anyMatch(c -> !c.isExternalCall()
            && !c.isSelfManaged());
    }

    /**
     * @return {@code true} if the audio is currently muted, {@code false} otherwise.
     */
    private boolean isMuted() {
        if (mCallsManager.getAudioState() == null) {
            return false;
        }
        return mCallsManager.getAudioState().isMuted();
    }

    private void sendCrashedInCallServiceNotification(String packageName) {
        PackageManager packageManager = mContext.getPackageManager();
        CharSequence appName;
+7 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ public class VideoProviderProxy extends Connection.VideoProvider {
     */
    public interface Listener {
        void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
        void onSetCamera(Call call, String cameraId);
    }

    /**
@@ -346,6 +347,12 @@ public class VideoProviderProxy extends Connection.VideoProvider {
                    return;
                }
            }

            // Inform other Telecom components of the change in camera status.
            for (Listener listener : mListeners) {
                listener.onSetCamera(mCall, cameraId);
            }

            try {
                mConectionServiceVideoProvider.setCamera(cameraId, callingPackage,
                        targetSdkVersion);
+4 −2
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
import android.telecom.CallAudioState;
import android.telecom.InCallService;
import android.telecom.Log;
import android.telecom.ParcelableCall;
@@ -168,6 +169,8 @@ public class InCallControllerTests extends TelecomTestCase {
        mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, mDefaultDialerCache,
                mTimeoutsAdapter);
        when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
        when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                .thenReturn(mNotificationManager);
        mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
                mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
                mEmergencyCallHelper, mCarModeTracker, mClockProxy);
@@ -177,8 +180,6 @@ public class InCallControllerTests extends TelecomTestCase {
        verify(mMockSystemStateHelper).addListener(systemStateListenerArgumentCaptor.capture());
        mSystemStateListener = systemStateListenerArgumentCaptor.getValue();

        when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                .thenReturn(mNotificationManager);
        // Companion Apps don't have CONTROL_INCALL_EXPERIENCE permission.
        doAnswer(invocation -> {
            int uid = invocation.getArgument(0);
@@ -210,6 +211,7 @@ public class InCallControllerTests extends TelecomTestCase {
        when(mMockPackageManager.checkPermission(
                matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
                matches(NONUI_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
        when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
    }

    @Override