• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 
17 package com.android.server.telecom.testapps;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.media.MediaPlayer;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.support.v4.content.LocalBroadcastManager;
29 import android.telecom.Conference;
30 import android.telecom.Connection;
31 import android.telecom.DisconnectCause;
32 import android.telecom.PhoneAccount;
33 import android.telecom.ConnectionRequest;
34 import android.telecom.ConnectionService;
35 import android.telecom.PhoneAccountHandle;
36 import android.telecom.TelecomManager;
37 import android.telecom.VideoProfile;
38 import android.util.Log;
39 import android.widget.Toast;
40 
41 import com.android.server.telecom.testapps.R;
42 
43 import java.lang.String;
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Random;
47 
48 /**
49  * Service which provides fake calls to test the ConnectionService interface.
50  * TODO: Rename all classes in the directory to Dummy* (e.g., DummyConnectionService).
51  */
52 public class TestConnectionService extends ConnectionService {
53     /**
54      * Intent extra used to pass along the video state for a new test call.
55      */
56     public static final String EXTRA_START_VIDEO_STATE = "extra_start_video_state";
57 
58     public static final String EXTRA_HANDLE = "extra_handle";
59 
60     /**
61      * Random number generator used to generate phone numbers.
62      */
63     private Random mRandom = new Random();
64 
65     private final class TestConference extends Conference {
66 
67         private final Connection.Listener mConnectionListener = new Connection.Listener() {
68             @Override
69             public void onDestroyed(Connection c) {
70                 removeConnection(c);
71                 if (getConnections().size() == 0) {
72                     setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
73                     destroy();
74                 }
75             }
76         };
77 
TestConference(Connection a, Connection b)78         public TestConference(Connection a, Connection b) {
79             super(null);
80             setConnectionCapabilities(
81                     Connection.CAPABILITY_SUPPORT_HOLD |
82                     Connection.CAPABILITY_HOLD |
83                     Connection.CAPABILITY_MUTE |
84                     Connection.CAPABILITY_MANAGE_CONFERENCE);
85             addConnection(a);
86             addConnection(b);
87 
88             a.addConnectionListener(mConnectionListener);
89             b.addConnectionListener(mConnectionListener);
90 
91             a.setConference(this);
92             b.setConference(this);
93 
94             setActive();
95         }
96 
97         @Override
onDisconnect()98         public void onDisconnect() {
99             for (Connection c : getConnections()) {
100                 c.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
101                 c.destroy();
102             }
103         }
104 
105         @Override
onSeparate(Connection connection)106         public void onSeparate(Connection connection) {
107             if (getConnections().contains(connection)) {
108                 connection.setConference(null);
109                 removeConnection(connection);
110                 connection.removeConnectionListener(mConnectionListener);
111             }
112         }
113 
114         @Override
onHold()115         public void onHold() {
116             for (Connection c : getConnections()) {
117                 c.setOnHold();
118             }
119             setOnHold();
120         }
121 
122         @Override
onUnhold()123         public void onUnhold() {
124             for (Connection c : getConnections()) {
125                 c.setActive();
126             }
127             setActive();
128         }
129     }
130 
131     final class TestConnection extends Connection {
132         private final boolean mIsIncoming;
133 
134         /** Used to cleanup camera and media when done with connection. */
135         private TestVideoProvider mTestVideoCallProvider;
136 
137         private BroadcastReceiver mHangupReceiver = new BroadcastReceiver() {
138             @Override
139             public void onReceive(Context context, Intent intent) {
140                 setDisconnected(new DisconnectCause(DisconnectCause.MISSED));
141                 destroyCall(TestConnection.this);
142                 destroy();
143             }
144         };
145 
146         private BroadcastReceiver mUpgradeRequestReceiver = new BroadcastReceiver() {
147             @Override
148             public void onReceive(Context context, Intent intent) {
149                 final int request = Integer.parseInt(intent.getData().getSchemeSpecificPart());
150                 final VideoProfile videoProfile = new VideoProfile(request);
151                 mTestVideoCallProvider.receiveSessionModifyRequest(videoProfile);
152             }
153         };
154 
TestConnection(boolean isIncoming)155         TestConnection(boolean isIncoming) {
156             mIsIncoming = isIncoming;
157             // Assume all calls are video capable.
158             int capabilities = getConnectionCapabilities();
159             capabilities |= CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL;
160             capabilities |= CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL;
161             capabilities |= CAPABILITY_CAN_UPGRADE_TO_VIDEO;
162             capabilities |= CAPABILITY_MUTE;
163             capabilities |= CAPABILITY_SUPPORT_HOLD;
164             capabilities |= CAPABILITY_HOLD;
165             capabilities |= CAPABILITY_RESPOND_VIA_TEXT;
166             setConnectionCapabilities(capabilities);
167 
168             if (isIncoming) {
169                 putExtra(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
170             }
171             LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(
172                     mHangupReceiver, new IntentFilter(TestCallActivity.ACTION_HANGUP_CALLS));
173             final IntentFilter filter =
174                     new IntentFilter(TestCallActivity.ACTION_SEND_UPGRADE_REQUEST);
175             filter.addDataScheme("int");
176             LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(
177                     mUpgradeRequestReceiver, filter);
178         }
179 
startOutgoing()180         void startOutgoing() {
181             setDialing();
182             mHandler.postDelayed(new Runnable() {
183                 @Override
184                 public void run() {
185                     setActive();
186                     activateCall(TestConnection.this);
187                 }
188             }, 4000);
189         }
190 
191         /** ${inheritDoc} */
192         @Override
onAbort()193         public void onAbort() {
194             destroyCall(this);
195             destroy();
196         }
197 
198         /** ${inheritDoc} */
199         @Override
onAnswer(int videoState)200         public void onAnswer(int videoState) {
201             setVideoState(videoState);
202             activateCall(this);
203             setActive();
204             updateConferenceable();
205         }
206 
207         /** ${inheritDoc} */
208         @Override
onPlayDtmfTone(char c)209         public void onPlayDtmfTone(char c) {
210             if (c == '1') {
211                 setDialing();
212             }
213         }
214 
215         /** ${inheritDoc} */
216         @Override
onStopDtmfTone()217         public void onStopDtmfTone() { }
218 
219         /** ${inheritDoc} */
220         @Override
onDisconnect()221         public void onDisconnect() {
222             setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
223             destroyCall(this);
224             destroy();
225         }
226 
227         /** ${inheritDoc} */
228         @Override
onHold()229         public void onHold() {
230             setOnHold();
231         }
232 
233         /** ${inheritDoc} */
234         @Override
onReject()235         public void onReject() {
236             setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
237             destroyCall(this);
238             destroy();
239         }
240 
241         /** ${inheritDoc} */
242         @Override
onUnhold()243         public void onUnhold() {
244             setActive();
245         }
246 
setTestVideoCallProvider(TestVideoProvider testVideoCallProvider)247         public void setTestVideoCallProvider(TestVideoProvider testVideoCallProvider) {
248             mTestVideoCallProvider = testVideoCallProvider;
249         }
250 
cleanup()251         public void cleanup() {
252             LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(
253                     mHangupReceiver);
254             LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(
255                     mUpgradeRequestReceiver);
256         }
257 
258         /**
259          * Stops playback of test videos.
260          */
stopAndCleanupMedia()261         private void stopAndCleanupMedia() {
262             if (mTestVideoCallProvider != null) {
263                 mTestVideoCallProvider.stopAndCleanupMedia();
264                 mTestVideoCallProvider.stopCamera();
265             }
266         }
267     }
268 
269     private final List<TestConnection> mCalls = new ArrayList<>();
270     private final Handler mHandler = new Handler();
271 
272     /** Used to play an audio tone during a call. */
273     private MediaPlayer mMediaPlayer;
274 
275     @Override
onUnbind(Intent intent)276     public boolean onUnbind(Intent intent) {
277         log("onUnbind");
278         mMediaPlayer = null;
279         return super.onUnbind(intent);
280     }
281 
282     @Override
onConference(Connection a, Connection b)283     public void onConference(Connection a, Connection b) {
284         addConference(new TestConference(a, b));
285     }
286 
287     @Override
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerAccount, final ConnectionRequest originalRequest)288     public Connection onCreateOutgoingConnection(
289             PhoneAccountHandle connectionManagerAccount,
290             final ConnectionRequest originalRequest) {
291 
292         final Uri handle = originalRequest.getAddress();
293         String number = originalRequest.getAddress().getSchemeSpecificPart();
294         log("call, number: " + number);
295 
296         // Crash on 555-DEAD to test call service crashing.
297         if ("5550340".equals(number)) {
298             throw new RuntimeException("Goodbye, cruel world.");
299         }
300 
301         Bundle extras = originalRequest.getExtras();
302         String gatewayPackage = extras.getString(TelecomManager.GATEWAY_PROVIDER_PACKAGE);
303         Uri originalHandle = extras.getParcelable(TelecomManager.GATEWAY_ORIGINAL_ADDRESS);
304 
305         if (extras.containsKey(TelecomManager.EXTRA_CALL_SUBJECT)) {
306             String callSubject = extras.getString(TelecomManager.EXTRA_CALL_SUBJECT);
307             log("Got subject: " + callSubject);
308             Toast.makeText(getApplicationContext(), "Got subject :" + callSubject,
309                     Toast.LENGTH_SHORT).show();
310         }
311 
312         log("gateway package [" + gatewayPackage + "], original handle [" +
313                 originalHandle + "]");
314 
315         final TestConnection connection = new TestConnection(false /* isIncoming */);
316         setAddress(connection, handle);
317 
318         // If the number starts with 555, then we handle it ourselves. If not, then we
319         // use a remote connection service.
320         // TODO: Have a special phone number to test the account-picker dialog flow.
321         if (number != null && number.startsWith("555")) {
322             // Normally we would use the original request as is, but for testing purposes, we are
323             // adding ".." to the end of the number to follow its path more easily through the logs.
324             final ConnectionRequest request = new ConnectionRequest(
325                     originalRequest.getAccountHandle(),
326                     Uri.fromParts(handle.getScheme(),
327                     handle.getSchemeSpecificPart() + "..", ""),
328                     originalRequest.getExtras(),
329                     originalRequest.getVideoState());
330             connection.setVideoState(originalRequest.getVideoState());
331             addVideoProvider(connection);
332             addCall(connection);
333             connection.startOutgoing();
334 
335             for (Connection c : getAllConnections()) {
336                 c.setOnHold();
337             }
338         } else {
339             log("Not a test number");
340         }
341         return connection;
342     }
343 
344     @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerAccount, final ConnectionRequest request)345     public Connection onCreateIncomingConnection(
346             PhoneAccountHandle connectionManagerAccount,
347             final ConnectionRequest request) {
348         PhoneAccountHandle accountHandle = request.getAccountHandle();
349         ComponentName componentName = new ComponentName(this, TestConnectionService.class);
350 
351         if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
352             final TestConnection connection = new TestConnection(true);
353             // Get the stashed intent extra that determines if this is a video call or audio call.
354             Bundle extras = request.getExtras();
355             int videoState = extras.getInt(EXTRA_START_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY);
356             Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
357 
358             // Use dummy number for testing incoming calls.
359             Uri address = providedHandle == null ?
360                     Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(
361                             VideoProfile.isVideo(videoState)), null)
362                     : providedHandle;
363             connection.setVideoState(videoState);
364 
365             Bundle connectionExtras = connection.getExtras();
366             if (connectionExtras == null) {
367                 connectionExtras = new Bundle();
368             }
369 
370             // Randomly choose a varying length call subject.
371             int subjectFormat = mRandom.nextInt(3);
372             if (subjectFormat == 0) {
373                 connectionExtras.putString(Connection.EXTRA_CALL_SUBJECT,
374                         "This is a test of call subject lines. Subjects for a call can be long " +
375                                 " and can go even longer.");
376             } else if (subjectFormat == 1) {
377                 connectionExtras.putString(Connection.EXTRA_CALL_SUBJECT,
378                         "This is a test of call subject lines.");
379             }
380             connection.putExtras(connectionExtras);
381 
382             setAddress(connection, address);
383 
384             addVideoProvider(connection);
385 
386             addCall(connection);
387 
388             ConnectionRequest newRequest = new ConnectionRequest(
389                     request.getAccountHandle(),
390                     address,
391                     request.getExtras(),
392                     videoState);
393             connection.setVideoState(videoState);
394             return connection;
395         } else {
396             return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.ERROR,
397                     "Invalid inputs: " + accountHandle + " " + componentName));
398         }
399     }
400 
401     @Override
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)402     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
403             final ConnectionRequest request) {
404         PhoneAccountHandle accountHandle = request.getAccountHandle();
405         ComponentName componentName = new ComponentName(this, TestConnectionService.class);
406         if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
407             final TestConnection connection = new TestConnection(false);
408             final Bundle extras = request.getExtras();
409             final Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
410 
411             Uri handle = providedHandle == null ?
412                     Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(false), null)
413                     : providedHandle;
414 
415             connection.setAddress(handle,  TelecomManager.PRESENTATION_ALLOWED);
416             connection.setDialing();
417 
418             addCall(connection);
419             return connection;
420         } else {
421             return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.ERROR,
422                     "Invalid inputs: " + accountHandle + " " + componentName));
423         }
424     }
425 
addVideoProvider(TestConnection connection)426     private void addVideoProvider(TestConnection connection) {
427         TestVideoProvider testVideoCallProvider =
428                 new TestVideoProvider(getApplicationContext(), connection);
429         connection.setVideoProvider(testVideoCallProvider);
430 
431         // Keep reference to original so we can clean up the media players later.
432         connection.setTestVideoCallProvider(testVideoCallProvider);
433     }
434 
activateCall(TestConnection connection)435     private void activateCall(TestConnection connection) {
436         if (mMediaPlayer == null) {
437             mMediaPlayer = createMediaPlayer();
438         }
439         if (!mMediaPlayer.isPlaying()) {
440             mMediaPlayer.start();
441         }
442     }
443 
destroyCall(TestConnection connection)444     private void destroyCall(TestConnection connection) {
445         connection.cleanup();
446         mCalls.remove(connection);
447 
448         // Ensure any playing media and camera resources are released.
449         connection.stopAndCleanupMedia();
450 
451         // Stops audio if there are no more calls.
452         if (mCalls.isEmpty() && mMediaPlayer != null && mMediaPlayer.isPlaying()) {
453             mMediaPlayer.stop();
454             mMediaPlayer.release();
455             mMediaPlayer = createMediaPlayer();
456         }
457 
458         updateConferenceable();
459     }
460 
addCall(TestConnection connection)461     private void addCall(TestConnection connection) {
462         mCalls.add(connection);
463         updateConferenceable();
464     }
465 
updateConferenceable()466     private void updateConferenceable() {
467         List<Connection> freeConnections = new ArrayList<>();
468         freeConnections.addAll(mCalls);
469         for (int i = 0; i < freeConnections.size(); i++) {
470             if (freeConnections.get(i).getConference() != null) {
471                 freeConnections.remove(i);
472             }
473         }
474         for (int i = 0; i < freeConnections.size(); i++) {
475             Connection c = freeConnections.remove(i);
476             c.setConferenceableConnections(freeConnections);
477             freeConnections.add(i, c);
478         }
479     }
480 
setAddress(Connection connection, Uri address)481     private void setAddress(Connection connection, Uri address) {
482         connection.setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
483         if ("5551234".equals(address.getSchemeSpecificPart())) {
484             connection.setCallerDisplayName("Hello World", TelecomManager.PRESENTATION_ALLOWED);
485         }
486     }
487 
createMediaPlayer()488     private MediaPlayer createMediaPlayer() {
489         // Prepare the media player to play a tone when there is a call.
490         MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.beep_boop);
491         mediaPlayer.setLooping(true);
492         return mediaPlayer;
493     }
494 
log(String msg)495     private static void log(String msg) {
496         Log.w("telecomtestcs", "[TestConnectionService] " + msg);
497     }
498 
499     /**
500      * Generates a random phone number of format 555YXXX.  Where Y will be {@code 1} if the
501      * phone number is for a video call and {@code 0} for an audio call.  XXX is a randomly
502      * generated phone number.
503      *
504      * @param isVideo {@code True} if the call is a video call.
505      * @return The phone number.
506      */
getDummyNumber(boolean isVideo)507     private String getDummyNumber(boolean isVideo) {
508         int videoDigit = isVideo ? 1 : 0;
509         int number = mRandom.nextInt(999);
510         return String.format("555%s%03d", videoDigit, number);
511     }
512 }
513 
514