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