BasicCallTests.java revision bcf23de07aed59ed34d0121a76d29b6ed9a14288
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.telecom.tests;
18
19import static org.mockito.Matchers.any;
20import static org.mockito.Matchers.anyInt;
21import static org.mockito.Matchers.anyString;
22import static org.mockito.Matchers.eq;
23import static org.mockito.Matchers.isNull;
24import static org.mockito.Mockito.never;
25import static org.mockito.Mockito.timeout;
26import static org.mockito.Mockito.verify;
27import static org.mockito.Mockito.verifyZeroInteractions;
28import static org.mockito.Mockito.when;
29
30import android.content.Context;
31import android.content.IContentProvider;
32import android.media.AudioManager;
33import android.net.Uri;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.Looper;
37import android.os.Process;
38import android.provider.BlockedNumberContract;
39import android.telecom.Call;
40import android.telecom.CallAudioState;
41import android.telecom.Connection;
42import android.telecom.ConnectionRequest;
43import android.telecom.DisconnectCause;
44import android.telecom.Log;
45import android.telecom.ParcelableCall;
46import android.telecom.PhoneAccount;
47import android.telecom.PhoneAccountHandle;
48import android.telecom.TelecomManager;
49import android.telecom.VideoProfile;
50import android.support.test.filters.FlakyTest;
51import android.test.suitebuilder.annotation.LargeTest;
52import android.test.suitebuilder.annotation.MediumTest;
53
54import com.android.internal.telecom.IInCallAdapter;
55import com.android.internal.telephony.CallerInfo;
56
57import com.google.common.base.Predicate;
58
59import org.mockito.invocation.InvocationOnMock;
60import org.mockito.stubbing.Answer;
61
62import java.util.concurrent.BrokenBarrierException;
63import java.util.concurrent.CountDownLatch;
64import java.util.concurrent.CyclicBarrier;
65import java.util.concurrent.TimeUnit;
66
67import org.mockito.ArgumentCaptor;
68
69/**
70 * Performs various basic call tests in Telecom.
71 */
72public class BasicCallTests extends TelecomSystemTest {
73    private static final String TEST_BUNDLE_KEY = "android.telecom.extra.TEST";
74    private static final String TEST_EVENT = "android.telecom.event.TEST";
75
76    @LargeTest
77    public void testSingleOutgoingCallLocalDisconnect() throws Exception {
78        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
79                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
80
81        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
82        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
83        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
84
85        when(mClockProxy.currentTimeMillis()).thenReturn(TEST_DISCONNECT_TIME);
86        when(mClockProxy.elapsedRealtime()).thenReturn(TEST_DISCONNECT_ELAPSED_TIME);
87        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
88        assertEquals(Call.STATE_DISCONNECTED,
89                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
90        assertEquals(Call.STATE_DISCONNECTED,
91                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
92        assertEquals(TEST_CONNECT_TIME,
93                mInCallServiceFixtureX.getCall(ids.mCallId).getConnectTimeMillis());
94        assertEquals(TEST_CONNECT_TIME,
95                mInCallServiceFixtureY.getCall(ids.mCallId).getConnectTimeMillis());
96        assertEquals(TEST_CREATE_TIME,
97                mInCallServiceFixtureX.getCall(ids.mCallId).getCreationTimeMillis());
98        assertEquals(TEST_CREATE_TIME,
99                mInCallServiceFixtureY.getCall(ids.mCallId).getCreationTimeMillis());
100
101        verifyNoBlockChecks();
102    }
103
104    @LargeTest
105    public void testSingleOutgoingCallRemoteDisconnect() throws Exception {
106        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
107                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
108
109        when(mClockProxy.currentTimeMillis()).thenReturn(TEST_DISCONNECT_TIME);
110        when(mClockProxy.elapsedRealtime()).thenReturn(TEST_DISCONNECT_ELAPSED_TIME);
111        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
112        assertEquals(Call.STATE_DISCONNECTED,
113                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
114        assertEquals(Call.STATE_DISCONNECTED,
115                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
116        verifyNoBlockChecks();
117    }
118
119    /**
120     * Tests the {@link TelecomManager#acceptRingingCall()} API.  Tests simple case of an incoming
121     * audio-only call.
122     *
123     * @throws Exception
124     */
125    @LargeTest
126    public void testTelecomManagerAcceptRingingCall() throws Exception {
127        IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
128                mConnectionServiceFixtureA);
129
130        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
131        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
132
133        // Use TelecomManager API to answer the ringing call.
134        TelecomManager telecomManager = (TelecomManager) mComponentContextFixture.getTestDouble()
135                .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
136        telecomManager.acceptRingingCall();
137
138        verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
139                .answer(eq(ids.mConnectionId), any());
140        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
141
142        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
143    }
144
145    /**
146     * Tests the {@link TelecomManager#acceptRingingCall()} API.  Tests simple case of an incoming
147     * video call, which should be answered as video.
148     *
149     * @throws Exception
150     */
151    @LargeTest
152    public void testTelecomManagerAcceptRingingVideoCall() throws Exception {
153        IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
154                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
155
156        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
157        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
158
159        // Use TelecomManager API to answer the ringing call; the default expected behavior is to
160        // answer using whatever video state the ringing call requests.
161        TelecomManager telecomManager = (TelecomManager) mComponentContextFixture.getTestDouble()
162                .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
163        telecomManager.acceptRingingCall();
164
165        // Answer video API should be called
166        verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
167                .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_BIDIRECTIONAL), any());
168        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
169
170        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
171    }
172
173    /**
174     * Tests the {@link TelecomManager#acceptRingingCall(int)} API.  Tests answering a video call
175     * as an audio call.
176     *
177     * @throws Exception
178     */
179    @LargeTest
180    public void testTelecomManagerAcceptRingingVideoCallAsAudio() throws Exception {
181        IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
182                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
183
184        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
185        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
186
187        // Use TelecomManager API to answer the ringing call.
188        TelecomManager telecomManager = (TelecomManager) mComponentContextFixture.getTestDouble()
189                .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
190        telecomManager.acceptRingingCall(VideoProfile.STATE_AUDIO_ONLY);
191
192        // The generic answer method on the ConnectionService is used to answer audio-only calls.
193        verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
194                .answer(eq(ids.mConnectionId), any());
195        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
196
197        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
198    }
199
200    /**
201     * Tests the {@link TelecomManager#acceptRingingCall()} API.  Tests simple case of an incoming
202     * video call, where an attempt is made to answer with an invalid video state.
203     *
204     * @throws Exception
205     */
206    @LargeTest
207    public void testTelecomManagerAcceptRingingInvalidVideoState() throws Exception {
208        IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
209                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
210
211        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
212        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
213
214        // Use TelecomManager API to answer the ringing call; the default expected behavior is to
215        // answer using whatever video state the ringing call requests.
216        TelecomManager telecomManager = (TelecomManager) mComponentContextFixture.getTestDouble()
217                .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
218        telecomManager.acceptRingingCall(999 /* invalid videostate */);
219
220        // Answer video API should be called
221        verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
222                .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_BIDIRECTIONAL), any());
223        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
224        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
225    }
226
227    @LargeTest
228    public void testSingleIncomingCallLocalDisconnect() throws Exception {
229        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
230                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
231        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
232        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
233        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
234
235        when(mClockProxy.currentTimeMillis()).thenReturn(TEST_DISCONNECT_TIME);
236        when(mClockProxy.elapsedRealtime()).thenReturn(TEST_DISCONNECT_ELAPSED_TIME);
237        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
238        assertEquals(Call.STATE_DISCONNECTED,
239                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
240        assertEquals(Call.STATE_DISCONNECTED,
241                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
242    }
243
244    @LargeTest
245    public void testSingleIncomingCallRemoteDisconnect() throws Exception {
246        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
247                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
248
249        when(mClockProxy.currentTimeMillis()).thenReturn(TEST_DISCONNECT_TIME);
250        when(mClockProxy.elapsedRealtime()).thenReturn(TEST_DISCONNECT_ELAPSED_TIME);
251        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
252        assertEquals(Call.STATE_DISCONNECTED,
253                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
254        assertEquals(Call.STATE_DISCONNECTED,
255                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
256    }
257
258    @LargeTest
259    public void testIncomingEmergencyCallback() throws Exception {
260        // Make an outgoing emergency call
261        String phoneNumber = "650-555-1212";
262        IdPair ids = startAndMakeDialingEmergencyCall(phoneNumber,
263                mPhoneAccountE0.getAccountHandle(), mConnectionServiceFixtureA);
264        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
265        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
266
267        // Incoming call should be marked as a potential emergency callback
268        Bundle extras = new Bundle();
269        extras.putParcelable(
270                TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
271                Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null));
272        mTelecomSystem.getTelecomServiceImpl().getBinder()
273                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
274
275        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
276        ArgumentCaptor<ConnectionRequest> connectionRequestCaptor
277            = ArgumentCaptor.forClass(ConnectionRequest.class);
278        verify(mConnectionServiceFixtureA.getTestDouble())
279                .createConnection(any(PhoneAccountHandle.class), anyString(),
280                        connectionRequestCaptor.capture(), eq(true), eq(false), any());
281
282        assert(connectionRequestCaptor.getValue().getExtras().containsKey(
283            android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS));
284        assertTrue(connectionRequestCaptor.getValue().getExtras().getLong(
285            android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0) > 0);
286        assert(connectionRequestCaptor.getValue().getExtras().containsKey(
287            TelecomManager.EXTRA_INCOMING_CALL_ADDRESS));
288    }
289
290    @LargeTest
291    public void testOutgoingCallAndSelectPhoneAccount() throws Exception {
292        // Remove default PhoneAccount so that the Call moves into the correct
293        // SELECT_PHONE_ACCOUNT state.
294        mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
295                null, Process.myUserHandle());
296        int startingNumConnections = mConnectionServiceFixtureA.mConnectionById.size();
297        int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
298        String callId = startOutgoingPhoneCallWithNoPhoneAccount("650-555-1212",
299                mConnectionServiceFixtureA);
300        assertEquals(Call.STATE_SELECT_PHONE_ACCOUNT,
301                mInCallServiceFixtureX.getCall(callId).getState());
302        assertEquals(Call.STATE_SELECT_PHONE_ACCOUNT,
303                mInCallServiceFixtureY.getCall(callId).getState());
304        mInCallServiceFixtureX.mInCallAdapter.phoneAccountSelected(callId,
305                mPhoneAccountA0.getAccountHandle(), false);
306
307        IdPair ids = outgoingCallPhoneAccountSelected(mPhoneAccountA0.getAccountHandle(),
308                startingNumConnections, startingNumCalls, mConnectionServiceFixtureA);
309
310        when(mClockProxy.currentTimeMillis()).thenReturn(TEST_DISCONNECT_TIME);
311        when(mClockProxy.elapsedRealtime()).thenReturn(TEST_DISCONNECT_ELAPSED_TIME);
312        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
313        assertEquals(Call.STATE_DISCONNECTED,
314                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
315        assertEquals(Call.STATE_DISCONNECTED,
316                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
317    }
318
319    @LargeTest
320    public void testIncomingCallFromContactWithSendToVoicemailIsRejected() throws Exception {
321        Bundle extras = new Bundle();
322        extras.putParcelable(
323                TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
324                Uri.fromParts(PhoneAccount.SCHEME_TEL, "650-555-1212", null));
325        mTelecomSystem.getTelecomServiceImpl().getBinder()
326                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
327
328        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
329        verify(mConnectionServiceFixtureA.getTestDouble())
330                .createConnection(any(PhoneAccountHandle.class), anyString(),
331                        any(ConnectionRequest.class), eq(true), eq(false), any());
332
333        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
334        assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
335        for (CallerInfoAsyncQueryFactoryFixture.Request request :
336                mCallerInfoAsyncQueryFactoryFixture.mRequests) {
337            CallerInfo sendToVoicemailCallerInfo = new CallerInfo();
338            sendToVoicemailCallerInfo.shouldSendToVoicemail = true;
339            request.replyWithCallerInfo(sendToVoicemailCallerInfo);
340        }
341
342        assertTrueWithTimeout(new Predicate<Void>() {
343            @Override
344            public boolean apply(Void aVoid) {
345                return mConnectionServiceFixtureA.mConnectionService.rejectedCallIds.size() == 1;
346            }
347        });
348        assertTrueWithTimeout(new Predicate<Void>() {
349            @Override
350            public boolean apply(Void aVoid) {
351                return mMissedCallNotifier.missedCallsNotified.size() == 1;
352            }
353        });
354
355        verify(mInCallServiceFixtureX.getTestDouble(), never())
356                .setInCallAdapter(any(IInCallAdapter.class));
357        verify(mInCallServiceFixtureY.getTestDouble(), never())
358                .setInCallAdapter(any(IInCallAdapter.class));
359    }
360
361    @LargeTest
362    public void testIncomingCallCallerInfoLookupTimesOutIsAllowed() throws Exception {
363        when(mClockProxy.currentTimeMillis()).thenReturn(TEST_CREATE_TIME);
364        when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CREATE_ELAPSED_TIME);
365        Bundle extras = new Bundle();
366        extras.putParcelable(
367                TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
368                Uri.fromParts(PhoneAccount.SCHEME_TEL, "650-555-1212", null));
369        mTelecomSystem.getTelecomServiceImpl().getBinder()
370                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
371
372        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
373        verify(mConnectionServiceFixtureA.getTestDouble())
374                .createConnection(any(PhoneAccountHandle.class), anyString(),
375                        any(ConnectionRequest.class), eq(true), eq(false), any());
376
377        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
378        // Never reply to the caller info lookup.
379        assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
380
381        verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
382                .setInCallAdapter(any(IInCallAdapter.class));
383        verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
384                .setInCallAdapter(any(IInCallAdapter.class));
385
386        assertEquals(0, mConnectionServiceFixtureA.mConnectionService.rejectedCallIds.size());
387        assertEquals(0, mMissedCallNotifier.missedCallsNotified.size());
388
389        assertTrueWithTimeout(new Predicate<Void>() {
390            @Override
391            public boolean apply(Void v) {
392                return mInCallServiceFixtureX.mInCallAdapter != null;
393            }
394        });
395
396        verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
397                .addCall(any(ParcelableCall.class));
398        verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
399                .addCall(any(ParcelableCall.class));
400
401        when(mClockProxy.currentTimeMillis()).thenReturn(TEST_CONNECT_TIME);
402        when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CONNECT_ELAPSED_TIME);
403        disconnectCall(mInCallServiceFixtureX.mLatestCallId,
404                mConnectionServiceFixtureA.mLatestConnectionId);
405    }
406
407    @LargeTest
408    public void testIncomingCallFromBlockedNumberIsRejected() throws Exception {
409        String phoneNumber = "650-555-1212";
410        blockNumber(phoneNumber);
411
412        Bundle extras = new Bundle();
413        extras.putParcelable(
414                TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
415                Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null));
416        mTelecomSystem.getTelecomServiceImpl().getBinder()
417                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
418
419        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
420        verify(mConnectionServiceFixtureA.getTestDouble())
421                .createConnection(any(PhoneAccountHandle.class), anyString(),
422                        any(ConnectionRequest.class), eq(true), eq(false), any());
423
424        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
425        assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
426        for (CallerInfoAsyncQueryFactoryFixture.Request request :
427                mCallerInfoAsyncQueryFactoryFixture.mRequests) {
428            request.reply();
429        }
430
431        assertTrueWithTimeout(new Predicate<Void>() {
432            @Override
433            public boolean apply(Void aVoid) {
434                return mConnectionServiceFixtureA.mConnectionService.rejectedCallIds.size() == 1;
435            }
436        });
437        assertEquals(0, mMissedCallNotifier.missedCallsNotified.size());
438
439        verify(mInCallServiceFixtureX.getTestDouble(), never())
440                .setInCallAdapter(any(IInCallAdapter.class));
441        verify(mInCallServiceFixtureY.getTestDouble(), never())
442                .setInCallAdapter(any(IInCallAdapter.class));
443    }
444
445    @LargeTest
446    public void testIncomingCallBlockCheckTimesoutIsAllowed() throws Exception {
447        final CountDownLatch latch = new CountDownLatch(1);
448        String phoneNumber = "650-555-1212";
449        blockNumberWithAnswer(phoneNumber, new Answer<Bundle>() {
450            @Override
451            public Bundle answer(InvocationOnMock invocation) throws Throwable {
452                latch.await(TEST_TIMEOUT * 2, TimeUnit.MILLISECONDS);
453                Bundle bundle = new Bundle();
454                bundle.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, true);
455                return bundle;
456            }
457        });
458
459        IdPair ids = startAndMakeActiveIncomingCall(
460                phoneNumber, mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
461        latch.countDown();
462
463        assertEquals(0, mConnectionServiceFixtureA.mConnectionService.rejectedCallIds.size());
464        assertEquals(0, mMissedCallNotifier.missedCallsNotified.size());
465        disconnectCall(ids.mCallId, ids.mConnectionId);
466    }
467
468    public void do_testDeadlockOnOutgoingCall() throws Exception {
469        final IdPair ids = startOutgoingPhoneCall("650-555-1212",
470                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
471                Process.myUserHandle());
472        rapidFire(
473                new Runnable() {
474                    @Override
475                    public void run() {
476                        while (mCallerInfoAsyncQueryFactoryFixture.mRequests.size() > 0) {
477                            mCallerInfoAsyncQueryFactoryFixture.mRequests.remove(0).reply();
478                        }
479                    }
480                },
481                new Runnable() {
482                    @Override
483                    public void run() {
484                        try {
485                            mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
486                        } catch (Exception e) {
487                            Log.e(this, e, "");
488                        }
489                    }
490                });
491    }
492
493    @MediumTest
494    public void testDeadlockOnOutgoingCall() throws Exception {
495        for (int i = 0; i < 100; i++) {
496            BasicCallTests test = new BasicCallTests();
497            test.setContext(getContext());
498            test.setTestContext(getTestContext());
499            test.setName(getName());
500            test.setUp();
501            test.do_testDeadlockOnOutgoingCall();
502            test.tearDown();
503        }
504    }
505
506    @LargeTest
507    public void testIncomingThenOutgoingCalls() throws Exception {
508        // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
509        IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
510                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
511        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
512                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
513
514        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(incoming.mCallId);
515        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(outgoing.mCallId);
516    }
517
518    @LargeTest
519    public void testOutgoingThenIncomingCalls() throws Exception {
520        // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
521        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
522                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
523        IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
524                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
525        verify(mConnectionServiceFixtureA.getTestDouble())
526                .hold(eq(outgoing.mConnectionId), any());
527        mConnectionServiceFixtureA.mConnectionById.get(outgoing.mConnectionId).state =
528                Connection.STATE_HOLDING;
529        mConnectionServiceFixtureA.sendSetOnHold(outgoing.mConnectionId);
530        assertEquals(Call.STATE_HOLDING,
531                mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
532        assertEquals(Call.STATE_HOLDING,
533                mInCallServiceFixtureY.getCall(outgoing.mCallId).getState());
534
535        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(incoming.mCallId);
536        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(outgoing.mCallId);
537    }
538
539    @LargeTest
540    public void testAudioManagerOperations() throws Exception {
541        AudioManager audioManager = (AudioManager) mComponentContextFixture.getTestDouble()
542                .getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
543
544        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
545                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
546
547        verify(audioManager, timeout(TEST_TIMEOUT)).requestAudioFocusForCall(anyInt(), anyInt());
548        verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
549                .setMode(AudioManager.MODE_IN_CALL);
550
551        mInCallServiceFixtureX.mInCallAdapter.mute(true);
552        verify(mAudioService, timeout(TEST_TIMEOUT))
553                .setMicrophoneMute(eq(true), any(String.class), any(Integer.class));
554        mInCallServiceFixtureX.mInCallAdapter.mute(false);
555        verify(mAudioService, timeout(TEST_TIMEOUT))
556                .setMicrophoneMute(eq(false), any(String.class), any(Integer.class));
557
558        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
559        verify(audioManager, timeout(TEST_TIMEOUT))
560                .setSpeakerphoneOn(true);
561        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
562        verify(audioManager, timeout(TEST_TIMEOUT))
563                .setSpeakerphoneOn(false);
564
565        mConnectionServiceFixtureA.
566                sendSetDisconnected(outgoing.mConnectionId, DisconnectCause.REMOTE);
567
568        verify(audioManager, timeout(TEST_TIMEOUT))
569                .abandonAudioFocusForCall();
570        verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
571                .setMode(AudioManager.MODE_NORMAL);
572    }
573
574    private void rapidFire(Runnable... tasks) {
575        final CyclicBarrier barrier = new CyclicBarrier(tasks.length);
576        final CountDownLatch latch = new CountDownLatch(tasks.length);
577        for (int i = 0; i < tasks.length; i++) {
578            final Runnable task = tasks[i];
579            new Thread(new Runnable() {
580                @Override
581                public void run() {
582                    try {
583                        barrier.await();
584                        task.run();
585                    } catch (InterruptedException | BrokenBarrierException e){
586                        Log.e(BasicCallTests.this, e, "Unexpectedly interrupted");
587                    } finally {
588                        latch.countDown();
589                    }
590                }
591            }).start();
592        }
593        try {
594            latch.await();
595        } catch (InterruptedException e) {
596            Log.e(BasicCallTests.this, e, "Unexpectedly interrupted");
597        }
598    }
599
600    @MediumTest
601    public void testBasicConferenceCall() throws Exception {
602        makeConferenceCall();
603    }
604
605    @MediumTest
606    public void testAddCallToConference1() throws Exception {
607        ParcelableCall conferenceCall = makeConferenceCall();
608        IdPair callId3 = startAndMakeActiveOutgoingCall("650-555-1214",
609                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
610        // testAddCallToConference{1,2} differ in the order of arguments to InCallAdapter#conference
611        mInCallServiceFixtureX.getInCallAdapter().conference(
612                conferenceCall.getId(), callId3.mCallId);
613        Thread.sleep(200);
614
615        ParcelableCall call3 = mInCallServiceFixtureX.getCall(callId3.mCallId);
616        ParcelableCall updatedConference = mInCallServiceFixtureX.getCall(conferenceCall.getId());
617        assertEquals(conferenceCall.getId(), call3.getParentCallId());
618        assertEquals(3, updatedConference.getChildCallIds().size());
619        assertTrue(updatedConference.getChildCallIds().contains(callId3.mCallId));
620    }
621
622    @MediumTest
623    public void testAddCallToConference2() throws Exception {
624        ParcelableCall conferenceCall = makeConferenceCall();
625        IdPair callId3 = startAndMakeActiveOutgoingCall("650-555-1214",
626                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
627        mInCallServiceFixtureX.getInCallAdapter()
628                .conference(callId3.mCallId, conferenceCall.getId());
629        Thread.sleep(200);
630
631        ParcelableCall call3 = mInCallServiceFixtureX.getCall(callId3.mCallId);
632        ParcelableCall updatedConference = mInCallServiceFixtureX.getCall(conferenceCall.getId());
633        assertEquals(conferenceCall.getId(), call3.getParentCallId());
634        assertEquals(3, updatedConference.getChildCallIds().size());
635        assertTrue(updatedConference.getChildCallIds().contains(callId3.mCallId));
636    }
637
638    /**
639     * Tests the {@link Call#pullExternalCall()} API.  Verifies that if a call is not an external
640     * call, no pull call request is made to the connection service.
641     *
642     * @throws Exception
643     */
644    @MediumTest
645    public void testPullNonExternalCall() throws Exception {
646        // TODO: Revisit this unit test once telecom support for filtering external calls from
647        // InCall services is implemented.
648        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
649                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
650        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
651
652        // Attempt to pull the call and verify the API call makes it through
653        mInCallServiceFixtureX.mInCallAdapter.pullExternalCall(ids.mCallId);
654        Thread.sleep(TEST_TIMEOUT);
655        verify(mConnectionServiceFixtureA.getTestDouble(), never())
656                .pullExternalCall(eq(ids.mCallId), any());
657    }
658
659    /**
660     * Tests the {@link Connection#sendConnectionEvent(String, Bundle)} API.
661     *
662     * @throws Exception
663     */
664    @MediumTest
665    public void testSendConnectionEventNull() throws Exception {
666        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
667                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
668        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
669        mConnectionServiceFixtureA.sendConnectionEvent(ids.mConnectionId, TEST_EVENT, null);
670        verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
671                .onConnectionEvent(ids.mCallId, TEST_EVENT, null);
672    }
673
674    /**
675     * Tests the {@link Connection#sendConnectionEvent(String, Bundle)} API.
676     *
677     * @throws Exception
678     */
679    @MediumTest
680    public void testSendConnectionEventNotNull() throws Exception {
681        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
682                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
683        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
684
685        Bundle testBundle = new Bundle();
686        testBundle.putString(TEST_BUNDLE_KEY, "TEST");
687
688        ArgumentCaptor<Bundle> bundleArgumentCaptor = ArgumentCaptor.forClass(Bundle.class);
689        mConnectionServiceFixtureA.sendConnectionEvent(ids.mConnectionId, TEST_EVENT, testBundle);
690        verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
691                .onConnectionEvent(eq(ids.mCallId), eq(TEST_EVENT), bundleArgumentCaptor.capture());
692        assert (bundleArgumentCaptor.getValue().containsKey(TEST_BUNDLE_KEY));
693    }
694
695    /**
696     * Tests the {@link Call#sendCallEvent(String, Bundle)} API.
697     *
698     * @throws Exception
699     */
700    @MediumTest
701    public void testSendCallEventNull() throws Exception {
702        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
703                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
704        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
705
706        mInCallServiceFixtureX.mInCallAdapter.sendCallEvent(ids.mCallId, TEST_EVENT, null);
707        verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
708                .sendCallEvent(eq(ids.mConnectionId), eq(TEST_EVENT), isNull(Bundle.class), any());
709    }
710
711    /**
712     * Tests the {@link Call#sendCallEvent(String, Bundle)} API.
713     *
714     * @throws Exception
715     */
716    @MediumTest
717    public void testSendCallEventNonNull() throws Exception {
718        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
719                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
720        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
721
722        Bundle testBundle = new Bundle();
723        testBundle.putString(TEST_BUNDLE_KEY, "TEST");
724
725        ArgumentCaptor<Bundle> bundleArgumentCaptor = ArgumentCaptor.forClass(Bundle.class);
726        mInCallServiceFixtureX.mInCallAdapter.sendCallEvent(ids.mCallId, TEST_EVENT,
727                testBundle);
728        verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
729                .sendCallEvent(eq(ids.mConnectionId), eq(TEST_EVENT),
730                        bundleArgumentCaptor.capture(), any());
731        assert (bundleArgumentCaptor.getValue().containsKey(TEST_BUNDLE_KEY));
732    }
733
734    private void blockNumber(String phoneNumber) throws Exception {
735        blockNumberWithAnswer(phoneNumber, new Answer<Bundle>() {
736            @Override
737            public Bundle answer(InvocationOnMock invocation) throws Throwable {
738                Bundle bundle = new Bundle();
739                bundle.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, true);
740                return bundle;
741            }
742        });
743    }
744
745    private void blockNumberWithAnswer(String phoneNumber, Answer answer) throws Exception {
746        when(getBlockedNumberProvider().call(
747                anyString(),
748                eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
749                eq(phoneNumber),
750                isNull(Bundle.class))).thenAnswer(answer);
751    }
752
753    private void verifyNoBlockChecks() {
754        verifyZeroInteractions(getBlockedNumberProvider());
755    }
756
757    private IContentProvider getBlockedNumberProvider() {
758        return mSpyContext.getContentResolver().acquireProvider(BlockedNumberContract.AUTHORITY);
759    }
760
761    private void disconnectCall(String callId, String connectionId) throws Exception {
762        when(mClockProxy.currentTimeMillis()).thenReturn(TEST_DISCONNECT_TIME);
763        when(mClockProxy.elapsedRealtime()).thenReturn(TEST_DISCONNECT_ELAPSED_TIME);
764        mConnectionServiceFixtureA.sendSetDisconnected(connectionId, DisconnectCause.LOCAL);
765        assertEquals(Call.STATE_DISCONNECTED, mInCallServiceFixtureX.getCall(callId).getState());
766        assertEquals(Call.STATE_DISCONNECTED, mInCallServiceFixtureY.getCall(callId).getState());
767        assertEquals(TEST_CREATE_TIME,
768                mInCallServiceFixtureX.getCall(callId).getCreationTimeMillis());
769        assertEquals(TEST_CREATE_TIME,
770                mInCallServiceFixtureY.getCall(callId).getCreationTimeMillis());
771    }
772
773    /**
774     * Tests to make sure that the Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY property is set on a
775     * Call that is based on a Connection with the Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY
776     * property set.
777     */
778    @MediumTest
779    public void testCdmaEnhancedPrivacyVoiceCall() throws Exception {
780        mConnectionServiceFixtureA.mConnectionServiceDelegate.mProperties =
781                Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY;
782
783        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
784                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
785        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
786
787        assertTrue(Call.Details.hasProperty(
788                mInCallServiceFixtureX.getCall(ids.mCallId).getProperties(),
789                Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY));
790    }
791
792    /**
793     * Tests to make sure that Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY is dropped
794     * when the Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY property is removed from the Connection.
795     */
796    @MediumTest
797    public void testDropCdmaEnhancedPrivacyVoiceCall() throws Exception {
798        mConnectionServiceFixtureA.mConnectionServiceDelegate.mProperties =
799                Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY;
800
801        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
802                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
803        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
804        mConnectionServiceFixtureA.mLatestConnection.setConnectionProperties(0);
805
806        assertFalse(Call.Details.hasProperty(
807                mInCallServiceFixtureX.getCall(ids.mCallId).getProperties(),
808                Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY));
809    }
810
811    /**
812     * Tests the {@link Call#pullExternalCall()} API.  Ensures that an external call which is
813     * pullable can be pulled.
814     *
815     * @throws Exception
816     */
817    @LargeTest
818    public void testPullExternalCall() throws Exception {
819        // TODO: Revisit this unit test once telecom support for filtering external calls from
820        // InCall services is implemented.
821        mConnectionServiceFixtureA.mConnectionServiceDelegate.mCapabilities =
822                Connection.CAPABILITY_CAN_PULL_CALL;
823        mConnectionServiceFixtureA.mConnectionServiceDelegate.mProperties =
824                Connection.PROPERTY_IS_EXTERNAL_CALL;
825
826        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
827                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
828        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
829
830        // Attempt to pull the call and verify the API call makes it through
831        mInCallServiceFixtureX.mInCallAdapter.pullExternalCall(ids.mCallId);
832        verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
833                .pullExternalCall(eq(ids.mConnectionId), any());
834    }
835
836    /**
837     * Tests the {@link Call#pullExternalCall()} API.  Verifies that if an external call is not
838     * marked as pullable that the connection service does not get an API call to pull the external
839     * call.
840     *
841     * @throws Exception
842     */
843    @LargeTest
844    public void testPullNonPullableExternalCall() throws Exception {
845        // TODO: Revisit this unit test once telecom support for filtering external calls from
846        // InCall services is implemented.
847        mConnectionServiceFixtureA.mConnectionServiceDelegate.mProperties =
848                Connection.PROPERTY_IS_EXTERNAL_CALL;
849
850        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
851                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
852        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
853
854        // Attempt to pull the call and verify the API call makes it through
855        mInCallServiceFixtureX.mInCallAdapter.pullExternalCall(ids.mCallId);
856        Thread.sleep(TEST_TIMEOUT);
857        verify(mConnectionServiceFixtureA.getTestDouble(), never())
858                .pullExternalCall(eq(ids.mConnectionId), any());
859    }
860
861    @LargeTest
862    public void testEmergencyCallFailMoveToSecondSim() throws Exception {
863        IdPair ids = startAndMakeDialingEmergencyCall("650-555-1212",
864                mPhoneAccountE0.getAccountHandle(), mConnectionServiceFixtureA);
865        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
866        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
867
868        // The Emergency Call has failed on the default SIM with an ERROR Disconnect Cause. Retry
869        // with the other SIM PhoneAccount
870        IdPair newIds = triggerEmergencyRedial(mPhoneAccountE1.getAccountHandle(),
871                mConnectionServiceFixtureA, ids);
872
873        // Call should be active on the E1 PhoneAccount
874        mConnectionServiceFixtureA.sendSetActive(newIds.mConnectionId);
875        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(newIds.mCallId).getState());
876        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(newIds.mCallId).getState());
877        assertEquals(mInCallServiceFixtureX.getCall(ids.mCallId).getAccountHandle(),
878                mPhoneAccountE1.getAccountHandle());
879    }
880
881    /**
882     * Test scenario where the user starts an outgoing video call with no selected PhoneAccount, and
883     * then subsequently selects a PhoneAccount which supports video calling.
884     * @throws Exception
885     */
886    @LargeTest
887    public void testOutgoingCallSelectPhoneAccountVideo() throws Exception {
888        startOutgoingPhoneCallPendingCreateConnection("650-555-1212",
889                null, mConnectionServiceFixtureA,
890                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
891        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
892                .iterator().next();
893        assert(call.isVideoCallingSupported());
894        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
895
896        // Change the phone account to one which supports video calling.
897        call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
898        assert(call.isVideoCallingSupported());
899        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
900    }
901
902    /**
903     * Test scenario where the user starts an outgoing video call with no selected PhoneAccount, and
904     * then subsequently selects a PhoneAccount which does not support video calling.
905     * @throws Exception
906     */
907    @FlakyTest
908    @LargeTest
909    public void testOutgoingCallSelectPhoneAccountNoVideo() throws Exception {
910        startOutgoingPhoneCallPendingCreateConnection("650-555-1212",
911                null, mConnectionServiceFixtureA,
912                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
913        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
914                .iterator().next();
915        assert(call.isVideoCallingSupported());
916        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
917
918        // Change the phone account to one which does not support video calling.
919        call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
920        assert(!call.isVideoCallingSupported());
921        assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
922    }
923}
924