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