共计 4120 个字符,预计需要花费 11 分钟才能阅读完成。
什么是暗码?
在拨号盘中输入 *#*#<code>#*#*
后,APP 可以监控到这些输入,然后做相应的动作,比如启动应用,是不是有点骚。
下面看下这个骚操作是如何实现的。
效果预览
源码
DialtactsActivity#showDialpadFragment
DialtactsActivity 中有个 showDialpadFragment 方法,用来加载显示拨号盘,因此入口就从 showDialpadFragment 看起,基于 Android P 分析。
private void showDialpadFragment(boolean animate) { | |
//…… | |
final FragmentTransaction ft = getFragmentManager().beginTransaction(); | |
if (dialpadFragment == null) {dialpadFragment = new DialpadFragment(); | |
ft.add(R.id.dialtacts_container, dialpadFragment, TAG_DIALPAD_FRAGMENT); | |
} else {ft.show(dialpadFragment); | |
} | |
//…… | |
} |
具体实现在 DialpapFragment 中,看到 DialpapFragment 实现了 TextWatcher,TextWatcher 有 3 个重要方法,分别为:beforeTextChanged,onTextChanged 和 afterTextChanged,重点看 afterTextChanged 方法。
DialpadFragment#afterTextChanged
public class DialpadFragment extends Fragment | |
implements View.OnClickListener, | |
View.OnLongClickListener, | |
View.OnKeyListener, | |
AdapterView.OnItemClickListener, | |
TextWatcher, | |
PopupMenu.OnMenuItemClickListener, | |
DialpadKeyButton.OnPressedListener { | |
//…… | |
@Override | |
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 (!digitsFilledByIntent | |
&& SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), digits)) { | |
// A special sequence was entered, clear the digits | |
digits.getText().clear(); | |
} | |
if (isDigitsEmpty()) { | |
digitsFilledByIntent = false; | |
digits.setCursorVisible(false); | |
} | |
if (dialpadQueryListener != null) {dialpadQueryListener.onDialpadQueryChanged(digits.getText().toString()); | |
} | |
updateDeleteButtonEnabledState();} | |
//…… | |
} |
这里调用了 SpecialCharSequenceMgr 辅助工具类的 handleChars 方法,看这个方法。
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) | |
|| handleRegulatoryInfoDisplay(context, dialString) | |
|| handlePinEntry(context, dialString) | |
|| handleAdnEntry(context, dialString, textField) | |
|| handleSecretCode(context, dialString)) {return true;} | |
if (MotorolaUtils.handleSpecialCharSequence(context, input)) {return true;} | |
return false; | |
} |
handleChars 方法中,会对各种特殊的 secret code 进行匹配处理,这里我们看 handleSecretCode。
SpecialCharSequenceMgr#handleSecretCode
static boolean handleSecretCode(Context context, String input) { | |
// Secret code specific to OEMs should be handled first. | |
if (TranssionUtils.isTranssionSecretCode(input)) {TranssionUtils.handleTranssionSecretCode(context, input); | |
return true; | |
} | |
// Secret codes are accessed by dialing *#*#<code>#*#* or "*#<code_starting_with_number>#" | |
if (input.length() > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {String secretCode = input.substring(4, input.length() - 4); | |
TelephonyManagerCompat.handleSecretCode(context, secretCode); | |
return true; | |
} | |
return false; | |
} |
再看下 TelephonyManagerCompat.handleSecretCode 方法。
TelephonyManagerCompat#handleSecretCode
public static void handleSecretCode(Context context, String secretCode) { | |
// Must use system service on O+ to avoid using broadcasts, which are not allowed on O+. | |
if (BuildCompat.isAtLeastO()) {if (!TelecomUtil.isDefaultDialer(context)) { | |
LogUtil.e( | |
"TelephonyManagerCompat.handleSecretCode", | |
"not default dialer, cannot send special code"); | |
return; | |
} | |
context.getSystemService(TelephonyManager.class).sendDialerSpecialCode(secretCode); | |
} else { | |
// System service call is not supported pre-O, so must use a broadcast for N-. | |
Intent intent = | |
new Intent(SECRET_CODE_ACTION, Uri.parse("android_secret_code://" + secretCode)); | |
context.sendBroadcast(intent); | |
} | |
} |
可以看到在拨号中接收到*#*#<code>#*#*
这样的指令时,程序会对外发送广播,这就意味着我们能够接收这个广播然后可以做我们想做的事情。
接下来我们看看这个接受广播代码是怎么写。
应用
首先在 AndroidManifest 文件中注册广播接收器。
<receiver | |
android:name=".SecretCodeReceiver"> | |
<intent-filter> | |
<action android:name="android.provider.Telephony.SECRET_CODE" /> | |
<data android:scheme="android_secret_code" android:host="1010" /> | |
</intent-filter> | |
</receiver> |
接收广播,启动应用。
public class SecretCodeReceiver extends BroadcastReceiver { | |
@Override | |
public void onReceive(Context context, Intent intent) {if (intent != null && SECRET_CODE_ACTION.equals(intent.getAction())){Intent i = new Intent(Intent.ACTION_MAIN); | |
i.setClass(context, MainActivity.class); | |
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
context.startActivity(i); | |
} | |
} | |
} |
这样只要在拨号中输入 *#*#1010#*#*
就能启动相应的应用程序,OK,收功。
公众号
我的公众号:吴小龙同学,欢迎关注交流~
正文完