* working first release

This commit is contained in:
Danyi Dávid 2019-08-27 08:57:45 +02:00
parent 6268d85d55
commit 7d60afaac4
31 changed files with 844 additions and 40 deletions

View File

@ -3,6 +3,115 @@
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>

15
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
</wildcardResourcePatterns>
</component>
</project>

4
.idea/encodings.xml generated
View File

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
<component name="Encoding" addBOMForNewFiles="with NO BOM">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

2
.idea/misc.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8 (3)" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -16,6 +16,14 @@ android {
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [
"room.schemaLocation" : "$projectDir/schemas".toString(),
"room.incremental" : "true",
"room.expandProjection": "true"]
}
}
}
buildTypes {
release {
@ -31,17 +39,24 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.preference:preference:1.1.0-rc01'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'khttp:khttp:0.1.0'
implementation 'org.greenrobot:eventbus:3.1.1'
// implementation 'khttp:khttp:0.1.0'
implementation 'io.karn:khttp-android:0.1.2'
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
//implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava support for Room
///implementation "androidx.room:room-rxjava2:$room_version"

View File

@ -0,0 +1,58 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "3c41cc5248bccd081b1049c7971f1456",
"entities": [
{
"tableName": "pending_sms",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `contact` TEXT NOT NULL, `message` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `direction` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "contact",
"columnName": "contact",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "direction",
"columnName": "direction",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3c41cc5248bccd081b1049c7971f1456')"
]
}
}

View File

@ -0,0 +1,58 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "3c41cc5248bccd081b1049c7971f1456",
"entities": [
{
"tableName": "pending_sms",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `contact` TEXT NOT NULL, `message` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `direction` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "contact",
"columnName": "contact",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "direction",
"columnName": "direction",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3c41cc5248bccd081b1049c7971f1456')"
]
}
}

View File

@ -3,7 +3,6 @@
package="hu.yvan.smsproxy">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.INTERNET"/>
@ -14,6 +13,26 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".activity.SettingsActivity"
android:label="@string/title_activity_settings"
android:parentActivityName=".activity.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="hu.yvan.smsproxy.activity.MainActivity"/>
</activity>
<activity
android:name=".activity.MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".service.SMSManager"
android:exported="false">
@ -24,7 +43,7 @@
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
</application>

View File

@ -0,0 +1,107 @@
package hu.yvan.smsproxy
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.room.Room
import hu.yvan.smsproxy.db.AppDatabase
import hu.yvan.smsproxy.db.SMS
/**
* A fragment representing a list of Items.
* Activities containing this fragment MUST implement the
* [SmsFragment.OnListFragmentInteractionListener] interface.
*/
class SmsFragment : Fragment() {
// TODO: Customize parameters
private var columnCount = 1
private var listener: OnListFragmentInteractionListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
columnCount = it.getInt(ARG_COLUMN_COUNT)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_sms_list, container, false)
// Set the adapter
if (view is RecyclerView) {
with(view) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
val db = Room.databaseBuilder(context, AppDatabase::class.java, "sms_storage")
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build()
val smses = db.smsDao().getAll().toMutableList()
Log.d(SmsFragment::class.java.simpleName, db.smsDao().getAll().count().toString())
adapter = SmsRecyclerViewAdapter(smses, listener)
}
}
return view
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnListFragmentInteractionListener) {
listener = context
} else {
throw RuntimeException(context.toString() + " must implement OnListFragmentInteractionListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
*
*
* See the Android Training lesson
* [Communicating with Other Fragments](http://developer.android.com/training/basics/fragments/communicating.html)
* for more information.
*/
interface OnListFragmentInteractionListener {
// TODO: Update argument type and name
fun onListFragmentInteraction(sms: SMS?, adapter: SmsRecyclerViewAdapter)
}
companion object {
// TODO: Customize parameter argument names
const val ARG_COLUMN_COUNT = "column-count"
// TODO: Customize parameter initialization
@JvmStatic
fun newInstance(columnCount: Int) =
SmsFragment().apply {
arguments = Bundle().apply {
putInt(ARG_COLUMN_COUNT, columnCount)
}
}
}
}

View File

@ -0,0 +1,3 @@
package hu.yvan.smsproxy
class SmsPersistedEvent(public val smsId: Long) {}

View File

@ -0,0 +1,67 @@
package hu.yvan.smsproxy
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import hu.yvan.smsproxy.SmsFragment.OnListFragmentInteractionListener
import hu.yvan.smsproxy.db.SMS
import kotlinx.android.synthetic.main.fragment_sms.view.*
/**
* [RecyclerView.Adapter] that can display a [SMS] and makes a call to the
* specified [OnListFragmentInteractionListener].
* TODO: Replace the implementation with code for your data type.
*/
class SmsRecyclerViewAdapter(
private val mValues: MutableList<SMS>,
private val mListener: OnListFragmentInteractionListener?
) : RecyclerView.Adapter<SmsRecyclerViewAdapter.ViewHolder>() {
private val mOnClickListener: View.OnClickListener
init {
mOnClickListener = View.OnClickListener { v ->
val item = v.tag as SMS
// Notify the active callbacks interface (the activity, if the fragment is attached to
// one) that an item has been selected.
mListener?.onListFragmentInteraction(item, this)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_sms, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mValues[position]
holder.mIdView.text = item.id.toString()
holder.mContentView.text = item.message
with(holder.mView) {
tag = item
setOnClickListener(mOnClickListener)
}
}
override fun getItemCount(): Int = mValues.size
inner class ViewHolder(val mView: View) : RecyclerView.ViewHolder(mView) {
val mIdView: TextView = mView.item_number
val mContentView: TextView = mView.content
override fun toString(): String {
return super.toString() + " '" + mContentView.text + "'"
}
}
fun removeItem(sms: SMS) {
mValues.remove(sms)
}
}

View File

@ -0,0 +1,81 @@
package hu.yvan.smsproxy.activity
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.room.Room
import hu.yvan.smsproxy.SmsRecyclerViewAdapter
import hu.yvan.smsproxy.R
import hu.yvan.smsproxy.SmsFragment
import hu.yvan.smsproxy.SmsPersistedEvent
import hu.yvan.smsproxy.db.AppDatabase
import hu.yvan.smsproxy.db.SMS
import hu.yvan.smsproxy.service.SMSManager
import kotlinx.android.synthetic.main.activity_main.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MainActivity : AppCompatActivity(), SmsFragment.OnListFragmentInteractionListener {
lateinit var savedAdapter: SmsRecyclerViewAdapter
override fun onListFragmentInteraction(sms: SMS?, adapter: SmsRecyclerViewAdapter) {
savedAdapter = adapter
Log.d(TAG, "interact: $sms")
if (sms != null) {
SMSManager.startActionSMSResend(this, sms)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.RECEIVE_SMS),
MY_PERMISSIONS_REQUEST_READ_CONTACTS)
}
fab.setOnClickListener { view ->
startActivity(Intent(this, SettingsActivity::class.java))
}
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
Log.d(TAG, "preferences: ${prefs.all}")
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
//Subscribe, run it on UI
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEvent(event: SmsPersistedEvent) {
val db = Room.databaseBuilder(this, AppDatabase::class.java, "sms_storage")
.allowMainThreadQueries()
.build()
.smsDao()
val sms = db.findById(event.smsId)
savedAdapter.removeItem(sms)
savedAdapter.notifyDataSetChanged()
db.delete(sms)
}
companion object {
private val TAG = MainActivity::class.java.simpleName
const val MY_PERMISSIONS_REQUEST_READ_CONTACTS = 1
}
}

View File

@ -0,0 +1,25 @@
package hu.yvan.smsproxy.activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import hu.yvan.smsproxy.R
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
}

View File

@ -3,7 +3,7 @@ package hu.yvan.smsproxy.db
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = arrayOf(SMS::class), version = 1)
@Database(entities = arrayOf(SMS::class), version = 2)
abstract class AppDatabase : RoomDatabase() {
abstract fun smsDao(): SMSDao
}

View File

@ -5,15 +5,17 @@ import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "pending_sms")
data class SMS (
@ColumnInfo(name = "contact") val contact: String?,
@ColumnInfo(name = "message") val message: String?,
@ColumnInfo(name = "timestamp") val timestamp: String?,
@ColumnInfo(name = "direction") val direction: Boolean?
data class SMS(
@ColumnInfo(name = "contact") val contact: String,
@ColumnInfo(name = "message") val message: String,
@ColumnInfo(name = "timestamp") val timestamp: Long,
@ColumnInfo(name = "direction") val direction: Boolean
) {
@PrimaryKey(autoGenerate = true) val id: Int? = null
@PrimaryKey(autoGenerate = true) var id: Long = 0
companion object {
const val DIRECTION_SENT = false
const val DIRECTION_RECEIVED = true
}
}

View File

@ -13,15 +13,18 @@ interface SMSDao {
@Query("SELECT * FROM pending_sms WHERE id IN (:smsIds)")
fun loadAllByIds(smsIds: IntArray): List<SMS>
// @Query("SELECT * FROM sms WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1")
// fun findByName(first: String, last: String): SMS
@Query("SELECT * FROM pending_sms WHERE id = :id")
fun findById(id: Int): SMS
fun findById(id: Long): SMS
@Insert
fun insert(sms: SMS): Long
@Insert
fun insertAll(vararg sms: SMS)
@Delete
fun delete(sms: SMS)
@Query("DELETE FROM pending_sms WHERE id = :id")
fun delete(id: Long)
}

View File

@ -5,18 +5,23 @@ import android.content.Intent
import android.content.Context
import android.telephony.SmsMessage
import android.util.Log
import androidx.preference.PreferenceManager
import androidx.room.Room
import hu.yvan.smsproxy.SmsPersistedEvent
import hu.yvan.smsproxy.db.AppDatabase
import hu.yvan.smsproxy.db.SMS
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
private const val SMS_RECEIVED_ACTION = "hu.yvan.smsproxy.service.action.SMS_RECEIVED"
private const val SMS_RESEND_ACTION = "hu.yvan.smsproxy.service.action.SMS_RESEND"
private const val EXTRA_PARAM_ID = "hu.yvan.smsproxy.service.extra.ID"
private const val EXTRA_PARAM_CONTACT = "hu.yvan.smsproxy.service.extra.CONTACT"
private const val EXTRA_PARAM_MESSAGE = "hu.yvan.smsproxy.service.extra.MESSAGE"
private const val EXTRA_PARAM_TIMESTAMP = "hu.yvan.smsproxy.service.extra.TIMESTAMP"
private const val SMS_ENPOINT = "https://sms-store.yvan.hu/store/sent/{{uniquehash}}"
private const val API_URI_BASE = "%s://%s/store/received/%s"
/**
* An [IntentService] subclass for handling asynchronous task requests in
@ -27,42 +32,68 @@ class SMSManager : IntentService("SMSManager") {
override fun onHandleIntent(intent: Intent?) {
when (intent?.action) {
SMS_RECEIVED_ACTION -> {
val contact = intent.getStringExtra(EXTRA_PARAM_CONTACT)
val message = intent.getStringExtra(EXTRA_PARAM_MESSAGE)
val timestamp = intent.getStringExtra(EXTRA_PARAM_TIMESTAMP)
val contact = intent.getStringExtra(EXTRA_PARAM_CONTACT) as String
val message = intent.getStringExtra(EXTRA_PARAM_MESSAGE) as String
val timestamp = intent.getLongExtra(EXTRA_PARAM_TIMESTAMP, 0)
handleActionSMSReceived(contact, message, timestamp)
}
SMS_RESEND_ACTION -> {
val id = intent.getLongExtra(EXTRA_PARAM_ID, 0)
val contact = intent.getStringExtra(EXTRA_PARAM_CONTACT) as String
val message = intent.getStringExtra(EXTRA_PARAM_MESSAGE) as String
val timestamp = intent.getLongExtra(EXTRA_PARAM_TIMESTAMP, 0)
handleActionSMSReceived(id, contact, message, timestamp)
}
}
}
private fun handleActionSMSReceived(contact: String?, message: String?, timestamp: String?) {
private fun handleActionSMSReceived(id: Long, contact: String, message: String, timestamp: Long) {
if (sendHttpRequest(id, contact, message, timestamp)) {
Log.d(TAG, "messageid: $id")
EventBus.getDefault().post(SmsPersistedEvent(id))
}
}
private fun handleActionSMSReceived(contact: String, message: String, timestamp: Long) {
val sms = SMS(contact, message, timestamp, SMS.DIRECTION_RECEIVED)
val db = Room.databaseBuilder(this, AppDatabase::class.java, "sms_storage").build()
db.smsDao().insertAll(sms)
val smsId = db.smsDao().insert(sms)
if (sendHttpRequest(smsId, sms.contact, sms.message, sms.timestamp)) {
db.smsDao().delete(smsId)
}
}
private fun sendHttpRequest(id: Long, contact: String, message: String, timestamp: Long): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
try {
Log.d(TAG, "sending POST request")
val response = khttp.post(
SMS_ENPOINT,
API_URI_BASE.format(
if (prefs.getBoolean("api_secure", true)) "https" else "http",
prefs.getString("api_url",""),
prefs.getString("api_key","")
),
headers = mapOf("content-type" to "application/json"),
json = mapOf(
"contactName" to sms.contact,
"contactNumber" to sms.contact,
"when" to sms.timestamp,
"text" to sms.message
"id" to id,
"contactName" to contact,
"contactNumber" to contact,
"when" to "@" + timestamp.div(1000),
"text" to message
)
)
if (response.statusCode == 200) {
db.smsDao().delete(sms)
}
return response.statusCode == 200
} catch (e: Exception) {
Log.e(TAG, "khttp: " + e.localizedMessage)
}
return false
}
companion object {
private val TAG = SMSManager::class.java.simpleName
/**
* Starts this service to perform action Foo with the given parameters. If
* Starts this service to perform action SMSRecieved with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
@ -77,5 +108,23 @@ class SMSManager : IntentService("SMSManager") {
}
context.startService(intent)
}
/**
* Starts this service to perform action SMSResend with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
@JvmStatic
fun startActionSMSResend(context: Context, sms: SMS) {
val intent = Intent(context, SMSManager::class.java).apply {
action = SMS_RESEND_ACTION
putExtra(EXTRA_PARAM_ID, sms.id)
putExtra(EXTRA_PARAM_CONTACT, sms.contact)
putExtra(EXTRA_PARAM_MESSAGE, sms.message)
putExtra(EXTRA_PARAM_TIMESTAMP, sms.timestamp)
}
context.startService(intent)
}
}
}

View File

@ -10,11 +10,11 @@ import android.util.Log
class SMSReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.i(TAG, "Broadcast received: " + intent.action)
if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION == intent.action) {
Log.i(TAG, "Processing incoming SMS")
val intentExtras = intent.extras
val format = intentExtras?.getString("format")
val pdus: Array<Any>? = intentExtras?.get(PDU_TYPE) as Array<Any>?
@Suppress("UNCHECKED_CAST") val pdus: Array<Any>? = intentExtras?.get(PDU_TYPE) as Array<Any>?
(0 until pdus!!.size).forEach { i ->
val smsMessage = SmsMessage.createFromPdu(pdus[i] as ByteArray, format)
SMSManager.startActionSMSRecieved(context, smsMessage)

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".activity.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:fitsSystemWindows="true"
android:layout_height="@dimen/app_bar_height"
android:layout_width="match_parent"
android:theme="@style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:toolbarId="@+id/toolbar"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimary">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_main"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@android:drawable/ic_menu_preferences"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">
<fragment android:name="hu.yvan.smsproxy.SmsFragment"
android:id="@+id/sms_fragment"
android:layout_weight="2"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.core.widget.NestedScrollView>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/item_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem"/>
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem"/>
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
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/list"
android:name="hu.yvan.smsproxy.SmsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".SmsFragment"
tools:listitem="@layout/fragment_sms"/>

View File

@ -0,0 +1,9 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@ -0,0 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="hu.yvan.smsproxy.activity.MainActivity">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>

View File

@ -0,0 +1,5 @@
<resources>
<dimen name="app_bar_height">180dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="text_margin">16dp</dimen>
</resources>

View File

@ -1,3 +1,17 @@
<resources>
<string name="app_name">SMS Proxy Application</string>
<string name="title_activity_main">SMS Proxy</string>
<string name="action_settings">Settings</string>
<string name="title_activity_settings">Settings</string>
<!-- Preference Titles -->
<string name="general_header">API</string>
<!-- Messages Preferences -->
<string name="api_url">Server address</string>
<string name="api_key">User key</string>
<string name="api_secure">Use HTTPS</string>
<string name="api_secure_on">Yes</string>
<string name="api_secure_off">No</string>
</resources>

View File

@ -7,5 +7,11 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
</resources>

View File

@ -0,0 +1,43 @@
<!--
~ Copyright 2018 The app Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<androidx.preference.PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
app:title="@string/general_header">
<EditTextPreference
app:key="api_url"
app:title="@string/api_url"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
app:key="api_key"
app:title="@string/api_key"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:key="api_secure"
app:title="@string/api_secure"
app:summaryOn="@string/api_secure_on"
app:summaryOff="@string/api_secure_off"
app:dependency="api_url"
app:defaultValue="true" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -1,14 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.31'
ext.kotlin_version = '1.3.41'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -19,7 +18,7 @@ allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}

View File

@ -1,6 +1,6 @@
#Fri Aug 16 09:44:48 CEST 2019
#Wed Aug 21 14:27:08 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip