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