- Aug 24, 2000
- 4,154
- 4
- 81
Good Morning,
This is going to be a very long write-up so if you're curious/interested, I recommend grabbing a cup of coffee and carving out some time to dig through this with me.
Ok.. got your coffee? Great! Here we go!
I have been working on an Android app that is mostly for my own educational interests but has some applicability where I work.
It is essentially an application that once installed queries the phone's location sensor and reports back the location in the form of a speudo-XML string to a server I have written in LabView which then translates the string to KML and exports to Google Earth to display the phone's location on a map. The server side is done, but the mobile app is giving me hell!
The basic functions of the app are to 'Start' 'Stop' and 'Send' where start and stop trigger a timer loop which will periodically make the call to update the server with the phone's current location and derived GPS time, while Send can be pressed from the UI to perform this function manually.
The app can be controlled one of two ways. Either through the User Interface or by responding to coded SMS messages that trigger certain functions.
For the most part I have it all working except for a software bug that I can't seem to get past. What happens is this:
-- When the app is started from the UI, it creates a runnable that handles the periodic updating.
-- IF and ONLY IF the runnable is called from the UI (the start button) it will respond appropriately to the
Stop button and stop the runnable by removing the callback.
--When the app is triggered in response to an SMS command it will create a situation where the runnable cannot be stopped. That is, it will respond to the stop command either from the UI button press or a coded SMS, but only momentarily at which point the runnable automatically restarts. This behavior cannot be broken by removing callbacks and I have experimented with several approaches thus far.
I will post the relevant code, comments, and logcat output relevant to the different test cases. Hopefully somebody here much smarter than me can spot the issue.
Feel free to copy and compile this code if you wish to test it yourself. I used Android Studio but I'm sure Eclipse, etc. will do the job as well.
Android Manifest
activity_main.xml
MainActivity.java
LocationServices.java
SendMessage.java
IncomingSms.java
SmsButler.java
TimerButler.java
ContextHandler.java
****LogCat Output****
05-09 08:05:42.731 5037-5037/my.testapp I/Adreno-EGL: <qeglDrvAPI_eglInitialize:320>: EGL 1.4 QUALCOMM build: (CL3776187)
OpenGL ES Shader Compiler Version:
Build Date: 10/15/13 Tue
Local Branch:
Remote Branch: partner/upstream
Local Patches:
Reconstruct Branch:
05-09 08:05:42.771 5037-5037/my.testapp D/OpenGLRenderer: Enabling debug mode 0
05-09 08:05:42.922 5037-5037/my.testapp I/ActivityManager: Timeline: Activity_idle id: android.os.BinderProxy@418a9048 time:330054410
==I started the runnable by clicking the UI button=
05-09 08:05:58.377 5037-5037/my.testapp I/System.out: StartApp UI button clicked
05-09 08:05:58.377 5037-5037/my.testapp I/System.out: Starting Application
05-09 08:05:58.387 5037-5037/my.testapp I/System.out: Thread my.testapp.TimerButler$1@41915678 started.
==I clicked the UI start button a second time to verify some code I wrote to prevent multiple
instances of the runnable==
05-09 08:06:03.121 5037-5037/my.testapp I/System.out: StartApp UI button clicked
05-09 08:06:03.121 5037-5037/my.testapp I/System.out: Application thread is already running
==I clicked the UI stop button to stop the runnable==
==Just take my work for it this worked as expected given the order of events thus far==
05-09 08:06:08.066 5037-5037/my.testapp I/System.out: StopApp UI button clicked
05-09 08:06:08.066 5037-5037/my.testapp I/System.out: Stopping my.testapp.TimerButler$1@41915678
==I started the runnable this time using an SMS command==
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: SMS From: 5552221005 : #startApp
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: SMS: #startApp command received
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: Starting Application
==The app runnable thread started as expected and the app appears to function normally==
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: Thread my.testapp.TimerButler$1@41aa23e8 started.
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: #startApp response sent to: 5552221005
==This is another debug statement that indicates the sentMessage method was called==
==In this case it was triggered by they runnable. Evidence the program is executing==
05-09 08:07:20.196 5037-5037/my.testapp I/System.out: Executing sendMessage()
==Here I sent another SMS command to stop the runnable==
==It responds and for about a split second the indicator on the UI I have set up
shows the app stopped, however within no-time, it retarts itself and the app keeps on running==
05-09 08:07:25.822 5037-5037/my.testapp I/System.out: SMS From: 5552221005 : #stopApp
05-09 08:07:25.822 5037-5037/my.testapp I/System.out: SMS: #stopApp command received
05-09 08:07:25.822 5037-5037/my.testapp I/System.out: Stopping my.testapp.TimerButler$1@41baaf20
05-09 08:07:25.832 5037-5037/my.testapp I/System.out: #stopApp response sent to: 5552221005
==The screen went to sleep here triggering this message==
05-09 08:07:41.357 5037-5037/my.testapp I/ActivityManager: Timeline: Activity_idle id: android.os.BinderProxy@418a9048 time:330172848
==Remember the last thing I did was try to stop the runnable thread that was initially
started with an SMS command. Here I press the UI start button==
==The app responds with the appropriate response considering the runnable never
actually stopped==
05-09 08:07:47.653 5037-5037/my.testapp I/System.out: StartApp UI button clicked
05-09 08:07:47.653 5037-5037/my.testapp I/System.out: Application thread is already running
==Every 30 seconds the runnable calls the sendMessage==
05-09 08:07:50.346 5037-5037/my.testapp I/System.out: Executing sendMessage()
==This is an attempt to stop the runnable using the UI however when
runnable is stared in response to an SMS command this becomes uneffective as well==
==Remember: This works when the runnable is trigger by the UI start button - no problems==
05-09 08:07:55.090 5037-5037/my.testapp I/System.out: StopApp UI button clicked
05-09 08:07:55.090 5037-5037/my.testapp I/System.out: Stopping my.testapp.TimerButler$1@41915678
==Just to verify the runnable is running in spite of being instructed to stop==
05-09 08:08:08.303 5037-5037/my.testapp I/System.out: StartApp UI button clicked
==Note the reponse==
05-09 08:08:08.303 5037-5037/my.testapp I/System.out: Application thread is already running
==And the fact the method continues to be called every 30 seconds==
05-09 08:08:20.445 5037-5037/my.testapp I/System.out: Executing sendMessage()
05-09 08:08:50.545 5037-5037/my.testapp I/System.out: Executing sendMessage()
==An additional SMS start command encounters the same logic and returns
an appropriate response for when runnable is running==
05-09 08:09:16.009 5037-5037/my.testapp I/System.out: SMS From: 5552221005 : #startApp
05-09 08:09:16.009 5037-5037/my.testapp I/System.out: SMS: #startApp command received
==Note the response
05-09 08:09:16.009 5037-5037/my.testapp I/System.out: Application thread is already running
05-09 08:09:16.009 5037-5037/my.testapp I/System.out: #startApp response sent to: 5552221005
==And again the app continues to run==
05-09 08:09:20.704 5037-5037/my.testapp I/System.out: Executing sendMessage()
Whew!!! That was alot and yes I know I'm a shitty programmer but I'm trying to learn so cut me some slack.
If someone can nudge me in the right direction here I'll buy you an e-Beer!:beer:
Thanks!!
-JR
This is going to be a very long write-up so if you're curious/interested, I recommend grabbing a cup of coffee and carving out some time to dig through this with me.
Ok.. got your coffee? Great! Here we go!
I have been working on an Android app that is mostly for my own educational interests but has some applicability where I work.
It is essentially an application that once installed queries the phone's location sensor and reports back the location in the form of a speudo-XML string to a server I have written in LabView which then translates the string to KML and exports to Google Earth to display the phone's location on a map. The server side is done, but the mobile app is giving me hell!
The basic functions of the app are to 'Start' 'Stop' and 'Send' where start and stop trigger a timer loop which will periodically make the call to update the server with the phone's current location and derived GPS time, while Send can be pressed from the UI to perform this function manually.
The app can be controlled one of two ways. Either through the User Interface or by responding to coded SMS messages that trigger certain functions.
For the most part I have it all working except for a software bug that I can't seem to get past. What happens is this:
-- When the app is started from the UI, it creates a runnable that handles the periodic updating.
-- IF and ONLY IF the runnable is called from the UI (the start button) it will respond appropriately to the
Stop button and stop the runnable by removing the callback.
--When the app is triggered in response to an SMS command it will create a situation where the runnable cannot be stopped. That is, it will respond to the stop command either from the UI button press or a coded SMS, but only momentarily at which point the runnable automatically restarts. This behavior cannot be broken by removing callbacks and I have experimented with several approaches thus far.
I will post the relevant code, comments, and logcat output relevant to the different test cases. Hopefully somebody here much smarter than me can spot the issue.
Feel free to copy and compile this code if you wish to test it yourself. I used Android Studio but I'm sure Eclipse, etc. will do the job as well.
Android Manifest
Code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="my.testapp">
<!-- Declare permissions needed by LocationServices class -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Declare permissions needed by SendMessage class -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Declare permission needed by SmsButler class -->
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".IncomingSms">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
activity_main.xml
Code:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="my.testapp.MainActivity">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:id="@+id/linearLayoutMain">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/linearLayoutMain"
android:layout_alignParentStart="true"
android:background="#963F51B5"
android:id="@+id/linearLayoutPlayer"
android:focusableInTouchMode="true">
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Player ID:"
android:id="@+id/tvPlayer"
android:textColor="#AAFFFFFF"
android:textSize="18dp" />
<TextView
android:layout_width="0dp"
android:layout_height="40dp"
android:id="@+id/tvPlayerInfo"
android:textColor="#FFFFFF"
android:textSize="22dp"
android:layout_weight="1"
android:hint="--ASSIGN NAME--"
android:text="Joe's Phone" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/linearLayoutMain"
android:layout_alignParentStart="true"
android:background="#963F51B5"
android:id="@+id/linearLayoutLatitude">
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Latitude:"
android:id="@+id/tvLatitude"
android:textColor="#AAFFFFFF"
android:textSize="18dp" />
<TextView
android:layout_width="0dp"
android:layout_height="40dp"
android:id="@+id/tvLatInfo"
android:textColor="#FFFFFF"
android:textSize="22dp"
android:layout_weight="1"
android:hint="--null--" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/linearLayoutMain"
android:layout_alignParentStart="true"
android:background="#963F51B5"
android:id="@+id/linearLayoutLongitude">
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Longitude:"
android:id="@+id/tvLongitude"
android:textColor="#AAFFFFFF"
android:textSize="18dp" />
<TextView
android:layout_width="0dp"
android:layout_height="40dp"
android:id="@+id/tvLongInfo"
android:textColor="#FFFFFF"
android:textSize="22dp"
android:layout_weight="1"
android:hint="--null--" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="80dp"
android:id="@+id/tvTime"
android:background="#963F51B5"
android:hint="HH:MM:SS"
android:gravity="center_vertical|center_horizontal"
android:textColor="#88FFFFFF"
android:textColorHint="#88FFFFFF"
android:textIsSelectable="true"
android:textSize="40dp"
android:textStyle="italic" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical|center_horizontal">
<Button
android:layout_width="120dp"
android:layout_height="wrap_content"
android:text="START"
android:id="@+id/bStart"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical|center_horizontal"
android:orientation="vertical">
<Button
android:id="@+id/bStop"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
android:text="STOP" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical|center_horizontal">
<Button
android:id="@+id/bSend"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
android:text="SEND" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayoutSatInfo">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/tvAppStat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_weight="1"
android:gravity="right"
android:text="App Status: "
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#88000000"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/tvAppStatInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="-Waiting-"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#66000000"
android:textIsSelectable="true"
android:textStyle="normal" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/tvSatSnr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_weight="1"
android:gravity="right"
android:text="Sat SNR: "
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#88000000"
android:textStyle="bold" />
<TextView
android:id="@+id/tvSatSnrInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="--null--"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#FFFFFFFF"
android:textIsSelectable="true" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
MainActivity.java
Code:
package my.testapp;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity
{
/////////////////////////////////////////////////////////////
//Declare a context for Android and set to this activity
// re-use this context when calling other constructors or
// methods that require it.
/////////////////////////////////////////////////////////////
public final Context c = this;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Instantiate an object of LocationServices and call the
//getLocationUpdates method. Pass this context 'c' to the
//method so it can update the TextViews.
final LocationServices locationServices = new LocationServices();
locationServices.getLocationUpdates(c);
//Instantiate an object of SendMessage
final SendMessage sendMessage = new SendMessage();
//Instantiate and object of TimerButler and call
//its startHandler method.
final TimerButler timerButler = new TimerButler();
//Pass this context to the ContextHandler
ContextHandler.setC(c);
////////////////////////////////////////////////////////////
//Set up the START button to perform some action
////////////////////////////////////////////////////////////
final Button bStart = (Button) findViewById(R.id.bStart);
bStart.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
Toast.makeText(c, "Starting Thread", Toast.LENGTH_SHORT).show();
System.out.println("StartApp UI button clicked"); //Just some code for debugging with logcat
timerButler.startHandler(); //startTimer();
}
});//End setOnClickListener
////////////////////////////////////////////////////////////
//Set up the STOP button to perform some action
////////////////////////////////////////////////////////////
final Button bStop = (Button) findViewById(R.id.bStop);
bStop.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
Toast.makeText(c, "Stopping Thread", Toast.LENGTH_SHORT).show();
System.out.println("StopApp UI button clicked"); //Just some code for debugging with logcat
timerButler.stopHandler(); //stopTimer();
}
});//End setOnClickListener
////////////////////////////////////////////////////////////
//Set up the SEND button to perform some action
////////////////////////////////////////////////////////////
final Button bSend = (Button) findViewById(R.id.bSend);
bSend.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
Toast.makeText(c, "Sending Location Information", Toast.LENGTH_SHORT).show();
sendMessage.sendMessage();
System.out.println(locationServices.getNmea()); //Just some code for debugging with logcat
}
});//End setOnClickListener
}//End onCreate
}//End MainActivity
LocationServices.java
Code:
package my.testapp;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.widget.TextView;
import java.text.SimpleDateFormat;
public class LocationServices {
//////////////////////////////////////////////////////////////////
//Declare some variables for storing GPS information
//We make these variable static so multiple objects of this class
// will reference the same instance of these variables
//////////////////////////////////////////////////////////////////
private static double dblLatInfo = 0.0, dblLongInfo = 0.0, dblElvInfo = 0.0;
private static String sFormatTime, sNmea = "";
//Declare some TextViews for displaying information
private TextView tvLatInfo, tvLongInfo, tvTime;
//Declare LocationManager and LocationListener for calling the GPS
private LocationManager locationManager;
private LocationListener locationListener;
public void getLocationUpdates(Context c)
{
/////////////////////////////////////////////////////////////////////////////////
//Initialize the TextView elements
//Since this class is a non-activity class, we must cast to MainActivity class
// for these elements, then reference the context passed to the constructor
// from the activity class
/////////////////////////////////////////////////////////////////////////////////
tvLatInfo = (TextView) ((MainActivity)c).findViewById(R.id.tvLatInfo);
tvLongInfo = (TextView) ((MainActivity)c).findViewById(R.id.tvLongInfo);
tvTime = (TextView) ((MainActivity)c).findViewById(R.id.tvTime);
///////////////////////////////////////////////////
//Perform permission checks for location services
///////////////////////////////////////////////////
if (ActivityCompat.checkSelfPermission(c, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(c, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
{
return;
}
////////////////////////////
//Initialize the GPS service
////////////////////////////
locationManager = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE);
/////////////////////////////////////////////
// Instantiate the location listener and
// get location sensor position data
/////////////////////////////////////////////
locationListener = new LocationListener()
{
@SuppressLint({"SimpleDateFormat", "SetTextI18n"})
@Override
//Update TextViews and store location information on Location Change
public void onLocationChanged(Location location)
{
//Format GPS timestamp into human-readable format
sFormatTime = new SimpleDateFormat("HH:mm:ss").format(location.getTime());
//////////////////////////////////////////////////////////
//We only update location and time data from a GPS source
// if the locationListener is using something
// other than GPS, display a warning message and
// nullify the displayed location but retain the
// last location coordinates stored in dbl variables
//////////////////////////////////////////////////////////
if (location.getProvider().equals(LocationManager.GPS_PROVIDER))
{
//Update the textviews on the main screen
tvTime.setText("GPS Time: "+sFormatTime);
tvLatInfo.setText(""+location.getLatitude());
tvLongInfo.setText(""+location.getLongitude());
//Store the current location information
dblLatInfo = location.getLatitude();
dblLongInfo = location.getLongitude();
dblElvInfo = location.getAltitude();
}
else
{
//Update the textviews on the main screen
tvTime.setText("GPS SIGNAL LOST!!!");
tvLatInfo.setText("");
tvLongInfo.setText("");
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
}
@Override
public void onProviderEnabled(String provider)
{
}
@Override
public void onProviderDisabled(String provider)
{
//Start the Location Provider if location is disabled
//Use the context 'c' passed to the constructor for this class
//--commented out-- Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
// --commented out-- c.startActivity();
}
};
//////////////////////////////////////////////////////////////////////////////
//Set location update triggers
//(FromSource, TimeInMilliseconds, DistanceInMeters, CastTo: locationListener)
//////////////////////////////////////////////////////////////////////////////
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 0, locationListener);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, locationListener);
/////////////////////////////////////////////
//Get and store NMEA Sentence Information
/////////////////////////////////////////////
locationManager.addNmeaListener(new GpsStatus.NmeaListener()
{
@Override
public void onNmeaReceived(long timestamp, String nmea)
{
sNmea = "NMEA Scentence: " + nmea;
}
});
}//End getLocaitonUpdates method
/////////////////////////////////////////////////////////////
//Getter methods for accessing information from another class
/////////////////////////////////////////////////////////////
public String getNmea()
{
return sNmea;
}
public double getLat()
{
return dblLatInfo;
}
public double getLong()
{
return dblLongInfo;
}
public double getElv()
{
return dblElvInfo;
}
public String getTime()
{
return sFormatTime;
}
}//End public class LocationServices
SendMessage.java
Code:
package my.testapp;
import android.content.Context;
import android.os.AsyncTask;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class SendMessage
{
//Declare a some variables for a formatted message
private String sMessage, sTime;
private String sNmea;
private double dblLatInfo;
private double dblLongInfo;
private double dblElvInfo;
//Instantiate LocationServices object
LocationServices locationServices = new LocationServices();
//////////////////////////////////////////////////////////
//Support method declared private is visible only to this
//class. Calls getters from LocationServices to create
//the XML formatted message
//////////////////////////////////////////////////////////
private void createMessage()
{
dblLatInfo = locationServices.getLat();
dblLongInfo = locationServices.getLong();
dblElvInfo = locationServices.getElv();
sNmea = locationServices.getNmea();
sTime = locationServices.getTime();
sMessage = "<MESSAGE><ID>My ID</ID><LAT>"+dblLatInfo+"</LAT><LNG>"
+dblLongInfo+"</LNG><ELV>"+dblElvInfo+"</ELV><NMEA>"+sNmea
+"</NMEA><TIME>"+sTime+"</TIME></MESSAGE><EOF>";
}
/////////////////////////////////////////////////////////////
//Calls the createMessage support method.
//Calls the doInBackground method from private class SendThis
// and passes sMessage String as a parameter
/////////////////////////////////////////////////////////////
public void sendMessage()
{
createMessage();
System.out.println("Executing sendMessage()"); //Debugging message
new SendThis().execute(sMessage); //Pass this to doInBackground
}
///////////////////////////////////////////////////////////////////////
//Private Class SendThis sends sMessage via UDP as a background process
///////////////////////////////////////////////////////////////////////
private class SendThis extends AsyncTask<String,Void,Void>
{
@Override
////////////////////////////////////////////////////////////////////
//doInBackground method inherited from AsyncTask accepts one or more
// String objects into 'params' as an ArrayList. Gets passed in
////////////////////////////////////////////////////////////////////
protected Void doInBackground(String... params)
{
//Create instance String variable to hold the message and set value
String sSendMe = params[0];
//Created instance inetAddress variable and initialize
InetAddress inetAddress = null;
//Set the inetAddress value and handle any errors
try
{
inetAddress = InetAddress.getByName("10.0.0.23");
}
catch (UnknownHostException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
//Create int and DatagramSocket variables and initialize
int udpPort=55505;
DatagramSocket udpSocket = null;
//Instantiate the DatagramSocket and handle any errors
try
{
udpSocket = new DatagramSocket();
}
catch (SocketException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
//Convert message into an array of bytes
byte[] btArrData = sSendMe.getBytes();
//Create the datagram packet from the byte array
DatagramPacket dataPac = new DatagramPacket(btArrData, btArrData.length, inetAddress, udpPort);
try
{
//Send the datagram packet
udpSocket.send(dataPac);
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}//End doInBackground
}//End private class SendThis
}//End public class SendMessage
IncomingSms.java
Code:
package my.testapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.telephony.SmsMessage;
///////////////////////////////////////////////////////////
//Create a lister for incoming SMS messages to set up SMS
// control over certain application functions
///////////////////////////////////////////////////////////
public class IncomingSms extends BroadcastReceiver
{
//Declare variable to hold the SMS Originator information and
// the received SMS body
private static String sSmsOriginator;
private static String sSmsBody;
//Instantiate the smsButler object to perform some
// SMS driven functions
private SmsButler smsButler = new SmsButler();
@Override
public void onReceive(Context context, Intent intent)
{
//Use a try-catch statement for error handling
try
{
//Retrieves a map of extended SMS functionality
// Get/Read current incoming SMS
Bundle myBundle = intent.getExtras();
SmsMessage [] messages = null;
String strMessage = "";
if (myBundle != null)
{
//////////////////////////////////////////////////
//Create an object array of messages from 'pdus'
// == a pdu is a Protocol Data Unit, the
// the industry format for an SMS message.
// A large message might be broken into many,
// which is why it is an array of objects.
//////////////////////////////////////////////////
Object [] pdus = (Object[]) myBundle.get("pdus");
///////////////////////////////////////////////////////////////////////
//Deconstruct "messages" objects array and pull the most recent message
// into the currentMessage object so we can deal with only the most
// recent segment of a larger SMS.
///////////////////////////////////////////////////////////////////////
messages = new SmsMessage[pdus.length];
for (int i = 0; i < messages.length; i++)
{
///////////////////////////////////////////////////////
//If message uses newer 3GPP2 formatting for
// CDMA/LTE networks, use the first method to
// handle the message format.
//
// Else use the legacy (depreciated) method for
// handling 3GPP formatted messages
// (GSM/UMTS)
///////////////////////////////////////////////////////
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String format = myBundle.getString("format");
messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i], format);
}
else {
//Line-through indicates depreciated method
// used for handling legacy formats
messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
}
//A simple string builder to concatenate a message to display for debugging
strMessage += "SMS From: " + messages[i].getOriginatingAddress();
strMessage += " : ";
strMessage += messages[i].getMessageBody();
strMessage += "\n";
//Setter method to send a reply
sSmsOriginator = messages[i].getDisplayOriginatingAddress();
sSmsBody = messages[i].getMessageBody();
}//End for loop
System.out.println(strMessage); //Debugging message
fireSmsSender();
//Log.e("SMS", strMessage);
//Toast.makeText(context, strMessage, Toast.LENGTH_SHORT).show();
}//End if statement
}//End try
//Handle any errors that may occur
catch (Exception e)
{
e.printStackTrace();
}//End catch
}//End onReceive method
//Support method for passing information to SmsButler
private void fireSmsSender()
{
smsButler.smsAction(sSmsOriginator,sSmsBody);
}
}//End IncomingSms class
SmsButler.java
Code:
package my.testapp;
import android.content.Context;
import android.telephony.SmsManager;
public class SmsButler
{
//Declare some String variables for this class
private String sSmsOriginator, sSmsReceived, sSmsBody;
//Instantiate an SmsManager to handle SMS responses
private SmsManager smsManager = SmsManager.getDefault();
//Instantiate these objects so we can call their methods.
private TimerButler timerButler = new TimerButler();
private LocationServices locationServices = new LocationServices();
private SendMessage sendMessage = new SendMessage();
////////////////////////////////////////////////////////////////////////////
//Main worker method for this class. Accepts two string arguments from
// the IncomingSms class and makes a determination on what to do based
// on the contents of the message received.
////////////////////////////////////////////////////////////////////////////
public void smsAction (String originator, String received)
{
//Accepts parameters passed from IncomingSms class
sSmsOriginator = originator;
sSmsReceived = received;
//If "#getTime" SMS message is received, get GPS time from
// LocationServices and reply to sender
if (sSmsReceived.compareTo("#getTime") == 0)
{
System.out.println("SMS: #getTime command received");//Debugging message
sSmsBody = locationServices.getTime();
smsManager.sendTextMessage(sSmsOriginator, null, sSmsBody, null, null);
System.out.println("#getTime response sent to: "+sSmsOriginator);//Debugging message
}
//If "#getPos" SMS message is received, get position data
// from LocationServices and reply to sender
else if (sSmsReceived.compareTo("#getPos") == 0)
{
System.out.println("SMS: #getPos command received");//Debugging message
sSmsBody = "\nLat: "+locationServices.getLat()
+"\nLong: "+locationServices.getLong()
+"\nElv: "+locationServices.getElv();
smsManager.sendTextMessage(sSmsOriginator, null, sSmsBody, null, null);
System.out.println("#getPos response sent to: "+sSmsOriginator);//Debugging message
}
//If "#sendPos" SMS message is received, get position data
// from LocationServices and send to upstream server
else if (sSmsReceived.compareTo("#sendPos") == 0)
{
System.out.println("SMS: #sendPos command received");//Debugging message
sendMessage.sendMessage();
smsManager.sendTextMessage(sSmsOriginator, null, "#sendPos acknowledged", null, null);
System.out.println("#sendPos response sent to: "+sSmsOriginator);//Debugging message
}
//If "#startApp" SMS message is received, call timerButler to
// start the timerRunnable process to periodically update upstream server
else if (sSmsReceived.compareTo("#startApp") == 0)
{
System.out.println("SMS: #startApp command received");//Debugging message
timerButler.startHandler();
smsManager.sendTextMessage(sSmsOriginator, null, "#startApp acknowledged", null, null);
System.out.println("#startApp response sent to: "+sSmsOriginator);//Debugging message
}
//If "#stopApp" SMS message is received, call timerButler to
// stop the timerRunnable process
else if (sSmsReceived.compareTo("#stopApp") == 0)
{
System.out.println("SMS: #stopApp command received");//Debugging message
timerButler.stopHandler();
smsManager.sendTextMessage(sSmsOriginator, null, "#stopApp acknowledged", null, null);
System.out.println("#stopApp response sent to: "+sSmsOriginator);//Debugging message
}
//If SMS message does not match a special command string, ignore it
else
{
System.out.println("SMS: Invalid command! -Ignoring");//Debugging message
}
}
}
TimerButler.java
Code:
package my.testapp;
import android.content.Context;
import android.os.AsyncTask;
import android.os.CountDownTimer;
import android.widget.TextView;
import android.os.Handler;
import android.os.Handler.Callback; //I have plans for this later.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static java.lang.Thread.*;
public class TimerButler //--commented out-- extends AsyncTask <Void, Void, Void>
{
//Declare some variables
private static String sAppStarted = "Running";
private static String sAppStopped = "Stopped!";
private static TextView tvAppStatInfo;
//--commented out-- private long startTime = 0;
private int index = 0;
private int index2 = 0;
private static boolean isRunning = false;
private boolean killMe;
//--commented out-- private static Runnable thisRunnable;
//--commented out-- private static String threadID;
//Instantiate a sendMessage object
private static SendMessage sendMessage = new SendMessage();
//Instantiate a new handle for time based operations
private Handler timerHandler = new Handler();
//--commented out-- private ExecutorService executorService = Executors.newSingleThreadExecutor();
//Instantiate and define a runnable thread
private Runnable timerRunnable = new Runnable()
{
@Override
public void run()
{
//Keep updating this value while thread is running
//Prevents startHandler from calling multiple instances of this
isRunning = true;
//Update a TextView to indicate status
if (index < 5)
{
tvAppStatInfo.setText(sAppStarted);
sAppStarted = sAppStarted + ".";
index++;
}
else
{
sAppStarted = "Running";
index = 0;
}
//Call the sendMessage method every 30 seconds to update the upstream server
if (index2 < 29)
{
index2++;
}
else
{
sendMessage.sendMessage();
index2 = 0;
}
timerHandler.postDelayed(this, 1000);
}
};//End runnable
//--commented out-- private Future timerRunnableFuture = executorService.submit(timerRunnable);
//A handler method to start the runnable
public void startHandler()
{
if (isRunning == false) {
tvAppStatInfo = (TextView) ((MainActivity)ContextHandler.getC()).findViewById(R.id.tvAppStatInfo);
timerHandler.postDelayed(timerRunnable, 0);
//--commented out-- thisRunnable = timerRunnable;
//--commented out-- threadID = thisRunnable.toString();
System.out.println("Starting Application");
System.out.println("Thread " + timerRunnable + " started.\n\n");
} else {
System.out.println("Application thread is already running");
}
}
//A handler method to stop the runnable
public void stopHandler()
{
// --commented out-- if (isRunning == true)
//--commented out-- {
tvAppStatInfo = (TextView) ((MainActivity)ContextHandler.getC()).findViewById(R.id.tvAppStatInfo);
//--commented out-- thisThread.interrupt();
timerHandler.removeCallbacks(timerRunnable);
System.out.println("Stopping "+timerRunnable+"\n\n");
tvAppStatInfo.setText(sAppStopped);
isRunning = false;
//--commented out-- }
//--commented out-- else
//--commented out-- {
//--commented out-- System.out.println("Application thread is not running");
//--commented out-- }
}
}
/*===============Leftover code from Other Approaches - some snippets may be useful still======================*/
/*===========================================Limited functionality using Async====================
private static String sAppStarted = "Running";
private static String sAppStopped = "Stopped!";
private static TextView tvAppStatInfo;
private ContextHandler contextHandler = new ContextHandler();
private int index = 0;
private int index2 = 0;
private static boolean isRunning = false;
private Thread thisThread;
private long threadID;
private Context c;
//Instantiate a sendMessage object
private static SendMessage sendMessage = new SendMessage();
@Override
protected Void doInBackground(Void... params)
{
if(!isCancelled())
{
c = contextHandler.getC();
tvAppStatInfo = (TextView) ((MainActivity) c).findViewById(R.id.tvAppStatInfo);
countDownA(c);
thisThread = currentThread();
threadID = thisThread.getId();
System.out.println("Background Thread " + threadID + " started");
}
return null;
}
public void countDownA (Context c)
{
if (isRunning)
{
long lCountDownDuration = 30000;
long lCountDownTick = 1000;
new CountDownTimer(lCountDownDuration - 1000, lCountDownTick) {
@Override
public void onTick(long millisUntilFinished) {
if (index < 5) {
sAppStarted = sAppStarted + ".";
index++;
} else
{
sAppStarted = "Running";
index = 0;
}
tvAppStatInfo.setText(sAppStarted);
}
@Override
public void onFinish() {
tvAppStatInfo.setText("Sending!");
sendMessage.sendMessage();
countDownKickStart();
}
}.start();
}
}
private void countDownKickStart ()
{
long lCountDownKickstart = 1000;
long lKickstartTick = 1000;
new CountDownTimer(lCountDownKickstart, lKickstartTick)
{
@Override
public void onTick(long millisUntilFinished)
{
//Method is inherited abstract so we must give it something to do
byte aByte;
}
@Override
public void onFinish()
{
if(isRunning) {
sAppStarted = "Running";
countDownA(c);
}
else
{
tvAppStatInfo.setText("Stopped!");
}
}
}.start();
}
public void startTimer()
{
isRunning = true;
cancel(false);
System.out.println("isCanceled "+isCancelled());
doInBackground();
}
public void stopTimer()
{
cancel(true);
isRunning = false;
System.out.println("Stop Command received");
System.out.println("icCanceled"+isCancelled());
} */
/*====================working with limited functionality... does NOT extend Async===========================
//Declare some variables
private static String sAppStarted = "Running";
private static String sAppStopped = "Stopped!";
private static TextView tvAppStatInfo;
private long startTime = 0;
private int index = 0;
private int index2 = 0;
private static boolean isRunning = false;
private boolean killMe;
private Thread thisThread;
private static long threadID;
private ContextHandler contextHandler = new ContextHandler();
//Instantiate a sendMessage object
private static SendMessage sendMessage = new SendMessage();
//Instantiate a new handle for time based operations
private Handler timerHandler = new Handler();
//private ExecutorService executorService = Executors.newSingleThreadExecutor();
//Instantiate and define a runnable thread
private Runnable timerRunnable = new Runnable() {
@Override
public void run() {
if (killMe = true);
{
timerHandler.removeCallbacks(timerRunnable);
isRunning =false;
}
isRunning = true;
//Update a TextView to indicate status
if (index < 5) {
tvAppStatInfo.setText(sAppStarted);
sAppStarted = sAppStarted + ".";
index++;
} else {
sAppStarted = "Running";
index = 0;
}
//Call the sendMessage method every 30 seconds
if (index2 < 29) {
index2++;
} else {
sendMessage.sendMessage();
index2 = 0;
}
timerHandler.postDelayed(this, 1000);
}
};//End runnable
// private Future timerRunnableFuture = executorService.submit(timerRunnable);
//A method to start the handler thread. Accepts a Context passed from MainActivity call
// so that this non-activity class can update the TextViews
public void startHandler(Context c)
{
killMe = false;
if (isRunning == false)
{
tvAppStatInfo = (TextView) ((MainActivity)c).findViewById(R.id.tvAppStatInfo);
timerHandler.postDelayed(timerRunnable, 0);
thisThread = currentThread();
threadID = thisThread.getId();
System.out.println("Thread "+threadID+" started.");
System.out.println("Starting Application");
}
else
{
System.out.println ("Application thread is already running");
}
}
public void stopHandler(Context c)
{
if (isRunning == true)
{
tvAppStatInfo = (TextView) ((MainActivity)c).findViewById(R.id.tvAppStatInfo);
//thisThread.interrupt();
timerHandler.removeCallbacks(timerRunnable);
System.out.println ("Stopping Application");
tvAppStatInfo.setText(sAppStopped);
isRunning = false;
killMe = true;
}
else
{
System.out.println ("Applicaiton thread is not running");
}
}
=======================================end limited functionality solutions =============*/
ContextHandler.java
Code:
package my.testapp;
import android.content.Context;
//A simple class for passing the context from Main in situations where
// it would be impossible to pass the context in directly
public class ContextHandler
{
//This is declared 'static' since we will be passing the context in during
// the call from MainActivity and passing the context out to other non-activity classes.
private static Context C;
public static Context getC()
{
return C;
}
public static void setC(Context c)
{
C = c;
}
}
****LogCat Output****
05-09 08:05:42.731 5037-5037/my.testapp I/Adreno-EGL: <qeglDrvAPI_eglInitialize:320>: EGL 1.4 QUALCOMM build: (CL3776187)
OpenGL ES Shader Compiler Version:
Build Date: 10/15/13 Tue
Local Branch:
Remote Branch: partner/upstream
Local Patches:
Reconstruct Branch:
05-09 08:05:42.771 5037-5037/my.testapp D/OpenGLRenderer: Enabling debug mode 0
05-09 08:05:42.922 5037-5037/my.testapp I/ActivityManager: Timeline: Activity_idle id: android.os.BinderProxy@418a9048 time:330054410
==I started the runnable by clicking the UI button=
05-09 08:05:58.377 5037-5037/my.testapp I/System.out: StartApp UI button clicked
05-09 08:05:58.377 5037-5037/my.testapp I/System.out: Starting Application
05-09 08:05:58.387 5037-5037/my.testapp I/System.out: Thread my.testapp.TimerButler$1@41915678 started.
==I clicked the UI start button a second time to verify some code I wrote to prevent multiple
instances of the runnable==
05-09 08:06:03.121 5037-5037/my.testapp I/System.out: StartApp UI button clicked
05-09 08:06:03.121 5037-5037/my.testapp I/System.out: Application thread is already running
==I clicked the UI stop button to stop the runnable==
==Just take my work for it this worked as expected given the order of events thus far==
05-09 08:06:08.066 5037-5037/my.testapp I/System.out: StopApp UI button clicked
05-09 08:06:08.066 5037-5037/my.testapp I/System.out: Stopping my.testapp.TimerButler$1@41915678
==I started the runnable this time using an SMS command==
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: SMS From: 5552221005 : #startApp
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: SMS: #startApp command received
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: Starting Application
==The app runnable thread started as expected and the app appears to function normally==
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: Thread my.testapp.TimerButler$1@41aa23e8 started.
05-09 08:06:51.118 5037-5037/my.testapp I/System.out: #startApp response sent to: 5552221005
==This is another debug statement that indicates the sentMessage method was called==
==In this case it was triggered by they runnable. Evidence the program is executing==
05-09 08:07:20.196 5037-5037/my.testapp I/System.out: Executing sendMessage()
==Here I sent another SMS command to stop the runnable==
==It responds and for about a split second the indicator on the UI I have set up
shows the app stopped, however within no-time, it retarts itself and the app keeps on running==
05-09 08:07:25.822 5037-5037/my.testapp I/System.out: SMS From: 5552221005 : #stopApp
05-09 08:07:25.822 5037-5037/my.testapp I/System.out: SMS: #stopApp command received
05-09 08:07:25.822 5037-5037/my.testapp I/System.out: Stopping my.testapp.TimerButler$1@41baaf20
05-09 08:07:25.832 5037-5037/my.testapp I/System.out: #stopApp response sent to: 5552221005
==The screen went to sleep here triggering this message==
05-09 08:07:41.357 5037-5037/my.testapp I/ActivityManager: Timeline: Activity_idle id: android.os.BinderProxy@418a9048 time:330172848
==Remember the last thing I did was try to stop the runnable thread that was initially
started with an SMS command. Here I press the UI start button==
==The app responds with the appropriate response considering the runnable never
actually stopped==
05-09 08:07:47.653 5037-5037/my.testapp I/System.out: StartApp UI button clicked
05-09 08:07:47.653 5037-5037/my.testapp I/System.out: Application thread is already running
==Every 30 seconds the runnable calls the sendMessage==
05-09 08:07:50.346 5037-5037/my.testapp I/System.out: Executing sendMessage()
==This is an attempt to stop the runnable using the UI however when
runnable is stared in response to an SMS command this becomes uneffective as well==
==Remember: This works when the runnable is trigger by the UI start button - no problems==
05-09 08:07:55.090 5037-5037/my.testapp I/System.out: StopApp UI button clicked
05-09 08:07:55.090 5037-5037/my.testapp I/System.out: Stopping my.testapp.TimerButler$1@41915678
==Just to verify the runnable is running in spite of being instructed to stop==
05-09 08:08:08.303 5037-5037/my.testapp I/System.out: StartApp UI button clicked
==Note the reponse==
05-09 08:08:08.303 5037-5037/my.testapp I/System.out: Application thread is already running
==And the fact the method continues to be called every 30 seconds==
05-09 08:08:20.445 5037-5037/my.testapp I/System.out: Executing sendMessage()
05-09 08:08:50.545 5037-5037/my.testapp I/System.out: Executing sendMessage()
==An additional SMS start command encounters the same logic and returns
an appropriate response for when runnable is running==
05-09 08:09:16.009 5037-5037/my.testapp I/System.out: SMS From: 5552221005 : #startApp
05-09 08:09:16.009 5037-5037/my.testapp I/System.out: SMS: #startApp command received
==Note the response
05-09 08:09:16.009 5037-5037/my.testapp I/System.out: Application thread is already running
05-09 08:09:16.009 5037-5037/my.testapp I/System.out: #startApp response sent to: 5552221005
==And again the app continues to run==
05-09 08:09:20.704 5037-5037/my.testapp I/System.out: Executing sendMessage()
Whew!!! That was alot and yes I know I'm a shitty programmer but I'm trying to learn so cut me some slack.
If someone can nudge me in the right direction here I'll buy you an e-Beer!:beer:
Thanks!!
-JR
Last edited: