SMS-Forward
๐ฒ Forward text messages from/to your Android phone.
๋ฌธ์์ค๋ฉด ๋ค๋ฅธ ์ ํ ๋๋ ํ ๋ ๊ทธ๋จ ๋๋ ์นํ ์ผ๋ก ์ ๋ฌ.
settings.gradle
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
include ':app'
root build.gradle
plugins {
// id 'com.android.application' version '8.1.2' apply false
// id 'com.android.library' version '8.1.2' apply false
// Gradle JDK: OpenJDK 17
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
}
src/build.gradle
apply plugin: 'com.android.application'
android {
compileSdk 34
defaultConfig {
applicationId "com.enixcoda.smsforward"
minSdkVersion 25
versionCode 2
versionName "1.0.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled = true
shrinkResources = true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
namespace 'com.enixcoda.smsforward'
}
dependencies {
implementation 'com.sun.mail:android-mail:1.6.4'
implementation 'com.sun.mail:android-activation:1.6.4'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.14.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".SMSReceiver"
android:permission="android.permission.BROADCAST_SMS"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
activity_main.xml
<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>
root_preferences.xml
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:iconSpaceReserved="false">
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/header_sms">
<SwitchPreferenceCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:key="@string/key_enable_sms"
app:iconSpaceReserved="false"
app:title="@string/enable_sms" />
<EditTextPreference
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:defaultValue=""
android:key="@string/key_target_sms"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/target_title_sms"
app:iconSpaceReserved="false"
app:summary="@string/target_summary_sms" />
</PreferenceCategory>
<PreferenceCategory
app:title="@string/header_telegram"
app:iconSpaceReserved="false">
<SwitchPreferenceCompat
app:iconSpaceReserved="false"
android:key="@string/key_enable_telegram"
app:title="@string/enable_telegram" />
<EditTextPreference
android:defaultValue=""
android:key="@string/key_target_telegram"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/target_title_telegram"
app:iconSpaceReserved="false" />
<EditTextPreference
android:defaultValue=""
android:key="@string/key_telegram_apikey"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/title_telegram_apikey"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
app:title="@string/header_web"
app:iconSpaceReserved="false">
<SwitchPreferenceCompat
app:iconSpaceReserved="false"
android:key="@string/key_enable_web"
app:title="@string/enable_web" />
<EditTextPreference
android:defaultValue=""
android:key="@string/key_target_web"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/target_title_web"
android:summary="@string/target_summary_web"
app:iconSpaceReserved="false" />
</PreferenceCategory>
</PreferenceScreen>
Forwarder.java
package com.enixcoda.smsforward;
import android.telephony.SmsManager;
import android.util.Log;
public class Forwarder {
static final int MAX_SMS_LENGTH = 120;
public static void sendSMS(String number, String content) {
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(number, null, content, null, null);
}
public static void forwardViaSMS(String senderNumber, String forwardContent, String forwardNumber) {
String forwardPrefix = String.format("From %s:\n", senderNumber);
try {
if ((forwardPrefix + forwardContent).getBytes().length > MAX_SMS_LENGTH) {
// there is a length limit in SMS, if the message length exceeds it, separate the meta data and content
sendSMS(forwardNumber, forwardPrefix);
sendSMS(forwardNumber, forwardContent);
} else {
// if it's not too long, combine meta data and content to save money
sendSMS(forwardNumber, forwardPrefix + forwardContent);
}
} catch (RuntimeException e) {
Log.d(Forwarder.class.toString(), e.toString());
}
}
public static void forwardViaTelegram(String senderNumber, String message, String targetTelegramID, String telegramToken) {
new ForwardTaskForTelegram(senderNumber, message, targetTelegramID, telegramToken).execute();
}
public static void forwardViaWeb(String senderNumber, String message, String endpoint) {
new ForwardTaskForWeb(senderNumber, message, endpoint).execute();
}
}
ForwardTaskForTelegram.java
package com.enixcoda.smsforward;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
public class ForwardTaskForTelegram extends AsyncTask<Void, Void, Void> {
String senderNumber;
String message;
String chatId;
String token;
public ForwardTaskForTelegram(String senderNumber, String message, String chatId, String token) {
this.senderNumber = senderNumber;
this.message = message;
this.chatId = chatId;
this.token = token;
}
@Override
protected Void doInBackground(Void... voids) {
try {
sendViaTelegram(
chatId,
String.format("Message from %s:\n%s", senderNumber, message),
token
);
} catch (IOException e) {
Log.d(Forwarder.class.toString(), e.toString());
}
return null;
}
private void sendViaTelegram(String chatId, String message, String token) throws IOException {
TaskForWeb.httpRequest(new Uri.Builder()
.scheme("https")
.authority("api.telegram.org")
.appendPath(String.format("bot%s", token))
.appendPath("sendMessage")
.appendQueryParameter("chat_id", chatId)
.appendQueryParameter("text", message)
.build().toString(), message);
}
}
ForwardTaskForWeb.java
package com.enixcoda.smsforward;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
public class ForwardTaskForWeb extends AsyncTask<Void, Void, Void> {
String senderNumber;
String message;
String endpoint;
public ForwardTaskForWeb(String senderNumber, String message, String endpoint) {
this.senderNumber = senderNumber;
this.message = message;
this.endpoint = endpoint;
}
@Override
protected Void doInBackground(Void... voids) {
try {
JSONObject body = new JSONObject();
body.put("from", senderNumber);
body.put("message", message);
TaskForWeb.httpRequest(endpoint, body.toString());
} catch (IOException e) {
Log.d(Forwarder.class.toString(), e.toString());
} catch (JSONException e) {
Log.d(Forwarder.class.toString(), e.toString());
}
return null;
}
}
MainActivity.java
package com.enixcoda.smsforward;
import android.Manifest;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceFragmentCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermissions(new String[] {
Manifest.permission.SEND_SMS,
Manifest.permission.INTERNET,
Manifest.permission.READ_CONTACTS
}, 0);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings, new SettingsFragment())
.commit();
}
}
public static class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
}
}
}
SMSReceiver.java
package com.enixcoda.smsforward;
import static android.provider.ContactsContract.CommonDataKinds.*;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
import androidx.preference.PreferenceManager;
public class SMSReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(android.provider.Telephony.Sms.Intents.SMS_RECEIVED_ACTION))
return;
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
final boolean enableSMS = sharedPreferences.getBoolean(context.getString(R.string.key_enable_sms), false);
final String targetNumber = sharedPreferences.getString(context.getString(R.string.key_target_sms), "");
final boolean enableWeb = sharedPreferences.getBoolean(context.getString(R.string.key_enable_web), false);
final String targetWeb = sharedPreferences.getString(context.getString(R.string.key_target_web), "");
final boolean enableTelegram = sharedPreferences.getBoolean(context.getString(R.string.key_enable_telegram), false);
final String targetTelegram = sharedPreferences.getString(context.getString(R.string.key_target_telegram), "");
final String telegramToken = sharedPreferences.getString(context.getString(R.string.key_telegram_apikey), "");
if (!enableSMS && !enableTelegram && !enableWeb) return;
final Bundle bundle = intent.getExtras();
final Object[] pduObjects = (Object[]) bundle.get("pdus");
if (pduObjects == null) return;
for (Object messageObj : pduObjects) {
SmsMessage currentMessage = SmsMessage.createFromPdu((byte[]) messageObj, (String) bundle.get("format"));
String senderNumber = currentMessage.getDisplayOriginatingAddress();
String senderNames = lookupContactName(context, senderNumber);
String senderLabel = (senderNames.isEmpty() ? "" : senderNames + " ") + "(" + senderNumber + ")";
String rawMessageContent = currentMessage.getDisplayMessageBody();
Log.w("SMSReceiver", "targetNumber -> " + targetNumber);
Log.w("SMSReceiver", "senderNumber -> " + senderNumber);
Log.w("SMSReceiver", "senderNames -> " + senderNames);
Log.w("SMSReceiver", "senderLabel -> " + senderLabel);
Log.w("SMSReceiver", "rawMessageContent -> " + rawMessageContent);
if (senderNumber.equals(targetNumber)) {
Forwarder.sendSMS("์ฌ๊ธฐ์ ์ํ๋ ์ ํ๋ฒํธ ๋ฃ๊ธฐ", "์ฌ๊ธฐ์ ์ํ๋ ๋ด์ฉ ๋ฃ๊ธฐ");
} else {
// normal message, forwarded
if (enableSMS && !targetNumber.equals(""))
Forwarder.forwardViaSMS(senderLabel, rawMessageContent, targetNumber);
if (enableTelegram && !targetTelegram.equals("") && !telegramToken.equals(""))
Forwarder.forwardViaTelegram(senderLabel, rawMessageContent, targetTelegram, telegramToken);
if (enableWeb && !targetWeb.equals(""))
Forwarder.forwardViaWeb(senderLabel, rawMessageContent, targetWeb);
}
}
}
private String lookupContactName(Context context, String phoneNumber) {
Uri filterUri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
String[] projection = new String[]{Phone.DISPLAY_NAME};
String[] senderContactNames = {};
try (Cursor cur = context.getContentResolver().query(filterUri, projection, null, null, null)) {
if (cur != null) {
senderContactNames = new String[cur.getCount()];
int i = 0;
while (cur.moveToNext()) {
senderContactNames[i] = cur.getString(0);
i++;
}
}
}
return String.join(", ", senderContactNames);
}
}
TaskForWeb.java
package com.enixcoda.smsforward;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
public class TaskForWeb extends AsyncTask<Void, Void, Void> {
static public void httpRequest(String endpoint, String content) throws IOException {
URL url = new URL(endpoint);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json; utf-8");
connection.setDoOutput(true);
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
out.write(content);
out.flush();
out.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
Log.d(Forwarder.class.toString(), String.valueOf(connection.getResponseCode()));
Log.d(Forwarder.class.toString(), response.toString());
connection.disconnect();
}
@Override
protected Void doInBackground(Void... voids) {
return null;
}
}
๊ธฐํ ๋น์ทํ ํ๋ก์ ํธ
WARNING |
๊ฒ์ฆ์ด ํ์ํจ |