Android 短信转发神器

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

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

嗯,它来了~


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

嗯,这次是真来了~

  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(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(R.id.close_button).setOnClickListener {
                mViewModel.deleteStrategy(requireContext(), item)
            }
        }
    }

}
  1. MainViewModel
class MainViewModel : ViewModel() {

    private val mExecutor = Executors.newSingleThreadExecutor()
    private lateinit var mStrategyLiveData: LiveData>

    fun getAllStrategies(context: Context): LiveData> {
        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 {
        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>

    @Query("SELECT * FROM Strategy WHERE uid IN (:ids)")
    fun loadAllByIds(ids: IntArray): LiveData>

    /*@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 = 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


    
    

    
        
            
                

                
            
        

        
            
                
            
        
    


  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


    

  1. main_activity.xml


  1. main_fragment.xml



    

    


  1. add_strategy_dialog.xml



    

        

        
    

    

        

        

    

    

        

        
    

    

        
  1. item_strategy.xml



    

        

            

            
        

        

            

            
        

        

            

            
        

        

    

    

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

你可能感兴趣的:(Android 短信转发神器)