前言

喜欢的小伙伴欢迎关注,我会定期分享Android知识点及解析,还会不断更新的BATJ面试专题,欢迎大家前来探讨交流,如有好的文章也欢迎投稿。

什么是暗码?

在拨号盘中输入*#*#<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,收功。


觉得文章不错的小伙伴帮忙点点赞加关注哦 ,有什么问题的话也欢迎大家前来探讨交流。