Skip to content

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

๊ฒ€์ฆ์ด ํ•„์š”ํ•จ

See also

Favorite site