TestConnectionService.java revision 95ac44451d432cbbb3908efed490e90a803edf22
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 17package com.android.server.telecom.testapps; 18 19import android.content.BroadcastReceiver; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.media.MediaPlayer; 25import android.net.Uri; 26import android.os.Bundle; 27import android.os.Handler; 28import android.support.v4.content.LocalBroadcastManager; 29import android.telecom.AudioState; 30import android.telecom.Conference; 31import android.telecom.Connection; 32import android.telecom.DisconnectCause; 33import android.telecom.PhoneAccount; 34import android.telecom.ConnectionRequest; 35import android.telecom.ConnectionService; 36import android.telecom.PhoneAccountHandle; 37import android.telecom.TelecomManager; 38import android.telecom.VideoProfile; 39import android.util.Log; 40 41import com.android.server.telecom.testapps.R; 42 43import java.lang.String; 44import java.util.ArrayList; 45import java.util.List; 46import 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 */ 52public 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 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 99 public void onDisconnect() { 100 for (Connection c : getConnections()) { 101 c.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE)); 102 c.destroy(); 103 } 104 } 105 106 @Override 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 116 public void onHold() { 117 for (Connection c : getConnections()) { 118 c.setOnHold(); 119 } 120 setOnHold(); 121 } 122 123 @Override 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 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 setConnectionCapabilities(capabilities); 167 168 LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver( 169 mHangupReceiver, new IntentFilter(TestCallActivity.ACTION_HANGUP_CALLS)); 170 final IntentFilter filter = 171 new IntentFilter(TestCallActivity.ACTION_SEND_UPGRADE_REQUEST); 172 filter.addDataScheme("int"); 173 LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver( 174 mUpgradeRequestReceiver, filter); 175 } 176 177 void startOutgoing() { 178 setDialing(); 179 mHandler.postDelayed(new Runnable() { 180 @Override 181 public void run() { 182 setActive(); 183 activateCall(TestConnection.this); 184 } 185 }, 4000); 186 } 187 188 /** ${inheritDoc} */ 189 @Override 190 public void onAbort() { 191 destroyCall(this); 192 destroy(); 193 } 194 195 /** ${inheritDoc} */ 196 @Override 197 public void onAnswer(int videoState) { 198 setVideoState(videoState); 199 activateCall(this); 200 setActive(); 201 updateConferenceable(); 202 } 203 204 /** ${inheritDoc} */ 205 @Override 206 public void onPlayDtmfTone(char c) { 207 if (c == '1') { 208 setDialing(); 209 } 210 } 211 212 /** ${inheritDoc} */ 213 @Override 214 public void onStopDtmfTone() { } 215 216 /** ${inheritDoc} */ 217 @Override 218 public void onDisconnect() { 219 setDisconnected(new DisconnectCause(DisconnectCause.REMOTE)); 220 destroyCall(this); 221 destroy(); 222 } 223 224 /** ${inheritDoc} */ 225 @Override 226 public void onHold() { 227 setOnHold(); 228 } 229 230 /** ${inheritDoc} */ 231 @Override 232 public void onReject() { 233 setDisconnected(new DisconnectCause(DisconnectCause.REJECTED)); 234 destroyCall(this); 235 destroy(); 236 } 237 238 /** ${inheritDoc} */ 239 @Override 240 public void onUnhold() { 241 setActive(); 242 } 243 244 @Override 245 public void onAudioStateChanged(AudioState state) { } 246 247 248 public void setTestVideoCallProvider(TestVideoProvider testVideoCallProvider) { 249 mTestVideoCallProvider = testVideoCallProvider; 250 } 251 252 public void cleanup() { 253 LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver( 254 mHangupReceiver); 255 LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver( 256 mUpgradeRequestReceiver); 257 } 258 259 /** 260 * Stops playback of test videos. 261 */ 262 private void stopAndCleanupMedia() { 263 if (mTestVideoCallProvider != null) { 264 mTestVideoCallProvider.stopAndCleanupMedia(); 265 mTestVideoCallProvider.stopCamera(); 266 } 267 } 268 } 269 270 private final List<TestConnection> mCalls = new ArrayList<>(); 271 private final Handler mHandler = new Handler(); 272 273 /** Used to play an audio tone during a call. */ 274 private MediaPlayer mMediaPlayer; 275 276 @Override 277 public boolean onUnbind(Intent intent) { 278 log("onUnbind"); 279 mMediaPlayer = null; 280 return super.onUnbind(intent); 281 } 282 283 @Override 284 public void onConference(Connection a, Connection b) { 285 addConference(new TestConference(a, b)); 286 } 287 288 @Override 289 public Connection onCreateOutgoingConnection( 290 PhoneAccountHandle connectionManagerAccount, 291 final ConnectionRequest originalRequest) { 292 293 final Uri handle = originalRequest.getAddress(); 294 String number = originalRequest.getAddress().getSchemeSpecificPart(); 295 log("call, number: " + number); 296 297 // Crash on 555-DEAD to test call service crashing. 298 if ("5550340".equals(number)) { 299 throw new RuntimeException("Goodbye, cruel world."); 300 } 301 302 Bundle extras = originalRequest.getExtras(); 303 String gatewayPackage = extras.getString(TelecomManager.GATEWAY_PROVIDER_PACKAGE); 304 Uri originalHandle = extras.getParcelable(TelecomManager.GATEWAY_ORIGINAL_ADDRESS); 305 306 log("gateway package [" + gatewayPackage + "], original handle [" + 307 originalHandle + "]"); 308 309 final TestConnection connection = new TestConnection(false /* isIncoming */); 310 connection.setAddress(handle, TelecomManager.PRESENTATION_ALLOWED); 311 312 // If the number starts with 555, then we handle it ourselves. If not, then we 313 // use a remote connection service. 314 // TODO: Have a special phone number to test the account-picker dialog flow. 315 if (number != null && number.startsWith("555")) { 316 // Normally we would use the original request as is, but for testing purposes, we are 317 // adding ".." to the end of the number to follow its path more easily through the logs. 318 final ConnectionRequest request = new ConnectionRequest( 319 originalRequest.getAccountHandle(), 320 Uri.fromParts(handle.getScheme(), 321 handle.getSchemeSpecificPart() + "..", ""), 322 originalRequest.getExtras(), 323 originalRequest.getVideoState()); 324 connection.setVideoState(originalRequest.getVideoState()); 325 addVideoProvider(connection); 326 addCall(connection); 327 connection.startOutgoing(); 328 329 for (Connection c : getAllConnections()) { 330 c.setOnHold(); 331 } 332 } else { 333 log("Not a test number"); 334 } 335 return connection; 336 } 337 338 @Override 339 public Connection onCreateIncomingConnection( 340 PhoneAccountHandle connectionManagerAccount, 341 final ConnectionRequest request) { 342 PhoneAccountHandle accountHandle = request.getAccountHandle(); 343 ComponentName componentName = new ComponentName(this, TestConnectionService.class); 344 345 if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) { 346 final TestConnection connection = new TestConnection(true); 347 // Get the stashed intent extra that determines if this is a video call or audio call. 348 Bundle extras = request.getExtras(); 349 boolean isVideoCall = extras.getBoolean(EXTRA_IS_VIDEO_CALL); 350 Uri providedHandle = extras.getParcelable(EXTRA_HANDLE); 351 352 // Use dummy number for testing incoming calls. 353 Uri address = providedHandle == null ? 354 Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(isVideoCall), null) 355 : providedHandle; 356 357 int videoState = isVideoCall ? 358 VideoProfile.VideoState.BIDIRECTIONAL : 359 VideoProfile.VideoState.AUDIO_ONLY; 360 connection.setVideoState(videoState); 361 connection.setAddress(address, TelecomManager.PRESENTATION_ALLOWED); 362 363 addVideoProvider(connection); 364 365 addCall(connection); 366 367 ConnectionRequest newRequest = new ConnectionRequest( 368 request.getAccountHandle(), 369 address, 370 request.getExtras(), 371 videoState); 372 connection.setVideoState(videoState); 373 return connection; 374 } else { 375 return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.ERROR, 376 "Invalid inputs: " + accountHandle + " " + componentName)); 377 } 378 } 379 380 @Override 381 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 382 final ConnectionRequest request) { 383 PhoneAccountHandle accountHandle = request.getAccountHandle(); 384 ComponentName componentName = new ComponentName(this, TestConnectionService.class); 385 if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) { 386 final TestConnection connection = new TestConnection(false); 387 final Bundle extras = request.getExtras(); 388 final Uri providedHandle = extras.getParcelable(EXTRA_HANDLE); 389 390 Uri handle = providedHandle == null ? 391 Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(false), null) 392 : providedHandle; 393 394 connection.setAddress(handle, TelecomManager.PRESENTATION_ALLOWED); 395 connection.setDialing(); 396 397 addCall(connection); 398 return connection; 399 } else { 400 return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.ERROR, 401 "Invalid inputs: " + accountHandle + " " + componentName)); 402 } 403 } 404 405 private void addVideoProvider(TestConnection connection) { 406 TestVideoProvider testVideoCallProvider = 407 new TestVideoProvider(getApplicationContext(), connection); 408 connection.setVideoProvider(testVideoCallProvider); 409 410 // Keep reference to original so we can clean up the media players later. 411 connection.setTestVideoCallProvider(testVideoCallProvider); 412 } 413 414 private void activateCall(TestConnection connection) { 415 if (mMediaPlayer == null) { 416 mMediaPlayer = createMediaPlayer(); 417 } 418 if (!mMediaPlayer.isPlaying()) { 419 mMediaPlayer.start(); 420 } 421 } 422 423 private void destroyCall(TestConnection connection) { 424 connection.cleanup(); 425 mCalls.remove(connection); 426 427 // Ensure any playing media and camera resources are released. 428 connection.stopAndCleanupMedia(); 429 430 // Stops audio if there are no more calls. 431 if (mCalls.isEmpty() && mMediaPlayer != null && mMediaPlayer.isPlaying()) { 432 mMediaPlayer.stop(); 433 mMediaPlayer.release(); 434 mMediaPlayer = createMediaPlayer(); 435 } 436 437 updateConferenceable(); 438 } 439 440 private void addCall(TestConnection connection) { 441 mCalls.add(connection); 442 updateConferenceable(); 443 } 444 445 private void updateConferenceable() { 446 List<Connection> freeConnections = new ArrayList<>(); 447 freeConnections.addAll(mCalls); 448 for (int i = 0; i < freeConnections.size(); i++) { 449 if (freeConnections.get(i).getConference() != null) { 450 freeConnections.remove(i); 451 } 452 } 453 for (int i = 0; i < freeConnections.size(); i++) { 454 Connection c = freeConnections.remove(i); 455 c.setConferenceableConnections(freeConnections); 456 freeConnections.add(i, c); 457 } 458 } 459 460 private MediaPlayer createMediaPlayer() { 461 // Prepare the media player to play a tone when there is a call. 462 MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.beep_boop); 463 mediaPlayer.setLooping(true); 464 return mediaPlayer; 465 } 466 467 private static void log(String msg) { 468 Log.w("telecomtestcs", "[TestConnectionService] " + msg); 469 } 470 471 /** 472 * Generates a random phone number of format 555YXXX. Where Y will be {@code 1} if the 473 * phone number is for a video call and {@code 0} for an audio call. XXX is a randomly 474 * generated phone number. 475 * 476 * @param isVideo {@code True} if the call is a video call. 477 * @return The phone number. 478 */ 479 private String getDummyNumber(boolean isVideo) { 480 int videoDigit = isVideo ? 1 : 0; 481 int number = mRandom.nextInt(999); 482 return String.format("555%s%03d", videoDigit, number); 483 } 484} 485 486