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