背景
最近看到一个需要在拨号界面输入工程码,弹出指定界面的功能。类似输入”*#06#”弹出手机IMEI号,当然根据具体需求可以弹出隐藏在手机里面不针对终端用户使用的功能。下面就具体分析一下该功能的流程。
具体分析
在6.0中,拨号界面的显示和响应逻辑在DialpadFragment中。这个界面有一个TextEdit对象mDigits,里面容纳我们拨号的时候,输入的号码。
这个界面在初始化这个mDigits的时候就在上面添加了一个监听字符变化的监听器:
mDigits.addTextChangedListener(this);
这个监听器是一个TextWatcher类型的对象,当你输入字符之后,会触发下面的响应函数:
public void afterTextChanged(Editable input) {
// When DTMF dialpad buttons are being pressed, we delay SpecialCharSequenceMgr sequence,
// since some of SpecialCharSequenceMgr's behavior is too abrupt for the "touch-down"
// behavior.
if (!mDigitsFilledByIntent && SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
// A special sequence was entered, clear the digits
mDigits.getText().clear();
}
}
从而触发SpecialCharSequenceMgr的静态成员函数handleChars。
public static boolean handleChars(Context context, String input, EditText textField) {
//get rid of the separators so that the string gets parsed correctly
String dialString = PhoneNumberUtils.stripSeparators(input);
if (handleDeviceIdDisplay(context, dialString)
|| handlePRLVersion(context, dialString)
|| handleRegulatoryInfoDisplay(context, dialString)
|| handlePinEntry(context, dialString)
|| handleAdnEntry(context, dialString, textField)
|| handleSecretCode(context, dialString)) {
return true;
}
return false;
}
注意看if里面的这些函数,可以看到一些是处理”*#06#”之类硬编码进代码里面的工程码。如果自己要自定义的话,那么就会调用最后一个函数handleSecretCode。在这个函数里面,逻辑就很清晰了。
/**
* Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*.
* If a secret code is encountered an Intent is started with the android_secret_code://<code>
* URI.
*
* @param context the context to use
* @param input the text to check for a secret code in
* @return true if a secret code was encountered
*/
static boolean handleSecretCode(Context context, String input) {
// Secret codes are in the form *#*#<code>#*#*
int len = input.length();
if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
final Intent intent = new Intent(SECRET_CODE_ACTION, Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
context.sendBroadcast(intent);
return true;
}
if (!TextUtils.isEmpty(context.getString(R.string.oem_key_code_action))) {
if (len > 10 && !input.startsWith("*#*#")
&& input.startsWith("*#") && input.endsWith("#")) {
Intent intent = new Intent(context.getString(R.string.oem_key_code_action));
intent.putExtra(context.getString(R.string.oem_code), input);
context.sendBroadcast(intent);
return true;
}
}
return false;
}
工程码是“*#*#5567#*#*”或者“*#44565#”的形式。都是通过发送广播的形式,将不同的action发送出去。第一种发送的Intent是:
new Intent("android.provider.Telephony.SECRET_CODE", Uri.parse("android_secret_code://5567"));
注意,这个Uri里面解析出来,scheme是“android_secret_code”,host就是5567了。这个对应到一个BroadcastReceiver的data元素的内容。具体请搜索了解Intent-filter匹配规则。
第二种发送的Intent是:
//oem_key_code_action在xml中定义,在qualcomm中是为空的。oem_code也是为空的。所以如果各位需要自定义oem相关的内容,最好在给这两个赋值,并且在响应的activity中添加对应的内容。
new Intent("oem_key_code_action").putExtra("oem_code", input);
针对第一种情况,在Packages/apps/下面搜索这个action,会发现在Email模块里面的AndroidManifest.xml中有对这个action和data的响应:
<receiver
android:name=".service.EmailBroadcastReceiver"
android:enabled="true" android:permission="com.android.email.permission.ACCESS_PROVIDER">
<!-- To handle secret code to activate the debug screen. -->
<intent-filter>
<action android:name="android.provider.Telephony.SECRET_CODE" />
<data android:scheme="android_secret_code" android:host="36245" />
</intent-filter>
</receiver>
注意,这个对应上了action,以及对应的data。查看一下这个Receiver:
//EmailBroadcastReceiver.java
public void onReceive(Context context, Intent intent) {
EmailBroadcastProcessorService.processBroadcastIntent(context, intent)
}
当然你也可以在这个onReceiver里面启动一个activity,取决于你的实际需求。通过实际在拨号界面”*#*#36245#*#*”确实弹出了Email模块的设置界面。
整体UML图如下
图一 工程码响应流程
应用
如果是自己需要在项目中使用,只需要在对应模块的AndroidManifest中声明一个BroadcastReceiver,来响应指定action(注意,针对第一种情况和第二种情况的action不一样。对于第二种情况,需要自己声明对应的”oem_key_code_action”和”oem_code”)和Data。具体参考Email模块的AndroidManifest文件。然后在这个BroadcastReceiver里面的onReceive函数里面startActivity,启动你想要启动的界面即可。