How to analyze incoming message (JavaFX on Android)

1.3k views Asked by At

I ported my JavaFX application to my Android device. I want my application to read incoming SMS messages and store it in a database. I found several questions here in StackOverflow but I don't know how to implement in a JavaFX approach. Please help!

1

There are 1 answers

0
José Pereda On BEST ANSWER

These are the required steps to create and port a JavaFX application to an Android device, so you can track SMS messages, allowing:

  • sending SMS to the number you type. Warning: This may by subjected to costs to your mobile account.
  • reading all your list of SMS on your inbox.
  • listening to incoming SMS, and displaying the content when a new one appears.

Step 1

Using Gluon plugin for NetBeans create a new JavaFX project. Let's call it SMSTracker, with main class org.jpereda.sms.SMSTrackerFX. On build.gradle, update the jfxmobile plugin version to b9:

dependencies {
    classpath 'org.javafxports:jfxmobile-plugin:1.0.0-b9'
}

First of all let's create SMSMessage, a JavaFX pojo with our model:

public class SMSMessage {

    private final StringProperty id;
    private final StringProperty address;
    private final StringProperty msg;
    private final StringProperty readState; //"0" not read, "1" read sms
    private final StringProperty time;
    private final StringProperty folderName;

    public SMSMessage(String id, String address, String msg, String readState, String time, String folderName){
        this.id = new SimpleStringProperty(id);
        this.address = new SimpleStringProperty(address);
        this.msg = new SimpleStringProperty(msg);
        this.readState = new SimpleStringProperty(readState);
        this.time = new SimpleStringProperty(time);
        this.folderName = new SimpleStringProperty(folderName);
    }

    public String getId() {
        return id.get();
    }

    public StringProperty idProperty() {
        return id;
    }

    public String getAddress() {
        return address.get();
    }

    public StringProperty addressProperty() {
        return address;
    }

    public String getMsg() {
        return msg.get();
    }

    public StringProperty msgProperty() {
        return msg;
    }

    public String getReadState() {
        return readState.get();
    }

    public StringProperty readStateProperty() {
        return readState;
    }

    public String getTime() {
        return time.get();
    }

    public StringProperty timeProperty() {
        return time;
    }

    public String getFolderName() {
        return folderName.get();
    }

    public StringProperty folderNameProperty() {
        return folderName;
    }

    @Override
    public String toString(){
        return id.get()+ ": " + address.get() + ": " + msg.get();
    }
}

With ScenicBuilder and FXML or by code, create your UI. For this sample, it will be a simple UI with three main areas, for the three above mentioned options.

public class SMSTrackerFX extends Application {

    @Override
    public void start(Stage stage) {
        BorderPane root = new BorderPane();

        /*
        TOP :: Sending SMS
        Warning: This may by subjected to costs to your mobile account
        */
        Button buttonSend = new Button("Send SMS");

        TextField number = new TextField();
        number.setPromptText("Insert number");
        HBox.setHgrow(number, Priority.ALWAYS);
        HBox hbox = new HBox(10,buttonSend, number);

        TextField message = new TextField();
        message.setPromptText("Insert text");
        HBox.setHgrow(message, Priority.ALWAYS);

        VBox vboxTop = new VBox(10,hbox,message);

        buttonSend.disableProperty().bind(Bindings.createBooleanBinding(()->{
                return number.textProperty().isEmpty()
                        .or(message.textProperty().isEmpty()).get();
            }, number.textProperty(),message.textProperty()));

        vboxTop.setPadding(new Insets(10));
        root.setTop(vboxTop);

        /*
        CENTER :: Reading SMS Inbox
        */
        Button button = new Button("Read SMS Inbox");

        ListView<SMSMessage> view = new ListView<>();
        view.setCellFactory(data -> new SMSListCell());
        VBox.setVgrow(view, Priority.ALWAYS);

        VBox vboxCenter = new VBox(10,button,view);
        vboxCenter.setPadding(new Insets(10));
        root.setCenter(vboxCenter);

        /*
        BOTTOM :: Listening to incoming SMS
        */
        Label incoming = new Label("No messages");

        VBox vboxBottom = new VBox(10,new Label("Incoming SMS"),incoming);
        vboxBottom.setPadding(new Insets(10));

        root.setBottom(vboxBottom);

        Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
        Scene scene = new Scene(root, visualBounds.getWidth(), visualBounds.getHeight());
        stage.setScene(scene);
        stage.show();
    }

    private static class SMSListCell extends ListCell<SMSMessage> {

        @Override
        protected void updateItem(SMSMessage item, boolean empty) {
            super.updateItem(item, empty);
            if (item != null && !empty) {
                setGraphic(new Label(item.getId()+ ": " + item.getMsg()));
            } else {
                setGraphic(null);
            }
        }
    }

}

And this is what we have for now:

sms1

Step 2

Following HelloPlatform sample, we will add a PlatformService class and a PlatformProvider interface, with the required services:

public interface PlatformProvider {

    void sendSMS(String number, String message);

    List<SMSMessage> readSMSs();

    void listenToIncomingSMS();

    ObjectProperty<SMSMessage> messageProperty();

}

This interface will be implemented on each of the three platforms (desktop, iOS and Android). Obviously, we'll focus only on the Android implementation.

This is the project view on NetBeans with all the packages and files involved:

sms2

Step 3

Let's implement now the services on Android. For that I've followed several great answers at SO: sending SMS, read inbox and listening to incoming SMS.

From the last one, we can crete a class that sets an ObjectProperty with an SMSMessage object every time one is received:

public class SmsListener extends BroadcastReceiver {

    private final ObjectProperty<SMSMessage> messages = new SimpleObjectProperty<>();

    @Override
    public void onReceive(Context cntxt, Intent intent) {
        if(intent.getAction().equals(Intents.SMS_RECEIVED_ACTION)){
            for (SmsMessage smsMessage : Intents.getMessagesFromIntent(intent)) {
                SMSMessage sms = new SMSMessage("0", smsMessage.getOriginatingAddress(),
                        smsMessage.getMessageBody(), smsMessage.getStatus()==1?"read":"not read", 
                        Long.toString(smsMessage.getTimestampMillis()), "inbox");
                messages.set(sms);
            }
        }
    }

    public ObjectProperty<SMSMessage> messagesProperty() {
        return messages;
    }

}

In order to launch this listener, we use FXActivity, the class that extends the Android Context class and provides the access to the Android services, to register an instance of SmsListener:

public class AndroidPlatformProvider implements PlatformProvider {

    private final SmsListener receiver = new SmsListener();

    @Override
    public void listenToIncomingSMS() {
        FXActivity.getInstance().registerReceiver(receiver, new IntentFilter(Intents.SMS_RECEIVED_ACTION));
    }

    @Override
    public ObjectProperty<SMSMessage> messagesProperty() {
        return receiver.messagesProperty();
    }

}

Step 4

Finally, all we need to do is bind the property with our label on the UI and start the broadcast:

    @Override
    public void start(Stage stage) {
        ...    
    PlatformService.getInstance().messageProperty().addListener(
        (obs,s,s1)->{
            Platform.runLater(()->incoming.setText(s1.toString()));
        });

        // start broadcast
        PlatformService.getInstance().listenToIncomingSMS();
    }    

Note the use of Platform.runLater() to update the label: the broadcast thread is different than the JavaFX thread.

Step 5

The last step before building the apk consists on modifying the AndroidManifest.xml file to ask for the required permissions.

Since the jfxmobile-plugin creates one by default, running gradlew android at this stage will generate it. It is located under SMSTracker\build\javafxports\tmp\android folder.

Copy it to another location (SMSTracker\lib) and include its reference in the build.gradle file:

jfxmobile {
    android {
        manifest = 'lib/AndroidManifest.xml'
    }
}

Now edit the file and add the required permissions and the receiver:

<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jpereda.sms" android:versionCode="1" android:versionName="1.0">
        <supports-screens android:xlargeScreens="true"/>
        <uses-permission android:name="android.permission.READ_SMS"/>
        <uses-permission android:name="android.permission.SEND_SMS"/>
        <uses-permission android:name="android.permission.RECEIVE_SMS" />
        <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21"/>
        <application android:label="SMSTracker" android:name="android.support.multidex.MultiDexApplication">
                <activity android:name="javafxports.android.FXActivity" android:label="SMSTracker" android:configChanges="orientation|screenSize">
                        <meta-data android:name="main.class" android:value="org.jpereda.sms.SMSTrackerFX"/>
                        <meta-data android:name="debug.port" android:value="0"/>
                        <intent-filter>
                                <action android:name="android.intent.action.MAIN"/>
                                <category android:name="android.intent.category.LAUNCHER"/>
                        </intent-filter>
                </activity>
                <receiver android:name=".SmsListener">
                    <intent-filter android:priority="2147483647">
                        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
                    </intent-filter>
                </receiver>
        </application>
</manifest>

Save, build and run gradlew androidInstall to upload the apk to your device.

Full project

All the code for this application can be found here, including apk ready to be installed on Android devices.