From 3ae9fd54eeb99bdb203fde13c942180d820543ac Mon Sep 17 00:00:00 2001 From: Fanhuitong <981964879@qq.com> Date: Thu, 27 Jun 2024 09:35:35 +0800 Subject: [PATCH] =?UTF-8?q?version:5.7=20fix:=20update:=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E4=B8=80=E9=94=AE=E6=8B=A8=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 39 +- .../callwechat/CallWechatActivity.java | 85 ++++ .../callwechat/CallWechatViewModel.java | 18 + .../wecontact/AddWechatContactActivity.java | 130 +++++ .../wecontact/AddWechatContactViewModel.java | 17 + .../aios/adapter/WechatContactAdapter.java | 124 +++++ .../com/uiuios/aios/base/BaseApplication.java | 2 + .../java/com/uiuios/aios/bean/WechatInfo.java | 73 +++ .../com/uiuios/aios/config/CommonConfig.java | 4 + .../com/uiuios/aios/db/ContactDbHelper.java | 54 ++ .../com/uiuios/aios/db/ContactManager.java | 127 +++++ .../aios/dialog/DeleteContactDialog.java | 251 ++++++++++ .../fragment/contact/ContactFragment.java | 67 ++- .../fragment/contact/ContactViewModel.java | 96 ++-- .../aios/network/NetInterfaceManager.java | 18 + .../aios/service/WeAccessibilityService.java | 469 ++++++++++++++++++ .../uiuios/aios/utils/AccessibilityUtils.java | 43 ++ .../res/drawable-hdpi/wechat_call_phone.png | Bin 0 -> 2127 bytes .../res/drawable-hdpi/wechat_call_video.png | Bin 0 -> 1575 bytes .../res/drawable-hdpi/wechat_call_voice.png | Bin 0 -> 2577 bytes .../res/drawable/wechat_call_background.xml | 13 + .../layout/activity_add_wechat_contact.xml | 273 ++++++++++ .../main/res/layout/activity_wechat_call.xml | 203 ++++++++ .../main/res/layout/dialog_delete_contact.xml | 121 +++++ .../main/res/layout/fragment_contact_home.xml | 15 + app/src/main/res/layout/item_actions.xml | 2 + .../main/res/layout/item_contact_wechat.xml | 79 +++ app/src/main/res/values/strings.xml | 2 + .../res/xml/accessibility_service_config.xml | 9 + 30 files changed, 2286 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/com/uiuios/aios/activity/callwechat/CallWechatActivity.java create mode 100644 app/src/main/java/com/uiuios/aios/activity/callwechat/CallWechatViewModel.java create mode 100644 app/src/main/java/com/uiuios/aios/activity/wecontact/AddWechatContactActivity.java create mode 100644 app/src/main/java/com/uiuios/aios/activity/wecontact/AddWechatContactViewModel.java create mode 100644 app/src/main/java/com/uiuios/aios/adapter/WechatContactAdapter.java create mode 100644 app/src/main/java/com/uiuios/aios/bean/WechatInfo.java create mode 100644 app/src/main/java/com/uiuios/aios/db/ContactDbHelper.java create mode 100644 app/src/main/java/com/uiuios/aios/db/ContactManager.java create mode 100644 app/src/main/java/com/uiuios/aios/dialog/DeleteContactDialog.java create mode 100644 app/src/main/java/com/uiuios/aios/service/WeAccessibilityService.java create mode 100644 app/src/main/java/com/uiuios/aios/utils/AccessibilityUtils.java create mode 100644 app/src/main/res/drawable-hdpi/wechat_call_phone.png create mode 100644 app/src/main/res/drawable-hdpi/wechat_call_video.png create mode 100644 app/src/main/res/drawable-hdpi/wechat_call_voice.png create mode 100644 app/src/main/res/drawable/wechat_call_background.xml create mode 100644 app/src/main/res/layout/activity_add_wechat_contact.xml create mode 100644 app/src/main/res/layout/activity_wechat_call.xml create mode 100644 app/src/main/res/layout/dialog_delete_contact.xml create mode 100644 app/src/main/res/layout/item_contact_wechat.xml create mode 100644 app/src/main/res/xml/accessibility_service_config.xml diff --git a/app/build.gradle b/app/build.gradle index e04528c..440fdc7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ android { applicationId "com.uiuios.aios" minSdkVersion 24 targetSdkVersion 29 - versionCode 47 - versionName "5.6" + versionCode 48 + versionName "5.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f6c84c4..e19195f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -130,15 +130,15 @@ android:resumeWhilePausing="true" android:stateNotNeeded="true" android:windowSoftInputMode="adjustPan"> - - + + - - - - - - + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/uiuios/aios/activity/callwechat/CallWechatActivity.java b/app/src/main/java/com/uiuios/aios/activity/callwechat/CallWechatActivity.java new file mode 100644 index 0000000..71a6964 --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/activity/callwechat/CallWechatActivity.java @@ -0,0 +1,85 @@ +package com.uiuios.aios.activity.callwechat; + +import android.content.Intent; +import android.net.Uri; +import android.view.Gravity; +import android.view.View; + +import com.uiuios.aios.R; +import com.uiuios.aios.base.mvvm.BaseMvvmActivity; +import com.uiuios.aios.bean.WechatInfo; +import com.uiuios.aios.databinding.ActivityWechatCallBinding; +import com.uiuios.aios.service.WeAccessibilityService; + +public class CallWechatActivity extends BaseMvvmActivity { + + private WechatInfo mWechatInfo; + + @Override + public boolean setfitWindow() { + return true; + } + + @Override + protected int getLayoutId() { + return R.layout.activity_wechat_call; + } + + @Override + protected void initDataBinding() { + mViewModel.setCtx(this); + mViewModel.setVDBinding(mViewDataBinding); + mViewModel.setLifecycle(getLifecycleSubject()); + mViewDataBinding.setClick(new BtnClick()); + } + + @Override + protected void initView() { + getWindow().setGravity(Gravity.BOTTOM); + } + + @Override + protected void initData() { + Intent intent = getIntent(); + if (intent != null) { + WechatInfo wechatInfo = (WechatInfo) intent.getSerializableExtra("WechatInfo"); + if (wechatInfo != null) { + mWechatInfo = wechatInfo; + mViewDataBinding.setWehchatInfo(wechatInfo); + } + } + } + + + + public class BtnClick { + public void callPhone(View view) { + Intent dialIntent = new Intent(Intent.ACTION_CALL); + String phone = mWechatInfo.getMobile(); + Uri data = Uri.parse("tel:" + phone); + dialIntent.setData(data); + startActivity(dialIntent); + finish(); + } + + public void callWechatVideo(View view) { + Intent intent = new Intent(CallWechatActivity.this, WeAccessibilityService.class); + intent.putExtra("WechatInfo", mWechatInfo); + intent.putExtra("call_type", WeAccessibilityService.TYPE_VIDEO); + startService(intent); + finish(); + } + + public void callWechatVoice(View view) { + Intent intent = new Intent(CallWechatActivity.this, WeAccessibilityService.class); + intent.putExtra("WechatInfo", mWechatInfo); + intent.putExtra("call_type", WeAccessibilityService.TYPE_VOICE); + startService(intent); + finish(); + } + + public void exit(View view) { + finish(); + } + } +} diff --git a/app/src/main/java/com/uiuios/aios/activity/callwechat/CallWechatViewModel.java b/app/src/main/java/com/uiuios/aios/activity/callwechat/CallWechatViewModel.java new file mode 100644 index 0000000..fc11135 --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/activity/callwechat/CallWechatViewModel.java @@ -0,0 +1,18 @@ +package com.uiuios.aios.activity.callwechat; + +import com.trello.rxlifecycle4.android.ActivityEvent; +import com.uiuios.aios.base.mvvm.BaseViewModel; +import com.uiuios.aios.databinding.ActivityWechatCallBinding; + +public class CallWechatViewModel extends BaseViewModel { + + @Override + public ActivityWechatCallBinding getVDBinding() { + return binding; + } + + @Override + public void onDestroy() { + + } +} diff --git a/app/src/main/java/com/uiuios/aios/activity/wecontact/AddWechatContactActivity.java b/app/src/main/java/com/uiuios/aios/activity/wecontact/AddWechatContactActivity.java new file mode 100644 index 0000000..1b29394 --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/activity/wecontact/AddWechatContactActivity.java @@ -0,0 +1,130 @@ +package com.uiuios.aios.activity.wecontact; + +import android.text.TextUtils; +import android.util.Log; +import android.view.View; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; +import com.hjq.toast.Toaster; +import com.luck.picture.lib.basic.PictureSelector; +import com.luck.picture.lib.config.SelectMimeType; +import com.luck.picture.lib.entity.LocalMedia; +import com.luck.picture.lib.interfaces.OnResultCallbackListener; +import com.uiuios.aios.R; +import com.uiuios.aios.base.GlideEngine; +import com.uiuios.aios.base.mvvm.BaseMvvmActivity; +import com.uiuios.aios.bean.WechatInfo; +import com.uiuios.aios.databinding.ActivityAddWechatContactBinding; +import com.uiuios.aios.db.ContactManager; +import com.uiuios.aios.utils.ScreenUtil; + +import java.io.File; +import java.util.ArrayList; + +public class AddWechatContactActivity extends BaseMvvmActivity { + private static final String TAG = AddWechatContactActivity.class.getSimpleName(); + + private String mPictrueFilePath; + + + @Override + protected int getLayoutId() { + return R.layout.activity_add_wechat_contact; + } + + @Override + protected void initDataBinding() { + mViewModel.setCtx(this); + mViewModel.setVDBinding(mViewDataBinding); + mViewModel.setLifecycle(getLifecycleSubject()); + mViewDataBinding.setClick(new BtnClick()); + } + + @Override + protected void initView() { + + } + + @Override + protected void initData() { + + } + + + private void openSelector() { + PictureSelector.create(AddWechatContactActivity.this) + .openGallery(SelectMimeType.ofAll()) + .setSelectionMode(1) + .setImageEngine(GlideEngine.createGlideEngine()) + .forResult(new OnResultCallbackListener() { + @Override + public void onResult(ArrayList result) { + mPictrueFilePath = result.get(0).getRealPath(); + File file = new File(mPictrueFilePath); + if (file.exists()) { + RequestOptions options = new RequestOptions().transform(new RoundedCorners(ScreenUtil.dip2px(AddWechatContactActivity.this, 8F))); + Glide.with(mViewDataBinding.nvAvatar).load(file).apply(options).into(mViewDataBinding.nvAvatar); + } else { + mPictrueFilePath = ""; + } + } + + @Override + public void onCancel() { + Log.e(TAG, "onCancel: "); + } + }); + } + + public class BtnClick { + public void selectPic(View view){ + openSelector(); + } + + public void exit(View view){ + finish(); + } + + public void addContact(View view) { + if (TextUtils.isEmpty(mViewDataBinding.etName.getText())) { + Toaster.show("请输入微信备注"); + return; + } + if (TextUtils.isEmpty(mViewDataBinding.etGroup.getText())) { + Toaster.show("请输入微信群组标签"); + return; + } + if (TextUtils.isEmpty(mViewDataBinding.etPhone.getText())) { + Toaster.show("请输入手机号码"); + return; + } +// if (TextUtils.isEmpty(mPictrueFilePath)) { +// Toaster.show("请选择图片"); +// return; +// } + WechatInfo wechatInfo = new WechatInfo(); + if (!TextUtils.isEmpty(mViewDataBinding.etName.getText())) { + wechatInfo.setNickName(mViewDataBinding.etName.getText().toString()); + } + if (!TextUtils.isEmpty(mViewDataBinding.etGroup.getText())) { + wechatInfo.setGroupTag(mViewDataBinding.etGroup.getText().toString()); + } + if (!TextUtils.isEmpty(mViewDataBinding.etWechatId.getText())) { + wechatInfo.setWechatId(mViewDataBinding.etWechatId.getText().toString()); + } + if (!TextUtils.isEmpty(mViewDataBinding.etPhone.getText())) { + wechatInfo.setMobile(mViewDataBinding.etPhone.getText().toString()); + } + if (!TextUtils.isEmpty(mPictrueFilePath)) { + wechatInfo.setAvatarPath(mPictrueFilePath); + } + if (ContactManager.getInstance().addWechatInfo(wechatInfo)) { + Toaster.show("添加成功"); + finish(); + } + + } + } +} diff --git a/app/src/main/java/com/uiuios/aios/activity/wecontact/AddWechatContactViewModel.java b/app/src/main/java/com/uiuios/aios/activity/wecontact/AddWechatContactViewModel.java new file mode 100644 index 0000000..736b6eb --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/activity/wecontact/AddWechatContactViewModel.java @@ -0,0 +1,17 @@ +package com.uiuios.aios.activity.wecontact; + +import com.trello.rxlifecycle4.android.ActivityEvent; +import com.uiuios.aios.base.mvvm.BaseViewModel; +import com.uiuios.aios.databinding.ActivityAddWechatContactBinding; + +public class AddWechatContactViewModel extends BaseViewModel { + @Override + public ActivityAddWechatContactBinding getVDBinding() { + return binding; + } + + @Override + public void onDestroy() { + + } +} diff --git a/app/src/main/java/com/uiuios/aios/adapter/WechatContactAdapter.java b/app/src/main/java/com/uiuios/aios/adapter/WechatContactAdapter.java new file mode 100644 index 0000000..9154fdf --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/adapter/WechatContactAdapter.java @@ -0,0 +1,124 @@ +package com.uiuios.aios.adapter; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.hjq.toast.Toaster; +import com.shehuan.niv.NiceImageView; +import com.uiuios.aios.R; +import com.uiuios.aios.activity.callwechat.CallWechatActivity; +import com.uiuios.aios.alarm.AlarmClockData; +import com.uiuios.aios.bean.WechatInfo; +import com.uiuios.aios.db.ContactManager; +import com.uiuios.aios.dialog.DeleteContactDialog; +import com.uiuios.aios.dialog.DeleteDialog; +import com.uiuios.aios.service.WeAccessibilityService; +import com.uiuios.aios.utils.AccessibilityUtils; + +import java.util.List; + +public class WechatContactAdapter extends RecyclerView.Adapter { + private static final String TAG = WechatContactAdapter.class.getSimpleName(); + + private List mContactList; + private Context mContext; + + public static final String DIALER_PACKAGE = "com.android.dialer"; + public static final String DIALER_ADD_CONTACT = "com.uiui.aios.contact.add"; + + public void setContactList(List contactList) { + this.mContactList = contactList; + notifyDataSetChanged(); + } + + private OnLongClick mOnLongClick; + + public void setOnLongClick(OnLongClick onLongClick) { + mOnLongClick = onLongClick; + } + + public interface OnLongClick { + void setOnLongClickListener(WechatInfo wechatInfo); + } + + @NonNull + @Override + public ContactHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + mContext = parent.getContext(); + return new ContactHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact_wechat, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ContactHolder contactHolder, int position) { + WechatInfo contact = mContactList.get(position); + contactHolder.tv_name.setText(contact.getNickName()); + contactHolder.tv_phone.setText(contact.getMobile()); + Glide.with(contactHolder.iv_head).load(contact.getAvatarPath()).error(R.drawable.default_avatar).into(contactHolder.iv_head); + contactHolder.root.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (AccessibilityUtils.isAccessibilitySettingsOn(mContext)) { + Intent intent = new Intent(mContext, CallWechatActivity.class); + intent.putExtra("WechatInfo", contact); + mContext.startActivity(intent); + } else { + Toast.makeText(mContext, "请在无障碍服务中打开 - 关爱守护快捷服务", Toast.LENGTH_LONG).show(); + mContext.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); + } + } + }); + contactHolder.root.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (mOnLongClick != null) { + mOnLongClick.setOnLongClickListener(contact); + } + return false; + } + }); + } + + + private void checkAccessibility() { + if (!AccessibilityUtils.isAccessibilitySettingsOn(mContext)) { + Toast.makeText(mContext, "请在无障碍服务中打开 - 关爱守护快捷服务", Toast.LENGTH_LONG).show(); + mContext.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); + } else { + Log.e(TAG, "checkAccessibility: 无障碍服务已打开"); + } + } + + + @Override + public int getItemCount() { + return mContactList == null ? 0 : mContactList.size(); + } + + static class ContactHolder extends RecyclerView.ViewHolder { + ConstraintLayout root; + NiceImageView iv_head; + TextView tv_name; + TextView tv_phone; + + public ContactHolder(@NonNull View itemView) { + super(itemView); + root = itemView.findViewById(R.id.root); + iv_head = itemView.findViewById(R.id.iv_head); + tv_name = itemView.findViewById(R.id.tv_name); + tv_phone = itemView.findViewById(R.id.tv_phone); + } + } +} diff --git a/app/src/main/java/com/uiuios/aios/base/BaseApplication.java b/app/src/main/java/com/uiuios/aios/base/BaseApplication.java index efa056b..3f3b223 100644 --- a/app/src/main/java/com/uiuios/aios/base/BaseApplication.java +++ b/app/src/main/java/com/uiuios/aios/base/BaseApplication.java @@ -17,6 +17,7 @@ import com.tencent.bugly.crashreport.CrashReport; import com.tencent.mmkv.MMKV; import com.uiuios.aios.BuildConfig; import com.uiuios.aios.alarm.AlarmUtils; +import com.uiuios.aios.db.ContactManager; import com.uiuios.aios.manager.AmapManager; import com.uiuios.aios.manager.AppManager; import com.uiuios.aios.manager.AppStatusManager; @@ -75,6 +76,7 @@ public class BaseApplication extends Application { } catch (Exception e) { Log.e(TAG, "onCreate: " + e.getMessage()); } + ContactManager.init(this); } private void aliyunPushInit() { diff --git a/app/src/main/java/com/uiuios/aios/bean/WechatInfo.java b/app/src/main/java/com/uiuios/aios/bean/WechatInfo.java new file mode 100644 index 0000000..4fba7b2 --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/bean/WechatInfo.java @@ -0,0 +1,73 @@ +package com.uiuios.aios.bean; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; +import com.google.gson.JsonParser; + +import java.io.Serializable; + +public class WechatInfo implements Serializable { + private static final long serialVersionUID = -5241133870198591043L; + + int id ; + String nickName; + String groupTag; + String wechatId; + String mobile; + String avatarPath; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public String getGroupTag() { + return groupTag; + } + + public void setGroupTag(String groupTag) { + this.groupTag = groupTag; + } + + public String getWechatId() { + return wechatId; + } + + public void setWechatId(String wechatId) { + this.wechatId = wechatId; + } + + public String getMobile() { + return mobile; + } + + public void setMobile(String mobile) { + this.mobile = mobile; + } + + public String getAvatarPath() { + return avatarPath; + } + + public void setAvatarPath(String avatarPath) { + this.avatarPath = avatarPath; + } + + @NonNull + @Override + public String toString() { + return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString(); + } +} diff --git a/app/src/main/java/com/uiuios/aios/config/CommonConfig.java b/app/src/main/java/com/uiuios/aios/config/CommonConfig.java index a53fa3c..8f614a5 100644 --- a/app/src/main/java/com/uiuios/aios/config/CommonConfig.java +++ b/app/src/main/java/com/uiuios/aios/config/CommonConfig.java @@ -64,4 +64,8 @@ public class CommonConfig { public static final String WEATHER_NOW_KEY = "WEATHER_NOW_JSON_STRING"; public static final String WEATHER_DAILY_KEY = "WEATHER_DAILY_JSON_STRING"; + + + public static final String SETTING_CALL_TYPE_KEY = "setting_call_type_key"; + public static final String SETTING_AUTOMATIC_ANSWER_KEY = "setting_automatic_answer_key"; } diff --git a/app/src/main/java/com/uiuios/aios/db/ContactDbHelper.java b/app/src/main/java/com/uiuios/aios/db/ContactDbHelper.java new file mode 100644 index 0000000..e4d3994 --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/db/ContactDbHelper.java @@ -0,0 +1,54 @@ +package com.uiuios.aios.db; + +import android.content.Context; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Environment; + +import androidx.annotation.Nullable; + +import java.io.File; + +public class ContactDbHelper extends SQLiteOpenHelper { + public static final String DATABASE_NAME = "contactList.db"; + public static final String TABLE_NAME = "contact_table"; + + private static final int DATABASE_VERSION = 1; + + public ContactDbHelper(Context context) { +// super(context, DATABASE_FILE_NAME, null, DATABASE_VERSION); + super(context, context.getExternalCacheDir() + File.separator + DATABASE_NAME, null, DATABASE_VERSION); +// super(context, Environment.getExternalStorageDirectory().getPath() + File.separator + DATABASE_FILE_NAME, null, DATABASE_VERSION); + } + + public ContactDbHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { + super(context, name, factory, version); + } + + public ContactDbHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version, @Nullable DatabaseErrorHandler errorHandler) { + super(context, name, factory, version, errorHandler); + } + +// public ContactDbHelper(@Nullable Context context, @Nullable String name, int version, @NonNull SQLiteDatabase.OpenParams openParams) { +// super(context, name, version, openParams); +// } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + + "_id integer primary key autoincrement " + + ",nick_name varchar unique not null" + + ",group_tag varchar not null" + + ",wechatid varchar" + + ",mobile varchar" + + ",avatar_path varchar" + + ")" + ); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } +} diff --git a/app/src/main/java/com/uiuios/aios/db/ContactManager.java b/app/src/main/java/com/uiuios/aios/db/ContactManager.java new file mode 100644 index 0000000..3817d34 --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/db/ContactManager.java @@ -0,0 +1,127 @@ +package com.uiuios.aios.db; + +import android.annotation.SuppressLint; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import com.uiuios.aios.bean.WechatInfo; + +import java.util.ArrayList; +import java.util.List; + +public class ContactManager { + private static String TAG = ContactManager.class.getSimpleName(); + + @SuppressLint("StaticFieldLeak") + private static ContactManager sInstance; + private Context mContext; + private ContactDbHelper mDBHelper; + + + private ContactManager(Context context) { + if (context == null) { + throw new RuntimeException("Context is NULL"); + } + mContext = context; + mDBHelper = new ContactDbHelper(context); + } + + public static void init(Context context) { + if (sInstance == null) { + sInstance = new ContactManager(context); + } + } + + public static ContactManager getInstance() { + if (sInstance == null) { + throw new IllegalStateException("You must be init ContactManager first"); + } + return sInstance; + } + + public ContactDbHelper getDBHelper() { + if (mDBHelper == null) { + mDBHelper = new ContactDbHelper(mContext); + } + return mDBHelper; + } + + public boolean addWechatInfo(WechatInfo wechatInfo) { + SQLiteDatabase db = mDBHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put("nick_name", wechatInfo.getNickName()); + values.put("group_tag", wechatInfo.getGroupTag()); + values.put("wechatid", wechatInfo.getWechatId()); + values.put("mobile", wechatInfo.getMobile()); + values.put("avatar_path", wechatInfo.getAvatarPath()); +// long id = db.insert(ContactDbHelper.TABLE_NAME, null, values); + long id = -1; + db.beginTransaction(); + try { + id = db.insertWithOnConflict(ContactDbHelper.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); + db.setTransactionSuccessful(); + } catch (Exception e) { + Log.e(TAG, "addWechatInfo: " + e.getMessage()); + } finally { + db.endTransaction(); + } + return id > 0; + } + + public boolean delete(Integer sqlId) { + SQLiteDatabase db = mDBHelper.getWritableDatabase(); + long id = db.delete(ContactDbHelper.TABLE_NAME, "_id=?", new String[]{sqlId.toString()}); + return id > 0; + } + + public boolean deleteAll() { + SQLiteDatabase db = mDBHelper.getWritableDatabase(); + long id = db.delete(ContactDbHelper.TABLE_NAME, "", null); + return id > 0; + } + + public boolean updateWechatInfo(WechatInfo wechatInfo) { + SQLiteDatabase db = mDBHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put("nick_name", wechatInfo.getNickName()); + values.put("group_tag", wechatInfo.getGroupTag()); + values.put("wechatid", wechatInfo.getWechatId()); + values.put("mobile", wechatInfo.getMobile()); + values.put("avatar_path", wechatInfo.getAvatarPath()); + long id = db.update(ContactDbHelper.TABLE_NAME, values, "_id=?", new String[]{String.valueOf(wechatInfo.getId())}); + return id > 0; + } + + public List getWechatInfoList() { + List wechatInfoList = new ArrayList<>(); + SQLiteDatabase db = mDBHelper.getReadableDatabase(); + long time = System.currentTimeMillis(); + String sql = "select * from " + ContactDbHelper.TABLE_NAME; + Cursor cursor = null; + try { + cursor = db.rawQuery(sql, null); + while (cursor.moveToNext()) { + WechatInfo wechatInfo = new WechatInfo(); + wechatInfo.setId(cursor.getInt(cursor.getColumnIndex("_id"))); + wechatInfo.setNickName(cursor.getString(cursor.getColumnIndex("nick_name"))); + wechatInfo.setGroupTag(cursor.getString(cursor.getColumnIndex("group_tag"))); + wechatInfo.setWechatId(cursor.getString(cursor.getColumnIndex("wechatid"))); + wechatInfo.setMobile(cursor.getString(cursor.getColumnIndex("mobile"))); + wechatInfo.setAvatarPath(cursor.getString(cursor.getColumnIndex("avatar_path"))); + wechatInfoList.add(wechatInfo); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + db.close(); + } + Log.e(TAG, "getWechatInfoList: time = " + (System.currentTimeMillis() - time)); + return wechatInfoList; + } +} diff --git a/app/src/main/java/com/uiuios/aios/dialog/DeleteContactDialog.java b/app/src/main/java/com/uiuios/aios/dialog/DeleteContactDialog.java new file mode 100644 index 0000000..760a2ee --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/dialog/DeleteContactDialog.java @@ -0,0 +1,251 @@ +package com.uiuios.aios.dialog; + + +import android.content.Context; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import com.uiuios.aios.R; + + +/** + * description:自定义dialog + */ + +public class DeleteContactDialog extends AlertDialog { + /** + * 显示的图片 + */ + private ImageView imageIv; + + /** + * 显示的标题 + */ + private TextView titleTv; + + /** + * 显示的消息 + */ + private TextView messageTv; + + /** + * 确认和取消按钮 + */ + private TextView positiveBn; + private TextView negtiveBn; + + /** + * 按钮之间的分割线 + */ +// private View columnLineView; + + private Context mContext; + + public DeleteContactDialog(Context context) { + super(context, R.style.CustomDialog); + this.mContext = context; + } + + /** + * 都是内容数据 + */ + private String message; + private String title; + private String positive, negtive; + private int imageResId = -1; + + /** + * 底部是否只有一个按钮 + */ + private boolean isSingle = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.dialog_delete_contact); + //按空白处不能取消动画 + setCanceledOnTouchOutside(false); + //初始化界面控件 + initView(); + //初始化界面数据 + refreshView(); + //初始化界面控件的事件 + initEvent(); + } + + /** + * 初始化界面的确定和取消监听器 + */ + private void initEvent() { + //设置确定按钮被点击后,向外界提供监听 + positiveBn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onClickBottomListener != null) { + onClickBottomListener.onPositiveClick(); + } + } + }); + //设置取消按钮被点击后,向外界提供监听 + negtiveBn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onClickBottomListener != null) { + onClickBottomListener.onNegtiveClick(); + } + } + }); + } + + /** + * 初始化界面控件的显示数据 + */ + private void refreshView() { + //如果用户自定了title和message + if (!TextUtils.isEmpty(title)) { + titleTv.setText(title); + titleTv.setVisibility(View.VISIBLE); + } else { + titleTv.setVisibility(View.GONE); + } + if (!TextUtils.isEmpty(message)) { + messageTv.setText(message); + } + //如果设置按钮的文字 + if (!TextUtils.isEmpty(positive)) { + positiveBn.setText(positive); + } else { + positiveBn.setText("确定"); + } + if (!TextUtils.isEmpty(negtive)) { + negtiveBn.setText(negtive); + } else { + negtiveBn.setText("取消"); + } + + if (imageResId != -1) { + imageIv.setImageResource(imageResId); + imageIv.setVisibility(View.VISIBLE); + } else { + imageIv.setVisibility(View.GONE); + } + /** + * 只显示一个按钮的时候隐藏取消按钮,回掉只执行确定的事件 + */ +// if (isSingle) { +//// columnLineView.setVisibility(View.GONE); +// negtiveBn.setVisibility(View.GONE); +// } else { +// negtiveBn.setVisibility(View.VISIBLE); +//// columnLineView.setVisibility(View.VISIBLE); +// } + } + + @Override + public void show() { + super.show(); + refreshView(); + } + + /** + * 初始化界面控件 + */ + private void initView() { + negtiveBn = findViewById(R.id.negtive); + positiveBn = findViewById(R.id.positive); + titleTv = findViewById(R.id.title); + messageTv = findViewById(R.id.message); + imageIv = findViewById(R.id.image); +// columnLineView = findViewById(R.id.column_line); + } + + /** + * 设置确定取消按钮的回调 + */ + private OnClickBottomListener onClickBottomListener; + + public void setOnClickBottomListener(OnClickBottomListener onClickBottomListener) { + this.onClickBottomListener = onClickBottomListener; + } + + public interface OnClickBottomListener { + /** + * 点击确定按钮事件 + */ + void onPositiveClick(); + + /** + * 点击取消按钮事件 + */ + void onNegtiveClick(); + } + + public String getMessage() { + return message; + } + + public DeleteContactDialog setMessage(String message) { + this.message = message; + return this; + } + + public String getTitle() { + return title; + } + + public DeleteContactDialog setTitle(String title) { + this.title = title; + return this; + } + + public String getPositive() { + return positive; + } + + public DeleteContactDialog setPositive(String positive) { + this.positive = positive; + return this; + } + + public String getNegtive() { + return negtive; + } + + public DeleteContactDialog setNegtive(String negtive) { + this.negtive = negtive; + return this; + } + + public DeleteContactDialog setNegtiveText(String negtive) { + negtiveBn.setText(negtive); + return this; + } + + public int getImageResId() { + return imageResId; + } + + public boolean isSingle() { + return isSingle; + } + + public DeleteContactDialog setSingle(boolean single) { + isSingle = single; + return this; + } + + public DeleteContactDialog setImageResId(int imageResId) { + this.imageResId = imageResId; + return this; + } + + @Override + public void dismiss() { + super.dismiss(); + } +} diff --git a/app/src/main/java/com/uiuios/aios/fragment/contact/ContactFragment.java b/app/src/main/java/com/uiuios/aios/fragment/contact/ContactFragment.java index 6a28ca0..e1ad5cb 100644 --- a/app/src/main/java/com/uiuios/aios/fragment/contact/ContactFragment.java +++ b/app/src/main/java/com/uiuios/aios/fragment/contact/ContactFragment.java @@ -9,12 +9,15 @@ import android.view.View; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.GridLayoutManager; +import com.hjq.toast.Toaster; import com.uiuios.aios.R; -import com.uiuios.aios.activity.contact.AddContactActivity; -import com.uiuios.aios.adapter.HomeContactAdapter; +import com.uiuios.aios.activity.wecontact.AddWechatContactActivity; +import com.uiuios.aios.adapter.WechatContactAdapter; import com.uiuios.aios.base.mvvm.fragment.BaseMvvmFragment; -import com.uiuios.aios.bean.Contact; +import com.uiuios.aios.bean.WechatInfo; import com.uiuios.aios.databinding.FragmentContactHomeBinding; +import com.uiuios.aios.db.ContactManager; +import com.uiuios.aios.dialog.DeleteContactDialog; import java.util.List; @@ -22,7 +25,7 @@ public class ContactFragment extends BaseMvvmFragment>() { + mViewModel.getContactList().observe(this, new Observer>() { @Override - public void onChanged(List contacts) { + public void onChanged(List contacts) { if (contacts == null || contacts.size() == 0) { -// mViewDataBinding.tvPeople.setText("暂无数据"); + mViewDataBinding.tvNoContact.setVisibility(View.VISIBLE); + mViewDataBinding.rvContact.setVisibility(View.GONE); } else { -// mViewDataBinding.tvPeople.setText(contacts.size() + "人"); + mViewDataBinding.tvNoContact.setVisibility(View.GONE); + mViewDataBinding.rvContact.setVisibility(View.VISIBLE); } -// Contact contact = new Contact(); -// contact.setName("拨号"); -// contact.setMobile(OldContactAdapter.DIALER_PACKAGE); -// contacts.add(0, contact); mContactAdapter.setContactList(contacts); } }); @@ -73,10 +80,42 @@ public class ContactFragment extends BaseMvvmFragment { private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE); @@ -39,54 +46,87 @@ public class ContactViewModel extends BaseViewModel> mContactList = new MutableLiveData<>(); + private MutableLiveData> mContactList = new MutableLiveData<>(); - public MutableLiveData> getContactList() { + public MutableLiveData> getContactList() { return mContactList; } public void getContact() { - NetInterfaceManager.getInstance() - .getContactListObservable() + Observable.create(new ObservableOnSubscribe>() { + @Override + public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable { + emitter.onNext(ContactManager.getInstance().getWechatInfoList()); + } + }) .compose(RxLifecycle.bindUntilEvent(getLifecycle(), FragmentEvent.DESTROY)) - .subscribe(new Observer>>() { + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer>() { @Override public void onSubscribe(@NonNull Disposable d) { - Log.e("getContactList", "onSubscribe: "); + Log.e("getWechatInfoList", "onSubscribe: "); } @Override - public void onNext(@NonNull BaseResponse> listBaseResponse) { - Log.e("getContactList", "onNext: " + listBaseResponse); - if (listBaseResponse.code == 200) { - mMMKV.putString(UrlAddress.GET_MAIL_LIST, GsonUtils.toJSONString(listBaseResponse.data)); - mContactList.setValue(listBaseResponse.data); - } else { - mMMKV.putString(UrlAddress.GET_MAIL_LIST, ""); - mContactList.setValue(new ArrayList<>()); - } + public void onNext(@NonNull List wechatInfos) { + Log.e("getWechatInfoList", "onNext: " + wechatInfos); + mContactList.setValue(wechatInfos); } @Override public void onError(@NonNull Throwable e) { - Log.e("getContactList", "onError: " + e.getMessage()); - String jsonString = mMMKV.getString(UrlAddress.GET_MAIL_LIST, null); - Gson gson = new Gson(); - Type type = new TypeToken>() { - }.getType(); - List contacts = gson.fromJson(jsonString, type); - if (contacts == null) { - mContactList.setValue(new ArrayList<>()); - } else { - mContactList.setValue(contacts); - } - onComplete(); + Log.e("getWechatInfoList", "onError: " + e.getMessage()); + mContactList.setValue(null); } @Override public void onComplete() { - Log.e("getContactList", "onComplete: "); + Log.e("getWechatInfoList", "onComplete: "); } }); + +// NetInterfaceManager.getInstance() +// .getContactListObservable() +// .compose(RxLifecycle.bindUntilEvent(getLifecycle(), FragmentEvent.DESTROY)) +// .subscribe(new Observer>>() { +// @Override +// public void onSubscribe(@NonNull Disposable d) { +// Log.e("getContactList", "onSubscribe: "); +// } +// +// @Override +// public void onNext(@NonNull BaseResponse> listBaseResponse) { +// Log.e("getContactList", "onNext: " + listBaseResponse); +// if (listBaseResponse.code == 200) { +// mMMKV.putString(UrlAddress.GET_MAIL_LIST, GsonUtils.toJSONString(listBaseResponse.data)); +// mContactList.setValue(listBaseResponse.data); +// } else { +// mMMKV.putString(UrlAddress.GET_MAIL_LIST, ""); +// mContactList.setValue(new ArrayList<>()); +// } +// } +// +// @Override +// public void onError(@NonNull Throwable e) { +// Log.e("getContactList", "onError: " + e.getMessage()); +// String jsonString = mMMKV.getString(UrlAddress.GET_MAIL_LIST, null); +// Gson gson = new Gson(); +// Type type = new TypeToken>() { +// }.getType(); +// List contacts = gson.fromJson(jsonString, type); +// if (contacts == null) { +// mContactList.setValue(new ArrayList<>()); +// } else { +// mContactList.setValue(contacts); +// } +// onComplete(); +// } +// +// @Override +// public void onComplete() { +// Log.e("getContactList", "onComplete: "); +// } +// }); } } diff --git a/app/src/main/java/com/uiuios/aios/network/NetInterfaceManager.java b/app/src/main/java/com/uiuios/aios/network/NetInterfaceManager.java index 896e5b5..ea6d534 100644 --- a/app/src/main/java/com/uiuios/aios/network/NetInterfaceManager.java +++ b/app/src/main/java/com/uiuios/aios/network/NetInterfaceManager.java @@ -2,6 +2,7 @@ package com.uiuios.aios.network; import android.annotation.SuppressLint; import android.content.Context; +import android.os.Environment; import android.util.Log; import com.google.gson.Gson; @@ -211,6 +212,23 @@ public class NetInterfaceManager { Log.e("OKhttp ", " 打印HTTP请求完成 Headers \n"); } + private String getCacheDir() { + String cachePath; + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) + || !Environment.isExternalStorageRemovable()) { + if (mContext.getExternalCacheDir() != null) { + cachePath = mContext.getExternalCacheDir().getPath(); + } else if (mContext.getExternalFilesDir("cache") != null) { + cachePath = mContext.getExternalFilesDir("cache").getPath(); + } else { + cachePath = mContext.getCacheDir().getPath(); + } + } else { + cachePath = mContext.getCacheDir().getPath(); + } + return cachePath; + } + public static void init(Context context) { if (INSTANCE == null) { INSTANCE = new NetInterfaceManager(context); diff --git a/app/src/main/java/com/uiuios/aios/service/WeAccessibilityService.java b/app/src/main/java/com/uiuios/aios/service/WeAccessibilityService.java new file mode 100644 index 0000000..47a9852 --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/service/WeAccessibilityService.java @@ -0,0 +1,469 @@ +package com.uiuios.aios.service; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.GestureDescription; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Handler; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.Toast; + +import com.tencent.mmkv.MMKV; +import com.uiuios.aios.bean.WechatInfo; +import com.uiuios.aios.config.CommonConfig; + +public class WeAccessibilityService extends AccessibilityService { + private static final String TAG = WeAccessibilityService.class.getSimpleName(); + + private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE); + + + private static final String DIALER_TEXT = "音视频通话"; + private static final String CONTACT_TEXT = "通讯录"; + private static final String TAG_TEXT = "标签"; + private static final String TAG_NAME = "爱来伴"; + private static final String MORE_NAME = "更多功能按钮,已折叠"; + private static final String PARENT_VIDEO_TEXT = "视频通话"; + + private static final String VIDEO_TEXT = "视频通话"; + private static final String CALL_TEXT = "语音通话"; + + private static final String RECEIVE_DESCRIPTION = "接听"; + + public static final int TYPE_VOICE = 0; + public static final int TYPE_VIDEO = 1; + + private static final int WAIT_TIME = 500; + + private int mCallType = TYPE_VOICE; + + private WechatInfo mWechatInfo; + private Step mCurrentStep = Step.WAITING; + private String mName = "";//微信昵称 + private String mTagName = "";//微信联系人标签名 + private boolean mAutoAccept = false; + private boolean finished = true; + + private Handler handler = null; + private AccessibilityEvent input = null; + private Runnable runnable = new Runnable() { + @Override + public void run() { + _onAccessibilityEvent(input); + finished = true; + } + }; + + @Override + public void onCreate() { + super.onCreate(); + Log.e(TAG, "onCreate: "); + registerSettingReceiver(); + handler = new Handler(); + + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.e(TAG, "onStartCommand: "); + if (intent != null) { + mWechatInfo = (WechatInfo) intent.getSerializableExtra("WechatInfo"); + Log.e(TAG, "onStartCommand: wechatInfo = " + mWechatInfo); + mCallType = intent.getIntExtra("call_type", TYPE_VOICE); + mName = mWechatInfo.getNickName(); + String groupTag = mWechatInfo.getGroupTag(); + if (TextUtils.isEmpty(groupTag)) { + mTagName = TAG_NAME; + } else { + mTagName = groupTag; + } + mCurrentStep = Step.CLICK_CONTACT; + launchWeChat(); + } + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.e(TAG, "onDestroy: "); + if (mSettingBroadcastReceiver != null) { + unregisterReceiver(mSettingBroadcastReceiver); + } + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + Log.e(TAG, "onAccessibilityEvent: "); + Log.v(TAG, "onAccessibilityEvent: event = " + event.toString()); + if (finished) { + finished = false; + } else { + Log.v(TAG, "bounce"); + handler.removeCallbacks(runnable); + } + input = event; + handler.postDelayed(runnable, WAIT_TIME); + } + + + /** + * 1.在微信页面直接找到联系人拨打电话 + * 2.在联系人页面找到并拨打 + * 3.通过进入联系人-标签找到并拨打 + * + * @param event + */ + private void _onAccessibilityEvent(AccessibilityEvent event) { + Log.e(TAG, "_onAccessibilityEvent: " + mCurrentStep); + switch (mCurrentStep) { + case WAITING: + if (!mAutoAccept) return; + if (step(Property.DESCRIPTION, RECEIVE_DESCRIPTION, Step.WAITING)) { + mCurrentStep = Step.WAITING; + Toast.makeText(this, "自动接听视频/语音聊天", Toast.LENGTH_LONG).show(); + } + break; + case CLICK_HOME://主页能找到直接点击进去更多 + stepHome(Property.TEXT, mName); + break; + case CLICK_QUICK_WECHAT_CALL://点击更多页面 + step(Property.DESCRIPTION, MORE_NAME, Step.CLICK_TARGET); + break; + case CLICK_TARGET://点击视频通话 + stepCall(Property.TEXT, PARENT_VIDEO_TEXT); + break; + + case CLICK_CONTACT://进入通讯录界面 + step(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG); + break; + case FIND_CONTACT://模拟滑动找到联系人 + findContact(Property.TEXT, mName, Step.CLICK_NAME); + case FIND_TAG: + step(Property.TEXT, TAG_TEXT, Step.CLICK_TAG); + break; + case CLICK_TAG: + step(Property.TEXT, mTagName, Step.CLICK_NAME); + + break; + case CLICK_NAME://点击item + step(Property.TEXT, mName, Step.CLICK_INFO); + break; + case CLICK_INFO://进入个人信息页面 + step(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL); + break; + + case CLICK_CALL://打视频或者电话 + if (mCallType == TYPE_VIDEO) { + step(Property.TEXT, VIDEO_TEXT, Step.WAITING); + } else if (mCallType == TYPE_VOICE) { + step(Property.TEXT, CALL_TEXT, Step.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: + } + } + + @Override + public void onInterrupt() { + Log.e(TAG, "onInterrupt: "); + + } + + @Override + protected void onServiceConnected() { + super.onServiceConnected(); + Log.e(TAG, "onServiceConnected: "); + } + + private void launchWeChat() { + Intent intent = new Intent(); + ComponentName cmp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI"); + intent.setAction(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.setComponent(cmp); + startActivity(intent); + } + + private boolean step(Property type, String text, Step nextStep) { + AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); + if (node != null) { + clickNode(node); + Log.e(TAG, "step: mCurrentStep: " + mCurrentStep + " done"); + mCurrentStep = nextStep; + Log.e(TAG, "step: next: " + mCurrentStep); + return true; + } else { + return false; + } + } + + private boolean stepHome(Property type, String text) { + AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); + if (node != null) { + clickNode(node); + Log.e(TAG, "stepHome: mCurrentStep: " + mCurrentStep + " done"); + mCurrentStep = mCurrentStep.next(); + Log.e(TAG, "stepHome: next: " + mCurrentStep); + return true; + } else { + mCurrentStep = Step.CLICK_CONTACT; + return false; + } + } + + private int mFindCount = 0; + private int mMaxCount = 5; + + + private boolean findContact(Property type, String text, Step nextStep) { + AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); + if (node != null) { + clickNode(node); + Log.e("findContact", "mCurrentStep: " + mCurrentStep + " done"); + mCurrentStep = nextStep; + Log.e("findContact", "next: " + mCurrentStep); + mFindCount = 0; + return true; + } else { + if (mFindCount == mMaxCount) { + Log.e("findContact", "mCurrentStep: max"); + Toast.makeText(this, "没有找到联系人", Toast.LENGTH_LONG).show(); + mCurrentStep = Step.WAITING; + mFindCount = 0; + return false; + } else { + Log.e("findContact", "mCurrentStep: not found"); + mFindCount++; + Log.e("findContact", "mCurrentStep: mFindCount = " + mFindCount); + scrolDown(); + return false; + } + } + } + + + private AccessibilityNodeInfo findNode(AccessibilityNodeInfo root, Property type, String text) { + if (root == null) return null; +// Log.v(TAG, "findNode: getPackageName = " + root.getPackageName()); + Log.v(TAG, "findNode: getText = " + root.getText()); + Log.v(TAG, "findNode: getClassName = " + root.getClassName()); + Log.v(TAG, "findNode: getContentDescription = " + root.getContentDescription()); + boolean satisfied = false; + switch (type) { + case TEXT: + satisfied = root.getText() != null && text.contentEquals(root.getText()); + break; + case CLASS_NAME: + satisfied = root.getClassName() != null && text.contentEquals(root.getClassName()); + break; + case DESCRIPTION: + satisfied = root.getContentDescription() != null && text.contentEquals(root.getContentDescription()); + break; + default: + } + if (satisfied) { + return root; + } else { + for (int i = 0; i < root.getChildCount(); i++) { + AccessibilityNodeInfo result = findNode(root.getChild(i), type, text); + if (result != null) { + return result; + } + } + } + root.recycle(); + return null; + } + + private void clickNode(AccessibilityNodeInfo node) { + if (node.isClickable()) { + node.performAction(AccessibilityNodeInfo.ACTION_CLICK); + node.recycle(); + } else { + AccessibilityNodeInfo parent = node.getParent(); + node.recycle(); + clickNode(parent); + } + } + + private boolean stepCall(Property type, String text) { + AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); + if (node != null) { + Point point = getPointtByNode(node); + Log.e(TAG, "stepCall: " + point); + clickByNode(point.x, point.y + 30); +// clickNode(node); + Log.e(TAG, "stepCall: mCurrentStep " + mCurrentStep + " done"); + mCurrentStep = Step.CLICK_CALL; + Log.e(TAG, "stepCall: next " + mCurrentStep); + return true; + } else { + Log.e(TAG, "stepCall: not found"); + return false; + } + } + + //根据节点信息可获得对应的x,y坐标 + static Point getPointtByNode(AccessibilityNodeInfo node) { + if (node == null) { + return new Point(0, 0); + } + Rect rect = new Rect(); + node.getBoundsInScreen(rect); + Point point = new Point(rect.centerX(), rect.centerY()); + return point; + } + + + //实现对(x,y)坐标进行点击操作。 + private boolean clickByNode(int x, int y) { + Point point = new Point(x, y); + Path path = new Path(); + path.moveTo(point.x, point.y); + GestureDescription.Builder builder = new GestureDescription.Builder(); + builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200)); + GestureDescription gesture = builder.build(); + boolean isDispatched = dispatchGesture(gesture, new GestureResultCallback() { + @Override + public void onCompleted(GestureDescription gestureDescription) { + super.onCompleted(gestureDescription); + Log.e("clickByNode", "onCompleted: "); + } + + @Override + public void onCancelled(GestureDescription gestureDescription) { + super.onCancelled(gestureDescription); + Log.e("clickByNode", "onCompleted: "); + } + }, null); + return isDispatched; + } + + private boolean scrolDown() { + WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics dm = new DisplayMetrics(); + wm.getDefaultDisplay().getRealMetrics(dm); + int width = dm.widthPixels; // 屏幕宽度(像素) + int height = dm.heightPixels; // 屏幕高度(像素) + float density = dm.density; // 屏幕密度(0.75 / 1.0 / 1.5) + int densityDpi = dm.densityDpi; // 屏幕密度dpi(120 / 160 / 240) + // 屏幕宽度算法:屏幕宽度(像素)/屏幕密度 +// int screenWidth = (int) (width / density); // 屏幕宽度(dp) +// int screenHeight = (int) (height / density);// 屏幕高度(dp) + Log.e(TAG, "scrolDown: screenWidth = " + width); + Log.e(TAG, "scrolDown: screenHeight = " + height); + int center_X = width / 2; + int center_Y = height / 2; + Log.e("scrolDown", "center position:" + "(" + center_X + "," + center_Y + ")"); + Path path = new Path(); + path.moveTo(center_X, (int) (center_Y * 1.5)); //起点坐标。 + path.lineTo(center_X, (int) (center_Y * 0.5)); //终点坐标。 + GestureDescription.Builder builder = new GestureDescription.Builder(); + GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200)).build(); + boolean isDispatched = dispatchGesture(gestureDescription, new GestureResultCallback() { + @Override + public void onCompleted(GestureDescription gestureDescription) { + super.onCompleted(gestureDescription); + Log.d("scrolDown", "dispatchGesture ScrollUp onCompleted."); + path.close(); + } + + @Override + public void onCancelled(GestureDescription gestureDescription) { + super.onCancelled(gestureDescription); + Log.d("scrolDown", "dispatchGesture ScrollUp cancel."); + } + }, null); + + return isDispatched; + } + + + private enum Step { + WAITING, + CLICK_HOME, + CLICK_QUICK_WECHAT_CALL, + CLICK_TARGET, + + CLICK_CONTACT, + FIND_CONTACT, + CLICK_TAG, + FIND_TAG, + + CLICK_NAME, + CLICK_INFO, + + CLICK_CALL, + CLICK_VIDEO_CALL; + + private Step next() { + return values()[(this.ordinal() + 1) % values().length]; + } + } + + private enum Property { + TEXT, + CLASS_NAME, + 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; + + private void registerSettingReceiver() { + if (mSettingBroadcastReceiver == null) { + mSettingBroadcastReceiver = new SettingBroadcastReceiver(); + } + IntentFilter filter = new IntentFilter(); + filter.addAction(SETTING_CALL_TYPE_ACTION); + filter.addAction(SETTING_AUTOMATIC_ANSWER_ACTION); + registerReceiver(mSettingBroadcastReceiver, filter); + } + + private class SettingBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.e("SettingBroadcastReceiver", "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("SettingBroadcastReceiver", "onReceive: callType = " + callType); + break; + case SETTING_AUTOMATIC_ANSWER_ACTION: + boolean autoAnswer = intent.getBooleanExtra("auto_answer", false); + mAutoAccept = autoAnswer; + Log.e("SettingBroadcastReceiver", "onReceive: autoAnswer = " + autoAnswer); + break; + default: + } + } + } + + +} diff --git a/app/src/main/java/com/uiuios/aios/utils/AccessibilityUtils.java b/app/src/main/java/com/uiuios/aios/utils/AccessibilityUtils.java new file mode 100644 index 0000000..18e3367 --- /dev/null +++ b/app/src/main/java/com/uiuios/aios/utils/AccessibilityUtils.java @@ -0,0 +1,43 @@ +package com.uiuios.aios.utils; + +import android.content.Context; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import com.uiuios.aios.service.WeAccessibilityService; + +public class AccessibilityUtils { + private static final String TAG = AccessibilityUtils.class.getSimpleName(); + + public static boolean isAccessibilitySettingsOn(Context context) { + int accessibilityEnabled = 0; + final String service = context.getPackageName() + "/" + WeAccessibilityService.class.getCanonicalName(); + try { + accessibilityEnabled = Settings.Secure.getInt( + context.getApplicationContext().getContentResolver(), + android.provider.Settings.Secure.ACCESSIBILITY_ENABLED); + } catch (Settings.SettingNotFoundException e) { + Log.e(TAG, "Error finding setting, default accessibility to not found: " + e.getMessage()); + } + TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':'); + + if (accessibilityEnabled == 1) { + String settingValue = Settings.Secure.getString( + context.getApplicationContext().getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + if (settingValue != null) { + mStringColonSplitter.setString(settingValue); + while (mStringColonSplitter.hasNext()) { + String accessibilityService = mStringColonSplitter.next(); + if (accessibilityService.equalsIgnoreCase(service)) { + Log.v(TAG, "***ACCESSIBILITY IS ENABLED*** -----------------"); + return true; + } + } + } + } + Log.v(TAG, "***ACCESSIBILITY IS DISABLED***"); + return false; + } +} diff --git a/app/src/main/res/drawable-hdpi/wechat_call_phone.png b/app/src/main/res/drawable-hdpi/wechat_call_phone.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4f9d0b45e35562666b3831decd7918b3e08645 GIT binary patch literal 2127 zcmV-V2(b5wP)Px-2}wjjRCr$PolSBZHxPw;DBmOY(k15%l1^f2m$G~ZnRl>K-i0}dl{3VuEW_MG z)TF>6MV2gz1n~N2h{vS`G=PWC-Ho4_>4ZNjprAmfV3Q`PfDkM~prQc5QjwKL2^JYp zQGj5n$V#IGiwvkJK(JI~rBQ-K22>OvSSqs8D8V8FDhd!R6;5_cRk7kL zm56YaDu=442bLRDD`}W-p%^K$qXX9c=Px3C+VJ%I^>QaCi!0_^K2v}POKm%$L?DL5 z64elOuwV@vpZ_8{2D8DC2$M3$5EiTmA@KR=Q%L-9dyyjw%hoG^11qApdeVm>u^yE? z3plVMdaI2VbK%Bm9vPLTfnZ&y+$!VBOJ&cqtJ7;`OIA?<16JrTxTOvg`3h9>s$#&3 z9R{})q1Vd8qr7xuV2KF5Ryi#hH8EgmUx&#U(Eg(ITKhGsFM$E;{<^R4ikXGEoZh}O zYpv0SS_F&X&;a%}!Rq@f`SAP9K;LGYXcH`vnVjBUH9PAD18);7o*$~C)h1X;*3(({$7d04Ad0VG&XZ9x|=?)eICH@Nh{^7LB5 zE6HAD>4W9#wM2N^!G$dwuw1U3OqCy_Vw{ zBH^;+3iMKtoGyD_1Wf(p{#MRx6_>U3SRuR?Ut?5EOGZU|uu|tDyibegi5ZyW7xe+l zJ&|7u#hv+lUJkC|16GufNVpCLCAXj-SpIuk8?6`?bGg}D-WRMGA#o5X;%YJJ^YCVK z3-`^#T;6}1@14~E1$@DZ6B1Wb5nvNFJ@hNxN9pAJMLyCES?T|jlgGQrkoki}xCVZP z^VD>m`|#%kl9vq%SPZZt#I*COgZgdby<&qEb*1=x z0uR{@4*}JBrK_KN#Rn_wN^ysKNRV|h7wP_!{&6l{cP72O-FZZFke5*^r1)suIKWzZ zF2{G9D$Q#QvpVOa;{hx5N-+i^<-NeeH6F0SZ$p$%YmAy$xW)w*;nHOgc;Rt;U?piw zu9)af$+1|69lVbdEW)J+GU$wPykHS79k@X6a2+>Tgi8l5K&~}@u$a)};UZXDnsT2D z2N=P6rX}IRz;$|C9m%Q(lc0?XnfW$pD*alc@X&D;#K?X_O+XIU` z^{;~kIwx!stS9f~^gYFWKEv!qn0cI(sgi$OZLg~zVH3Hh)o zBtyf8Kql`w6LXA!fexE@$w-gYCSWRg)wYxbD+jD~*a2v1Lw{ zewNLE`3&R{R{qiIf7*kN+azNtVSl7@Vw literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/wechat_call_video.png b/app/src/main/res/drawable-hdpi/wechat_call_video.png new file mode 100644 index 0000000000000000000000000000000000000000..999dc457033cd829d76cb6cbf90e2958fe7f8614 GIT binary patch literal 1575 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 LEDg)`vEhFM=HAm@ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/wechat_call_background.xml b/app/src/main/res/drawable/wechat_call_background.xml new file mode 100644 index 0000000..4c41516 --- /dev/null +++ b/app/src/main/res/drawable/wechat_call_background.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_wechat_contact.xml b/app/src/main/res/layout/activity_add_wechat_contact.xml new file mode 100644 index 0000000..d7ae28e --- /dev/null +++ b/app/src/main/res/layout/activity_add_wechat_contact.xml @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_wechat_call.xml b/app/src/main/res/layout/activity_wechat_call.xml new file mode 100644 index 0000000..b6fe843 --- /dev/null +++ b/app/src/main/res/layout/activity_wechat_call.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_delete_contact.xml b/app/src/main/res/layout/dialog_delete_contact.xml new file mode 100644 index 0000000..b09c50e --- /dev/null +++ b/app/src/main/res/layout/dialog_delete_contact.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_contact_home.xml b/app/src/main/res/layout/fragment_contact_home.xml index abe0feb..7274186 100644 --- a/app/src/main/res/layout/fragment_contact_home.xml +++ b/app/src/main/res/layout/fragment_contact_home.xml @@ -28,6 +28,21 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4deee1a..f14b297 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,4 +11,6 @@ "安装快捷方式" "添加到主屏幕" + 关爱守护快捷服务 + 关爱守护一键拨打视频 diff --git a/app/src/main/res/xml/accessibility_service_config.xml b/app/src/main/res/xml/accessibility_service_config.xml new file mode 100644 index 0000000..b45611f --- /dev/null +++ b/app/src/main/res/xml/accessibility_service_config.xml @@ -0,0 +1,9 @@ +