美文网首页
Android 短信转发神器

Android 短信转发神器

作者: 雁过留声_泪落无痕 | 来源:发表于2021-09-16 10:14 被阅读0次

    如果你发现某平台有优惠活动,但是又愁手里的账号不够,这时你可能会想到请亲戚朋友帮忙,薅到羊毛后再请亲戚朋友搓一顿,那么你可能需要这个工具。

    你用亲戚朋友的手机号注册了多个账号,但是平台限制每天都要登录一次(账号密码登录还不行,必须用验证码登录),那么你还得每天打电话找朋友要验证码,此时你就在想,要是他们收到短信后能自动发送给我该多好啊。

    嗯,它来了~


    如果你开了某酷会员,某女友也想用一下,又不想每次上班的时候被打扰要求你给验证码,那么你也可以使用此工具,验证码即可自动转发到她手机上去。

    嗯,这次是真来了~

    1. MainActivity
    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            LogUtils.d("onCreate()")
            setContentView(R.layout.main_activity)
    
            if (savedInstanceState == null) {
                supportFragmentManager.beginTransaction()
                    .replace(R.id.container, MainFragment.newInstance())
                    .commitNow()
            }
    
            registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
                it.values.forEach { granted ->
                    if (!granted) {
                        finish()
                    }
                }
            }.launch(arrayOf(Manifest.permission.RECEIVE_SMS, Manifest.permission.SEND_SMS))
        }
    }
    
    1. MainFragment
    class MainFragment : Fragment() {
    
        companion object {
            fun newInstance() = MainFragment()
        }
    
        private lateinit var mBinding: MainFragmentBinding
        private lateinit var mViewModel: MainViewModel
        private lateinit var mAdapter: MyAdapter
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setHasOptionsMenu(true)
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            mBinding = MainFragmentBinding.inflate(inflater)
            return mBinding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            initViews()
            mViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
            mViewModel.getAllStrategies(requireContext()).observe(viewLifecycleOwner) {
                mBinding.emptyView.isVisible = it.isEmpty()
                mAdapter.setNewInstance(ArrayList(it))
            }
            mViewModel.getAllStrategies(requireContext())
        }
    
        override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
            super.onCreateOptionsMenu(menu, inflater)
            inflater.inflate(R.menu.menu_main_fragment, menu)
        }
    
        override fun onOptionsItemSelected(item: MenuItem): Boolean {
            if (item.itemId == R.id.add_strategy) {
                val dialog = AddStrategyDialog.newInstance()
                dialog.isCancelable = false
                dialog.show(childFragmentManager, "AddStrategyDialog")
            }
            return super.onOptionsItemSelected(item)
        }
    
        private fun initViews() {
            mAdapter = MyAdapter()
            mBinding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
            mBinding.recyclerView.adapter = mAdapter
        }
    
        private inner class MyAdapter :
            BaseQuickAdapter<Strategy, BaseViewHolder>(R.layout.item_strategy) {
            override fun convert(holder: BaseViewHolder, item: Strategy) {
                holder.setText(R.id.send_target, item.senTarget)
                holder.setText(R.id.match_words, item.matchWords.words)
                holder.setText(R.id.with_suffix, item.withSuffix)
                holder.getView<View>(R.id.close_button).setOnClickListener {
                    mViewModel.deleteStrategy(requireContext(), item)
                }
            }
        }
    
    }
    
    1. MainViewModel
    class MainViewModel : ViewModel() {
    
        private val mExecutor = Executors.newSingleThreadExecutor()
        private lateinit var mStrategyLiveData: LiveData<List<Strategy>>
    
        fun getAllStrategies(context: Context): LiveData<List<Strategy>> {
            if (!this::mStrategyLiveData.isInitialized) {
                mStrategyLiveData = AppDatabase.getAppDataBase(context).StrategyDao().getAll()
            }
            return mStrategyLiveData
        }
    
        fun addStrategy(context: Context, strategy: Strategy) {
            mExecutor.execute {
                AppDatabase.getAppDataBase(context).StrategyDao().insertAll(strategy)
            }
        }
    
        fun deleteStrategy(context: Context, strategy: Strategy) {
            mExecutor.execute {
                AppDatabase.getAppDataBase(context).StrategyDao().delete(strategy)
            }
        }
    
    }
    
    1. AddStrategyDialog
    class AddStrategyDialog : DialogFragment() {
    
        companion object {
            fun newInstance() = AddStrategyDialog()
        }
    
        private var mOnDismissListener: DialogInterface.OnDismissListener? = null
        private lateinit var mBinding: AddStrategyDialogBinding
        private lateinit var mViewModel: MainViewModel
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            mBinding = AddStrategyDialogBinding.inflate(layoutInflater)
            return mBinding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            init()
        }
    
        override fun onDismiss(dialog: DialogInterface) {
            super.onDismiss(dialog)
            mOnDismissListener?.onDismiss(dialog)
        }
    
        fun setOnDismissListener(onDismissListener: DialogInterface.OnDismissListener) {
            mOnDismissListener = onDismissListener
        }
    
        private fun init() {
            mViewModel =
                ViewModelProvider(parentFragment as ViewModelStoreOwner).get(MainViewModel::class.java)
    
            mBinding.cancelButton.setOnClickListener {
                dismiss()
            }
    
            mBinding.confirmButton.setOnClickListener {
                val sendTarget = mBinding.sendTarget.text.trim().toString()
                if (TextUtils.isEmpty(sendTarget)) {
                    ToastUtils.showShort("发送目标不能为空")
                    return@setOnClickListener
                }
    
                if (!RegexUtils.isMobileExact(sendTarget)) {
                    ToastUtils.showShort("发送目标不是有效的电话号码")
                    return@setOnClickListener
                }
    
                val matchWords = mBinding.matchWords.text.trim().toString()
                if (TextUtils.isEmpty(matchWords)) {
                    ToastUtils.showShort("匹配词语不能为空")
                    return@setOnClickListener
                }
    
                val strategy = Strategy(
                    0,
                    sendTarget,
                    MatchWords(matchWords),
                    mBinding.withSuffix.text.toString()
                )
    
                mViewModel.addStrategy(requireContext(), strategy)
                dismiss()
            }
        }
    
    }
    
    1. AppDatabase
    @Database(entities = [Strategy::class], version = 2 /*, exportSchema = false*/)
    abstract class AppDatabase : RoomDatabase() {
    
        companion object {
            private var mAppDatabase: AppDatabase? = null
    
            @JvmStatic
            fun getAppDataBase(context: Context): AppDatabase {
                if (null == mAppDatabase) {
                    mAppDatabase = Room.databaseBuilder(
                        context.applicationContext, AppDatabase::class.java, "sms_forward.db"
                    ).addMigrations(MIGRATION_1_2).build()
                }
    
                return mAppDatabase!!
            }
        }
    
        abstract fun StrategyDao(): StrategyDao
    }
    
    val MIGRATION_1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            // do nothing.
        }
    }
    
    1. Strategy
    @Entity
    @TypeConverters(StrategyConverter::class)
    data class Strategy(
        @PrimaryKey(autoGenerate = true) val uid: Int,
        @ColumnInfo(name = "send_target") val senTarget: String,
        @ColumnInfo(name = "match_words") val matchWords: MatchWords,
        @ColumnInfo(name = "with_suffix") val withSuffix: String?,
    )
    
    class MatchWords(val words: String) {
        fun getWords(): List<String> {
            return words.split(" ")
        }
    }
    
    class StrategyConverter {
        @TypeConverter
        fun objectToString(matchWords: MatchWords): String {
            return matchWords.words
        }
    
        @TypeConverter
        fun stringToObject(str: String): MatchWords {
            return MatchWords(str)
        }
    }
    
    1. StrategyDao
    @Dao
    interface StrategyDao {
    
        @Query("SELECT * FROM Strategy")
        fun getAll(): LiveData<List<Strategy>>
    
        @Query("SELECT * FROM Strategy WHERE uid IN (:ids)")
        fun loadAllByIds(ids: IntArray): LiveData<List<Strategy>>
    
        /*@Query("SELECT * FROM Strategy WHERE xxx LIKE :first AND xxx LIKE :last LIMIT 1")
        fun findByName(first: String, last: String): Strategy*/
    
        @Insert
        fun insertAll(vararg strategies: Strategy)
    
        @Delete
        fun delete(strategy: Strategy)
    
    }
    
    1. SMSReceiver
    class SMSReceiver : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            LogUtils.d("SMSReceiver#onReceive()")
    
            val strategyDao = AppDatabase.getAppDataBase(context).StrategyDao().getAll()
            if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION == intent.action) {
                val messages: Array<SmsMessage> = getMessagesFromIntent(intent)
                strategyDao.observeForever {
                    it.forEach { strategy ->
                        for (message in messages) {
                            strategy.matchWords.getWords().forEach { word ->
                                if (message.displayMessageBody.contains(word)) {
                                    forwardSMS(message.displayMessageBody, strategy)
                                }
                            }
                        }
                    }
                }
            }
        }
    
        private fun forwardSMS(body: String, strategy: Strategy) {
            val content: String = body + strategy.withSuffix
            val phone: String = strategy.senTarget
            if (!TextUtils.isEmpty(content) && !TextUtils.isEmpty(phone)) {
                val manager = SmsManager.getDefault()
                manager.divideMessage(content).forEach {
                    manager.sendTextMessage(phone, null, it, null, null)
                    LogUtils.d("send sms, to: ${phone}, content: $it")
                }
            }
        }
    
    }
    
    1. AndroidManifest
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="xxx">
    
        <uses-permission android:name="android.permission.SEND_SMS" />
        <uses-permission android:name="android.permission.RECEIVE_SMS" />
    
        <application
            android:name=".MainApplication"
            android:allowBackup="false"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.SMSForward">
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
            <receiver android:name=".receiver.SMSReceiver">
                <intent-filter android:priority="1000">
                    <action android:name="android.provider.Telephony.SMS_RECEIVED" />
                </intent-filter>
            </receiver>
        </application>
    
    </manifest>
    
    1. build.gradle
    android {
        compileSdk 30
    
        defaultConfig {
            applicationId "xxx"
            minSdk 21
            targetSdk 28
            versionCode 1
            versionName "1.0"
    
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = ["room.schemaLocation" : "$projectDir/schemas".toString()]
                }
            }
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        viewBinding {
            enabled = true
        }
    
        signingConfigs {
            release {
                xxx
            }
        }
    
        buildTypes {
            debug {
                signingConfig signingConfigs.release
            }
    
            release {
                signingConfig signingConfigs.release
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
    
    dependencies {
        implementation 'androidx.core:core-ktx:1.3.2'
        implementation 'androidx.appcompat:appcompat:1.3.1'
        implementation 'com.google.android.material:material:1.3.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    
        implementation "androidx.activity:activity-ktx:1.3.1"
        implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
        implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
    
        def room_version = "2.3.0"
        implementation "androidx.room:room-ktx:$room_version"
        kapt "androidx.room:room-compiler:$room_version"
    
        implementation 'com.blankj:utilcodex:1.30.5'
        implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.2'
    }
    
    1. menu_main_fragment.xml
    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <item
            android:id="@+id/add_strategy"
            android:icon="@drawable/ic_baseline_add_24"
            android:title="@string/add_strategy"
            app:showAsAction="always" />
    </menu>
    
    1. main_activity.xml
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity" />
    
    1. main_fragment.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.main.MainFragment">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <TextView
            android:id="@+id/empty_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="未添加策略"
            android:textSize="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
    1. add_strategy_dialog.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="8dp">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:orientation="horizontal">
    
            <TextView
                android:layout_width="60dp"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:text="发送目标" />
    
            <EditText
                android:id="@+id/send_target"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginStart="8dp"
                android:gravity="center_vertical"
                android:minWidth="200dp"
                android:textSize="14dp"
                tools:text="18888888888" />
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:orientation="horizontal">
    
            <TextView
                android:layout_width="60dp"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:text="包含文字" />
    
            <EditText
                android:id="@+id/match_words"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginStart="8dp"
                android:gravity="center_vertical"
                android:hint="多个请用空格分开"
                android:textSize="14dp"
                tools:text="This That" />
    
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:orientation="horizontal">
    
            <TextView
                android:layout_width="60dp"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:text="携带后缀" />
    
            <EditText
                android:id="@+id/with_suffix"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginStart="8dp"
                android:gravity="center_vertical"
                android:textSize="14dp"
                tools:text="[by hehe]" />
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginTop="16dp"
            android:gravity="end">
    
            <Button
                android:id="@+id/cancel_button"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="取消" />
    
            <Button
                android:id="@+id/confirm_button"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginStart="16dp"
                android:text="确定" />
        </LinearLayout>
    
    </LinearLayout>
    
    1. item_strategy.xml
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp">
    
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="8dp">
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="40dp">
    
                <TextView
                    android:layout_width="60dp"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:text="发送目标" />
    
                <TextView
                    android:id="@+id/send_target"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginStart="8dp"
                    android:gravity="center_vertical"
                    tools:text="18828078637" />
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="40dp">
    
                <TextView
                    android:layout_width="60dp"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:text="包含文字" />
    
                <TextView
                    android:id="@+id/match_words"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginStart="8dp"
                    android:gravity="center_vertical"
                    tools:text="This, That, These, Those" />
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="40dp">
    
                <TextView
                    android:layout_width="60dp"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:text="携带后缀" />
    
                <TextView
                    android:id="@+id/with_suffix"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginStart="8dp"
                    android:gravity="center_vertical"
                    tools:text="by hehe" />
            </LinearLayout>
    
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#e2e2e2" />
    
        </LinearLayout>
    
        <ImageButton
            android:id="@+id/close_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|top"
            android:src="@drawable/ic_baseline_close_24" />
    </FrameLayout>
    

    某些系统上需要开启应用的自启动、关联启动和后台启动。

    相关文章

      网友评论

          本文标题:Android 短信转发神器

          本文链接:https://www.haomeiwen.com/subject/tvcrgltx.html