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.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.ConnectionRequest;
     31 import android.telecom.ConnectionService;
     32 import android.telecom.PhoneAccountHandle;
     33 import android.telecom.TelecomManager;
     34 import android.telecom.VideoProfile;
     35 import android.util.Log;
     36 
     37 import com.android.server.telecom.tests.R;
     38 
     39 import java.lang.String;
     40 import java.util.ArrayList;
     41 import java.util.List;
     42 import java.util.Random;
     43 
     44 /**
     45  * Service which provides fake calls to test the ConnectionService interface.
     46  * TODO: Rename all classes in the directory to Dummy* (e.g., DummyConnectionService).
     47  */
     48 public class TestConnectionService extends ConnectionService {
     49     /**
     50      * Intent extra used to pass along whether a call is video or audio based on the user's choice
     51      * in the notification.
     52      */
     53     public static final String EXTRA_IS_VIDEO_CALL = "extra_is_video_call";
     54 
     55     public static final String EXTRA_HANDLE = "extra_handle";
     56 
     57     /**
     58      * Random number generator used to generate phone numbers.
     59      */
     60     private Random mRandom = new Random();
     61 
     62     private final class TestConference extends Conference {
     63 
     64         private final Connection.Listener mConnectionListener = new Connection.Listener() {
     65             @Override
     66             public void onDestroyed(Connection c) {
     67                 removeConnection(c);
     68                 if (getConnections().size() == 0) {
     69                     setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
     70                     destroy();
     71                 }
     72             }
     73         };
     74 
     75         public TestConference(Connection a, Connection b) {
     76             super(null);
     77             setConnectionCapabilities(
     78                     Connection.CAPABILITY_SUPPORT_HOLD |
     79                     Connection.CAPABILITY_HOLD |
     80                     Connection.CAPABILITY_MUTE |
     81                     Connection.CAPABILITY_MANAGE_CONFERENCE);
     82             addConnection(a);
     83             addConnection(b);
     84 
     85             a.addConnectionListener(mConnectionListener);
     86             b.addConnectionListener(mConnectionListener);
     87 
     88             a.setConference(this);
     89             b.setConference(this);
     90 
     91             setActive();
     92         }
     93 
     94         @Override
     95         public void onDisconnect() {
     96             for (Connection c : getConnections()) {
     97                 c.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
     98                 c.destroy();
     99             }
    100         }
    101 
    102         @Override
    103         public void onSeparate(Connection connection) {
    104             if (getConnections().contains(connection)) {
    105                 connection.setConference(null);
    106                 removeConnection(connection);
    107                 connection.removeConnectionListener(mConnectionListener);
    108             }
    109         }
    110 
    111         @Override
    112         public void onHold() {
    113             for (Connection c : getConnections()) {
    114                 c.setOnHold();
    115             }
    116             setOnHold();
    117         }
    118 
    119         @Override
    120         public void onUnhold() {
    121             for (Connection c : getConnections()) {
    122                 c.setActive();
    123             }
    124             setActive();
    125         }
    126     }
    127 
    128     private final class TestConnection extends Connection {
    129         private final boolean mIsIncoming;
    130 
    131         /** Used to cleanup camera and media when done with connection. */
    132         private TestVideoProvider mTestVideoCallProvider;
    133 
    134         TestConnection(boolean isIncoming) {
    135             mIsIncoming = isIncoming;
    136             // Assume all calls are video capable.
    137             int capabilities = getConnectionCapabilities();
    138             capabilities |= CAPABILITY_SUPPORTS_VT_LOCAL;
    139             capabilities |= CAPABILITY_MUTE;
    140             capabilities |= CAPABILITY_SUPPORT_HOLD;
    141             capabilities |= CAPABILITY_HOLD;
    142             setConnectionCapabilities(capabilities);
    143         }
    144 
    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
    158         public void onAbort() {
    159             destroyCall(this);
    160             destroy();
    161         }
    162 
    163         /** ${inheritDoc} */
    164         @Override
    165         public void onAnswer(int videoState) {
    166             setVideoState(videoState);
    167             activateCall(this);
    168             setActive();
    169             updateConferenceable();
    170         }
    171 
    172         /** ${inheritDoc} */
    173         @Override
    174         public void onPlayDtmfTone(char c) {
    175             if (c == '1') {
    176                 setDialing();
    177             }
    178         }
    179 
    180         /** ${inheritDoc} */
    181         @Override
    182         public void onStopDtmfTone() { }
    183 
    184         /** ${inheritDoc} */
    185         @Override
    186         public void onDisconnect() {
    187             setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
    188             destroyCall(this);
    189             destroy();
    190         }
    191 
    192         /** ${inheritDoc} */
    193         @Override
    194         public void onHold() {
    195             setOnHold();
    196         }
    197 
    198         /** ${inheritDoc} */
    199         @Override
    200         public void onReject() {
    201             setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
    202             destroyCall(this);
    203             destroy();
    204         }
    205 
    206         /** ${inheritDoc} */
    207         @Override
    208         public void onUnhold() {
    209             setActive();
    210         }
    211 
    212         @Override
    213         public void onAudioStateChanged(AudioState state) { }
    214 
    215         public void setTestVideoCallProvider(TestVideoProvider testVideoCallProvider) {
    216             mTestVideoCallProvider = testVideoCallProvider;
    217         }
    218 
    219         /**
    220          * Stops playback of test videos.
    221          */
    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
    237     public boolean onUnbind(Intent intent) {
    238         log("onUnbind");
    239         mMediaPlayer = null;
    240         return super.onUnbind(intent);
    241     }
    242 
    243     @Override
    244     public void onConference(Connection a, Connection b) {
    245         addConference(new TestConference(a, b));
    246     }
    247 
    248     @Override
    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
    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
    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 
    370     private void activateCall(TestConnection connection) {
    371         if (mMediaPlayer == null) {
    372             mMediaPlayer = createMediaPlayer();
    373         }
    374         if (!mMediaPlayer.isPlaying()) {
    375             mMediaPlayer.start();
    376         }
    377     }
    378 
    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 
    395     private void addCall(TestConnection connection) {
    396         mCalls.add(connection);
    397         updateConferenceable();
    398     }
    399 
    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 
    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 
    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      */
    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