• 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.ComponentName;
20 import android.content.Intent;
21 import android.media.MediaPlayer;
22 import android.net.Uri;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.telecom.AudioState;
26 import android.telecom.Conference;
27 import android.telecom.Connection;
28 import android.telecom.DisconnectCause;
29 import android.telecom.PhoneAccount;
30 import android.telecom.PhoneCapabilities;
31 import android.telecom.ConnectionRequest;
32 import android.telecom.ConnectionService;
33 import android.telecom.PhoneAccountHandle;
34 import android.telecom.RemoteConnection;
35 import android.telecom.StatusHints;
36 import android.telecom.TelecomManager;
37 import android.telecom.VideoProfile;
38 import android.util.Log;
39 
40 import com.android.server.telecom.tests.R;
41 
42 import java.lang.String;
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.Random;
46 
47 /**
48  * Service which provides fake calls to test the ConnectionService interface.
49  * TODO: Rename all classes in the directory to Dummy* (e.g., DummyConnectionService).
50  */
51 public class TestConnectionService extends ConnectionService {
52     /**
53      * Intent extra used to pass along whether a call is video or audio based on the user's choice
54      * in the notification.
55      */
56     public static final String EXTRA_IS_VIDEO_CALL = "extra_is_video_call";
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 
81             addConnection(a);
82             addConnection(b);
83 
84             a.addConnectionListener(mConnectionListener);
85             b.addConnectionListener(mConnectionListener);
86 
87             a.setConference(this);
88             b.setConference(this);
89 
90             setActive();
91         }
92 
93         @Override
onDisconnect()94         public void onDisconnect() {
95             for (Connection c : getConnections()) {
96                 c.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
97                 c.destroy();
98             }
99         }
100 
101         @Override
onSeparate(Connection connection)102         public void onSeparate(Connection connection) {
103             if (getConnections().contains(connection)) {
104                 connection.setConference(null);
105                 removeConnection(connection);
106                 connection.removeConnectionListener(mConnectionListener);
107             }
108         }
109 
110         @Override
onHold()111         public void onHold() {
112             for (Connection c : getConnections()) {
113                 c.setOnHold();
114             }
115             setOnHold();
116         }
117 
118         @Override
onUnhold()119         public void onUnhold() {
120             for (Connection c : getConnections()) {
121                 c.setActive();
122             }
123             setActive();
124         }
125     }
126 
127     private final class TestConnection extends Connection {
128         private final boolean mIsIncoming;
129 
130         /** Used to cleanup camera and media when done with connection. */
131         private TestVideoProvider mTestVideoCallProvider;
132 
TestConnection(boolean isIncoming)133         TestConnection(boolean isIncoming) {
134             mIsIncoming = isIncoming;
135             // Assume all calls are video capable.
136             int capabilities = getCallCapabilities();
137             capabilities |= PhoneCapabilities.SUPPORTS_VT_LOCAL;
138             capabilities |= PhoneCapabilities.ADD_CALL;
139             capabilities |= PhoneCapabilities.MUTE;
140             capabilities |= PhoneCapabilities.SUPPORT_HOLD;
141             capabilities |= PhoneCapabilities.HOLD;
142             setCallCapabilities(capabilities);
143         }
144 
startOutgoing()145         void startOutgoing() {
146             setDialing();
147             mHandler.postDelayed(new Runnable() {
148                 @Override
149                 public void run() {
150                     setActive();
151                     activateCall(TestConnection.this);
152                 }
153             }, 4000);
154         }
155 
156         /** ${inheritDoc} */
157         @Override
onAbort()158         public void onAbort() {
159             destroyCall(this);
160             destroy();
161         }
162 
163         /** ${inheritDoc} */
164         @Override
onAnswer(int videoState)165         public void onAnswer(int videoState) {
166             setVideoState(videoState);
167             activateCall(this);
168             setActive();
169             updateConferenceable();
170         }
171 
172         /** ${inheritDoc} */
173         @Override
onPlayDtmfTone(char c)174         public void onPlayDtmfTone(char c) {
175             if (c == '1') {
176                 setDialing();
177             }
178         }
179 
180         /** ${inheritDoc} */
181         @Override
onStopDtmfTone()182         public void onStopDtmfTone() { }
183 
184         /** ${inheritDoc} */
185         @Override
onDisconnect()186         public void onDisconnect() {
187             setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
188             destroyCall(this);
189             destroy();
190         }
191 
192         /** ${inheritDoc} */
193         @Override
onHold()194         public void onHold() {
195             setOnHold();
196         }
197 
198         /** ${inheritDoc} */
199         @Override
onReject()200         public void onReject() {
201             setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
202             destroyCall(this);
203             destroy();
204         }
205 
206         /** ${inheritDoc} */
207         @Override
onUnhold()208         public void onUnhold() {
209             setActive();
210         }
211 
212         @Override
onAudioStateChanged(AudioState state)213         public void onAudioStateChanged(AudioState state) { }
214 
setTestVideoCallProvider(TestVideoProvider testVideoCallProvider)215         public void setTestVideoCallProvider(TestVideoProvider testVideoCallProvider) {
216             mTestVideoCallProvider = testVideoCallProvider;
217         }
218 
219         /**
220          * Stops playback of test videos.
221          */
stopAndCleanupMedia()222         private void stopAndCleanupMedia() {
223             if (mTestVideoCallProvider != null) {
224                 mTestVideoCallProvider.stopAndCleanupMedia();
225                 mTestVideoCallProvider.stopCamera();
226             }
227         }
228     }
229 
230     private final List<TestConnection> mCalls = new ArrayList<>();
231     private final Handler mHandler = new Handler();
232 
233     /** Used to play an audio tone during a call. */
234     private MediaPlayer mMediaPlayer;
235 
236     @Override
onUnbind(Intent intent)237     public boolean onUnbind(Intent intent) {
238         log("onUnbind");
239         mMediaPlayer = null;
240         return super.onUnbind(intent);
241     }
242 
243     @Override
onConference(Connection a, Connection b)244     public void onConference(Connection a, Connection b) {
245         addConference(new TestConference(a, b));
246     }
247 
248     @Override
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerAccount, final ConnectionRequest originalRequest)249     public Connection onCreateOutgoingConnection(
250             PhoneAccountHandle connectionManagerAccount,
251             final ConnectionRequest originalRequest) {
252 
253         final Uri handle = originalRequest.getAddress();
254         String number = originalRequest.getAddress().getSchemeSpecificPart();
255         log("call, number: " + number);
256 
257         // Crash on 555-DEAD to test call service crashing.
258         if ("5550340".equals(number)) {
259             throw new RuntimeException("Goodbye, cruel world.");
260         }
261 
262         Bundle extras = originalRequest.getExtras();
263         String gatewayPackage = extras.getString(TelecomManager.GATEWAY_PROVIDER_PACKAGE);
264         Uri originalHandle = extras.getParcelable(TelecomManager.GATEWAY_ORIGINAL_ADDRESS);
265 
266         log("gateway package [" + gatewayPackage + "], original handle [" +
267                 originalHandle + "]");
268 
269         final TestConnection connection = new TestConnection(false /* isIncoming */);
270         connection.setAddress(handle, TelecomManager.PRESENTATION_ALLOWED);
271 
272         // If the number starts with 555, then we handle it ourselves. If not, then we
273         // use a remote connection service.
274         // TODO: Have a special phone number to test the account-picker dialog flow.
275         if (number != null && number.startsWith("555")) {
276             // Normally we would use the original request as is, but for testing purposes, we are
277             // adding ".." to the end of the number to follow its path more easily through the logs.
278             final ConnectionRequest request = new ConnectionRequest(
279                     originalRequest.getAccountHandle(),
280                     Uri.fromParts(handle.getScheme(),
281                     handle.getSchemeSpecificPart() + "..", ""),
282                     originalRequest.getExtras(),
283                     originalRequest.getVideoState());
284 
285             addCall(connection);
286             connection.startOutgoing();
287 
288             for (Connection c : getAllConnections()) {
289                 c.setOnHold();
290             }
291         } else {
292             log("Not a test number");
293         }
294         return connection;
295     }
296 
297     @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerAccount, final ConnectionRequest request)298     public Connection onCreateIncomingConnection(
299             PhoneAccountHandle connectionManagerAccount,
300             final ConnectionRequest request) {
301         PhoneAccountHandle accountHandle = request.getAccountHandle();
302         ComponentName componentName = new ComponentName(this, TestConnectionService.class);
303 
304         if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
305             final TestConnection connection = new TestConnection(true);
306             // Get the stashed intent extra that determines if this is a video call or audio call.
307             Bundle extras = request.getExtras();
308             boolean isVideoCall = extras.getBoolean(EXTRA_IS_VIDEO_CALL);
309             Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
310 
311             // Use dummy number for testing incoming calls.
312             Uri address = providedHandle == null ?
313                     Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(isVideoCall), null)
314                     : providedHandle;
315             if (isVideoCall) {
316                 TestVideoProvider testVideoCallProvider =
317                         new TestVideoProvider(getApplicationContext());
318                 connection.setVideoProvider(testVideoCallProvider);
319 
320                 // Keep reference to original so we can clean up the media players later.
321                 connection.setTestVideoCallProvider(testVideoCallProvider);
322             }
323 
324             int videoState = isVideoCall ?
325                     VideoProfile.VideoState.BIDIRECTIONAL :
326                     VideoProfile.VideoState.AUDIO_ONLY;
327             connection.setVideoState(videoState);
328             connection.setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
329 
330             addCall(connection);
331 
332             ConnectionRequest newRequest = new ConnectionRequest(
333                     request.getAccountHandle(),
334                     address,
335                     request.getExtras(),
336                     videoState);
337             connection.setVideoState(videoState);
338             return connection;
339         } else {
340             return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.ERROR,
341                     "Invalid inputs: " + accountHandle + " " + componentName));
342         }
343     }
344 
345     @Override
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)346     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
347             final ConnectionRequest request) {
348         PhoneAccountHandle accountHandle = request.getAccountHandle();
349         ComponentName componentName = new ComponentName(this, TestConnectionService.class);
350         if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
351             final TestConnection connection = new TestConnection(false);
352             final Bundle extras = request.getExtras();
353             final Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
354 
355             Uri handle = providedHandle == null ?
356                     Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(false), null)
357                     : providedHandle;
358 
359             connection.setAddress(handle,  TelecomManager.PRESENTATION_ALLOWED);
360             connection.setDialing();
361 
362             addCall(connection);
363             return connection;
364         } else {
365             return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.ERROR,
366                     "Invalid inputs: " + accountHandle + " " + componentName));
367         }
368     }
369 
activateCall(TestConnection connection)370     private void activateCall(TestConnection connection) {
371         if (mMediaPlayer == null) {
372             mMediaPlayer = createMediaPlayer();
373         }
374         if (!mMediaPlayer.isPlaying()) {
375             mMediaPlayer.start();
376         }
377     }
378 
destroyCall(TestConnection connection)379     private void destroyCall(TestConnection connection) {
380         mCalls.remove(connection);
381 
382         // Ensure any playing media and camera resources are released.
383         connection.stopAndCleanupMedia();
384 
385         // Stops audio if there are no more calls.
386         if (mCalls.isEmpty() && mMediaPlayer != null && mMediaPlayer.isPlaying()) {
387             mMediaPlayer.stop();
388             mMediaPlayer.release();
389             mMediaPlayer = createMediaPlayer();
390         }
391 
392         updateConferenceable();
393     }
394 
addCall(TestConnection connection)395     private void addCall(TestConnection connection) {
396         mCalls.add(connection);
397         updateConferenceable();
398     }
399 
updateConferenceable()400     private void updateConferenceable() {
401         List<Connection> freeConnections = new ArrayList<>();
402         freeConnections.addAll(mCalls);
403         for (int i = 0; i < freeConnections.size(); i++) {
404             if (freeConnections.get(i).getConference() != null) {
405                 freeConnections.remove(i);
406             }
407         }
408         for (int i = 0; i < freeConnections.size(); i++) {
409             Connection c = freeConnections.remove(i);
410             c.setConferenceableConnections(freeConnections);
411             freeConnections.add(i, c);
412         }
413     }
414 
createMediaPlayer()415     private MediaPlayer createMediaPlayer() {
416         // Prepare the media player to play a tone when there is a call.
417         MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.beep_boop);
418         mediaPlayer.setLooping(true);
419         return mediaPlayer;
420     }
421 
log(String msg)422     private static void log(String msg) {
423         Log.w("telecomtestcs", "[TestConnectionService] " + msg);
424     }
425 
426     /**
427      * Generates a random phone number of format 555YXXX.  Where Y will be {@code 1} if the
428      * phone number is for a video call and {@code 0} for an audio call.  XXX is a randomly
429      * generated phone number.
430      *
431      * @param isVideo {@code True} if the call is a video call.
432      * @return The phone number.
433      */
getDummyNumber(boolean isVideo)434     private String getDummyNumber(boolean isVideo) {
435         int videoDigit = isVideo ? 1 : 0;
436         int number = mRandom.nextInt(999);
437         return String.format("555%s%03d", videoDigit, number);
438     }
439 }
440 
441