AOSP-电话监听
这个监听并不是说通话中进行语音监听,而是通过自动拨号,并且不显示拨号UI,也不会发出监听方的声音。
常用在Android系统的客户端,比如说儿童手表,远程摄像头之类的。
当然,这个功能也可以做成录音机,录音后把声音发送到特定的服务器。
播放的方式按量计费,实时监听。
如何修改呢?
我们先梳理一下使用流程:
- 管理中从服务器向手表下发监听指令,携带目标号码的
- 终端设备收到指令后,向目标号码进行拨号
- 目标号码手机接听电话,对终端设备进行监听,终端设备无法听到监听手机的声音,监听手机可以听到终端设备的声音
- 手机挂断后状态恢复正常
由以上这个流程,我们其实做法也很简单。
- 接收到监听的指令以后,设置一个全局共享的变量,设置当前为监听模式
- 设置完变量后,进行拨号
- 拨号要拉起UI的地方拿到这个变量进行判断,如果是监听模式则不拉起界面,并且设置外放的声音静音
- 监听到通话结束的时候我们修改标记量为非监听模式,恢复音量,一切恢复正常。
细节问题
- 如果按正常流程走,拨号的时候,会亮屏,拨号完毕会亮屏
- 如果电话应用crash掉了,那么走不到通话结束的流程,那么标记量无法修改,后面的来电和拨号无法使用
标记量的修改位置
- 指令下发,设置为监听模式
- 电话挂断,设置为非监听模式
- 电话应用crash掉,设置为非监听模式
- 设备重启,设置为非监听模式
以上几个操作,就是为了防止意外情况,防御代码
具体实现
- 标记量,我们可以使用内容提供者,或者全局的prop来设置这个监听的状态。
比如说我可以使用prop来设置,persist.sys.sob.call.monitor,这个长度不要超过31位,要不可能会报错的。
然后指令下发的时候,把这个值设置为true.
SystemProperties.set("persist.sys.sob.call.monitor", "true");
- 设置完标记量,那就开始拨号吧
Intent intent = new Intent(Intent.ACTION_CALL);
Log.d("do call number ==> " + data);
intent.setData(Uri.parse("tel:" + data));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (ActivityCompat.checkSelfPermission(runtime.getContextProxy().context,
Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
Log.d("no permission to call...");
SystemProperties.set("persist.sys.sob.call.monitor", "false");
return;
}
context.startActivity(intent);
- 拨号以后呢,流程上会走到哪里呢?
这个具体的流程就不和大家去看了,在AOSP的视频课程里会大概和大家去跟一下,这里面直接告诉大家了。
在packages/apps/InCallUI这个应用里有一个类叫做InCallPresenter。
在这个类的
private void showInCall(boolean showDialpad)
这个方法里,就是显示拨号UI的了。
在这个方法里,我们稍加判断,不就阔以控制UI是否显示了吗?
//add by TrillGates
//得加判断,如果静默监听,则不拉起该UI
String isMonitorMode = SystemProperties.get("persist.sys.sob.call.monitor", "false");
Log.d(TAG, "isMonitorMode==> " + isMonitorMode);
if ("true".equals(isMonitorMode)) {
//不打开界面了,并且外放静音
VolumePresenter.getInstance(mContext).updateVolume(0);
} else {
mContext.startActivity(getInCallIntent(showDialpad));
}
至于静音,你可以静麦克风的输入,静外放的声音,静铃声
到这里,自动拨号也不会有UI显示了,也不会有声音了。
但是屏幕会亮呀,虽然显示的是Launcher,是不是怪怪的呀。
- 如何确定是由什么引起的屏幕亮起呢?
我们知道,让屏幕亮起可以通过WakeLock,也可以通过PowerManager的wakeUp方法。
我们只要在这两个方法里把调用栈输出,然后去复现一次即可确定是谁把屏幕唤醒的。
我抓了一次Log实验下来:
需要修改有
- /packages/services/Telephony/src/com/android/phone/CallHandlerServiceProxy.java
private void wakeUpScreen() {
- Log.d(TAG, "isAppMonitorSet ? wakeUpScreen()");
- if(CallNotifier.isAppMonitorSet)return;//zyb add
+ //如果是监听模式,那么就不要亮屏了
+ //code by TrillGates
+ String isMonitorStr =SystemProperties.get("persist.sys.sob.call.monitor","false");
+ if("true".equals(isMonitorStr)){
+ return;
+ }
final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
pm.wakeUp(SystemClock.uptimeMillis());
}
修改/packages/services/Telephony/src/com/android/phone/MsmsPhoneGlobals.java
里的WakeLock的flag,不要亮起屏幕,只唤醒CPU即可
- mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, LOG_TAG);
+ //comment by TrillGates
+ //mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, LOG_TAG);
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
// lock used to keep the processor awake, when we don't care for the display.
- mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
- | PowerManager.ON_AFTER_RELEASE, LOG_TAG);
+
+ //mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
+ // | PowerManager.ON_AFTER_RELEASE, LOG_TAG);
+
+ mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
并且亮屏的时候要加以判断
void wakeUpScreen() {
synchronized (this) {
+ //如果是监听模式,那么就不要亮屏了
+ //code by TrillGates
+ String isMonitorStr =SystemProperties.get("persist.sys.sob.call.monitor","false");
+ if("true".equals(isMonitorStr)){
+ return;
+ }
...
到这里,就已经是可以不亮屏了。
- 电话状态的监听
public class PhoneCallListener {
private static final String TAG = "PhoneCallListener";
private final Context mContext;
private final AudioManager mAudioManager;
public PhoneCallListener(Context context) {
this.mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Util.setSystemProperties("persist.sys.call.monitor", "false");
TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String phoneNumber) {
ICLogger.d("state == > " + state);
ICLogger.d("phoneNumber == > " + phoneNumber);
if (state == TelephonyManager.CALL_STATE_IDLE) {
//可以延时一定时间去修改值,防止结束后亮屏
new Thread(() -> {
SystemClock.sleep(2 * 1000);
Systempropertis.get("persist.sys.sob.call.monitor", "false");
ICLogger.d("persist.sys.call.monitor ==> " + Systempropertis.get("persist.sys.sob.call.monitor"));
}).start();
updateVolume(100);
}
}
}, PhoneStateListener.LISTEN_CALL_STATE);
}
public void updateVolume(int progress) {
Log.d(TAG, "progress ==> " + progress);
//进行换算
int volume = (int) (progress * 15.0f / 100);
Log.d(TAG, "volume ==> " + volume);
mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, volume, 0);
int ringerMode = mAudioManager.getRingerMode();
Log.d(TAG, "ringerMode ==> " + ringerMode);
mAudioManager.setStreamVolume(AudioManager.STREAM_RING, volume, 0);
}
}
以上延时的,同学们可以换一些优雅的方式去写,我随便new了个线程这种是不对的, 得管理起来。
其他的话,就是在开机的时候,注册此监听的时候,要重置一下默认值,预防某些情况下,监听完了,无法重置默认值导致来电无法显示,或者无法拨号。