增加写入拨号,读取数据,优化无障碍

This commit is contained in:
2025-07-08 02:35:26 +08:00
parent 4b86e4fd02
commit 4ad7351a58
11 changed files with 402 additions and 28 deletions

View File

@@ -1,15 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ttstd.elderlyassistant">
<!--NFC权限-->
<uses-permission android:name="android.permission.NFC" />
<!--声明只有带NFC的手机才可以下载-->
<!-- NFC权限 -->
<uses-permission android:name="android.permission.NFC" /> <!-- 声明只有带NFC的手机才可以下载 -->
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<application
android:name=".base.BaseApplication"
android:allowBackup="true"
@@ -28,7 +25,11 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activity.setting.SettingsActivity" />
<activity
android:name=".activity.write.WriteMessageActivity"
android:exported="true"
android:launchMode="singleTop" />
<activity
android:name=".activity.nfc.NfcActivity"
android:exported="true"
@@ -46,10 +47,11 @@
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="vnd.android.cursor.item/ndef_msg" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service

View File

@@ -64,14 +64,15 @@ public class SelectToSpeakService extends AccessibilityService {
private static final String DIALER_HANDS_FREE_TEXT = "免提";
private static final String DIALER_HANDS_FREE_CLOSE_TEXT = "免提,已关闭";
public static final int TYPE_VOICE = 0;
public static final int TYPE_VIDEO = 1;
public static final int TYPE_VOICE = 2;
public static final int TYPE_VIDEO = 3;
private static final int WAIT_TIME = 1500;
private int mCallType = TYPE_VOICE;
private WechatContact mContact;
private int mCallType = TYPE_VOICE;
private Step mCurrentStep = Step.WAITING;
private String mName = "";//微信昵称
private boolean mAutoAccept = false;
@@ -114,10 +115,15 @@ public class SelectToSpeakService extends AccessibilityService {
if (intent != null) {
mContact = (WechatContact) intent.getSerializableExtra("WechatInfo");
Log.e(TAG, "onStartCommand: wechatInfo = " + mContact);
mCallType = intent.getIntExtra("call_type", TYPE_VOICE);
mName = mContact.getName();
mCurrentStep = Step.CLICK_HOME;
launchWeChat();
if (mContact != null) {
mName = mContact.getName();
mCallType = mContact.getOperation();
mCurrentStep = Step.CLICK_HOME;
launchWeChat();
} else {
Toaster.showLong("没有获取到联系人数据");
}
}
return super.onStartCommand(intent, flags, startId);
}
@@ -819,7 +825,6 @@ public class SelectToSpeakService extends AccessibilityService {
DESCRIPTION
}
public static final String SETTING_CALL_TYPE_ACTION = "setting_call_type_action";
public static final String SETTING_AUTOMATIC_ANSWER_ACTION = "setting_automatic_answer_action";
private SettingBroadcastReceiver mSettingBroadcastReceiver;
@@ -829,7 +834,6 @@ public class SelectToSpeakService extends AccessibilityService {
mSettingBroadcastReceiver = new SettingBroadcastReceiver();
}
IntentFilter filter = new IntentFilter();
filter.addAction(SETTING_CALL_TYPE_ACTION);
filter.addAction(SETTING_AUTOMATIC_ANSWER_ACTION);
registerReceiver(mSettingBroadcastReceiver, filter);
}
@@ -841,11 +845,6 @@ public class SelectToSpeakService extends AccessibilityService {
Log.e("SettingReceiver", "onReceive: " + action);
if (TextUtils.isEmpty(action)) return;
switch (action) {
case SETTING_CALL_TYPE_ACTION:
int callType = intent.getIntExtra("call_type", TYPE_VOICE);
mCallType = callType;
Log.e("SettingReceiver", "onReceive: callType = " + callType);
break;
case SETTING_AUTOMATIC_ANSWER_ACTION:
boolean autoAnswer = intent.getBooleanExtra("auto_answer", false);
mAutoAccept = autoAnswer;

View File

@@ -21,6 +21,7 @@ import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.hjq.toast.Toaster;
import com.ttstd.elderlyassistant.R;
import com.ttstd.elderlyassistant.activity.setting.SettingsActivity;
import com.ttstd.elderlyassistant.bean.WechatContact;
import com.ttstd.elderlyassistant.databinding.ActivityMainBinding;
import com.ttstd.elderlyassistant.utils.AccessibilityUtils;
@@ -188,5 +189,9 @@ public class MainActivity extends AppCompatActivity {
public void openAccessibility(View view) {
AccessibilityUtils.openAccessibilitySettings(MainActivity.this);
}
public void openSettings(View view) {
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
}
}
}

View File

@@ -0,0 +1,33 @@
package com.ttstd.elderlyassistant.activity.setting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import com.ttstd.elderlyassistant.R;
import com.ttstd.elderlyassistant.activity.write.WriteMessageActivity;
import com.ttstd.elderlyassistant.databinding.ActivitySettingsBinding;
public class SettingsActivity extends AppCompatActivity {
private ActivitySettingsBinding mBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_settings);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_settings);
mBinding.setClick(new BtnClick());
}
public class BtnClick {
public void openWriteSettings(View view) {
startActivity(new Intent(SettingsActivity.this, WriteMessageActivity.class));
}
}
}

View File

@@ -0,0 +1,156 @@
package com.ttstd.elderlyassistant.activity.write;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import com.hjq.toast.Toaster;
import com.ttstd.elderlyassistant.R;
import com.ttstd.elderlyassistant.databinding.ActivityWriteMessageBinding;
import com.ttstd.elderlyassistant.utils.HexUtils;
import com.ttstd.elderlyassistant.utils.WriteMessageUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class WriteMessageActivity extends AppCompatActivity {
private static final String TAG = "WriteMessageActivity";
private ActivityWriteMessageBinding mBinding;
private NfcAdapter mNfcAdapter;
private PendingIntent mPendingIntent;
private Tag mTag;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_write_message);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_write_message);
mBinding.setClick(new BtnClick());
// 获取NFC适配器实例
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
Toast.makeText(this, "设备不支持NFC", Toast.LENGTH_SHORT).show();
finish();
return;
}
}
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart: ");
//此处adapter需要重新获取否则无法获取message
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
//一旦截获NFC消息就会通过PendingIntent调用窗口
mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0);
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume: ");
// 启用前台调度实时监听NFC标签
if (mNfcAdapter != null) {
/*TAG: Tech [android.nfc.tech.NfcA, android.nfc.tech.MifareUltralight, android.nfc.tech.Ndef]*/
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
}
}
@Override
protected void onPause() {
super.onPause();
Log.e(TAG, "onPause: ");
// 禁用前台调度,减少资源消耗
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String action = intent.getAction();
Log.e(TAG, "onNewIntent: action = " + action);
if (TextUtils.isEmpty(action)) return;
switch (action) {
case NfcAdapter.ACTION_NDEF_DISCOVERED:
case NfcAdapter.ACTION_TAG_DISCOVERED:
//1.获取Tag对象
mTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (mTag != null) {
//2.获取Ndef的实例
Ndef ndef = Ndef.get(mTag);
String id = HexUtils.bytesToHexString(mTag.getId());
mBinding.tvId.setText("已连接: " + id);
Log.e(TAG, "onNewIntent: id = " + id);
if (ndef != null) {
Log.e(TAG, "onNewIntent: type = " + ndef.getType());
Log.e(TAG, "onNewIntent: MaxSize = " + ndef.getMaxSize());
parseNdefData(mTag);
}
}
break;
default:
}
}
// 解析 NDEF 数据
private void parseNdefData(Tag tag) {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
try {
ndef.connect();
NdefMessage ndefMessage = ndef.getNdefMessage();
if (ndefMessage != null) {
// 提取第一条记录的文本
NdefRecord record = ndefMessage.getRecords()[0];
String payload = new String(record.getPayload(), StandardCharsets.UTF_8);
Log.e("NFC", "NDEF Text: " + payload.substring(1)); // 跳过编码头
}
} catch (Exception e) {
Log.e("NFC", "NDEF解析失败" + e.getMessage());
} finally {
try {
ndef.close();
} catch (IOException e) {
}
}
}
}
public class BtnClick {
public void write(View view) {
if (mTag == null) {
Toaster.showLong("NFC卡片未连接");
return;
}
Editable editable = mBinding.etPhone.getText();
if (TextUtils.isEmpty(editable)) {
Toaster.showLong("请输入电话");
return;
}
WriteMessageUtils.writePhoneToTag(mTag, editable.toString());
}
}
}

View File

@@ -56,6 +56,14 @@ public class WechatContact implements Serializable {
this.tag = tag;
}
public int getOperation() {
return operation;
}
public void setOperation(int operation) {
this.operation = operation;
}
@NonNull
@Override
public String toString() {

View File

@@ -0,0 +1,15 @@
package com.ttstd.elderlyassistant.utils;
public class HexUtils {
// 字节数组转16进制字符串优化版
public static String bytesToHexString(byte[] bytes) {
if (bytes == null) return "";
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b)); // 直接格式化为大写16进制
}
return sb.toString();
}
}

View File

@@ -0,0 +1,45 @@
package com.ttstd.elderlyassistant.utils;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import java.io.IOException;
public class WriteMessageUtils {
public static void writePhoneToTag(Tag tag, String phoneNumber) {
// 构建tel:协议的URI
Uri uri = Uri.parse("tel:" + phoneNumber);
// 创建URI类型的NdefRecord
NdefRecord uriRecord = NdefRecord.createUri(uri);
NdefMessage message = new NdefMessage(new NdefRecord[]{uriRecord});
try {
Ndef ndef = Ndef.get(tag);
ndef.connect();
ndef.writeNdefMessage(message); // 写入NDEF消息
ndef.close();
} catch (IOException | FormatException e) {
e.printStackTrace();
}
}
public static void writeUrlToTag(Tag tag, String url) {
// 确保URL包含协议如http://
Uri uri = Uri.parse(url);
NdefRecord uriRecord = NdefRecord.createUri(uri);
NdefMessage message = new NdefMessage(new NdefRecord[]{uriRecord});
try {
Ndef ndef = Ndef.get(tag);
ndef.connect();
ndef.writeNdefMessage(message);
ndef.close();
} catch (IOException | FormatException e) {
e.printStackTrace();
}
}
}

View File

@@ -16,17 +16,34 @@
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{click::openAccessibility}"
android:text="打开无障碍权限"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{click::openAccessibility}"
android:text="打开无障碍权限"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{click::openSettings}"
android:text="设置"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".activity.setting.SettingsActivity">
<data>
<variable
name="click"
type="com.ttstd.elderlyassistant.activity.setting.SettingsActivity.BtnClick" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{click::openWriteSettings}"
android:text="写入数据"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".activity.write.WriteMessageActivity">
<data>
<variable
name="click"
type="com.ttstd.elderlyassistant.activity.write.WriteMessageActivity.BtnClick" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent">
<EditText
android:id="@+id/et_phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="phone"
android:inputType="phone"
android:maxLines="1"
android:singleLine="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{click::write}"
android:text="写入"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_phone" />
<TextView
android:id="@+id/tv_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>