From f242a915bcc4929d5d52b4523ce52da265b2d0b9 Mon Sep 17 00:00:00 2001 From: tongtongstudio Date: Fri, 18 Jul 2025 01:46:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=80=9A=E8=BF=87scheme?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E6=8B=A8=E5=8F=B7=E5=92=8C=E8=A7=86=E9=A2=91?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=95=B0=E6=8D=AE=E5=8A=A0=E5=AF=86?= =?UTF-8?q?=E3=80=82=E4=BC=98=E5=8C=96ContactInfo=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E3=80=82=E4=BC=98=E5=8C=96=E6=97=A0=E9=9A=9C?= =?UTF-8?q?=E7=A2=8D=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 39 ++- .../selecttospeak/SelectToSpeakService.java | 186 ++++++------ .../activity/main/MainActivity.java | 6 +- .../activity/nfc/NfcActivity.java | 16 +- .../activity/scheme/SchemeActivity.java | 175 ++++++++++++ .../activity/write/WriteMessageActivity.java | 136 ++++++++- .../activity/write/WriteMessageViewModel.java | 11 + .../{WechatContact.java => ContactInfo.java} | 37 ++- .../ttstd/elderlyassistant/utils/AESUtil.java | 35 +++ .../utils/WriteMessageUtils.java | 57 ++-- app/src/main/res/layout/activity_nfc.xml | 14 +- app/src/main/res/layout/activity_scheme.xml | 9 + .../res/layout/activity_write_message.xml | 264 ++++++++++++++---- 13 files changed, 760 insertions(+), 225 deletions(-) create mode 100644 app/src/main/java/com/ttstd/elderlyassistant/activity/scheme/SchemeActivity.java create mode 100644 app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageViewModel.java rename app/src/main/java/com/ttstd/elderlyassistant/bean/{WechatContact.java => ContactInfo.java} (64%) create mode 100644 app/src/main/java/com/ttstd/elderlyassistant/utils/AESUtil.java create mode 100644 app/src/main/res/layout/activity_scheme.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ebd5c8e..02dd7a3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,12 @@ + + + + @@ -53,16 +57,31 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + () { @Override public void subscribe(@NonNull ObservableEmitter emitter) throws Throwable { @@ -106,23 +108,23 @@ public class SelectToSpeakService extends AccessibilityService { .subscribe(new Consumer() { @Override public void accept(AccessibilityEvent accessibilityEvent) throws Throwable { - Log.e(TAG, "accept: "); - _onAccessibilityEvent(accessibilityEvent); + Log.e(TAG, "initDisposable accept: "); + parseAccessibilityEvent(accessibilityEvent); } }); - Log.e(TAG, "dealingAccessibilityEvent: mDisposable hash = " + mDisposable.hashCode()); + Log.e(TAG, "initDisposable: mDisposable hash = " + mDisposable.hashCode()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.e(TAG, "onStartCommand: "); if (intent != null) { - mContact = (WechatContact) intent.getSerializableExtra("WechatInfo"); + mContact = (ContactInfo) intent.getSerializableExtra("WechatInfo"); Log.e(TAG, "onStartCommand: wechatInfo = " + mContact); if (mContact != null) { mName = mContact.getName(); mCallType = mContact.getOperation(); - mCurrentStep = Step.CLICK_HOME; + mCurrentProcedure = Procedure.CLICK_HOME; launchWeChat(); } else { Toaster.showLong("没有获取到联系人数据"); @@ -155,12 +157,12 @@ public class SelectToSpeakService extends AccessibilityService { } private void checkClassName(AccessibilityEvent event) { - Log.e(TAG, "checkClassName: mCurrentStep = " + mCurrentStep); + Log.e(TAG, "checkClassName: mCurrentStep = " + mCurrentProcedure); if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { String currentPackageName = event.getPackageName().toString(); String currentClassName = event.getClassName().toString(); - switch (mCurrentStep) { + switch (mCurrentProcedure) { case WAITING: if (!TextUtils.isEmpty(currentPackageName) && "com.android.incallui".equals(currentPackageName)) { Log.e(TAG, "checkClassName: to dialer hands free"); @@ -178,7 +180,7 @@ public class SelectToSpeakService extends AccessibilityService { case "com.tencent.mm.plugin.account.ui.WelcomeActivity": case "com.tencent.mm.plugin.account.ui.LoginPasswordUI": Toaster.showLong("请先登录微信"); - mCurrentStep = Step.WAITING; + mCurrentProcedure = Procedure.WAITING; break; case "com.tencent.mm.plugin.label.ui.ContactLabelManagerUI": @@ -197,20 +199,20 @@ public class SelectToSpeakService extends AccessibilityService { * * @param event */ - private void _onAccessibilityEvent(AccessibilityEvent event) { - Log.e(TAG, "_onAccessibilityEvent: " + mCurrentStep); - switch (mCurrentStep) { + private void parseAccessibilityEvent(AccessibilityEvent event) { + Log.e(TAG, "parseAccessibilityEvent: " + mCurrentProcedure); + switch (mCurrentProcedure) { case WAITING: mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_CALL_AUTO_ACCEPT_KEY, false); - Log.e(TAG, "_onAccessibilityEvent: mAutoAccept = " + mAutoAccept); + Log.e(TAG, "parseAccessibilityEvent: mAutoAccept = " + mAutoAccept); if (!mAutoAccept) { return; } if (stepAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) { - mCurrentStep = Step.WECHAT_HANDS_FREE; + mCurrentProcedure = Procedure.WECHAT_HANDS_FREE; Toast.makeText(this, "已自动接听视频/语音", Toast.LENGTH_LONG).show(); } else { - mCurrentStep = Step.WAITING; + mCurrentProcedure = Procedure.WAITING; // clickAnswer(); } break; @@ -221,29 +223,29 @@ public class SelectToSpeakService extends AccessibilityService { if (findHandsFree(Property.DESCRIPTION, DIALER_HANDS_FREE_CLOSE_TEXT)) { dialerHandsFree(Property.TEXT, DIALER_HANDS_FREE_TEXT); } else { - mCurrentStep = Step.WAITING; + mCurrentProcedure = Procedure.WAITING; } break; case CLICK_HOME://主页能找到直接点击进去更多 if (stepHome(Property.TEXT, mName)) { - Log.e(TAG, "_onAccessibilityEvent: not found contact in home"); + Log.e(TAG, "parseAccessibilityEvent: not found contact in home"); } else { - clickViewById("com.tencent.mm:id/jha", Step.CLICK_SEARCH); + clickViewById("com.tencent.mm:id/jha", Procedure.CLICK_SEARCH); // step(Property.DESCRIPTION, SEARCH_TEXT, Step.CLICK_SEARCH); } break; case CLICK_SEARCH: - putString(mName, Step.CLICK_SEARCH_CONTACT); + putString(mName, Procedure.CLICK_SEARCH_CONTACT); break; case CLICK_SEARCH_CONTACT: - if (findSearchContact(Step.FIND_CONTACT)) { - findSearchContact(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL); + if (findSearchContact(Procedure.FIND_CONTACT)) { + findSearchContact(Property.TEXT, mName, Procedure.CLICK_QUICK_WECHAT_CALL); } else { Toaster.show("没有找到联系人"); } break; case CLICK_QUICK_WECHAT_CALL://点击更多页面 - clickViewById("com.tencent.mm:id/bjz", Step.CLICK_TARGET); + clickViewById("com.tencent.mm:id/bjz", Procedure.CLICK_TARGET); // step(Property.DESCRIPTION, MORE_NAME, Step.CLICK_TARGET); break; case CLICK_TARGET://点击视频通话 @@ -252,40 +254,34 @@ public class SelectToSpeakService extends AccessibilityService { break; case CLICK_CONTACT://进入通讯录界面 - if (stepHome(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG)) { - Log.e(TAG, "_onAccessibilityEvent: enter contact"); + if (stepHome(Property.TEXT, CONTACT_TEXT, Procedure.FIND_TAG)) { + Log.e(TAG, "parseAccessibilityEvent: enter contact"); } else { touchContact(); } break; case FIND_CONTACT://模拟滑动找到联系人 - findSearchContact(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL); + findSearchContact(Property.TEXT, mName, Procedure.CLICK_QUICK_WECHAT_CALL); break; case FIND_TAG: - step(Property.TEXT, TAG_TEXT, Step.CLICK_TAG); + step(Property.TEXT, TAG_TEXT, Procedure.CLICK_TAG); break; case CLICK_TAG: break; case CLICK_NAME://点击item - findContact(Property.TEXT, mName, Step.CLICK_INFO); + findContact(Property.TEXT, mName, Procedure.CLICK_INFO); break; case CLICK_INFO://进入个人信息页面 - stepCallDialog(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL); + stepCallDialog(Property.TEXT, DIALER_TEXT, Procedure.CLICK_CALL); break; case CLICK_CALL://打视频或者电话 if (mCallType == TYPE_VIDEO) { - step(Property.TEXT, VIDEO_TEXT, Step.WAITING); + step(Property.TEXT, VIDEO_TEXT, Procedure.WAITING); } else if (mCallType == TYPE_VOICE) { - step(Property.TEXT, CALL_TEXT, Step.WAITING); + step(Property.TEXT, VOICE_TEXT, Procedure.WAITING); } break; -// case CLICK_VIDEO_CALL: -// if (step(Property.TEXT, VIDEO_TEXT)) { -// Log.d(TAG, "finish, now: " + mCurrentStep); -// Toast.makeText(this, "成功发起视频聊天", Toast.LENGTH_LONG).show(); -// } -// break; default: } } @@ -320,7 +316,7 @@ public class SelectToSpeakService extends AccessibilityService { } } - private boolean step(Property type, String text, Step nextStep) { + private boolean step(Property type, String text, Procedure nextProcedure) { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { Rect rect = new Rect(); @@ -330,9 +326,9 @@ public class SelectToSpeakService extends AccessibilityService { return false; } clickNode(node); - Log.e(TAG, "step: mCurrentStep: " + mCurrentStep + " done"); - mCurrentStep = nextStep; - Log.e(TAG, "step: next: " + mCurrentStep); + Log.e(TAG, "step: mCurrentStep: " + mCurrentProcedure + " done"); + mCurrentProcedure = nextProcedure; + Log.e(TAG, "step: next: " + mCurrentProcedure); return true; } else { return false; @@ -340,7 +336,7 @@ public class SelectToSpeakService extends AccessibilityService { } // TODO: 2025/2/8 先把通讯录点击的换成node - private boolean stepHome(Property type, String text, Step nextStep) { + private boolean stepHome(Property type, String text, Procedure nextProcedure) { AccessibilityNodeInfo node = findNode(getWindows(), type, text); if (node != null) { Rect rect = new Rect(); @@ -350,9 +346,9 @@ public class SelectToSpeakService extends AccessibilityService { return false; } clickNode(node); - Log.e(TAG, "step: mCurrentStep: " + mCurrentStep + " done"); - mCurrentStep = nextStep; - Log.e(TAG, "step: next: " + mCurrentStep); + Log.e(TAG, "step: mCurrentStep: " + mCurrentProcedure + " done"); + mCurrentProcedure = nextProcedure; + Log.e(TAG, "step: next: " + mCurrentProcedure); return true; } else { return false; @@ -363,35 +359,35 @@ public class SelectToSpeakService extends AccessibilityService { private void touchContact() { boolean successful = clickByPoint(268, 1440); if (successful) { - mCurrentStep = Step.FIND_TAG; + mCurrentProcedure = Procedure.FIND_TAG; } else { - mCurrentStep = Step.WAITING; + mCurrentProcedure = Procedure.WAITING; Toaster.show("点击失败,请重试"); } } - private boolean findSearchContact(Step nextStep) { + private boolean findSearchContact(Procedure nextProcedure) { List nodeInfos = findNodesByViewId("com.tencent.mm:id/gzf"); Log.e(TAG, "findSearchContact: " + nodeInfos); Optional optional = nodeInfos.stream().findAny(); return optional.isPresent(); } - private void findSearchContact(Property type, String text, Step nextStep) { + private void findSearchContact(Property type, String text, Procedure nextProcedure) { List nodeInfos = findNodesByViewId("com.tencent.mm:id/odf"); Log.e(TAG, "findSearchContact: " + nodeInfos); Optional optional = nodeInfos.stream().findAny(); if (optional.isPresent()) { AccessibilityNodeInfo nodeInfo = optional.get(); clickNode(nodeInfo); - mCurrentStep = nextStep; + mCurrentProcedure = nextProcedure; } else { Toaster.show("没有找到联系人"); - mCurrentStep = Step.WAITING; + mCurrentProcedure = Procedure.WAITING; } } - private void putString(String text, Step nextStep) { + private void putString(String text, Procedure nextProcedure) { List nodeInfos = findNodesByViewId("com.tencent.mm:id/d98"); Optional optional = nodeInfos.stream().findAny(); if (optional.isPresent()) { @@ -405,20 +401,20 @@ public class SelectToSpeakService extends AccessibilityService { //see https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo.AccessibilityAction#ACTION_IME_ENTER nodeInfo.performAction(ACTION_IME_ENTER_ID); } - mCurrentStep = nextStep; + mCurrentProcedure = nextProcedure; } else { Toaster.show("没有找到搜索框"); - mCurrentStep = Step.WAITING; + mCurrentProcedure = Procedure.WAITING; } } - private void clickViewById(String id, Step nextStep) { + private void clickViewById(String id, Procedure nextProcedure) { List nodeInfos = findNodesByViewId(id); Optional optional = nodeInfos.stream().findAny(); if (optional.isPresent()) { AccessibilityNodeInfo nodeInfo = optional.get(); clickNode(nodeInfo); - mCurrentStep = nextStep; + mCurrentProcedure = nextProcedure; } else { Toaster.show("没有找到搜索按钮"); } @@ -448,15 +444,15 @@ public class SelectToSpeakService extends AccessibilityService { return null; } - private boolean stepCallDialog(Property type, String text, Step nextStep) { + private boolean stepCallDialog(Property type, String text, Procedure nextProcedure) { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { Log.e(TAG, "stepCallDialog: isVisibleToUser: " + node.isVisibleToUser()); if (node.isVisibleToUser()) { clickNode(node); - Log.e(TAG, "stepCallDialog: mCurrentStep: " + mCurrentStep + " done"); - mCurrentStep = nextStep; - Log.e(TAG, "stepCallDialog: next: " + mCurrentStep); + Log.e(TAG, "stepCallDialog: mCurrentStep: " + mCurrentProcedure + " done"); + mCurrentProcedure = nextProcedure; + Log.e(TAG, "stepCallDialog: next: " + mCurrentProcedure); return true; } else { scrollDown(); @@ -466,7 +462,7 @@ public class SelectToSpeakService extends AccessibilityService { if (mFindCount == mMaxCount) { Log.e("stepCallDialog", "mCurrentStep: max"); Toast.makeText(this, "没有找到联系人", Toast.LENGTH_LONG).show(); - mCurrentStep = Step.WAITING; + mCurrentProcedure = Procedure.WAITING; mFindCount = 0; return false; } else { @@ -483,12 +479,12 @@ public class SelectToSpeakService extends AccessibilityService { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { clickNode(node); - Log.e(TAG, "stepHome: mCurrentStep: " + mCurrentStep + " done"); - mCurrentStep = Step.CLICK_QUICK_WECHAT_CALL; - Log.e(TAG, "stepHome: next: " + mCurrentStep); + Log.e(TAG, "stepHome: mCurrentStep: " + mCurrentProcedure + " done"); + mCurrentProcedure = Procedure.CLICK_QUICK_WECHAT_CALL; + Log.e(TAG, "stepHome: next: " + mCurrentProcedure); return true; } else { - mCurrentStep = Step.CLICK_SEARCH; + mCurrentProcedure = Procedure.CLICK_SEARCH; return false; } } @@ -497,20 +493,20 @@ public class SelectToSpeakService extends AccessibilityService { private int mMaxCount = 5; - private boolean findContact(Property type, String text, Step nextStep) { + private boolean findContact(Property type, String text, Procedure nextProcedure) { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { clickNode(node); - Log.e("findContact", "mCurrentStep: " + mCurrentStep + " done"); - mCurrentStep = nextStep; - Log.e("findContact", "next: " + mCurrentStep); + Log.e("findContact", "mCurrentStep: " + mCurrentProcedure + " done"); + mCurrentProcedure = nextProcedure; + Log.e("findContact", "next: " + mCurrentProcedure); mFindCount = 0; return true; } else { if (mFindCount == mMaxCount) { Log.e("findContact", "mCurrentStep: max"); Toast.makeText(this, "没有找到联系人", Toast.LENGTH_LONG).show(); - mCurrentStep = Step.WAITING; + mCurrentProcedure = Procedure.WAITING; mFindCount = 0; return false; } else { @@ -620,9 +616,9 @@ public class SelectToSpeakService extends AccessibilityService { Log.e(TAG, "stepCall: " + point); clickByPoint(point.x, point.y); // clickNode(node); - Log.e(TAG, "stepCall: mCurrentStep " + mCurrentStep + " done"); - mCurrentStep = Step.CLICK_CALL; - Log.e(TAG, "stepCall: next " + mCurrentStep); + Log.e(TAG, "stepCall: mCurrentStep " + mCurrentProcedure + " done"); + mCurrentProcedure = Procedure.CLICK_CALL; + Log.e(TAG, "stepCall: next " + mCurrentProcedure); return true; } else { Log.e(TAG, "stepCall: not found"); @@ -636,7 +632,7 @@ public class SelectToSpeakService extends AccessibilityService { if (accessibilityNodeInfo.isPresent()) { AccessibilityNodeInfo nodeInfo = accessibilityNodeInfo.get(); clickNode(nodeInfo); - mCurrentStep = Step.CLICK_CALL; + mCurrentProcedure = Procedure.CLICK_CALL; } else { Toaster.show("没有找到通话按钮"); } @@ -650,9 +646,9 @@ public class SelectToSpeakService extends AccessibilityService { clickByPoint(point.x, point.y - 50); clickByPoint(point.x, point.y); // clickNode(node); - Log.e(TAG, "stepAnswer: mCurrentStep " + mCurrentStep + " done"); - mCurrentStep = Step.WAITING; - Log.e(TAG, "stepAnswer: next " + mCurrentStep); + Log.e(TAG, "stepAnswer: mCurrentStep " + mCurrentProcedure + " done"); + mCurrentProcedure = Procedure.WAITING; + Log.e(TAG, "stepAnswer: next " + mCurrentProcedure); return true; } else { Log.e(TAG, "stepAnswer: not found"); @@ -667,9 +663,9 @@ public class SelectToSpeakService extends AccessibilityService { node.getBoundsInScreen(rect); Log.e(TAG, "dialerHandsFree: rect = " + rect); clickNode(node); - Log.e(TAG, "dialerHandsFree: mCurrentStep: " + mCurrentStep + " done"); - mCurrentStep = Step.WAITING; - Log.e(TAG, "dialerHandsFree: next: " + mCurrentStep); + Log.e(TAG, "dialerHandsFree: mCurrentStep: " + mCurrentProcedure + " done"); + mCurrentProcedure = Procedure.WAITING; + Log.e(TAG, "dialerHandsFree: next: " + mCurrentProcedure); return true; } else { return false; @@ -695,13 +691,13 @@ public class SelectToSpeakService extends AccessibilityService { clickByPoint(point.x, point.y - 50); clickByPoint(point.x, point.y); // clickNode(node); - Log.e(TAG, "handsFree: mCurrentStep " + mCurrentStep + " done"); - mCurrentStep = Step.WAITING; - Log.e(TAG, "handsFree: next " + mCurrentStep); + Log.e(TAG, "handsFree: mCurrentStep " + mCurrentProcedure + " done"); + mCurrentProcedure = Procedure.WAITING; + Log.e(TAG, "handsFree: next " + mCurrentProcedure); return true; } else { Log.e(TAG, "handsFree: not found"); - mCurrentStep = Step.WAITING; + mCurrentProcedure = Procedure.WAITING; return false; } } @@ -791,7 +787,7 @@ public class SelectToSpeakService extends AccessibilityService { return scrollScreen(0.5, 1.5); } - private enum Step { + private enum Procedure { WAITING, //微信免提 WECHAT_HANDS_FREE, @@ -822,7 +818,7 @@ public class SelectToSpeakService extends AccessibilityService { CLICK_CALL, CLICK_VIDEO_CALL; - private Step next() { + private Procedure next() { return values()[(this.ordinal() + 1) % values().length]; } } diff --git a/app/src/main/java/com/ttstd/elderlyassistant/activity/main/MainActivity.java b/app/src/main/java/com/ttstd/elderlyassistant/activity/main/MainActivity.java index 1c06ccc..0a4ad26 100644 --- a/app/src/main/java/com/ttstd/elderlyassistant/activity/main/MainActivity.java +++ b/app/src/main/java/com/ttstd/elderlyassistant/activity/main/MainActivity.java @@ -22,7 +22,7 @@ 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.bean.ContactInfo; import com.ttstd.elderlyassistant.databinding.ActivityMainBinding; import com.ttstd.elderlyassistant.utils.AccessibilityUtils; @@ -130,10 +130,10 @@ public class MainActivity extends AppCompatActivity { Log.e(TAG, "readNfcTag: textRecord = " + textRecord); Log.e(TAG, "readNfcTag: contentSize = " + contentSize); Gson gson = new Gson(); - Type type = new TypeToken() { + Type type = new TypeToken() { }.getType(); try { - WechatContact contact = gson.fromJson(textRecord, type); + ContactInfo contact = gson.fromJson(textRecord, type); Intent accessibilityIntent = new Intent(MainActivity.this, SelectToSpeakService.class); accessibilityIntent.putExtra("WechatInfo", contact); startService(accessibilityIntent); diff --git a/app/src/main/java/com/ttstd/elderlyassistant/activity/nfc/NfcActivity.java b/app/src/main/java/com/ttstd/elderlyassistant/activity/nfc/NfcActivity.java index 96f6da5..e3c3626 100644 --- a/app/src/main/java/com/ttstd/elderlyassistant/activity/nfc/NfcActivity.java +++ b/app/src/main/java/com/ttstd/elderlyassistant/activity/nfc/NfcActivity.java @@ -22,7 +22,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.bean.WechatContact; +import com.ttstd.elderlyassistant.bean.ContactInfo; import com.ttstd.elderlyassistant.databinding.ActivityNfcBinding; import com.ttstd.elderlyassistant.gson.GsonUtils; import com.ttstd.elderlyassistant.utils.NdefUtils; @@ -91,11 +91,11 @@ public class NfcActivity extends AppCompatActivity { if (!results.isEmpty()) { String jsonString = results.get(0); Gson gson = new Gson(); - Type type = new TypeToken() { + Type type = new TypeToken() { }.getType(); try { - WechatContact contact = gson.fromJson(jsonString, type); - mBinding.setWechatContact(contact); + ContactInfo contact = gson.fromJson(jsonString, type); + mBinding.setContactInfo(contact); } catch (Exception e) { Log.e(TAG, "resolveIntent: " + e.getMessage()); Toaster.showLong("NFC数据读取失败,请检查配置是否正确"); @@ -168,17 +168,17 @@ public class NfcActivity extends AppCompatActivity { public class BtnClick { public void write(View view) { String name = mBinding.etName.getText().toString(); - String phone = mBinding.etPhone.getText().toString(); + String phoneNumber = mBinding.etPhone.getText().toString(); if (TextUtils.isEmpty(name)) { Toaster.show("请输入联系人姓名"); return; } - if (TextUtils.isEmpty(phone)) { + if (TextUtils.isEmpty(phoneNumber)) { Toaster.show("请输入联系人手机号"); return; } - WechatContact wechatContact = new WechatContact(name, phone); - writeTag(GsonUtils.toJSONString(wechatContact), mTag); + ContactInfo contactInfo = new ContactInfo(name, phoneNumber); + writeTag(GsonUtils.toJSONString(contactInfo), mTag); } } } diff --git a/app/src/main/java/com/ttstd/elderlyassistant/activity/scheme/SchemeActivity.java b/app/src/main/java/com/ttstd/elderlyassistant/activity/scheme/SchemeActivity.java new file mode 100644 index 0000000..5f61171 --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/activity/scheme/SchemeActivity.java @@ -0,0 +1,175 @@ +package com.ttstd.elderlyassistant.activity.scheme; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import com.google.android.accessibility.selecttospeak.SelectToSpeakService; +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.bean.ContactInfo; +import com.ttstd.elderlyassistant.utils.AESUtil; + +import java.lang.reflect.Type; + +public class SchemeActivity extends AppCompatActivity { + private static final String TAG = "SchemeActivity"; + + private static final int REQUEST_CALL_PHONE = 1; + private static final int REQUEST_SEND_SMS = 2; + + private Gson mGson = new Gson(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_scheme); + Intent intent = getIntent(); + parseIntent(intent); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + parseIntent(intent); + } + + // 处理权限请求结果 + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_CALL_PHONE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // 用户授权后重新获取号码并拨号 + directCall(mTempPhoneNumber); + } else { + // 权限被拒绝,提示用户 + Toast.makeText(this, "权限被拒绝,无法拨号", Toast.LENGTH_SHORT).show(); + } + } +// else if (requestCode == REQUEST_SEND_SMS) { +// if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { +// sendSms(mTempPhoneNumber); // 用户授权后发送短信 +// } else { +// Toast.makeText(this, "短信发送权限被拒绝", Toast.LENGTH_SHORT).show(); +// } +// } + } + + + private void parseIntent(Intent intent) { + if (intent == null) { + return; + } + Log.e(TAG, "parseIntent: " + intent); + Uri uri = getIntent().getData(); + if (uri != null) { + String path = uri.getPath(); + Log.e(TAG, "parseIntent: path = " + path); + if (TextUtils.isEmpty(path)) { + return; + } + String detail = uri.getQueryParameter("detail"); + if (TextUtils.isEmpty(detail)) { + Toaster.show("没有获取到联系人数据"); + return; + } + Type listType = new TypeToken() { + }.getType(); + switch (path) { + case "/contactInfo": + try { + ContactInfo contactInfo = mGson.fromJson(detail, listType); + parseContactInfo(contactInfo); + } catch (Exception e) { + Log.e(TAG, "parseIntent: " + e.getMessage()); + } + break; + case "/encrypted": + try { + String decryptedString = AESUtil.decrypt(detail.trim()); + ContactInfo contactInfo = mGson.fromJson(decryptedString, listType); + parseContactInfo(contactInfo); + } catch (Exception e) { + Log.e(TAG, "parseEncryptedData: " + e.getMessage()); + } + break; + } + } + } + + private String mTempPhoneNumber; + + private void parseContactInfo(ContactInfo contactInfo) { + if (contactInfo != null) { + int operation = contactInfo.getOperation(); + String phoneNumber = contactInfo.getPhone(); + switch (operation) { + case 1: + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { + // 未授权时请求权限 + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CALL_PHONE); + mTempPhoneNumber = phoneNumber; + } else { + // 已授权则直接拨号 + directCall(phoneNumber); + } + break; + case 2: +// if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { +// // 动态请求权限 +// ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SEND_SMS}, REQUEST_SEND_SMS); +// mTempPhoneNumber = phoneNumber; +// } else { + sendSms(phoneNumber); +// } + break; + case 3: + case 4: + Intent accessibilityIntent = new Intent(SchemeActivity.this, SelectToSpeakService.class); + accessibilityIntent.putExtra("WechatInfo", contactInfo); + startService(accessibilityIntent); + break; + case 5: + break; + case 6: + break; + default: + } + } + } + + private void directCall(String phoneNumber) { + if (TextUtils.isEmpty(phoneNumber)) { + Toaster.show("未获取到手机号码"); + return; + } + Intent dialerIntent = new Intent(Intent.ACTION_CALL); + Uri uri = Uri.parse("tel:" + phoneNumber); + dialerIntent.setData(uri); + startActivity(dialerIntent); + } + + private void sendSms(String phoneNumber) { + if (TextUtils.isEmpty(phoneNumber)) { + Toaster.show("未获取到手机号码"); + return; + } + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse("sms:" + phoneNumber)); + intent.putExtra("sms_body", ""); + startActivity(intent); + } +} diff --git a/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageActivity.java b/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageActivity.java index c9ddbca..8ceabb9 100644 --- a/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageActivity.java +++ b/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageActivity.java @@ -2,6 +2,7 @@ package com.ttstd.elderlyassistant.activity.write; import android.app.PendingIntent; import android.content.Intent; +import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; @@ -17,13 +18,19 @@ import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; +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.bean.ContactInfo; import com.ttstd.elderlyassistant.databinding.ActivityWriteMessageBinding; +import com.ttstd.elderlyassistant.gson.GsonUtils; +import com.ttstd.elderlyassistant.utils.AESUtil; import com.ttstd.elderlyassistant.utils.HexUtils; import com.ttstd.elderlyassistant.utils.WriteMessageUtils; import java.io.IOException; +import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; public class WriteMessageActivity extends AppCompatActivity { @@ -31,15 +38,19 @@ public class WriteMessageActivity extends AppCompatActivity { private ActivityWriteMessageBinding mBinding; + private Gson mGson = new Gson(); + private NfcAdapter mNfcAdapter; private PendingIntent mPendingIntent; private Tag mTag; + private int mOperationId; @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.setLifecycleOwner(this); mBinding.setClick(new BtnClick()); // 获取NFC适配器实例 @@ -124,10 +135,48 @@ public class WriteMessageActivity extends AppCompatActivity { // 提取第一条记录的文本 NdefRecord record = ndefMessage.getRecords()[0]; String payload = new String(record.getPayload(), StandardCharsets.UTF_8); - Log.e("NFC", "NDEF Text: " + payload.substring(1)); // 跳过编码头 + String text = payload.substring(1); + Log.e(TAG, "parseNdefData: NDEF Text: " + text); // 跳过编码头 + + Uri uri = Uri.parse(text); + String path = uri.getPath(); + if (TextUtils.isEmpty(path)) { + Toaster.show("没有获取到联系人数据"); + return; + } + String detail = uri.getQueryParameter("detail"); + if (TextUtils.isEmpty(detail)) { + Toaster.show("没有获取到联系人数据"); + return; + } + Type listType = new TypeToken() { + }.getType(); + + switch (path) { + case "/contactInfo": + mBinding.rbNoEncryption.setChecked(true); + try { + ContactInfo contactInfo = mGson.fromJson(detail, listType); + setContactInfo(contactInfo); + } catch (Exception e) { + Log.e(TAG, "parseNdefData: " + e.getMessage()); + } + break; + case "/encrypted": + mBinding.rbEncryption.setChecked(true); + try { + String trimString = detail.trim(); + String decryptedString = AESUtil.decrypt(trimString); + ContactInfo contactInfo = mGson.fromJson(decryptedString, listType); + setContactInfo(contactInfo); + } catch (Exception e) { + Log.e(TAG, "parseEncryptedData: " + e.getMessage()); + } + break; + } } } catch (Exception e) { - Log.e("NFC", "NDEF解析失败" + e.getMessage()); + Log.e(TAG, "NDEF解析失败" + e.getMessage()); } finally { try { ndef.close(); @@ -138,21 +187,70 @@ public class WriteMessageActivity extends AppCompatActivity { } } + private void setContactInfo(ContactInfo contactInfo) { + mBinding.setContactInfo(contactInfo); + } + 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("请输入电话"); + Editable nameEditable = mBinding.etName.getText(); + Editable phoneEditable = mBinding.etPhone.getText(); + Editable wxidEditable = mBinding.etWxid.getText(); + Editable qqEditable = mBinding.etQq.getText(); + Editable tagEditable = mBinding.etTag.getText(); + + if (TextUtils.isEmpty(nameEditable)) { + Toaster.showLong("请输入姓名或昵称"); return; } - String phone = editable.toString(); - if (!phone.startsWith("+86")) - phone = "+86" + phone; - WriteMessageUtils.writePhoneToTag(mTag, phone); + String name = nameEditable.toString(); + String phoneNumber = phoneEditable.toString(); + String wechatId = wxidEditable.toString(); + String qq = qqEditable.toString(); + String tag = tagEditable.toString(); + + int checkedId = mBinding.rgOperation.getCheckedRadioButtonId(); + if (checkedId != -1) { + View checkedView = mBinding.rgOperation.findViewById(checkedId); + int index = mBinding.rgOperation.indexOfChild(checkedView); + Log.d("SelectedIndex", "位置: " + index); // 输出索引(从0开始) + mOperationId = index + 1; + } else { + Log.d("SelectedIndex", "未选中任何项"); + mOperationId = 1; + } + + ContactInfo contactInfo = new ContactInfo(); + contactInfo.setName(name); + contactInfo.setPhone(phoneNumber); + contactInfo.setWxid(wechatId); + contactInfo.setQq(qq); + contactInfo.setTag(tag); + contactInfo.setOperation(mOperationId); + + if (mBinding.rbEncryption.isChecked()) { + try { + String jsonString = GsonUtils.toJSONString(contactInfo); + String encryptedString = AESUtil.encrypt(jsonString.trim()); + WriteMessageUtils.writeUrlAddPackage(mTag, "elderlyassistant://ttstd.com/encrypted?detail=" + encryptedString, "com.ttstd.elderlyassistant"); + Toaster.show("写入联系人成功"); + } catch (Exception e) { + e.printStackTrace(); + Toaster.show("写入联系人失败"); + } + } else { + try { + WriteMessageUtils.writeUrlAddPackage(mTag, "elderlyassistant://ttstd.com/contactInfo?detail=" + GsonUtils.toJSONString(contactInfo), "com.ttstd.elderlyassistant"); + Toaster.show("写入联系人成功"); + } catch (Exception e) { + e.printStackTrace(); + Toaster.show("写入联系人失败"); + } + } } public void writeQqMusic(View view) { @@ -160,15 +258,29 @@ public class WriteMessageActivity extends AppCompatActivity { Toaster.showLong("NFC卡片未连接"); return; } - WriteMessageUtils.writeUrlToTag(mTag, "qqmusic://qq.com/media/playSonglist?p={\"song\":[{\"songmid\":\"003Hr2m138zmPR\"}]}"); + try { + WriteMessageUtils.writeUrlToTag(mTag, "qqmusic://qq.com/media/playSonglist?p={\"song\":[{\"songmid\":\"003Hr2m138zmPR\"}]}"); + Toaster.show("写入联系人成功"); + } catch (Exception e) { + e.printStackTrace(); + Toaster.show("写入联系人失败"); + } } - public void writeQqMusicForIos(View view) { + public void writeUrlAddPackage(View view) { if (mTag == null) { Toaster.showLong("NFC卡片未连接"); return; } - WriteMessageUtils.writeUrlToTagIos(mTag, "qqmusic://qq.com/media/playSonglist?p={\"song\":[{\"songmid\":\"003aAPj81VWrbL\"}]}","com.tencent.qqmusic"); + try { + WriteMessageUtils.writeUrlAddPackage(mTag, + "elderlyassistant://ttstd.com/contactInfo?detail={ \"name\": \"教父\", \"phone\": \"18108347897\", \"tag\": \"家人\", \"wxid\": \"wx_godfather\", \"operation\": 1, \"version\": 1 }", + "com.ttstd.elderlyassistant"); + Toaster.show("写入联系人成功"); + } catch (Exception e) { + e.printStackTrace(); + Toaster.show("写入联系人失败"); + } } } diff --git a/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageViewModel.java b/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageViewModel.java new file mode 100644 index 0000000..0b63b93 --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageViewModel.java @@ -0,0 +1,11 @@ +package com.ttstd.elderlyassistant.activity.write; + +import androidx.databinding.ObservableInt; +import androidx.lifecycle.ViewModel; + +import com.ttstd.elderlyassistant.R; + +public class WriteMessageViewModel extends ViewModel { + + +} diff --git a/app/src/main/java/com/ttstd/elderlyassistant/bean/WechatContact.java b/app/src/main/java/com/ttstd/elderlyassistant/bean/ContactInfo.java similarity index 64% rename from app/src/main/java/com/ttstd/elderlyassistant/bean/WechatContact.java rename to app/src/main/java/com/ttstd/elderlyassistant/bean/ContactInfo.java index b408bd6..2fc390c 100644 --- a/app/src/main/java/com/ttstd/elderlyassistant/bean/WechatContact.java +++ b/app/src/main/java/com/ttstd/elderlyassistant/bean/ContactInfo.java @@ -7,24 +7,26 @@ import com.google.gson.JsonParser; import java.io.Serializable; -public class WechatContact implements Serializable { +public class ContactInfo implements Serializable { private static final long serialVersionUID = -5570426400839799887L; public String name; public String phone; public String tag; + public String wxid; + public String qq; /** - * 0 电话 1 短信 2 微信语音 3 微信视频 + * 1 电话 2 短信 3 微信语音 4 微信视频 5 QQ语音 6 QQ视频 */ int operation; int version; - public WechatContact() { - + public ContactInfo() { + this.version = 1; } - public WechatContact(String name, String phone) { + public ContactInfo(String name, String phone) { this.name = name; this.phone = phone; this.operation = 3; @@ -47,7 +49,6 @@ public class WechatContact implements Serializable { this.phone = phone; } - public String getTag() { return tag; } @@ -56,6 +57,22 @@ public class WechatContact implements Serializable { this.tag = tag; } + public String getWxid() { + return wxid; + } + + public void setWxid(String wxid) { + this.wxid = wxid; + } + + public String getQq() { + return qq; + } + + public void setQq(String qq) { + this.qq = qq; + } + public int getOperation() { return operation; } @@ -64,6 +81,14 @@ public class WechatContact implements Serializable { this.operation = operation; } + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + @NonNull @Override public String toString() { diff --git a/app/src/main/java/com/ttstd/elderlyassistant/utils/AESUtil.java b/app/src/main/java/com/ttstd/elderlyassistant/utils/AESUtil.java new file mode 100644 index 0000000..94b26a1 --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/utils/AESUtil.java @@ -0,0 +1,35 @@ +package com.ttstd.elderlyassistant.utils; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.IvParameterSpec; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class AESUtil { + private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; // 使用 CBC 模式和 PKCS5 填充 + private static final String SECRET_KEY = "YourSecretKey123"; // 密钥(16/24/32 字节) + private static final String IV = "RandomInitVector"; // 初始化向量(16 字节) + + // 加密 + public static String encrypt(String input) throws Exception { + Cipher cipher = Cipher.getInstance(ALGORITHM); + SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes()); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); + byte[] encryptedBytes = cipher.doFinal(input.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(encryptedBytes); // Base64 编码输出 + } + + // 解密 + public static String decrypt(String encryptedInput) throws Exception { + Cipher cipher = Cipher.getInstance(ALGORITHM); + SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes()); + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + byte[] decodedBytes = Base64.getDecoder().decode(encryptedInput); + byte[] decryptedBytes = cipher.doFinal(decodedBytes); + return new String(decryptedBytes, StandardCharsets.UTF_8); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ttstd/elderlyassistant/utils/WriteMessageUtils.java b/app/src/main/java/com/ttstd/elderlyassistant/utils/WriteMessageUtils.java index b8c3a40..ff87419 100644 --- a/app/src/main/java/com/ttstd/elderlyassistant/utils/WriteMessageUtils.java +++ b/app/src/main/java/com/ttstd/elderlyassistant/utils/WriteMessageUtils.java @@ -15,60 +15,45 @@ import java.io.IOException; public class WriteMessageUtils { private static final String TAG = "WriteMessageUtils"; - public static void writePhoneToTag(Tag tag, String phoneNumber) { + public static void writePhoneToTag(Tag tag, String phoneNumber) throws Exception { // 构建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(); - Toaster.show("写入联系人成功"); - } catch (IOException | FormatException e) { - e.printStackTrace(); - } + Ndef ndef = Ndef.get(tag); + ndef.connect(); + ndef.writeNdefMessage(message); // 写入NDEF消息 + ndef.close(); + } - public static void writeUrlToTag(Tag tag, String url) { + public static void writeUrlToTag(Tag tag, String url) throws Exception { // 确保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(); - Toaster.show("写入Url成功"); - } catch (IOException | FormatException e) { - e.printStackTrace(); - } + Ndef ndef = Ndef.get(tag); + ndef.connect(); + ndef.writeNdefMessage(message); + ndef.close(); } - public static void writeUrlToTagIos(Tag tag, String url, String pkg) { + public static void writeUrlAddPackage(Tag tag, String url, String pkg) throws Exception { + Log.e(TAG, "writeUrlAddPackage: url = " + url); + String fixedUrl = url.replace("+", "%2B"); // 替换 + 为 %2B // 1. 创建HTTP Record(HTTPS链接) - Uri uri = Uri.parse(url); + Uri uri = Uri.parse(fixedUrl); NdefRecord httpRecord = NdefRecord.createUri(uri); - -// 2. 创建Android应用包名Record + // 2. 创建Android应用包名Record NdefRecord androidRecord = NdefRecord.createApplicationRecord(pkg); - -// 3. 按顺序组合记录 + // 3. 按顺序组合记录 NdefMessage message = new NdefMessage(new NdefRecord[]{httpRecord, androidRecord}); - -// 4. 写入NFC标签 - try { - Ndef ndef = Ndef.get(tag); // 获取标签对象 - ndef.connect(); - ndef.writeNdefMessage(message); - } catch (Exception e) { - Log.e(TAG, "writeUrlToTagIos: " + e.getMessage()); - } - + // 4. 写入NFC标签 + Ndef ndef = Ndef.get(tag); // 获取标签对象 + ndef.connect(); + ndef.writeNdefMessage(message); } } diff --git a/app/src/main/res/layout/activity_nfc.xml b/app/src/main/res/layout/activity_nfc.xml index 6e2d022..36f426d 100644 --- a/app/src/main/res/layout/activity_nfc.xml +++ b/app/src/main/res/layout/activity_nfc.xml @@ -11,8 +11,8 @@ type="com.ttstd.elderlyassistant.activity.nfc.NfcActivity.BtnClick" /> + name="contactInfo" + type="com.ttstd.elderlyassistant.bean.ContactInfo" /> diff --git a/app/src/main/res/layout/activity_scheme.xml b/app/src/main/res/layout/activity_scheme.xml new file mode 100644 index 0000000..3a382c6 --- /dev/null +++ b/app/src/main/res/layout/activity_scheme.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_write_message.xml b/app/src/main/res/layout/activity_write_message.xml index 88ea55d..e83564e 100644 --- a/app/src/main/res/layout/activity_write_message.xml +++ b/app/src/main/res/layout/activity_write_message.xml @@ -9,67 +9,235 @@ + + + + - + android:layout_height="match_parent"> - - - -