├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── react │ ├── SmsModule.java │ └── SmsPackage.java ├── example ├── .buckconfig ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── App.js ├── __tests__ │ └── App.js ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── build_defs.bzl │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ │ └── xml-v28 │ │ │ │ └── react_native_config.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle ├── app.json ├── babel.config.js ├── index.js ├── ios │ ├── example-tvOS │ │ └── Info.plist │ ├── example-tvOSTests │ │ └── Info.plist │ ├── example.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── example-tvOS.xcscheme │ │ │ └── example.xcscheme │ ├── example │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── main.m │ └── exampleTests │ │ ├── Info.plist │ │ └── exampleTests.m ├── package-lock.json ├── package.json └── yarn.lock ├── index.js └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX 3 | # 4 | .DS_Store 5 | 6 | # node.js 7 | # 8 | node_modules/ 9 | npm-debug.log 10 | yarn-error.log 11 | 12 | 13 | # Xcode 14 | # 15 | build/ 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata 25 | *.xccheckout 26 | *.moved-aside 27 | DerivedData 28 | *.hmap 29 | *.ipa 30 | *.xcuserstate 31 | project.xcworkspace 32 | 33 | 34 | # Android/IntelliJ 35 | # 36 | build/ 37 | .idea 38 | .gradle 39 | local.properties 40 | *.iml 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2017] [Brian Kabiro] 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-get-sms-android 2 | 3 | Module that supports interaction with the Messaging API on Android 4 | 5 | The package allows you to: 6 | 7 | - get messages 8 | - send messages 9 | - delete messages 10 | 11 | > Decided to start this package because [_react-native-android-sms_](https://github.com/msmakhlouf/react-native-android-sms) wasn't maintained at the time. 12 | 13 | --- 14 | 15 | ## Getting started 16 | 17 | #### Yarn 18 | 19 | `$ yarn add react-native-get-sms-android` 20 | 21 | #### Npm 22 | 23 | `$ npm install react-native-get-sms-android --save` 24 | 25 | ### Mostly automatic installation 26 | 27 | `$ react-native link react-native-get-sms-android` 28 | 29 | #### Manual installation 30 | 31 | _android/settings.gradle_ 32 | 33 | include ':react-native-get-sms-android' 34 | project(':react-native-get-sms-android').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-get-sms-android/android') 35 | 36 | _android/app/build.gradle_ 37 | 38 | dependencies{ 39 | compile project(':react-native-get-sms-android') 40 | } 41 | 42 | _MainApplication.java_ 43 | 44 | import com.react.SmsPackage; 45 | 46 | @Override 47 | protected List getPackages() { 48 | return Arrays.asList( 49 | new MainReactPackage(), 50 | new SmsPackage() 51 | // (...) 52 | ); 53 | } 54 | 55 | #### Android Permissions 56 | 57 | **Note**: This has changed from 2.x. See `Upgrading to 2.x` section if using <=2.x 58 | 59 | Add permissions to your `android/app/src/main/AndroidManifest.xml` file. 60 | 61 | ```xml 62 | ... 63 | 64 | 65 | 66 | ... 67 | ``` 68 | 69 | ## Upgrading to 2.x 70 | 71 | You need to add permissions manually. `react-native-get-sms-android` does not automatically require permissions from 2.x. Refer to [this](https://github.com/briankabiro/react-native-get-sms-android/issues/34) issue. 72 | 73 | You need to require permissions in your `AndroidManifest.xml` file's `application` element based on what functions you plan to use like [the official documentation](https://developer.android.com/guide/topics/permissions/overview) describes: 74 | 75 | | Function | Permission needed | 76 | | ------------------- | ---------------------------- | 77 | | SmsAndroid.list | android.permission.READ_SMS | 78 | | SmsAndroid.delete | android.permission.WRITE_SMS | 79 | | SmsAndroid.autoSend | android.permission.SEND_SMS | 80 | 81 | ## Usage 82 | 83 | ### List SMS Messages 84 | 85 | ```javascript 86 | import SmsAndroid from 'react-native-get-sms-android'; 87 | 88 | /* List SMS messages matching the filter */ 89 | var filter = { 90 | box: 'inbox', // 'inbox' (default), 'sent', 'draft', 'outbox', 'failed', 'queued', and '' for all 91 | 92 | /** 93 | * the next 3 filters can work together, they are AND-ed 94 | * 95 | * minDate, maxDate filters work like this: 96 | * - If and only if you set a maxDate, it's like executing this SQL query: 97 | * "SELECT * from messages WHERE (other filters) AND date <= maxDate" 98 | * - Same for minDate but with "date >= minDate" 99 | */ 100 | minDate: 1554636310165, // timestamp (in milliseconds since UNIX epoch) 101 | maxDate: 1556277910456, // timestamp (in milliseconds since UNIX epoch) 102 | bodyRegex: '(.*)How are you(.*)', // content regex to match 103 | 104 | /** the next 5 filters should NOT be used together, they are OR-ed so pick one **/ 105 | read: 0, // 0 for unread SMS, 1 for SMS already read 106 | _id: 1234, // specify the msg id 107 | thread_id: 12, // specify the conversation thread_id 108 | address: '+1888------', // sender's phone number 109 | body: 'How are you', // content to match 110 | /** the next 2 filters can be used for pagination **/ 111 | indexFrom: 0, // start from index 0 112 | maxCount: 10, // count of SMS to return each time 113 | }; 114 | 115 | SmsAndroid.list( 116 | JSON.stringify(filter), 117 | (fail) => { 118 | console.log('Failed with this error: ' + fail); 119 | }, 120 | (count, smsList) => { 121 | console.log('Count: ', count); 122 | console.log('List: ', smsList); 123 | var arr = JSON.parse(smsList); 124 | 125 | arr.forEach(function(object) { 126 | console.log('Object: ' + object); 127 | console.log('-->' + object.date); 128 | console.log('-->' + object.body); 129 | }); 130 | }, 131 | ); 132 | 133 | /* 134 | Each sms will be represents by a JSON object represented below 135 | 136 | { 137 | "_id": 1234, 138 | "thread_id": 3, 139 | "address": "2900", 140 | "person": -1, 141 | "date": 1365053816196, 142 | "date_sent": 0, 143 | "protocol": 0, 144 | "read": 1, 145 | "status": -1, 146 | "type": 1, 147 | "body": "Hello There, I am an SMS", 148 | "service_center": "+60162999922", 149 | "locked": 0, 150 | "error_code": -1, 151 | "sub_id": -1, 152 | "seen": 1, 153 | "deletable": 0, 154 | "sim_slot": 0, 155 | "hidden": 0, 156 | "app_id": 0, 157 | "msg_id": 0, 158 | "reserved": 0, 159 | "pri": 0, 160 | "teleservice_id": 0, 161 | "svc_cmd": 0, 162 | "roam_pending": 0, 163 | "spam_report": 0, 164 | "secret_mode": 0, 165 | "safe_message": 0, 166 | "favorite": 0 167 | } 168 | 169 | */ 170 | ``` 171 | 172 | ### Delete SMS Message 173 | 174 | Delete an sms with id. If the message with the specified id does not exist it will fail with error: `SMS not found` 175 | 176 | ```javascript 177 | import SmsAndroid from 'react-native-get-sms-android'; 178 | 179 | SmsAndroid.delete( 180 | _id, 181 | (fail) => { 182 | console.log('Failed with this error: ' + fail); 183 | }, 184 | (success) => { 185 | console.log('SMS deleted successfully'); 186 | }, 187 | ); 188 | ``` 189 | 190 | #### Important note on deleting messages 191 | For Android > 5, the only app permitted to **delete** an SMS message is the app installed as the default SMS handler. 192 | 193 | If your app is not set as the default SMS handler, it will not be able to delete. See this [thread](https://stackoverflow.com/questions/8614211/deleting-android-sms-programmatically) on Stack Overflow for more details. 194 | 195 | ### Send SMS Message (automatically) 196 | 197 | Send an sms directly with React without user interaction. 198 | 199 | ```javascript 200 | import SmsAndroid from 'react-native-get-sms-android'; 201 | 202 | SmsAndroid.autoSend( 203 | phoneNumber, 204 | message, 205 | (fail) => { 206 | console.log('Failed with this error: ' + fail); 207 | }, 208 | (success) => { 209 | console.log('SMS sent successfully'); 210 | }, 211 | ); 212 | ``` 213 | 214 | #### Send message to multiple numbers 215 | 216 | ```javascript 217 | import SmsAndroid from 'react-native-get-sms-android'; 218 | 219 | let phoneNumbers = { 220 | "addressList": ["123", "456"] 221 | }; 222 | 223 | SmsAndroid.autoSend( 224 | JSON.stringify(phoneNumbers), 225 | message, 226 | (fail) => { 227 | console.log('Failed with this error: ' + fail); 228 | }, 229 | (success) => { 230 | console.log('SMS sent successfully'); 231 | }, 232 | ); 233 | ``` 234 | 235 | 236 | #### Event listeners 237 | 238 | An event will be thrown when the sms has been delivered. If the sms was delivered successfully the message will be "SMS delivered" otherwise the message will be "SMS not delivered" 239 | 240 | ```js 241 | import { DeviceEventEmitter } from 'react-native'; 242 | 243 | DeviceEventEmitter.addListener('sms_onDelivery', (msg) => { 244 | console.log(msg); 245 | }); 246 | ``` 247 | 248 | ## Note 249 | 250 | - Does not work with Expo as it's not possible to include custom native modules beyond the React Native APIs and components that are available in the Expo client app. The information [here](https://github.com/react-community/create-react-native-app/blob/master/react-native-scripts/template/README.md#ejecting-from-create-react-native-app) might help with integrating the module while still using Expo. 251 | 252 | ## Contributions welcome! 253 | 254 | Feel free to open an issue or a Pull Request. 255 | 256 | ## Thanks 257 | 258 | - [react-native-android-sms](https://github.com/msmakhlouf/react-native-android-sms) 259 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.3.1' 10 | } 11 | } 12 | 13 | apply plugin: 'com.android.library' 14 | 15 | def safeExtGet(prop, fallback) { 16 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 17 | } 18 | 19 | android { 20 | compileSdkVersion safeExtGet('compileSdkVersion', 28) 21 | 22 | defaultConfig { 23 | minSdkVersion safeExtGet('minSdkVersion', 16) 24 | targetSdkVersion safeExtGet('targetSdkVersion', 28) 25 | versionCode 1 26 | versionName "1.0.0" 27 | } 28 | lintOptions { 29 | abortOnError false 30 | } 31 | } 32 | 33 | repositories { 34 | google() 35 | maven { 36 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 37 | url "$rootDir/../node_modules/react-native/android" 38 | } 39 | jcenter() 40 | } 41 | 42 | dependencies { 43 | compileOnly "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" 44 | } 45 | 46 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/java/com/react/SmsModule.java: -------------------------------------------------------------------------------- 1 | 2 | package com.react; 3 | 4 | import com.facebook.react.bridge.Arguments; 5 | import com.facebook.react.bridge.Callback; 6 | import com.facebook.react.bridge.NativeModule; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContext; 9 | import com.facebook.react.modules.core.DeviceEventManagerModule; 10 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 11 | import com.facebook.react.bridge.ReactMethod; 12 | import com.facebook.react.bridge.ReadableMap; 13 | import com.facebook.react.bridge.WritableMap; 14 | 15 | import android.content.ContentValues; 16 | import android.os.Bundle; 17 | import android.widget.Toast; 18 | import android.app.LoaderManager; 19 | import android.app.PendingIntent; 20 | import android.app.PendingIntent.CanceledException; 21 | import android.content.ContentResolver; 22 | import android.content.CursorLoader; 23 | import android.content.Intent; 24 | import android.content.Loader; 25 | import android.content.Context; 26 | import android.database.Cursor; 27 | import android.net.Uri; 28 | import android.app.Activity; 29 | import android.content.BroadcastReceiver; 30 | import android.content.IntentFilter; 31 | 32 | import android.telephony.SmsManager; 33 | import android.telephony.SmsMessage; 34 | 35 | import java.util.HashMap; 36 | import java.util.Map; 37 | import java.util.ArrayList; 38 | 39 | import org.json.JSONArray; 40 | import org.json.JSONException; 41 | import org.json.JSONObject; 42 | 43 | public class SmsModule extends ReactContextBaseJavaModule /*implements LoaderManager.LoaderCallbacks*/ { 44 | // private LoaderManager mManager; 45 | private Cursor smsCursor; 46 | private Map smsList; 47 | private Map smsListBody; 48 | Activity mActivity = null; 49 | private static Context context; 50 | private ReactContext mReactContext; 51 | private Callback cb_autoSend_succ = null; 52 | private Callback cb_autoSend_err = null; 53 | 54 | public SmsModule(ReactApplicationContext reactContext) { 55 | super(reactContext); 56 | mReactContext = reactContext; 57 | smsList = new HashMap(); 58 | context = reactContext.getApplicationContext(); 59 | } 60 | 61 | @Override 62 | public String getName() { 63 | return "Sms"; 64 | } 65 | 66 | @ReactMethod 67 | public void list(String filter, final Callback errorCallback, final Callback successCallback) { 68 | try { 69 | JSONObject filterJ = new JSONObject(filter); 70 | String uri_filter = filterJ.has("box") ? filterJ.optString("box") : "inbox"; 71 | int fread = filterJ.has("read") ? filterJ.optInt("read") : -1; 72 | int fid = filterJ.has("_id") ? filterJ.optInt("_id") : -1; 73 | int ftid = filterJ.has("thread_id") ? filterJ.optInt("thread_id") : -1; 74 | String faddress = filterJ.optString("address"); 75 | String fcontent = filterJ.optString("body"); 76 | String fContentRegex = filterJ.optString("bodyRegex"); 77 | int indexFrom = filterJ.has("indexFrom") ? filterJ.optInt("indexFrom") : 0; 78 | int maxCount = filterJ.has("maxCount") ? filterJ.optInt("maxCount") : -1; 79 | String selection = filterJ.has("selection") ? filterJ.optString("selection") : ""; 80 | String sortOrder = filterJ.has("sortOrder") ? filterJ.optString("sortOrder") : null; 81 | long maxDate = filterJ.has("maxDate") ? filterJ.optLong("maxDate") : -1; 82 | long minDate = filterJ.has("minDate") ? filterJ.optLong("minDate") : -1; 83 | Cursor cursor = context.getContentResolver().query(Uri.parse("content://sms/" + uri_filter), null, selection, null, 84 | sortOrder); 85 | int c = 0; 86 | JSONArray jsons = new JSONArray(); 87 | 88 | while (cursor != null && cursor.moveToNext()) { 89 | boolean matchFilter = true; 90 | if (fid > -1) 91 | matchFilter = fid == cursor.getInt(cursor.getColumnIndex("_id")); 92 | else if (ftid > -1) 93 | matchFilter = ftid == cursor.getInt(cursor.getColumnIndex("thread_id")); 94 | else if (fread > -1) 95 | matchFilter = fread == cursor.getInt(cursor.getColumnIndex("read")); 96 | else if (faddress != null && !faddress.isEmpty()) 97 | matchFilter = faddress.equals(cursor.getString(cursor.getColumnIndex("address")).trim()); 98 | else if (fcontent != null && !fcontent.isEmpty()) 99 | matchFilter = fcontent.equals(cursor.getString(cursor.getColumnIndex("body")).trim()); 100 | 101 | if (fContentRegex != null && !fContentRegex.isEmpty()) 102 | matchFilter = matchFilter && cursor.getString(cursor.getColumnIndex("body")).matches(fContentRegex); 103 | if (maxDate > -1) 104 | matchFilter = matchFilter && maxDate >= cursor.getLong(cursor.getColumnIndex("date")); 105 | if (minDate > -1) 106 | matchFilter = matchFilter && minDate <= cursor.getLong(cursor.getColumnIndex("date")); 107 | if (matchFilter) { 108 | if (c >= indexFrom) { 109 | if (maxCount > 0 && c >= indexFrom + maxCount) 110 | break; 111 | // Long dateTime = Long.parseLong(cursor.getString(cursor.getColumnIndex("date"))); 112 | // String message = cursor.getString(cursor.getColumnIndex("body")); 113 | JSONObject json; 114 | json = getJsonFromCursor(cursor); 115 | jsons.put(json); 116 | 117 | } 118 | c++; 119 | } 120 | } 121 | cursor.close(); 122 | try { 123 | successCallback.invoke(c, jsons.toString()); 124 | } catch (Exception e) { 125 | errorCallback.invoke(e.getMessage()); 126 | } 127 | } catch (JSONException e) { 128 | errorCallback.invoke(e.getMessage()); 129 | return; 130 | } 131 | } 132 | 133 | private JSONObject getJsonFromCursor(Cursor cur) { 134 | JSONObject json = new JSONObject(); 135 | 136 | int nCol = cur.getColumnCount(); 137 | String[] keys = cur.getColumnNames(); 138 | try { 139 | for (int j = 0; j < nCol; j++) 140 | switch (cur.getType(j)) { 141 | case Cursor.FIELD_TYPE_NULL: 142 | json.put(keys[j], null); 143 | break; 144 | case Cursor.FIELD_TYPE_INTEGER: 145 | json.put(keys[j], cur.getLong(j)); 146 | break; 147 | case Cursor.FIELD_TYPE_FLOAT: 148 | json.put(keys[j], cur.getFloat(j)); 149 | break; 150 | case Cursor.FIELD_TYPE_STRING: 151 | json.put(keys[j], cur.getString(j)); 152 | break; 153 | case Cursor.FIELD_TYPE_BLOB: 154 | json.put(keys[j], cur.getBlob(j)); 155 | } 156 | } catch (Exception e) { 157 | return null; 158 | } 159 | 160 | return json; 161 | } 162 | 163 | @ReactMethod 164 | public void send(String addresses, String text, final Callback errorCallback, final Callback successCallback) { 165 | mActivity = getCurrentActivity(); 166 | try { 167 | JSONObject jsonObject = new JSONObject(addresses); 168 | JSONArray addressList = jsonObject.getJSONArray("addressList"); 169 | int n; 170 | if ((n = addressList.length()) > 0) { 171 | PendingIntent sentIntent = PendingIntent.getBroadcast(mActivity, 0, new Intent("SENDING_SMS"), 0); 172 | SmsManager sms = SmsManager.getDefault(); 173 | for (int i = 0; i < n; i++) { 174 | String address; 175 | if ((address = addressList.optString(i)).length() > 0) 176 | sms.sendTextMessage(address, null, text, sentIntent, null); 177 | } 178 | } else { 179 | PendingIntent sentIntent = PendingIntent.getActivity(mActivity, 0, new Intent("android.intent.action.VIEW"), 0); 180 | Intent intent = new Intent("android.intent.action.VIEW"); 181 | intent.putExtra("sms_body", text); 182 | intent.setData(Uri.parse("sms:")); 183 | try { 184 | sentIntent.send(mActivity.getApplicationContext(), 0, intent); 185 | successCallback.invoke("OK"); 186 | } catch (PendingIntent.CanceledException e) { 187 | errorCallback.invoke(e.getMessage()); 188 | return; 189 | } 190 | } 191 | return; 192 | } catch (JSONException e) { 193 | errorCallback.invoke(e.getMessage()); 194 | return; 195 | } 196 | 197 | } 198 | 199 | @ReactMethod 200 | public void delete(Integer id, final Callback errorCallback, final Callback successCallback) { 201 | try { 202 | int res = context.getContentResolver().delete(Uri.parse("content://sms/" + id), null, null); 203 | if (res > 0) { 204 | successCallback.invoke("OK"); 205 | } else { 206 | errorCallback.invoke("SMS not found"); 207 | } 208 | return; 209 | } catch (Exception e) { 210 | errorCallback.invoke(e.getMessage()); 211 | return; 212 | } 213 | } 214 | 215 | private void sendEvent(ReactContext reactContext, String eventName, String params) { 216 | reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params); 217 | } 218 | 219 | private void sendCallback(String message, boolean success) { 220 | if (success && cb_autoSend_succ != null) { 221 | cb_autoSend_succ.invoke(message); 222 | cb_autoSend_succ = null; 223 | } else if (!success && cb_autoSend_err != null) { 224 | cb_autoSend_err.invoke(message); 225 | cb_autoSend_err = null; 226 | } 227 | 228 | } 229 | 230 | 231 | @ReactMethod 232 | public void autoSend(String phoneNumber, String message, final Callback errorCallback, 233 | final Callback successCallback) { 234 | 235 | cb_autoSend_succ = successCallback; 236 | cb_autoSend_err = errorCallback; 237 | 238 | try { 239 | String SENT = "SMS_SENT"; 240 | String DELIVERED = "SMS_DELIVERED"; 241 | ArrayList sentPendingIntents = new ArrayList(); 242 | ArrayList deliveredPendingIntents = new ArrayList(); 243 | 244 | PendingIntent sentPI = PendingIntent.getBroadcast(context, 0, new Intent(SENT), 0); 245 | PendingIntent deliveredPI = PendingIntent.getBroadcast(context, 0, new Intent(DELIVERED), 0); 246 | 247 | //---when the SMS has been sent--- 248 | context.registerReceiver(new BroadcastReceiver() { 249 | @Override 250 | public void onReceive(Context arg0, Intent arg1) { 251 | switch (getResultCode()) { 252 | case Activity.RESULT_OK: 253 | sendCallback("SMS sent", true); 254 | break; 255 | case SmsManager.RESULT_ERROR_GENERIC_FAILURE: 256 | sendCallback("Generic failure", false); 257 | break; 258 | case SmsManager.RESULT_ERROR_NO_SERVICE: 259 | sendCallback("No service", false); 260 | break; 261 | case SmsManager.RESULT_ERROR_NULL_PDU: 262 | sendCallback("Null PDU", false); 263 | break; 264 | case SmsManager.RESULT_ERROR_RADIO_OFF: 265 | sendCallback("Radio off", false); 266 | break; 267 | } 268 | } 269 | }, new IntentFilter(SENT)); 270 | 271 | //---when the SMS has been delivered--- 272 | context.registerReceiver(new BroadcastReceiver() { 273 | @Override 274 | public void onReceive(Context arg0, Intent arg1) { 275 | switch (getResultCode()) { 276 | case Activity.RESULT_OK: 277 | sendEvent(mReactContext, "sms_onDelivery", "SMS delivered"); 278 | break; 279 | case Activity.RESULT_CANCELED: 280 | sendEvent(mReactContext, "sms_onDelivery", "SMS not delivered"); 281 | break; 282 | } 283 | } 284 | }, new IntentFilter(DELIVERED)); 285 | 286 | SmsManager sms = SmsManager.getDefault(); 287 | ArrayList parts = sms.divideMessage(message); 288 | 289 | for (int i = 0; i < parts.size(); i++) { 290 | sentPendingIntents.add(i, sentPI); 291 | deliveredPendingIntents.add(i, deliveredPI); 292 | } 293 | sms.sendMultipartTextMessage(phoneNumber, null, parts, sentPendingIntents, deliveredPendingIntents); 294 | 295 | ContentValues values = new ContentValues(); 296 | values.put("address", phoneNumber); 297 | values.put("body", message); 298 | context.getContentResolver().insert(Uri.parse("content://sms/sent"), values); 299 | 300 | } catch (Exception e) { 301 | sendCallback(e.getMessage(), false); 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /android/src/main/java/com/react/SmsPackage.java: -------------------------------------------------------------------------------- 1 | 2 | package com.react; 3 | 4 | import android.app.Activity; 5 | 6 | import com.facebook.react.ReactPackage; 7 | import com.facebook.react.bridge.JavaScriptModule; 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | 18 | 19 | public class SmsPackage implements ReactPackage { 20 | 21 | @Override 22 | public List createNativeModules(ReactApplicationContext reactApplicationContext) { 23 | List modules = new ArrayList<>(); 24 | modules.add(new SmsModule(reactApplicationContext)); 25 | return modules; 26 | } 27 | 28 | public List> createJSModules() { 29 | return Collections.emptyList(); 30 | } 31 | 32 | @Override 33 | public List createViewManagers(ReactApplicationContext reactApplicationContext) { 34 | return Arrays.asList(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /example/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | 16 | ; Ignore polyfills 17 | .*/Libraries/polyfills/.* 18 | 19 | ; Ignore metro 20 | .*/node_modules/metro/.* 21 | 22 | [include] 23 | 24 | [libs] 25 | node_modules/react-native/Libraries/react-native/react-native-interface.js 26 | node_modules/react-native/flow/ 27 | node_modules/react-native/flow-github/ 28 | 29 | [options] 30 | emoji=true 31 | 32 | esproposal.optional_chaining=enable 33 | esproposal.nullish_coalescing=enable 34 | 35 | module.system=haste 36 | module.system.haste.use_name_reducers=true 37 | # get basename 38 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' 39 | # strip .js or .js.flow suffix 40 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' 41 | # strip .ios suffix 42 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' 43 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' 44 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' 45 | module.system.haste.paths.blacklist=.*/__tests__/.* 46 | module.system.haste.paths.blacklist=.*/__mocks__/.* 47 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* 48 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* 49 | 50 | munge_underscores=true 51 | 52 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 53 | 54 | module.file_ext=.js 55 | module.file_ext=.jsx 56 | module.file_ext=.json 57 | module.file_ext=.native.js 58 | 59 | suppress_type=$FlowIssue 60 | suppress_type=$FlowFixMe 61 | suppress_type=$FlowFixMeProps 62 | suppress_type=$FlowFixMeState 63 | 64 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 65 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 66 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 68 | 69 | [version] 70 | ^0.86.0 71 | -------------------------------------------------------------------------------- /example/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | * @flow 7 | * @lint-ignore-every XPLATJSCOPYRIGHT1 8 | */ 9 | 10 | import React, { Component } from "react"; 11 | import { 12 | Alert, 13 | Platform, 14 | Button, 15 | Clipboard, 16 | StyleSheet, 17 | Text, 18 | View, 19 | ScrollView, 20 | TextInput, 21 | TouchableOpacity, 22 | PermissionsAndroid 23 | } from "react-native"; 24 | import SmsAndroid from "react-native-get-sms-android"; 25 | 26 | const instructions = Platform.select({ 27 | ios: "Press Cmd+R to reload,\n" + "Cmd+D or shake for dev menu", 28 | android: 29 | "Double tap R on your keyboard to reload,\n" + 30 | "Shake or press menu button for dev menu" 31 | }); 32 | 33 | type Props = {}; 34 | export default class App extends Component { 35 | constructor() { 36 | super(); 37 | this.state = { 38 | sendTo: "", 39 | sendBody: "", 40 | minDate: "", 41 | maxDate: "", 42 | smsList: [] 43 | }; 44 | } 45 | 46 | async componentDidMount() { 47 | if (Platform.OS === "android") { 48 | try { 49 | if (!(await this.checkPermissions())) { 50 | await this.requestPermissions(); 51 | } 52 | 53 | if (await this.checkPermissions()) { 54 | this.listSMS(); 55 | } 56 | } catch (e) { 57 | console.error(e); 58 | } 59 | } 60 | } 61 | 62 | async checkPermissions() { 63 | console.log("checking SMS permissions"); 64 | let hasPermissions = false; 65 | try { 66 | hasPermissions = await PermissionsAndroid.check( 67 | PermissionsAndroid.PERMISSIONS.READ_SMS 68 | ); 69 | if (!hasPermissions) return false; 70 | hasPermissions = await PermissionsAndroid.check( 71 | PermissionsAndroid.PERMISSIONS.SEND_SMS 72 | ); 73 | if (!hasPermissions) return false; 74 | } catch (e) { 75 | console.error(e); 76 | } 77 | return hasPermissions; 78 | } 79 | 80 | async requestPermissions() { 81 | let granted = {}; 82 | try { 83 | console.log("requesting SMS permissions"); 84 | granted = await PermissionsAndroid.requestMultiple( 85 | [ 86 | PermissionsAndroid.PERMISSIONS.READ_SMS, 87 | PermissionsAndroid.PERMISSIONS.SEND_SMS 88 | ], 89 | { 90 | title: "Example App SMS Features", 91 | message: "Example SMS App needs access to demonstrate SMS features", 92 | buttonNeutral: "Ask Me Later", 93 | buttonNegative: "Cancel", 94 | buttonPositive: "OK" 95 | } 96 | ); 97 | console.log(granted); 98 | if (granted === PermissionsAndroid.RESULTS.GRANTED) { 99 | console.log("You can use SMS features"); 100 | } else { 101 | console.log("SMS permission denied"); 102 | } 103 | } catch (err) { 104 | console.warn(err); 105 | } 106 | } 107 | 108 | 109 | sendSMS = () => { 110 | SmsAndroid.autoSend( 111 | this.state.sendTo, 112 | this.state.sendBody, 113 | err => { 114 | Alert.alert("Failed to send SMS. Check console"); 115 | console.log("SMS SEND ERROR", err); 116 | }, 117 | success => { 118 | Alert.alert("SMS sent successfully"); 119 | } 120 | ); 121 | } 122 | 123 | renderSendSMS = () => { 124 | return ( 125 | 126 | Send SMS 127 | To 128 | this.setState({ sendTo: text })} 131 | value={this.state.sendTo} 132 | keyboardType={"numeric"} 133 | /> 134 | Message 135 | this.setState({ sendBody: text })} 138 | value={this.state.sendBody} 139 | /> 140 |