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.internal.telephony;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.net.Uri;
24 import android.os.BadParcelableException;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.telephony.AccessNetworkConstants;
28 import android.telephony.Rlog;
29 import android.telephony.ServiceState;
30 import android.telephony.ims.ImsCallProfile;
31 import android.telephony.ims.ImsConferenceState;
32 import android.telephony.ims.ImsExternalCallState;
33 import android.telephony.ims.ImsReasonInfo;
34 
35 import com.android.ims.ImsCall;
36 import com.android.internal.telephony.gsm.SuppServiceNotification;
37 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
38 import com.android.internal.telephony.imsphone.ImsPhone;
39 import com.android.internal.telephony.imsphone.ImsPhoneCall;
40 import com.android.internal.telephony.test.TestConferenceEventPackageParser;
41 
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.FileNotFoundException;
45 import java.util.ArrayList;
46 import java.util.List;
47 
48 /**
49  * Telephony tester receives the following intents where {name} is the phone name
50  *
51  * adb shell am broadcast -a com.android.internal.telephony.{name}.action_detached
52  * adb shell am broadcast -a com.android.internal.telephony.{name}.action_attached
53  * adb shell am broadcast -a com.android.internal.telephony.TestConferenceEventPackage -e filename
54  *      test_filename.xml
55  * adb shell am broadcast -a com.android.internal.telephony.TestServiceState --ei data_rat 10 --ei
56  *      data_roaming_type 3
57  * adb shell am broadcast -a com.android.internal.telephony.TestServiceState --es action reset
58  *
59  */
60 public class TelephonyTester {
61     private static final String LOG_TAG = "TelephonyTester";
62     private static final boolean DBG = true;
63 
64     /**
65      * Test-only intent used to send a test conference event package to the IMS framework.
66      */
67     private static final String ACTION_TEST_CONFERENCE_EVENT_PACKAGE =
68             "com.android.internal.telephony.TestConferenceEventPackage";
69 
70     /**
71      * Test-only intent used to send a test dialog event package to the IMS framework.
72      */
73     private static final String ACTION_TEST_DIALOG_EVENT_PACKAGE =
74             "com.android.internal.telephony.TestDialogEventPackage";
75 
76     private static final String EXTRA_FILENAME = "filename";
77     private static final String EXTRA_STARTPACKAGE = "startPackage";
78     private static final String EXTRA_SENDPACKAGE = "sendPackage";
79     private static final String EXTRA_DIALOGID = "dialogId";
80     private static final String EXTRA_NUMBER = "number";
81     private static final String EXTRA_STATE = "state";
82     private static final String EXTRA_CANPULL = "canPull";
83 
84     /**
85      * Test-only intent used to trigger supp service notification failure.
86      */
87     private static final String ACTION_TEST_SUPP_SRVC_FAIL =
88             "com.android.internal.telephony.TestSuppSrvcFail";
89     private static final String EXTRA_FAILURE_CODE = "failureCode";
90 
91     /**
92      * Test-only intent used to trigger the signalling which occurs when a handover to WIFI fails.
93      */
94     private static final String ACTION_TEST_HANDOVER_FAIL =
95             "com.android.internal.telephony.TestHandoverFail";
96 
97     /**
98      * Test-only intent used to trigger signalling of a
99      * {@link com.android.internal.telephony.gsm.SuppServiceNotification} to the {@link ImsPhone}.
100      * Use {@link #EXTRA_CODE} to specify the
101      * {@link com.android.internal.telephony.gsm.SuppServiceNotification#code}.
102      */
103     private static final String ACTION_TEST_SUPP_SRVC_NOTIFICATION =
104             "com.android.internal.telephony.TestSuppSrvcNotification";
105 
106     private static final String EXTRA_CODE = "code";
107     private static final String EXTRA_TYPE = "type";
108 
109     /**
110      * Test-only intent used to trigger signalling that an IMS call is an emergency call.
111      */
112     private static final String ACTION_TEST_IMS_E_CALL =
113             "com.android.internal.telephony.TestImsECall";
114 
115     /**
116      * Test-only intent used to trigger a change to the current call's phone number.
117      * Use the {@link #EXTRA_NUMBER} extra to specify the new phone number.
118      */
119     private static final String ACTION_TEST_CHANGE_NUMBER =
120             "com.android.internal.telephony.TestChangeNumber";
121 
122     private static final String ACTION_TEST_SERVICE_STATE =
123             "com.android.internal.telephony.TestServiceState";
124 
125     private static final String EXTRA_ACTION = "action";
126     private static final String EXTRA_VOICE_RAT = "voice_rat";
127     private static final String EXTRA_DATA_RAT = "data_rat";
128     private static final String EXTRA_VOICE_REG_STATE = "voice_reg_state";
129     private static final String EXTRA_DATA_REG_STATE = "data_reg_state";
130     private static final String EXTRA_VOICE_ROAMING_TYPE = "voice_roaming_type";
131     private static final String EXTRA_DATA_ROAMING_TYPE = "data_roaming_type";
132     private static final String EXTRA_OPERATOR = "operator";
133 
134     private static final String ACTION_RESET = "reset";
135 
136     private static List<ImsExternalCallState> mImsExternalCallStates = null;
137 
138     private Intent mServiceStateTestIntent;
139 
140     private Phone mPhone;
141 
142     // The static intent receiver one for all instances and we assume this
143     // is running on the same thread as Dcc.
144     protected BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
145             @Override
146         public void onReceive(Context context, Intent intent) {
147             String action = intent.getAction();
148             try {
149                 if (DBG) log("sIntentReceiver.onReceive: action=" + action);
150                 if (action.equals(mPhone.getActionDetached())) {
151                     log("simulate detaching");
152                     mPhone.getServiceStateTracker().mDetachedRegistrants.get(
153                             AccessNetworkConstants.TRANSPORT_TYPE_WWAN).notifyRegistrants();
154                 } else if (action.equals(mPhone.getActionAttached())) {
155                     log("simulate attaching");
156                     mPhone.getServiceStateTracker().mAttachedRegistrants.get(
157                             AccessNetworkConstants.TRANSPORT_TYPE_WWAN).notifyRegistrants();
158                 } else if (action.equals(ACTION_TEST_CONFERENCE_EVENT_PACKAGE)) {
159                     log("inject simulated conference event package");
160                     handleTestConferenceEventPackage(context,
161                             intent.getStringExtra(EXTRA_FILENAME));
162                 } else if (action.equals(ACTION_TEST_DIALOG_EVENT_PACKAGE)) {
163                     log("handle test dialog event package intent");
164                     handleTestDialogEventPackageIntent(intent);
165                 } else if (action.equals(ACTION_TEST_SUPP_SRVC_FAIL)) {
166                     log("handle test supp svc failed intent");
167                     handleSuppServiceFailedIntent(intent);
168                 } else if (action.equals(ACTION_TEST_HANDOVER_FAIL)) {
169                     log("handle handover fail test intent");
170                     handleHandoverFailedIntent();
171                 } else if (action.equals(ACTION_TEST_SUPP_SRVC_NOTIFICATION)) {
172                     log("handle supp service notification test intent");
173                     sendTestSuppServiceNotification(intent);
174                 } else if (action.equals(ACTION_TEST_SERVICE_STATE)) {
175                     log("handle test service state changed intent");
176                     // Trigger the service state update. The replacement will be done in
177                     // overrideServiceState().
178                     mServiceStateTestIntent = intent;
179                     mPhone.getServiceStateTracker().sendEmptyMessage(
180                             ServiceStateTracker.EVENT_NETWORK_STATE_CHANGED);
181                 } else if (action.equals(ACTION_TEST_IMS_E_CALL)) {
182                     log("handle test IMS ecall intent");
183                     testImsECall();
184                 } else if (action.equals(ACTION_TEST_CHANGE_NUMBER)) {
185                     log("handle test change number intent");
186                     testChangeNumber(intent);
187                 } else {
188                     if (DBG) log("onReceive: unknown action=" + action);
189                 }
190             } catch (BadParcelableException e) {
191                 Rlog.w(LOG_TAG, e);
192             }
193         }
194     };
195 
TelephonyTester(Phone phone)196     TelephonyTester(Phone phone) {
197         mPhone = phone;
198 
199         if (Build.IS_DEBUGGABLE) {
200             IntentFilter filter = new IntentFilter();
201 
202             filter.addAction(mPhone.getActionDetached());
203             log("register for intent action=" + mPhone.getActionDetached());
204 
205             filter.addAction(mPhone.getActionAttached());
206             log("register for intent action=" + mPhone.getActionAttached());
207 
208             if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
209                 log("register for intent action=" + ACTION_TEST_CONFERENCE_EVENT_PACKAGE);
210                 filter.addAction(ACTION_TEST_CONFERENCE_EVENT_PACKAGE);
211                 filter.addAction(ACTION_TEST_DIALOG_EVENT_PACKAGE);
212                 filter.addAction(ACTION_TEST_SUPP_SRVC_FAIL);
213                 filter.addAction(ACTION_TEST_HANDOVER_FAIL);
214                 filter.addAction(ACTION_TEST_SUPP_SRVC_NOTIFICATION);
215                 filter.addAction(ACTION_TEST_IMS_E_CALL);
216                 mImsExternalCallStates = new ArrayList<ImsExternalCallState>();
217             } else {
218                 filter.addAction(ACTION_TEST_SERVICE_STATE);
219                 log("register for intent action=" + ACTION_TEST_SERVICE_STATE);
220             }
221             filter.addAction(ACTION_TEST_CHANGE_NUMBER);
222             phone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone.getHandler());
223         }
224     }
225 
dispose()226     void dispose() {
227         if (Build.IS_DEBUGGABLE) {
228             mPhone.getContext().unregisterReceiver(mIntentReceiver);
229         }
230     }
231 
log(String s)232     private static void log(String s) {
233         Rlog.d(LOG_TAG, s);
234     }
235 
handleSuppServiceFailedIntent(Intent intent)236     private void handleSuppServiceFailedIntent(Intent intent) {
237         ImsPhone imsPhone = (ImsPhone) mPhone;
238         if (imsPhone == null) {
239             return;
240         }
241         int code = intent.getIntExtra(EXTRA_FAILURE_CODE, 0);
242         imsPhone.notifySuppServiceFailed(PhoneInternalInterface.SuppService.values()[code]);
243     }
244 
handleHandoverFailedIntent()245     private void handleHandoverFailedIntent() {
246         // Attempt to get the active IMS call
247         ImsPhone imsPhone = (ImsPhone) mPhone;
248         if (imsPhone == null) {
249             return;
250         }
251 
252         ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall();
253         if (imsPhoneCall == null) {
254             return;
255         }
256 
257         ImsCall imsCall = imsPhoneCall.getImsCall();
258         if (imsCall == null) {
259             return;
260         }
261 
262         imsCall.getImsCallSessionListenerProxy().callSessionHandoverFailed(imsCall.getCallSession(),
263                 ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
264                 new ImsReasonInfo());
265     }
266 
267     /**
268      * Handles request to send a test conference event package to the active Ims call.
269      *
270      * @see com.android.internal.telephony.test.TestConferenceEventPackageParser
271      * @param context The context.
272      * @param fileName The name of the test conference event package file to read.
273      */
handleTestConferenceEventPackage(Context context, String fileName)274     private void handleTestConferenceEventPackage(Context context, String fileName) {
275         // Attempt to get the active IMS call before parsing the test XML file.
276         ImsPhone imsPhone = (ImsPhone) mPhone;
277         if (imsPhone == null) {
278             return;
279         }
280 
281         ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall();
282         if (imsPhoneCall == null) {
283             return;
284         }
285 
286         ImsCall imsCall = imsPhoneCall.getImsCall();
287         if (imsCall == null) {
288             return;
289         }
290 
291         File packageFile = new File(context.getFilesDir(), fileName);
292         final FileInputStream is;
293         try {
294             is = new FileInputStream(packageFile);
295         } catch (FileNotFoundException ex) {
296             log("Test conference event package file not found: " + packageFile.getAbsolutePath());
297             return;
298         }
299 
300         TestConferenceEventPackageParser parser = new TestConferenceEventPackageParser(is);
301         ImsConferenceState imsConferenceState = parser.parse();
302         if (imsConferenceState == null) {
303             return;
304         }
305 
306         imsCall.conferenceStateUpdated(imsConferenceState);
307     }
308 
309     /**
310      * Handles intents containing test dialog event package data.
311      *
312      * @param intent
313      */
handleTestDialogEventPackageIntent(Intent intent)314     private void handleTestDialogEventPackageIntent(Intent intent) {
315         ImsPhone imsPhone = (ImsPhone) mPhone;
316         if (imsPhone == null) {
317             return;
318         }
319         ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
320         if (externalCallTracker == null) {
321             return;
322         }
323 
324         if (intent.hasExtra(EXTRA_STARTPACKAGE)) {
325             mImsExternalCallStates.clear();
326         } else if (intent.hasExtra(EXTRA_SENDPACKAGE)) {
327             externalCallTracker.refreshExternalCallState(mImsExternalCallStates);
328             mImsExternalCallStates.clear();
329         } else if (intent.hasExtra(EXTRA_DIALOGID)) {
330             ImsExternalCallState state = new ImsExternalCallState(
331                     intent.getIntExtra(EXTRA_DIALOGID, 0),
332                     Uri.parse(intent.getStringExtra(EXTRA_NUMBER)),
333                     intent.getBooleanExtra(EXTRA_CANPULL, true),
334                     intent.getIntExtra(EXTRA_STATE,
335                             ImsExternalCallState.CALL_STATE_CONFIRMED),
336                     ImsCallProfile.CALL_TYPE_VOICE,
337                     false /* isHeld */
338                     );
339             mImsExternalCallStates.add(state);
340         }
341     }
342 
sendTestSuppServiceNotification(Intent intent)343     private void sendTestSuppServiceNotification(Intent intent) {
344         if (intent.hasExtra(EXTRA_CODE) && intent.hasExtra(EXTRA_TYPE)) {
345             int code = intent.getIntExtra(EXTRA_CODE, -1);
346             int type = intent.getIntExtra(EXTRA_TYPE, -1);
347             ImsPhone imsPhone = (ImsPhone) mPhone;
348             if (imsPhone == null) {
349                 return;
350             }
351             log("Test supp service notification:" + code);
352             SuppServiceNotification suppServiceNotification = new SuppServiceNotification();
353             suppServiceNotification.code = code;
354             suppServiceNotification.notificationType = type;
355             imsPhone.notifySuppSvcNotification(suppServiceNotification);
356         }
357     }
358 
overrideServiceState(ServiceState ss)359     void overrideServiceState(ServiceState ss) {
360         if (mServiceStateTestIntent == null || ss == null) return;
361         if (mServiceStateTestIntent.hasExtra(EXTRA_ACTION)
362                 && ACTION_RESET.equals(mServiceStateTestIntent.getStringExtra(EXTRA_ACTION))) {
363             log("Service state override reset");
364             return;
365         }
366 
367         // TODO: Fix this with modifing NetworkRegistrationInfo inside ServiceState. Do not call
368         // ServiceState's set methods directly.
369         /*if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_REG_STATE)) {
370             ss.setVoiceRegState(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_REG_STATE,
371                     ServiceState.STATE_OUT_OF_SERVICE));
372             log("Override voice service state with " + ss.getVoiceRegState());
373         }
374         if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_REG_STATE)) {
375             ss.setDataRegState(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE,
376                     ServiceState.STATE_OUT_OF_SERVICE));
377             log("Override data service state with " + ss.getDataRegState());
378         }
379         if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_RAT)) {
380             ss.setRilVoiceRadioTechnology(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_RAT,
381                     ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN));
382             log("Override voice rat with " + ss.getRilVoiceRadioTechnology());
383         }
384         if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_RAT)) {
385             ss.setRilDataRadioTechnology(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_RAT,
386                     ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN));
387             log("Override data rat with " + ss.getRilDataRadioTechnology());
388         }*/
389         if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_ROAMING_TYPE)) {
390             ss.setVoiceRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_ROAMING_TYPE,
391                     ServiceState.ROAMING_TYPE_UNKNOWN));
392             log("Override voice roaming type with " + ss.getVoiceRoamingType());
393         }
394         if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_ROAMING_TYPE)) {
395             ss.setDataRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_ROAMING_TYPE,
396                     ServiceState.ROAMING_TYPE_UNKNOWN));
397             log("Override data roaming type with " + ss.getDataRoamingType());
398         }
399         if (mServiceStateTestIntent.hasExtra(EXTRA_OPERATOR)) {
400             String operator = mServiceStateTestIntent.getStringExtra(EXTRA_OPERATOR);
401             ss.setOperatorName(operator, operator, "");
402             log("Override operator with " + operator);
403         }
404     }
405 
testImsECall()406     void testImsECall() {
407         // Attempt to get the active IMS call before parsing the test XML file.
408         ImsPhone imsPhone = (ImsPhone) mPhone;
409         if (imsPhone == null) {
410             return;
411         }
412 
413         ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall();
414         if (imsPhoneCall == null) {
415             return;
416         }
417 
418         ImsCall imsCall = imsPhoneCall.getImsCall();
419         if (imsCall == null) {
420             return;
421         }
422 
423         ImsCallProfile callProfile = imsCall.getCallProfile();
424         Bundle extras = callProfile.getCallExtras();
425         if (extras == null) {
426             extras = new Bundle();
427         }
428         extras.putBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL, true);
429         callProfile.mCallExtras = extras;
430         imsCall.getImsCallSessionListenerProxy().callSessionUpdated(imsCall.getSession(),
431                 callProfile);
432     }
433 
testChangeNumber(Intent intent)434     void testChangeNumber(Intent intent) {
435         if (!intent.hasExtra(EXTRA_NUMBER)) {
436             return;
437         }
438 
439         String newNumber = intent.getStringExtra(EXTRA_NUMBER);
440 
441         // Update all the calls.
442         mPhone.getForegroundCall().getConnections()
443                 .stream()
444                 .forEach(c -> {
445                     c.setAddress(newNumber, PhoneConstants.PRESENTATION_ALLOWED);
446                     c.setDialString(newNumber);
447                 });
448 
449         // <sigh>
450         if (mPhone instanceof GsmCdmaPhone) {
451             ((GsmCdmaPhone) mPhone).notifyPhoneStateChanged();
452             ((GsmCdmaPhone) mPhone).notifyPreciseCallStateChanged();
453         } else if (mPhone instanceof ImsPhone) {
454             ((ImsPhone) mPhone).notifyPhoneStateChanged();
455             ((ImsPhone) mPhone).notifyPreciseCallStateChanged();
456         }
457     }
458 }