http://apps.hi.baidu.com/share/detail/43342917
看到同事都用來“自動(dòng)重播”軟件來訂火車票了,我也下載一個(gè)來看看,下載回來反編譯一下看看,好像有廣告的啊,有一個(gè)好像是Google的還是那個(gè)廣告類在里面。索性自己弄一個(gè)好了,現(xiàn)在吸金軟件很多啊,自己編譯的才放心,呵呵!代碼不多,又有反編譯過來的代碼作為才看,雖然反編譯過來好像有的小地方不是很對,不過代碼邏輯還大概看得出來的,然后自己看了下Android SDK的文檔,Google了兩下,大概可以弄個(gè)出來。就是那個(gè)鬼模擬器和Eclipse太好資源了。在我的機(jī)器上幾乎跑不動(dòng)了,浪費(fèi)點(diǎn)時(shí)間。當(dāng)然看Android SDK文檔理解一下基本概念也花了不少時(shí)間。
不過很悲劇啊,早上起來快9點(diǎn)了,然后我用軟件一撥進(jìn)去了,結(jié)果我搞錯(cuò)時(shí)間了,以為還沒到我要買的日期就退了出來。等過一會(huì)再撥的時(shí)候票已經(jīng)賣完了,有同事說是分時(shí)間段放票出來的,再試試看或者有"軟a臥"也買了。
來看程序吧:
整個(gè)程序的代碼邏輯很簡單,就是先啟動(dòng)一個(gè)activity界面給用戶輸入,然后啟動(dòng)一個(gè)后臺service不停的自動(dòng)撥打相應(yīng)的號碼,直到撥通為止。Android下面相應(yīng)的關(guān)鍵功能實(shí)現(xiàn)代碼:
1. 撥打電話
通過Intent.ACTION_CALL 來請求系統(tǒng)的撥號Activity就可以了。在Android系統(tǒng)里面一個(gè) Intent其實(shí)就相當(dāng)于一條消息,然后系統(tǒng)就會(huì)自動(dòng)根據(jù)的你的請求去找相應(yīng)的功能模塊比如說package里面的那個(gè)類來加載了。應(yīng)用都事先注冊了說我自己能夠接受那些Intent的。撥打電話也有他的一個(gè)Intent 就是 Intent.ACTION_CALL ,其他的發(fā)送短信啊那些也有其他的,自己看下文檔就知道了。
private void PhoneCall()
{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mJustCall = false;
Uri localUri = Uri.parse("tel:" + mPhoneNumber);
Intent call = new Intent(Intent.ACTION_CALL, localUri);
call.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);;
startActivity(call);
//android.util.Log.v("TeleListener", "start to call");
mDialedCount ++;
}
注意這個(gè)功能要android.permission.CALL_PHONE的權(quán)限才能使用,所以要在AndroidManifest.xml 文件后面加上這句
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>
安裝程序的時(shí)候,有什么“需要收費(fèi)的服務(wù)什么的”提示就是這條了。
2. 手機(jī)通話狀態(tài)的查詢
這個(gè)程序還有一個(gè)功能就是能夠判斷手機(jī)是不是正在撥號啊通話啊,才好控制自動(dòng)重播的。這個(gè)Android有通知事件過來的,就像以前的“位置感應(yīng)"事件一樣,只要你注冊了這個(gè)通知事件,手機(jī)狀態(tài)變化的時(shí)候就自動(dòng)調(diào)用你Listener了。不過Android手機(jī)這個(gè)事件沒法區(qū)分“撥號”和“通話”這兩個(gè)狀態(tài)的,這兩者統(tǒng)一都是TelephonyManager.CALL_STATE_OFFHOOK狀態(tài),TelephonyManager.CALL_STATE_RINGING指的是外面撥進(jìn)來的響鈴吧。不過你看他系統(tǒng)里面其實(shí)是可以區(qū)分“撥號”和“通話”這兩個(gè)狀態(tài)的,打通了他就開始計(jì)時(shí),界面也顯示“正在通話”,只是它沒有向外部應(yīng)用開放這個(gè)接口而已。可以去看Android源碼的packages/apps/Phone/src/com/android/phone/ 下面的InCallScreen.java CallCard.java 等文件。鑒于我們的“xxxxx”,可以到http://www.netmite.com/android/mydroid/packages/apps/Phone/src/com/android/phone/ 這里去看吧,網(wǎng)上好像有很多提供在線源碼的
http://hi-android.info/src/com/android/phone/
http://gitorious.org/0xdroid/packages_apps_phone/trees/5f6f01ecda4336dfb47108e67ff909a65f14b820/src/com/android/phone
另外監(jiān)a聽這個(gè)事件需要權(quán)限<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
其實(shí)網(wǎng)上那些軟件比如說“別人大個(gè)電話過來播放隨機(jī)鈴聲”應(yīng)該也是通過這個(gè)來做到的吧。
TelephonyManager telephonyMgr = (TelephonyManager)getSystemService("phone");
TeleListener teleListener = new TeleListener(this);
telephonyMgr.listen(teleListener, 0);
class TeleListener extends PhoneStateListener {
private AutoRedialService manager;
public TeleListener(AutoRedialService a)
{
this.manager = a;
}
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
// 當(dāng)處于待機(jī)狀態(tài)中
case TelephonyManager.CALL_STATE_IDLE: {
manager.Log("IDLE");
//android.util.Log.v("TeleListener", "IDLE");
if (manager.ShouldStop() || manager.LastCallSucceed()) {
manager.stopSelf();
break;
}
PhoneCall();
break;
}
// 當(dāng)處于正在撥號出去,或者正在通話中
case TelephonyManager.CALL_STATE_OFFHOOK: {
manager.Log("OFFHOOK");
//android.util.Log.v("TeleListener", "OFFHOOK");
mJustCall = true;
//Timer t = new Timer();
break;
}
// 外面撥進(jìn)來,好沒有接撥號狀態(tài)中..
case TelephonyManager.CALL_STATE_RINGING: {
manager.Log("RINGING");
//android.util.Log.v("TeleListener", "RINGING");
break;
}
default:
break;
}
}
3. "通一話一記一錄"查詢 (媽的這都是關(guān)鍵詞啊,搞我半天發(fā)不上了)
上面說了不能區(qū)分"接通"與“撥號”狀態(tài),所以我想到的辦法只有撥完之后再去查看最近的通話記錄了,如果最后一條記錄的通話時(shí)間是大于0的,說明就撥通了,就不需要再重播了。這個(gè)用起來還可以,程序撥通就給我退出了。在Android里面這個(gè)信息需要向“content provider內(nèi)a容提供者”去要,其實(shí)就是類似查詢數(shù)據(jù)庫,可以查、增、減之類的,自己的程序也可以提供這種接口供別人查詢。注意這個(gè)也需要權(quán)a限
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
我一開始不知道,結(jié)果用那個(gè)蝸牛一樣模擬器調(diào)試了半天才調(diào)試出來。
private boolean LastCallSucceed ()
{
if ( mJustCall == false) {
return false;
}
String[] projection = new String[] {
Calls.NUMBER,
Calls.DURATION
};
ContentResolver cr = getContentResolver();
final Cursor cur = cr.query(android.provider.CallLog.Calls.CONTENT_URI,
projection,
null,
null,
Calls.DEFAULT_SORT_ORDER);
if (cur.moveToFirst()) {
int duration = cur.getInt(1);
//上次通話時(shí)間
if (duration > 0 )
{
//android.util.Log.v("TeleListener", "|"+ String.valueOf(duration) + "|");
//Log( "|"+ String.valueOf(duration) + "|");
return true;
}
}
return false;
}
4. Activity 和Service直接的通訊
在Android里面activity和service是分別屬于兩個(gè)不同的進(jìn)程的,要兩者交互還挺麻煩的,按照文檔說法,要實(shí)現(xiàn)bindservice等大堆接口,弄起來挺麻煩的,我這一個(gè)這么小玩意就算了吧。偷看了一下反編譯的出來的代碼,里面啟動(dòng)service的時(shí)候,activity發(fā)送出去Intent消息攜帶多一點(diǎn)信息過去,然后在service里面可以讀出來,但是service想傳點(diǎn)數(shù)據(jù)回activity就不行了。這種簡單的辦法也夠用了,還有朋友發(fā)現(xiàn)什么簡單辦法可以告訴我一下。
在 activity中
public void onClick(View v) {
switch (v.getId()) {
case R.id.Button_Start:
Intent msg = new Intent(this, AutoRedialService.class);
EditText pnumEdit = (EditText) findViewById(R.id.EditText_phoneNumber);
EditText retryEdit = (EditText)findViewById(R.id.EditText_RetryCount);
String phoneNumber = pnumEdit.getText().toString();
Integer i = Integer.decode(retryEdit.getText().toString());
int retryCount = i.intValue();
msg.putExtra("RetryCount", retryCount);
msg.putExtra("PhoneNumber",phoneNumber);
//retryEdit.setText(String.valueOf(retryCount-1));
startService(msg);
然后在service啟動(dòng)的時(shí)候可以讀出來
public void onStart(Intent intent, int startID) {
super.onStart(intent, startID);
mRetryCount = intent.getIntExtra("RetryCount", 0);
String tmp = intent.getStringExtra("PhoneNumber");
if (tmp != null)
{
mPhoneNumber = tmp;
}
寫完之后,我發(fā)程序發(fā)到我的手機(jī)上去試了一下,好像還行啊,有時(shí)需要撥幾十次才能打通電話,呵呵,真是省去不少功夫了。有點(diǎn)小問題,有時(shí)想停止的時(shí)候,不那么好控制,我把重?fù)荛g隔定為2秒了,要操作的很快才行啊。還有就是那個(gè)“撥號”“通話”兩種狀態(tài)的變化,感覺可以去讀取撥號程序的界面來判斷,如果能夠找到那個(gè)activity的實(shí)例,然后就好辦了再findViewById就可以讀出上面控件的狀態(tài)了。不過android好像很難獲取別的activity的實(shí)例,即使這個(gè)應(yīng)用是自己的同一個(gè)application啟動(dòng)起來的。看文檔好像通過android 測試接口Instrumentation 下面的的activitymonitor什么也許可以獲取的 到其他activity的實(shí)例,不過我沒有去試,這個(gè)需要建一個(gè)“android測試”項(xiàng)目然后從那開始吧。
完整的代碼
================AutoRedialService。java================================
package widebright.AutoRedial;
import java.util.Timer;
import android.app.Service;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.CallLog.Calls;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
public class AutoRedialService extends Service {
private int mRetryCount = 0;
private int mDialedCount = 0;
private String mPhoneNumber= "10086";
private String mDebugLog = "";
private boolean mJustCall = false;
public void Log (String text){
//mDebugLog += text;
}
@Override
public IBinder onBind(Intent msg) {
// 使用 bind的辦法,可以方便的在service和
//activity兩個(gè)不同的進(jìn)程直接交互,不過看代碼很多啊
return null;
}
private void PhoneCall()
{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mJustCall = false;
Uri localUri = Uri.parse("tel:" + mPhoneNumber);
Intent call = new Intent(Intent.ACTION_CALL, localUri);
call.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);;
startActivity(call);
//android.util.Log.v("TeleListener", "start to call");
mDialedCount ++;
}
private boolean ShouldStop()
{
return mDialedCount > mRetryCount;
}
private boolean LastCallSucceed ()
{
if ( mJustCall == false) {
return false;
}
String[] projection = new String[] {
Calls.NUMBER,
Calls.DURATION
};
ContentResolver cr = getContentResolver();
final Cursor cur = cr.query(android.provider.CallLog.Calls.CONTENT_URI,
projection,
null,
null,
Calls.DEFAULT_SORT_ORDER);
if (cur.moveToFirst()) {
int duration = cur.getInt(1);
//上次通話時(shí)間
if (duration > 0 )
{
//android.util.Log.v("TeleListener", "|"+ String.valueOf(duration) + "|");
//Log( "|"+ String.valueOf(duration) + "|");
return true;
}
}
return false;
}
public void onCreate()
{
super.onCreate();
}
public void onStart(Intent intent, int startID) {
super.onStart(intent, startID);
//android.util.Log.v("TeleListener", "starting haha");
// 獲取電話管理的一個(gè)類實(shí)例
mRetryCount = intent.getIntExtra("RetryCount", 0);
String tmp = intent.getStringExtra("PhoneNumber");
if (tmp != null)
{
mPhoneNumber = tmp;
}
TelephonyManager telephonyMgr = (TelephonyManager) this
.getSystemService(Context.TELEPHONY_SERVICE);
// 建立一個(gè)監(jiān)聽器來實(shí)時(shí)監(jiān)聽電話的通話狀態(tài)
telephonyMgr.listen(new TeleListener(this),
PhoneStateListener.LISTEN_CALL_STATE);
mDialedCount = 0;
//PhoneCall();
}
public void onDestroy()
{
TelephonyManager telephonyMgr = (TelephonyManager)getSystemService("phone");
TeleListener teleListener = new TeleListener(this);
telephonyMgr.listen(teleListener, 0);
mDialedCount = 0;
mRetryCount = 0;
mPhoneNumber= "10086";
super.onDestroy();
}
class TeleListener extends PhoneStateListener {
private AutoRedialService manager;
public TeleListener(AutoRedialService a)
{
this.manager = a;
}
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
// 當(dāng)處于待機(jī)狀態(tài)中
case TelephonyManager.CALL_STATE_IDLE: {
manager.Log("IDLE");
//android.util.Log.v("TeleListener", "IDLE");
if (manager.ShouldStop() || manager.LastCallSucceed()) {
manager.stopSelf();
break;
}
PhoneCall();
break;
}
// 當(dāng)處于正在撥號出去,或者正在通話中
case TelephonyManager.CALL_STATE_OFFHOOK: {
manager.Log("OFFHOOK");
//android.util.Log.v("TeleListener", "OFFHOOK");
mJustCall = true;
//Timer t = new Timer();
break;
}
// 外面撥進(jìn)來,好沒有接撥號狀態(tài)中..
case TelephonyManager.CALL_STATE_RINGING: {
manager.Log("RINGING");
//android.util.Log.v("TeleListener", "RINGING");
break;
}
default:
break;
}
}
}
}
===============================main.java================================
package widebright.AutoRedial;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class main extends Activity implements OnClickListener {
Button buttonStart, buttonStop;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//EditText pnumEdit = (EditText) findViewById(R.id.EditText_phoneNumber);
//EditText retryEdit = (EditText)findViewById(R.id.EditText_RetryCount);
//retryEdit.setText("10");
buttonStart = (Button) findViewById(R.id.Button_Start);
buttonStop = (Button) findViewById(R.id.Button_Stop);
buttonStart.setOnClickListener(this);
buttonStop.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.Button_Start:
Intent msg = new Intent(this, AutoRedialService.class);
EditText pnumEdit = (EditText) findViewById(R.id.EditText_phoneNumber);
EditText retryEdit = (EditText)findViewById(R.id.EditText_RetryCount);
String phoneNumber = pnumEdit.getText().toString();
Integer i = Integer.decode(retryEdit.getText().toString());
int retryCount = i.intValue();
msg.putExtra("RetryCount", retryCount);
msg.putExtra("PhoneNumber",phoneNumber);
//retryEdit.setText(String.valueOf(retryCount-1));
startService(msg);
break;
case R.id.Button_Stop:
stopService(new Intent(this, AutoRedialService.class));
break;
default:
break;
}
}
}
=====================AutoRedialManifest.xm===========================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="widebright.AutoRedial"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".main" android:icon="@drawable/icon"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:icon="@drawable/icon" android:label="@string/app_name"
android:name=".AutoRedialService">
</service>
</application>
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
</manifest>
============================layout/main.xml=============================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_gravity="right">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<TextView android:text="@string/number"
android:id="@+id/TextView01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</TextView>
<EditText android:layout_width="fill_parent"
android:singleLine="true"
android:layout_height="wrap_content"
android:text="10086"
android:phoneNumber="true"
android:id="@+id/EditText_phoneNumber">
</EditText>
<TextView android:text="@string/times"
android:id="@+id/TextView02"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<EditText android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="100"
android:id="@+id/EditText_RetryCount"
android:numeric="integer"
android:singleLine="true"
android:inputType="number">
</EditText>
<LinearLayout android:id="@+id/LinearLayout01" android:layout_width="fill_parent" android:layout_height="wrap_content">
<Button android:text="@string/start"
android:id="@+id/Button_Start"
android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_gravity="left">
</Button>
<Button android:text="@string/stop"
android:id="@+id/Button_Stop"
android:layout_height="wrap_content" android:layout_gravity="right" android:layout_width="wrap_content">
</Button>
</LinearLayout>
</LinearLayout>
==========================================
再發(fā)兩個(gè)網(wǎng)上找到的小實(shí)例,沒有測試過
1. 模擬鍵盤按鍵
final IWindowManager windowManager = IWindowManager.Stub
.asInterface(ServiceManager.getService("window"));
windowManager.injectchangerchangerKeyEvent(kEvent,true);
2. 攔截?fù)艽螂娫捥柎a操作
1.第一步,寫一個(gè)Receiver繼承自BroadcastReceiver
public class PhoneStatReceiver extends BroadcastReceiver{
private static final String TAG = "PhoneStatReceiver";
// private static MyPhoneStateListener phoneListener = new MyPhoneStateListener();
private static boolean incomingFlag = false;
private static String incoming_number = null;
@Override
public void onReceive(Context context, Intent intent) {
//如果是撥打電話
if(intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)){
incomingFlag = false;
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Log.i(TAG, "call OUT:"+phoneNumber);
}else{
//如果是來電
TelephonyManager tm =
(TelephonyManager)context.getSystemService(Service.TELEPHONY_SERVICE);
switch (tm.getCallState()) {
case TelephonyManager.CALL_STATE_RINGING:
incomingFlag = true;//標(biāo)識當(dāng)前是來電
incoming_number = intent.getStringExtra("incoming_number");
Log.i(TAG, "RINGING :"+ incoming_number);
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
if(incomingFlag){
Log.i(TAG, "incoming ACCEPT :"+ incoming_number);
}
break;
case TelephonyManager.CALL_STATE_IDLE:
if(incomingFlag){
Log.i(TAG, "incoming IDLE");
}
break;
}
}
}
}
第二步:在AndroidManifest.xml,配置寫好的Receiver,并攔截相應(yīng)的BroadCastAction,
另外注意加上相應(yīng)的權(quán)限。
<receiver android:name=".filter.PhoneStatReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
另外有撥打緊急號碼權(quán)限
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />