Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Device manager - silence call ringers when local notifications are silenced #9420

Merged
merged 11 commits into from
Oct 17, 2022
13 changes: 11 additions & 2 deletions src/LegacyCallHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialogTypes
import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
import { findDMForUser } from './utils/dm/findDMForUser';
import { getJoinedNonFunctionalMembers } from './utils/room/getJoinedNonFunctionalMembers';
import { localNotificationsAreSilenced } from './utils/notifications';

export const PROTOCOL_PSTN = 'm.protocol.pstn';
export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';
Expand Down Expand Up @@ -184,6 +185,11 @@ export default class LegacyCallHandler extends EventEmitter {
}
}

public isForcedSilent(): boolean {
const cli = MatrixClientPeg.get();
return localNotificationsAreSilenced(cli);
}

public silenceCall(callId: string): void {
this.silencedCalls.add(callId);
this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
Expand All @@ -194,13 +200,14 @@ export default class LegacyCallHandler extends EventEmitter {
}

public unSilenceCall(callId: string): void {
if (this.isForcedSilent) return;
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
this.silencedCalls.delete(callId);
this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
this.play(AudioID.Ring);
}

public isCallSilenced(callId: string): boolean {
return this.silencedCalls.has(callId);
return this.isForcedSilent() || this.silencedCalls.has(callId);
}

/**
Expand Down Expand Up @@ -582,7 +589,9 @@ export default class LegacyCallHandler extends EventEmitter {
action.value === "ring"
));

if (pushRuleEnabled && tweakSetToRing) {
console.log('hhhh', { pushRuleEnabled, tweakSetToRing });
t3chguy marked this conversation as resolved.
Show resolved Hide resolved

if (pushRuleEnabled && tweakSetToRing && !this.isForcedSilent()) {
this.play(AudioID.Ring);
} else {
this.silenceCall(call.callId);
Expand Down
2 changes: 2 additions & 0 deletions src/toasts/IncomingLegacyCallToast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export default class IncomingLegacyCallToast extends React.Component<IProps, ISt
const call = this.props.call;
const room = MatrixClientPeg.get().getRoom(LegacyCallHandler.instance.roomIdForCall(call));
const isVoice = call.type === CallType.Voice;
const callForcedSilent = LegacyCallHandler.instance.isForcedSilent();

const contentClass = classNames("mx_IncomingLegacyCallToast_content", {
"mx_IncomingLegacyCallToast_content_voice": isVoice,
Expand Down Expand Up @@ -128,6 +129,7 @@ export default class IncomingLegacyCallToast extends React.Component<IProps, ISt
</div>
<AccessibleTooltipButton
className={silenceClass}
disabled={callForcedSilent}
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
onClick={this.onSilenceClick}
title={this.state.silenced ? _t("Sound on") : _t("Silence call")}
/>
Expand Down
149 changes: 147 additions & 2 deletions test/LegacyCallHandler-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { IProtocol } from 'matrix-js-sdk/src/matrix';
import { CallEvent, CallState, CallType } from 'matrix-js-sdk/src/webrtc/call';
import {
IProtocol,
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
MatrixEvent,
PushRuleKind,
RuleId,
TweakName,
} from 'matrix-js-sdk/src/matrix';
import { CallEvent, CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import EventEmitter from 'events';
import { mocked } from 'jest-mock';
import { CallEventHandlerEvent } from 'matrix-js-sdk/src/webrtc/callEventHandler';

import LegacyCallHandler, {
LegacyCallHandlerEvent, PROTOCOL_PSTN, PROTOCOL_PSTN_PREFIXED, PROTOCOL_SIP_NATIVE, PROTOCOL_SIP_VIRTUAL,
Expand All @@ -28,6 +36,8 @@ import DMRoomMap from '../src/utils/DMRoomMap';
import SdkConfig from '../src/SdkConfig';
import { Action } from "../src/dispatcher/actions";
import { getFunctionalMembers } from "../src/utils/room/getFunctionalMembers";
import SettingsStore from '../src/settings/SettingsStore';
import { UIFeature } from '../src/settings/UIFeature';

jest.mock("../src/utils/room/getFunctionalMembers", () => ({
getFunctionalMembers: jest.fn(),
Expand Down Expand Up @@ -126,6 +136,7 @@ describe('LegacyCallHandler', () => {
// what addresses the app has looked up via pstn and native lookup
let pstnLookup: string;
let nativeLookup: string;
const deviceId = 'my-device';

beforeEach(async () => {
stubClient();
Expand All @@ -136,6 +147,7 @@ describe('LegacyCallHandler', () => {
fakeCall = new FakeCall(roomId);
return fakeCall;
};
MatrixClientPeg.get().deviceId = deviceId;

MatrixClientPeg.get().getThirdpartyProtocols = () => {
return Promise.resolve({
Expand Down Expand Up @@ -426,4 +438,137 @@ describe('LegacyCallHandler without third party protocols', () => {
// but it should appear to the user to be in thw native room for Bob
expect(callHandler.roomIdForCall(fakeCall)).toEqual(NATIVE_ROOM_ALICE);
});

describe('incoming calls', () => {
const roomId = 'test-room-id';

const mockAudioElement = {
play: jest.fn(),
pause: jest.fn(),
} as unknown as HTMLMediaElement;
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(SettingsStore, 'getValue').mockImplementation(setting =>
setting === UIFeature.Voip);

jest.spyOn(MatrixClientPeg.get(), 'supportsVoip').mockReturnValue(true);

MatrixClientPeg.get().isFallbackICEServerAllowed = jest.fn();
MatrixClientPeg.get().prepareToEncrypt = jest.fn();

MatrixClientPeg.get().pushRules = {
global: {
[PushRuleKind.Override]: [{
rule_id: RuleId.IncomingCall,
default: false,
enabled: true,
actions: [
{
set_tweak: TweakName.Sound,
value: 'ring',
},
]
,
}],
},
};

jest.spyOn(document, 'getElementById').mockReturnValue(mockAudioElement);

// silence local notifications by default
jest.spyOn(MatrixClientPeg.get(), 'getAccountData').mockImplementation((eventType) => {
if (eventType.includes(LOCAL_NOTIFICATION_SETTINGS_PREFIX.name)) {
return new MatrixEvent({
type: eventType,
content: {
is_silenced: true,
},
});
}
});
});

it('listens for incoming call events when voip is enabled', () => {
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
});
const cli = MatrixClientPeg.get();

cli.emit(CallEventHandlerEvent.Incoming, call);

// call added to call map
expect(callHandler.getCallForRoom(roomId)).toEqual(call);
});

it('rings when incoming call state is ringing and notifications set to ring', () => {
// remove local notification silencing mock for this test
jest.spyOn(MatrixClientPeg.get(), 'getAccountData').mockReturnValue(undefined);
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
});
const cli = MatrixClientPeg.get();

cli.emit(CallEventHandlerEvent.Incoming, call);

// call added to call map
expect(callHandler.getCallForRoom(roomId)).toEqual(call);
call.emit(CallEvent.State, CallState.Ringing, CallState.Connected);

// ringer audio element started
expect(mockAudioElement.play).toHaveBeenCalled();
});

it('does not ring when incoming call state is ringing but local notifications are silenced', () => {
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
});
const cli = MatrixClientPeg.get();

cli.emit(CallEventHandlerEvent.Incoming, call);

// call added to call map
expect(callHandler.getCallForRoom(roomId)).toEqual(call);
call.emit(CallEvent.State, CallState.Ringing, CallState.Connected);

// ringer audio element started
expect(mockAudioElement.play).not.toHaveBeenCalled();
expect(callHandler.isCallSilenced(call.callId)).toEqual(true);
});

it('should force calls to silent when local notifications are silenced', async () => {
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
});
const cli = MatrixClientPeg.get();

cli.emit(CallEventHandlerEvent.Incoming, call);

expect(callHandler.isForcedSilent()).toEqual(true);
expect(callHandler.isCallSilenced(call.callId)).toEqual(true);
});

it('does not unsilence calls when local notifications are silenced', async () => {
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
});
const cli = MatrixClientPeg.get();
const callHandlerEmitSpy = jest.spyOn(callHandler, 'emit');

cli.emit(CallEventHandlerEvent.Incoming, call);
// reset emit call count
callHandlerEmitSpy.mockClear();

callHandler.unSilenceCall(call.callId);
expect(callHandlerEmitSpy).not.toHaveBeenCalled();
// call still silenced
expect(callHandler.isCallSilenced(call.callId)).toEqual(true);
// ringer not played
expect(mockAudioElement.play).not.toHaveBeenCalled();
});
});
});