The program below is a response to a number of people who have tried to convert their Android phones to autocallers. The Android software doesn't have the full level of software to build a machine that will call a list of names and play a recorded message. PhoneCaller is about the best I could do to build an autocaller. It is fully tested on Android MOTA855, running version 2.2. Since it cannot detect line signals for the called side of the party, it plays a message over and over in hopes that the phone will answer before the loop stops. Also no software technique exists to play an audio message directly into the conversation. However, I discovered that by merely inserting a blank jack into the ear/microphone jack, the audio plays right into the conversation. Very unusual. Of course, a manifest and a layout is required to make this program completely functional. They are provided.
// PhoneCaller
// Fred Rounds, Programmer
//This program records any message you wish.
//When you press the "dial" button it will
//place calls to up to ten people
//in your contact list. The name
//must have an "*" attached to the
//end. The program calls all those
//people who have this end character.
//The recording will be played directly
//into the conversation, if you insert
//a phone jack into the appropriate
//location on the phone. Apparently
//the jack doesn't need to to be
//connected to anything. Yes,
//just a blank phone jack. This program
//has been tested only on Motorola MOTA855 Android
//phone. It works as you might expect.
//I have not been able to find a way to
//detect telephony signals for the called side
//of the connection.
package Phone.com;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import com.android.internal.telephony.ITelephony;//Seems to work better than using Intent.
import android.test.InstrumentationTestCase;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.*;
import android.view.KeyEvent;
@SuppressWarnings("unused")
public class PhoneActivity extends Activity implements OnClickListener {
MediaRecorder recorder;
private static final String RECORDED_FILE = "/message";
private static final int LENGTH_LONG = 20;
final String fileNameStr = "audio-android.3gp";
private static String mFileName = null;
private static String mFilePlay = null;
FileInputStream fis;
String text = "exiting";
int PICK_CONTACT=1;
ListenToPhoneState listener;
InstrumentationTestCase T;
String TAG = null;
boolean idle = false;
String PH[]= new String[10];
int numofcalls=0;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.record);
((Button) findViewById(R.id.Record)).setOnClickListener(this);
((Button) findViewById(R.id.StopRecording)).setOnClickListener(this);
((Button) findViewById(R.id.Play)).setOnClickListener(this);
((Button) findViewById(R.id.Dial)).setOnClickListener(this);
((Button) findViewById(R.id.Exit)).setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.Record: {
try {
recorder = new MediaRecorder();
FileOutputStream fos = openFileOutput(fileNameStr, MODE_PRIVATE);
mFileName = getFilesDir().getAbsolutePath();
mFileName += RECORDED_FILE;
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile(mFileName);
recorder.prepare();
recorder.start();
} catch (IOException e) {
System.out.println("IOException caught during recording.");
e.printStackTrace();
}
return;
}
case R.id.StopRecording: {
recorder.stop();
recorder.release();
System.out.println("Stop pressed.");
return;
}
case R.id.Play: {
Player();
return;
}
case R.id.Dial: {
//The first thing to do is get the contact name and phone number.
ContentResolver cr = getContentResolver();
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI,
null, null, null, null);
if (cur.getCount()>0 ) {
if (cur.moveToFirst()) {
do{
String name = cur.getString( // name of contact
cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
int time =2000;
//Toast.makeText(PhoneActivity.this, name, LENGTH_LONG).show();
if (Integer.parseInt(cur.getString(
cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
String contactId =
cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
Cursor phones = cr.query(ContactsContract.Data.CONTENT_URI, null,
ContactsContract.Data.CONTACT_ID + " = " + contactId, null, null);
String ident = cur.getString(
cur.getColumnIndex(ContactsContract.Contacts._ID));
Cursor pCur = cr.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?",
new String[]{ident}, null);
while(pCur.moveToNext()) {
// Do something with phones
//Get the phone number.
String number = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
if (name.endsWith("*")){ //check if contact ends with *.
((TextView)findViewById(R.id.Progress)).setText(name);
numofcalls=numofcalls+1;
PH[numofcalls]= number;//create an array of numbers
((TextView)findViewById(R.id.Progress)).setText(PH[numofcalls]);
} //end if statement
} //end loop
pCur.close();
}
}while (cur.moveToNext());
// if parser
// end do loop
cur.close();
}
} //end if count statement
//Call all the numbers.
for (int c=1; c<= numofcalls; c++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
call(PH[c]);
//Call each number and play the recording 15 times.
for (int count = 1; count<= 15; count++){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Player();
} //end player for loop
try {
// Java reflection to gain access to TelephonyManager's
// ITelephony getter
TelephonyManager tm = (TelephonyManager)
getSystemService(Context.TELEPHONY_SERVICE);
Class r = Class.forName(tm.getClass().getName());
Method m = r.getDeclaredMethod("getITelephony");
m.setAccessible(true);
com.android.internal.telephony.ITelephony telephonyService = (ITelephony) m.invoke(tm);
telephonyService.endCall();
((TextView)findViewById(R.id.Progress)).setText("hanging up");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG,
"FATAL ERROR: could not connect to telephony subsystem");
Log.e(TAG, "Exception object: " + e);
}// end catch*/
} //end for loop
finish();
return;
}//end dial
case R.id.Exit: {
//Deletes file then finishes.
((TextView)findViewById(R.id.Progress)).setText(text);
mFilePlay = getFilesDir().getAbsolutePath();
mFilePlay += RECORDED_FILE;
deleteFile("message");
finish();
}}//end exit
} //end switch
private void Player() {
AudioManager mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
mFilePlay = getFilesDir().getAbsolutePath();
mFilePlay += RECORDED_FILE;
MediaPlayer mp = new MediaPlayer();
int maxVolume=8;
mAudioManager.setStreamVolume(AudioManager.ROUTE_HEADSET, maxVolume, AudioManager.FLAG_SHOW_UI);
try {
fis = new FileInputStream(mFilePlay);
mp.setDataSource(fis.getFD());
mp.prepare();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
mp.start();
delay();
return;
}
private void delay(){
for (int i = 1; i<= 1000; i++){}
for (int j = 1; j<= 100; j++){}
for (int k = 1; k<= 1000; k++){}
return;
}
@SuppressWarnings("rawtypes")
private void call(String number) {
try {
TelephonyManager tm = (TelephonyManager)
getSystemService(Context.TELEPHONY_SERVICE);
listener = new ListenToPhoneState();
tm.listen(listener, ListenToPhoneState.LISTEN_CALL_STATE);
} catch (ActivityNotFoundException activityException) {
Log.e("telephony-example", "Call failed", activityException);
}
try {
// Java reflection to gain access to TelephonyManager's
// ITelephony getter
TelephonyManager tm = (TelephonyManager)
getSystemService(Context.TELEPHONY_SERVICE);
Class c = Class.forName(tm.getClass().getName());
Method m = c.getDeclaredMethod("getITelephony");
m.setAccessible(true);
com.android.internal.telephony.ITelephony telephonyService = (ITelephony) m.invoke(tm);
((TextView)findViewById(R.id.Progress)).setText("Dialing");
delay();
telephonyService.call(number);
listener = new ListenToPhoneState();
int state = tm.getCallState();
listener.onCallStateChanged(state, number);
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG,
"FATAL ERROR: could not connect to telephony subsystem");
Log.e(TAG, "Exception object: " + e);
}
return;
}
private class ListenToPhoneState extends PhoneStateListener {
//These routines don't check the called number, so probably they
//aren't useful.
public void onCallStateChanged(int state, String number) {
Log.i("telephony-example", "State changed: " + stateName(state));
}
String stateName(int state){
int time = 3000;
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
((TextView)findViewById(R.id.Progress)).setText("idle");
return "idle";
case TelephonyManager.CALL_STATE_OFFHOOK:
((TextView)findViewById(R.id.Progress)).setText("off hook");
delay();
try {
// Java reflection to gain access to TelephonyManager's
// ITelephony getter
TelephonyManager tm = (TelephonyManager)
getSystemService(Context.TELEPHONY_SERVICE);
Class c = Class.forName(tm.getClass().getName());
Method m = c.getDeclaredMethod("getITelephony");
m.setAccessible(true);
com.android.internal.telephony.ITelephony telephonyService = (ITelephony) m.invoke(tm);
telephonyService.endCall();
((TextView)findViewById(R.id.Progress)).setText("hanging up");
Thread.sleep(1000);
finish();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG,
"FATAL ERROR: could not connect to telephony subsystem");
Log.e(TAG, "Exception object: " + e);
}
//return "off-hook";
case TelephonyManager.CALL_STATE_RINGING:
((TextView)findViewById(R.id.Progress)).setText("ringing");
return "ringing";
}
return Integer.toString(state);
}
}
}
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="Phone.com"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>
<uses-permission android:name="android.permission.CALL_PRIVILEGED"></uses-permission>
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"></uses-permission>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"></uses-permission>
<application android:icon="@drawable/icon" android:label="@string/app_name" >
<activity android:name=".PhoneActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Screen layout:
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
android:id="@+id/widget0"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<Button
android:id="@+id/Record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Record"
android:layout_x="70px"
android:layout_y="22px"
>
</Button>
<Button
android:id="@+id/StopRecording"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop Recording"
android:textSize="12sp"
android:layout_x="190px"
android:layout_y="22px"
>
</Button>
<Button
android:id="@+id/Play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play"
android:layout_x="130px"
android:layout_y="122px"
>
</Button>
<Button
android:id="@+id/Dial"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dial"
android:layout_x="130px"
android:layout_y="252px"
>
</Button>
<TextView
android:id="@+id/Progress"
android:layout_width="126px"
android:layout_height="42px"
android:text="Progress"
android:layout_x="100px"
android:layout_y="192px"
>
</TextView>
<Button
android:id="@+id/Exit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Exit"
android:layout_x="130px"
android:layout_y="322px"
>
</Button>
</AbsoluteLayout>
|
|
3 Comments
I made a mistake regarding the simple insertion of a phone jack into the android socket. I wasn't able to replicate this. The only way I found of feeding the recording into the microphone is to turn the speakerphone on to high volume. This seems to work fairly well.
Thanks a lot for your Code.
Please can you show me how to import com.android.internal.telephony package in Eclipse?
Thanks in Advance
Nice! Thank you!