├── .github ├── FUNDING.yml └── workflows │ └── android.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── lint-baseline.xml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── net │ │ └── xcreen │ │ └── restsms │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── net │ │ │ └── xcreen │ │ │ └── restsms │ │ │ ├── AppContext.kt │ │ │ ├── MainActivity.kt │ │ │ ├── fragments │ │ │ ├── AboutAppFragment.kt │ │ │ ├── AboutFragment.kt │ │ │ ├── AboutMeFragment.kt │ │ │ ├── AboutThirdPartyLibrarysFragment.kt │ │ │ ├── HomeFragment.kt │ │ │ ├── LoggingDetailFragment.kt │ │ │ ├── LoggingFragment.kt │ │ │ └── SettingsFragment.kt │ │ │ └── server │ │ │ ├── SMSResponse.kt │ │ │ ├── SMSServer.kt │ │ │ ├── SMSServlet.kt │ │ │ ├── SMSWelcomeServlet.kt │ │ │ ├── ServerLogging.kt │ │ │ └── ServerService.kt │ └── res │ │ ├── drawable-hdpi │ │ ├── notification_icon.png │ │ └── notification_stop.png │ │ ├── drawable-mdpi │ │ ├── notification_icon.png │ │ └── notification_stop.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ ├── notification_icon.png │ │ └── notification_stop.png │ │ ├── drawable-xxhdpi │ │ ├── notification_icon.png │ │ └── notification_stop.png │ │ ├── drawable-xxxhdpi │ │ ├── notification_icon.png │ │ └── notification_stop.png │ │ ├── drawable │ │ ├── check_no.png │ │ ├── check_yes.png │ │ ├── ic_launcher_background.xml │ │ ├── launcher.png │ │ ├── nav_home.png │ │ ├── nav_info.png │ │ ├── nav_logging.png │ │ ├── nav_logo.png │ │ ├── nav_settings.png │ │ └── navigation_view_drawer_item.xml │ │ ├── layout │ │ ├── about_third_party_list_item.xml │ │ ├── activity_main.xml │ │ ├── fragment_about.xml │ │ ├── fragment_about_app.xml │ │ ├── fragment_about_me.xml │ │ ├── fragment_about_third_party_librarys.xml │ │ ├── fragment_home.xml │ │ ├── fragment_logging.xml │ │ ├── fragment_logging_detail.xml │ │ ├── fragment_settings.xml │ │ └── nav_header_main.xml │ │ ├── menu │ │ ├── activity_main_drawer.xml │ │ └── options_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── net │ └── xcreen │ └── restsms │ └── ExampleUnitTest.java ├── build.gradle ├── diagram_draw.io.png ├── diagram_draw.io.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | custom: https://paypal.me/xcreen 3 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: set up JDK 17 17 | uses: actions/setup-java@v4 18 | with: 19 | java-version: '17' 20 | distribution: 'temurin' 21 | - name: Make gradlew executable 22 | run: chmod +x ./gradlew 23 | - name: Build with Gradle 24 | run: ./gradlew build 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/* 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | restsms_keystore.txt 10 | restsms_keystore.jks 11 | app/release/* 12 | app/google-services.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Xcreen(David R) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RestSMS 2 | 3 | The RestSMS-App allows you to send SMS via Webservice from your Android-Device. 4 | 5 | ![Architecture](diagram_draw.io.png "RestSMS API Architecture") 6 | 7 | ### Requirements: 8 | - Android Version 6.0 or higher 9 | - Android-Device which is able to send SMS 10 | 11 | ### Android-Limit: 12 | Android´s default SMS-Limit are 30 SMS to a single phone number within 30 minutes. 13 | You can change your SMS-Limit for your device (root-permission is **not** required). 14 | #### How to change Android-Limit: 15 | 1. Make sure you have enabled USB-Debugging on your device and you are ready to use ADB. 16 | 2. Connect your device to the pc and open the terminal. 17 | 3. Open the adb-shell via the command: `adb shell` 18 | 4. Change the value of the SMS-Limit to the number of SMS you want to send within the 30 minutes timeframe. Via the command: 19 | `settings put global sms_outgoing_check_max_count 100` 20 | This command allows you to send 100 SMS to a phone number within the 30 minutes timeframe. 21 | 5. If you want to also change the timeframe, you can use the command: 22 | `settings put global sms_outgoing_check_interval_ms 900000` 23 | This command reduces the timeframe to 15 minutes. 24 | If you entered both commands, you would be able to send 100 SMS to a phone number within 15 minutes. 25 | ### API-Usage 26 | The default server-url is `http://127.0.0.1:8080/`. 27 | You can change the port in the settings-menu. The ip can be changed via the public network address, so you can access the api from anywhere. 28 | To send a SMS you can point to your address and use the /send-Endpoint (eg. `http://127.0.0.1:8080/send`). 29 | You have to send `phoneno` and `message` and if you enable authentication also `token` as post-parameter (you can use `form-data` and also `x-www-form-urlencoded`). 30 | A response you get a JSON with a `success` and a `message` variable. 31 | 32 | #### Example Curl (x-www-form-urlencoded) 33 | message = "Hello World" 34 | phoneno = "+4915100000000" (it´s also possible to define a list of phone numbers) 35 | token = "123" 36 | ```shell 37 | curl -X POST http://127.0.0.1:8080/send -H 'Cache-Control: no-cache' -H 'Content-Type: application/x-www-form-urlencoded' -d 'message=Hello%20World&phoneno=%2B4915100000000&token=123' 38 | ``` 39 | 40 | #### Successful Response 41 | ```json 42 | { 43 | "success": true 44 | } 45 | ``` 46 | ⚠️ That means the SMS got forwarded to your default SMS-App (packagename can be found under App-Information). The SMS still can get stuck in your default SMS-App. For example on a Emulator, you will get success = true, but the default SMS-App cant send the SMS, because the SIM-Card is just emulated. So success = true, does not mean that the SMS was already successful sent. 47 | 48 | #### Failed Response 49 | ```json 50 | { 51 | "message": "message or phoneno parameter are missing!", 52 | "success": false 53 | } 54 | ``` 55 | The message variable holds the error-message, so you can adjust your request. 56 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | defaultConfig { 6 | applicationId "net.xcreen.restsms" 7 | minSdkVersion 23 8 | compileSdk 34 9 | targetSdkVersion 34 10 | versionCode 11 11 | versionName "2.5.0" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | buildFeatures { 21 | buildConfig = true 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_17 25 | targetCompatibility JavaVersion.VERSION_17 26 | } 27 | lint { 28 | baseline file('lint-baseline.xml') 29 | disable 'GoogleAppIndexingWarning' 30 | } 31 | namespace 'net.xcreen.restsms' 32 | } 33 | 34 | dependencies { 35 | implementation fileTree(dir: 'libs', include: ['*.jar']) 36 | implementation "org.eclipse.jetty:jetty-server:$jetty_version" 37 | implementation "org.eclipse.jetty:jetty-servlet:$jetty_version" 38 | implementation 'com.google.code.gson:gson:2.11.0' 39 | implementation 'com.googlecode.libphonenumber:libphonenumber:8.13.49' 40 | implementation 'org.slf4j:slf4j-simple:1.7.36' 41 | implementation 'androidx.browser:browser:1.8.0' 42 | implementation 'androidx.appcompat:appcompat:1.6.1' 43 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 44 | implementation 'com.google.android.material:material:1.12.0' 45 | implementation 'androidx.constraintlayout:constraintlayout:2.2.0' 46 | implementation 'androidx.core:core-ktx:1.13.1' 47 | implementation 'androidx.preference:preference-ktx:1.2.1' 48 | testImplementation 'junit:junit:4.13.2' 49 | androidTestImplementation 'androidx.test:runner:1.6.2' 50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' 51 | } 52 | -------------------------------------------------------------------------------- /app/lint-baseline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 9 | 10 | 11 | 14 | 16 | 17 | 18 | 23 | 27 | 28 | 29 | 34 | 38 | 39 | 40 | 45 | 49 | 50 | 51 | 56 | 60 | 61 | 62 | 67 | 71 | 72 | 73 | 76 | 78 | 79 | 80 | 85 | 89 | 90 | 91 | 96 | 100 | 101 | 102 | 107 | 111 | 112 | 113 | 118 | 122 | 123 | 124 | 129 | 133 | 134 | 135 | 140 | 144 | 145 | 146 | 151 | 155 | 156 | 157 | 162 | 166 | 167 | 168 | 173 | 177 | 178 | 179 | 184 | 188 | 189 | 190 | 195 | 199 | 200 | 201 | 206 | 210 | 211 | 212 | 217 | 221 | 222 | 223 | 226 | 228 | 229 | 230 | 233 | 235 | 236 | 237 | 240 | 242 | 243 | 244 | 247 | 249 | 250 | 251 | 254 | 256 | 257 | 258 | 261 | 263 | 264 | 265 | 268 | 270 | 271 | 272 | 275 | 277 | 278 | 279 | 282 | 284 | 285 | 286 | 289 | 291 | 293 | 294 | 295 | 298 | 300 | 302 | 303 | 304 | 307 | 309 | 311 | 312 | 313 | 316 | 318 | 320 | 321 | 322 | 325 | 327 | 329 | 330 | 331 | 336 | 340 | 341 | 342 | 347 | 351 | 352 | 353 | 358 | 362 | 363 | 364 | 369 | 373 | 374 | 375 | 380 | 384 | 385 | 386 | 387 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/net/xcreen/restsms/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package net.xcreen.restsms; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("net.xcreen.restsms", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xcreen/RestSMS/d4b51ce150db56a537d1d003214eeb0755e25cd5/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/net/xcreen/restsms/AppContext.kt: -------------------------------------------------------------------------------- 1 | package net.xcreen.restsms 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import net.xcreen.restsms.server.SMSServer 6 | 7 | class AppContext : Application() { 8 | 9 | companion object { 10 | lateinit var appContext: Context 11 | } 12 | 13 | var smsServer = SMSServer() 14 | 15 | override fun onCreate() { 16 | super.onCreate() 17 | appContext = applicationContext 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/xcreen/restsms/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package net.xcreen.restsms 2 | 3 | import android.app.Activity 4 | import android.app.AlertDialog 5 | import android.content.DialogInterface 6 | import android.os.Bundle 7 | import android.view.Menu 8 | import android.view.MenuItem 9 | import android.widget.Toast 10 | import androidx.activity.OnBackPressedCallback 11 | import androidx.appcompat.app.ActionBarDrawerToggle 12 | import androidx.appcompat.app.AppCompatActivity 13 | import androidx.appcompat.widget.Toolbar 14 | import androidx.core.view.GravityCompat 15 | import androidx.drawerlayout.widget.DrawerLayout 16 | import androidx.fragment.app.Fragment 17 | import com.google.android.material.navigation.NavigationView 18 | import net.xcreen.restsms.fragments.AboutFragment 19 | import net.xcreen.restsms.fragments.HomeFragment 20 | import net.xcreen.restsms.fragments.LoggingFragment 21 | import net.xcreen.restsms.fragments.SettingsFragment 22 | import org.slf4j.impl.SimpleLogger 23 | import java.io.File 24 | 25 | class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { 26 | var toolbar: Toolbar? = null 27 | var drawerToggle: ActionBarDrawerToggle? = null 28 | var optionsMenu: Menu? = null 29 | var instance: Activity? = null 30 | var OPTION_ITEM_LOGGING_DELETE_ALL = 1000 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | setContentView(R.layout.activity_main) 35 | instance = this 36 | 37 | //Set SLF4J Log-Level 38 | System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "INFO") 39 | 40 | //Init Toolbar 41 | toolbar = findViewById(R.id.toolbar) 42 | setSupportActionBar(toolbar) 43 | 44 | //Init Navigation 45 | val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) 46 | drawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) 47 | drawerLayout.addDrawerListener(drawerToggle!!) 48 | drawerToggle!!.syncState() 49 | val navigationView: NavigationView = findViewById(R.id.nav_view) 50 | navigationView.setNavigationItemSelectedListener(this) 51 | 52 | //Don't replace Fragment, on orientation-change 53 | if (savedInstanceState == null) { 54 | //Set Home Fragment 55 | val fragmentTransaction = supportFragmentManager.beginTransaction() 56 | try { 57 | val homeFragment: Fragment = HomeFragment::class.java.getDeclaredConstructor().newInstance() 58 | fragmentTransaction.replace(R.id.main_framelayout, homeFragment).commit() 59 | } catch (ex: Exception) { 60 | ex.printStackTrace() 61 | } 62 | } 63 | 64 | onBackPressedDispatcher.addCallback(this, object: OnBackPressedCallback(true) { 65 | override fun handleOnBackPressed() { 66 | if (drawerLayout.isDrawerOpen(GravityCompat.START)) { 67 | drawerLayout.closeDrawer(GravityCompat.START) 68 | } else { 69 | isEnabled = false 70 | onBackPressedDispatcher.onBackPressed() 71 | } 72 | } 73 | }) 74 | } 75 | 76 | override fun onCreateOptionsMenu(optionsMenu: Menu): Boolean { 77 | this.optionsMenu = optionsMenu 78 | menuInflater.inflate(R.menu.options_menu, optionsMenu) 79 | return true 80 | } 81 | 82 | override fun onNavigationItemSelected(item: MenuItem): Boolean { 83 | val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) 84 | //Remove all Option-Menu-Items 85 | optionsMenu!!.clear() 86 | 87 | //Set new Fragment 88 | val fragment: Fragment 89 | when (item.itemId) { 90 | R.id.nav_settings -> { 91 | fragment = SettingsFragment() 92 | toolbar!!.setTitle(R.string.nav_item_settings) 93 | } 94 | R.id.nav_logging -> { 95 | fragment = LoggingFragment() 96 | toolbar!!.setTitle(R.string.nav_item_logging) 97 | //Add Option-Items (if not exist) 98 | if (optionsMenu!!.findItem(OPTION_ITEM_LOGGING_DELETE_ALL) == null) { 99 | val loggingDeleteAllItem = optionsMenu!!.add(Menu.NONE, OPTION_ITEM_LOGGING_DELETE_ALL, 1, R.string.logging_options_delete_all) 100 | loggingDeleteAllItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_NEVER) 101 | loggingDeleteAllItem.setOnMenuItemClickListener { 102 | val dialogClickListener = DialogInterface.OnClickListener { _, which -> 103 | when (which) { 104 | DialogInterface.BUTTON_POSITIVE -> { 105 | //Get all Logs 106 | val logPath = instance!!.filesDir.absolutePath + File.separator + "logs" 107 | val logDir = File(logPath) 108 | val logFiles = logDir.listFiles() 109 | if (logFiles != null) { 110 | //Delete all Logs 111 | for (logFile in logFiles) { 112 | logFile.delete() 113 | } 114 | } 115 | //Refresh List 116 | val fragmentTransaction = supportFragmentManager.beginTransaction() 117 | fragmentTransaction.replace(R.id.main_framelayout, LoggingFragment()).commit() 118 | //Show Success Message 119 | Toast.makeText(instance, R.string.logging_successful_deleted_all, Toast.LENGTH_LONG).show() 120 | } 121 | DialogInterface.BUTTON_NEGATIVE -> { 122 | } 123 | } 124 | } 125 | val builder = AlertDialog.Builder(instance) 126 | builder.setMessage(getString(R.string.logging_options_delete_all_quest)) 127 | .setTitle(getString(R.string.logging_options_delete_all)) 128 | .setPositiveButton(getString(R.string.yes), dialogClickListener) 129 | .setNegativeButton(getString(R.string.no), dialogClickListener) 130 | .show() 131 | true 132 | } 133 | } 134 | } 135 | R.id.nav_about -> { 136 | fragment = AboutFragment() 137 | toolbar!!.setTitle(R.string.nav_item_about) 138 | } 139 | R.id.nav_home -> { 140 | fragment = HomeFragment() 141 | toolbar!!.setTitle(R.string.app_name) 142 | } 143 | else -> { 144 | fragment = HomeFragment() 145 | toolbar!!.setTitle(R.string.app_name) 146 | } 147 | } 148 | //Replace Fragment 149 | val fragmentTransaction = supportFragmentManager.beginTransaction() 150 | fragmentTransaction.replace(R.id.main_framelayout, fragment).addToBackStack("fragBack").commit() 151 | drawerLayout.closeDrawer(GravityCompat.START) 152 | return true 153 | } 154 | } -------------------------------------------------------------------------------- /app/src/main/java/net/xcreen/restsms/fragments/AboutAppFragment.kt: -------------------------------------------------------------------------------- 1 | package net.xcreen.restsms.fragments 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.graphics.PorterDuff 7 | import android.os.Build 8 | import android.os.Bundle 9 | import android.provider.Telephony 10 | import android.telephony.TelephonyManager 11 | import android.view.LayoutInflater 12 | import android.view.View 13 | import android.view.ViewGroup 14 | import android.widget.ImageView 15 | import android.widget.TextView 16 | import androidx.core.app.ActivityCompat 17 | import androidx.fragment.app.Fragment 18 | import net.xcreen.restsms.BuildConfig 19 | import net.xcreen.restsms.R 20 | 21 | class AboutAppFragment : Fragment() { 22 | 23 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 24 | val rootView = inflater.inflate(R.layout.fragment_about_app, container, false) 25 | //Set Version-Name 26 | val versionResultTV = rootView.findViewById(R.id.about_app_version_result_textview) 27 | versionResultTV.text = BuildConfig.VERSION_NAME 28 | //Set Version 29 | val versionNameResultTV = rootView.findViewById(R.id.about_app_versionname_result_textview) 30 | versionNameResultTV.text = BuildConfig.VERSION_CODE.toString() 31 | //Check if has SMS-Permission 32 | val smsPermissionResultImageView = rootView.findViewById(R.id.about_app_smspermission_result_iv) 33 | try { 34 | smsPermissionResultImageView.setColorFilter(requireContext().getColor(R.color.colorError), PorterDuff.Mode.SRC_ATOP) 35 | if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED) { 36 | smsPermissionResultImageView.setImageDrawable(requireContext().getDrawable(R.drawable.check_yes)) 37 | smsPermissionResultImageView.setColorFilter(requireContext().getColor(R.color.colorPrimary), PorterDuff.Mode.SRC_ATOP) 38 | } 39 | } catch (ex: Exception) { 40 | ex.printStackTrace() 41 | } 42 | //Check Sim-State 43 | var primarySim = TelephonyManager.SIM_STATE_ABSENT 44 | var secondarySim = TelephonyManager.SIM_STATE_ABSENT 45 | try { 46 | val telephonyManager = requireContext().getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager 47 | primarySim = telephonyManager.simState 48 | if (Build.VERSION.SDK_INT >= 26) { 49 | primarySim = telephonyManager.getSimState(0) 50 | secondarySim = telephonyManager.getSimState(1) 51 | } 52 | } catch (ex: Exception) { 53 | ex.printStackTrace() 54 | } 55 | //Show Sim-State 56 | val simState1ResultTV = rootView.findViewById(R.id.about_app_sim1state_result_textview) 57 | val primarySimTextID = getTextIdBySimState(primarySim) 58 | if (primarySimTextID != 0) { 59 | simState1ResultTV.setText(primarySimTextID) 60 | } 61 | val simState2ResultTV = rootView.findViewById(R.id.about_app_sim2state_result_textview) 62 | val secondarySimTextID = getTextIdBySimState(secondarySim) 63 | if (secondarySimTextID != 0) { 64 | simState2ResultTV.setText(secondarySimTextID) 65 | } 66 | //Show Default-SMS-App 67 | val defaultSMSApp = Telephony.Sms.getDefaultSmsPackage(context) 68 | val defaultSMSAppResultTV = rootView.findViewById(R.id.about_app_defaultsmsapp_result_textview) 69 | if (defaultSMSApp != null) { 70 | defaultSMSAppResultTV.text = defaultSMSApp 71 | } 72 | return rootView 73 | } 74 | 75 | /** 76 | * Returns the String-ID for the Sim-Status 77 | * @param simState - SimState 78 | * @return id - Text-ID 79 | */ 80 | private fun getTextIdBySimState(simState: Int): Int { 81 | return when (simState) { 82 | TelephonyManager.SIM_STATE_ABSENT -> R.string.error_sim_absent 83 | TelephonyManager.SIM_STATE_PIN_REQUIRED -> R.string.error_sim_pin 84 | TelephonyManager.SIM_STATE_PUK_REQUIRED -> R.string.error_sim_puk 85 | TelephonyManager.SIM_STATE_NETWORK_LOCKED -> R.string.error_sim_network_pin 86 | TelephonyManager.SIM_STATE_READY -> R.string.error_sim_ready 87 | TelephonyManager.SIM_STATE_NOT_READY -> R.string.error_sim_not_ready 88 | TelephonyManager.SIM_STATE_PERM_DISABLED -> R.string.error_sim_disabled 89 | TelephonyManager.SIM_STATE_CARD_IO_ERROR -> R.string.error_sim_card_error 90 | TelephonyManager.SIM_STATE_CARD_RESTRICTED -> R.string.error_sim_restricted 91 | else -> 0 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/net/xcreen/restsms/fragments/AboutFragment.kt: -------------------------------------------------------------------------------- 1 | package net.xcreen.restsms.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.appcompat.widget.LinearLayoutCompat 8 | import androidx.fragment.app.Fragment 9 | import androidx.viewpager2.adapter.FragmentStateAdapter 10 | import androidx.viewpager2.widget.ViewPager2 11 | import com.google.android.material.appbar.AppBarLayout 12 | import com.google.android.material.tabs.TabLayout 13 | import com.google.android.material.tabs.TabLayout.OnTabSelectedListener 14 | import com.google.android.material.tabs.TabLayoutMediator 15 | import net.xcreen.restsms.R 16 | 17 | class AboutFragment : Fragment() { 18 | private var tabLayout: TabLayout? = null 19 | private var viewPager: ViewPager2? = null 20 | private var mainAppBarLayout: AppBarLayout? = null 21 | 22 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 23 | val rootView = inflater.inflate(R.layout.fragment_about, container, false) 24 | if (activity != null) { //Set Tabs 25 | tabLayout = TabLayout(requireActivity()) 26 | tabLayout!!.addTab(tabLayout!!.newTab()) // About-Me Tab 27 | tabLayout!!.addTab(tabLayout!!.newTab()) // App-Info Tab 28 | tabLayout!!.addTab(tabLayout!!.newTab()) // Third-Party-Info Tab 29 | tabLayout!!.tabGravity = TabLayout.GRAVITY_FILL 30 | tabLayout!!.setBackgroundColor(resources.getColor(R.color.colorDarkBlack, null)) 31 | tabLayout!!.setTabTextColors(resources.getColor(R.color.colorWhite, null), resources.getColor(R.color.colorLightBlue, null)) 32 | tabLayout!!.addOnTabSelectedListener(object : OnTabSelectedListener { 33 | override fun onTabSelected(tab: TabLayout.Tab) { 34 | viewPager!!.currentItem = tab.position 35 | } 36 | override fun onTabUnselected(tab: TabLayout.Tab) {} 37 | override fun onTabReselected(tab: TabLayout.Tab) {} 38 | }) 39 | //Add TabLayout to the AppBarLayout 40 | mainAppBarLayout = requireActivity().findViewById(R.id.appbar_layout) 41 | mainAppBarLayout!!.addView(tabLayout, LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) 42 | //Set Page-Adapter to ViewPager 43 | val pagerAdapter = FragmentPageAdapter(this, tabLayout!!.tabCount) 44 | viewPager = rootView.findViewById(R.id.about_view_pager) 45 | viewPager!!.adapter = pagerAdapter 46 | TabLayoutMediator(tabLayout!!, viewPager!!) { tab, position -> 47 | tab.text = when (position) { 48 | 0 -> resources.getText(R.string.about_about_me) 49 | 1 -> resources.getText(R.string.about_app_information) 50 | 2 -> resources.getText(R.string.about_third_party_librarys) 51 | else -> resources.getText(R.string.about_about_me) 52 | } 53 | }.attach() 54 | } 55 | return rootView 56 | } 57 | 58 | override fun onDestroyView() { 59 | super.onDestroyView() 60 | try { 61 | //Remove Tab-Layout 62 | mainAppBarLayout!!.removeView(tabLayout) 63 | } 64 | catch (ex: Exception){ 65 | ex.printStackTrace() 66 | } 67 | } 68 | } 69 | 70 | internal class FragmentPageAdapter(fragment: Fragment, private val numberOfTabs: Int) : FragmentStateAdapter(fragment) { 71 | override fun createFragment(position: Int): Fragment { 72 | return when (position) { 73 | 0 -> AboutMeFragment() 74 | 1 -> AboutAppFragment() 75 | 2 -> AboutThirdPartyLibrarysFragment() 76 | else -> AboutMeFragment() 77 | } 78 | } 79 | 80 | override fun getItemCount(): Int { 81 | return numberOfTabs 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/net/xcreen/restsms/fragments/AboutMeFragment.kt: -------------------------------------------------------------------------------- 1 | package net.xcreen.restsms.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.Button 8 | import androidx.browser.customtabs.CustomTabColorSchemeParams 9 | import androidx.fragment.app.Fragment 10 | import androidx.browser.customtabs.CustomTabsIntent 11 | 12 | import net.xcreen.restsms.R 13 | 14 | class AboutMeFragment : Fragment() { 15 | 16 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 17 | val rootView = inflater.inflate(R.layout.fragment_about_me, container, false) 18 | val customTabColorSchemeParams = CustomTabColorSchemeParams.Builder() 19 | .setToolbarColor(resources.getColor(R.color.colorDarkBlack, null)) 20 | .setNavigationBarColor(resources.getColor(R.color.colorDarkBlack, null)) 21 | .build() 22 | 23 | val donateBtn = rootView.findViewById