commit c87cf5b08d59c41b9c25c73bb9cc3b1db754d7cd Author: Anton Tananaev Date: Sun Jul 29 19:47:00 2012 +0400 Initial release diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..3db264f --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/ant.properties b/ant.properties new file mode 100644 index 0000000..b0971e8 --- /dev/null +++ b/ant.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked into Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..bbe6c90 --- /dev/null +++ b/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..7845bfb --- /dev/null +++ b/icon.svg @@ -0,0 +1,135 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icon512.png b/icon512.png new file mode 100644 index 0000000..fe37b28 Binary files /dev/null and b/icon512.png differ diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..450fd33 --- /dev/null +++ b/local.properties @@ -0,0 +1,10 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. + +# location of the SDK. This is only used by Ant +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=/home/user/Documents/android-sdk-linux diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..9b84a6b --- /dev/null +++ b/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-16 diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..0cdb54e Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 0000000..e479a8c Binary files /dev/null and b/res/drawable-ldpi/ic_launcher.png differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..0bb5d06 Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..3e05eb5 Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ diff --git a/res/layout/main.xml b/res/layout/main.xml new file mode 100644 index 0000000..8df7a59 --- /dev/null +++ b/res/layout/main.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/menu/main.xml b/res/menu/main.xml new file mode 100644 index 0000000..c5391c5 --- /dev/null +++ b/res/menu/main.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..1070fbe --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,8 @@ + + + Traccar Client + Invalid input parameters. + About + Android software GPS tracker. For more information visit:\n\nhttp://www.traccar.org + Close + diff --git a/src/org/traccar/client/Connection.java b/src/org/traccar/client/Connection.java new file mode 100644 index 0000000..6c7e536 --- /dev/null +++ b/src/org/traccar/client/Connection.java @@ -0,0 +1,153 @@ +/* + * Copyright 2012 Anton Tananaev (anton.tananaev@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.client; + +import java.io.*; +import java.net.*; +import java.util.*; +import android.os.Handler; +import android.content.Intent; +import android.content.Context; + +/** + * Connection with autoreconnect + */ +public class Connection { + + private String address; + private int port; + + private String connectMessage; + + private Socket socket; + private OutputStream socketStream; + + private String status; + + private Context context; + + /** + * Initialize connection parameters + */ + public Connection(String address, int port, String message) { + this.address = address; + this.port = port; + connectMessage = message; + setStatus(Traccar.STATUS_DISCONNECTED); + } + + /** + * Get address + */ + public String getAddress() { + return address; + } + + /** + * Get port + */ + public int getPort() { + return port; + } + + /** + * Set context + */ + public void setContext(Context context) { + this.context = context; + } + + /** + * Set connection status + */ + private void setStatus(String status) { + if (!status.equals(this.status)) { + this.status = status; + if (context != null) { + Intent intent = new Intent(Traccar.MSG_CONNECTION_STATUS); + intent.putExtra(Traccar.EXTRA_STATUS, status); + context.sendBroadcast(intent); + } + } + } + + /** + * Get connection status + */ + public String getStatus() { + return status; + } + + /** + * Establish connection + */ + public void connect() { + try { + socket = new Socket(address, port); + socket.setSoTimeout(Traccar.SOCKET_TIMEOUT); + socketStream = socket.getOutputStream(); + setStatus(Traccar.STATUS_CONNECTED); + send(connectMessage); + } catch (Exception e) { + reconnect(); + } + } + + /** + * Send message + */ + public void send(String message) { + try { + if (socketStream != null) { + socketStream.write(message.getBytes()); + socketStream.flush(); + } + } catch (Exception e) { + reconnect(); + } + } + + /** + * Close connection + */ + public void close() { + handler.removeCallbacks(task); + if (socket != null) { + try { + socket.close(); + } catch (Exception e) { + } + socket = null; + } + socketStream = null; + setStatus(Traccar.STATUS_DISCONNECTED); + } + + private void reconnect() { + close(); + handler.postDelayed(task, Traccar.RECONNECT_DELAY); + } + + private Handler handler = new Handler(); + private ReconnectTask task = new ReconnectTask(); + + class ReconnectTask implements Runnable { + @Override + public void run() { + connect(); + } + } +} diff --git a/src/org/traccar/client/Protocol.java b/src/org/traccar/client/Protocol.java new file mode 100644 index 0000000..0ec52ab --- /dev/null +++ b/src/org/traccar/client/Protocol.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012 Anton Tananaev (anton.tananaev@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.client; + +import java.util.Formatter; +import android.location.Location; + +/** + * Protocol formatting + */ +public class Protocol { + + /** + * Format device id message + */ + public static String createId(String id) { + StringBuilder s = new StringBuilder("$PGID,"); + Formatter f = new Formatter(s); + + s.append(id); + + byte checksum = 0; + for (byte b : s.substring(1).getBytes()) { + checksum ^= b; + } + f.format("*%02x\r\n", (int) checksum); + + return s.toString(); + } + + /** + * Format location message + */ + public static String createNmea(Location l) { + StringBuilder s = new StringBuilder("$GPRMC,"); + Formatter f = new Formatter(s); + + f.format("%1$tH%1$tM%1$tS.%1$tL,A,", l.getTime()); + + double lat = l.getLatitude(); + double lon = l.getLongitude(); + f.format("%02d%07.4f,%c,", (int) Math.abs(lat), Math.abs(lat) % 1 * 60, lat < 0 ? 'S' : 'N' ); + f.format("%03d%07.4f,%c,", (int) Math.abs(lon), Math.abs(lon) % 1 * 60, lon < 0 ? 'W' : 'E' ); + + f.format("%.2f,%.2f,", l.getSpeed(), l.getBearing()); + f.format("%1$td%1$tm%1$ty,,", l.getTime()); + + byte checksum = 0; + for (byte b : s.substring(1).getBytes()) { + checksum ^= b; + } + f.format("*%02x\r\n", (int) checksum); + + return s.toString(); + } +} + diff --git a/src/org/traccar/client/TraccarActivity.java b/src/org/traccar/client/TraccarActivity.java new file mode 100644 index 0000000..0f43901 --- /dev/null +++ b/src/org/traccar/client/TraccarActivity.java @@ -0,0 +1,209 @@ +/* + * Copyright 2012 Anton Tananaev (anton.tananaev@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.client; + +import java.util.*; +import android.app.*; +import android.content.*; +import android.os.*; +import android.text.*; +import android.text.method.*; +import android.text.util.*; +import android.view.*; +import android.widget.*; + +/** + * Global parameters + */ +class Traccar { + public static final String EXTRA_ADDRESS = "address"; + public static final String EXTRA_PORT = "port"; + public static final String EXTRA_PERIOD = "period"; + public static final String EXTRA_STATUS = "status"; + + public static final String DEFAULT_ADDRESS = "193.193.165.166"; + public static final String DEFAULT_PORT = "20100"; + public static final String DEFAULT_PERIOD = "60"; + + public static final String TRACCAR_SETTINGS = "traccar_settings"; + + public static final long RECONNECT_DELAY = 10 * 1000; + public static final int SOCKET_TIMEOUT = 2000; + + public static final String MSG_UPDATE = "org.traccar.client.MSG_UPDATE"; + public static final String MSG_SERVICE_CONFIG = "org.traccar.client.MSG_SERVICE_CONFIG"; + public static final String MSG_SERVICE_STATUS = "org.traccar.client.SERVICE_STATUS"; + public static final String MSG_CONNECTION_STATUS = "org.traccar.client.CONNECTION_STATUS"; + + public static final String STATUS_STOPPED = "Stopped"; + public static final String STATUS_RUNNING = "Running"; + + public static final String STATUS_CONNECTED = "Connected"; + public static final String STATUS_DISCONNECTED = "Disconnected"; +} + +/** + * Main user interface + */ +public class TraccarActivity extends Activity { + + private EditText editAddress; + private EditText editPort; + private EditText editPeriod; + private EditText editService; + private EditText editConnection; + private ToggleButton button; + + public boolean isServiceRunning() { + ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + List services = activityManager.getRunningServices(Integer.MAX_VALUE); + for (ActivityManager.RunningServiceInfo runningServiceInfo : services) { + if (runningServiceInfo.service.getClassName().equals(TraccarService.class.getName())){ + return true; + } + } + return false; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + // Bind elements + editAddress = (EditText) findViewById(R.id.edit_address); + editPort = (EditText) findViewById(R.id.edit_port); + editPeriod = (EditText) findViewById(R.id.edit_period); + editService = (EditText) findViewById(R.id.edit_service); + editConnection = (EditText) findViewById(R.id.edit_connection); + button = (ToggleButton) findViewById(R.id.button); + + editService.setText(Traccar.STATUS_STOPPED); + editConnection.setText(Traccar.STATUS_DISCONNECTED); + button.setChecked(isServiceRunning()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + //super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId() == R.id.about){ + showAboutDialog(); + return true; + } + return false; + } + + public void showAboutDialog() { + TextView message = new TextView(this); + SpannableString str = new SpannableString(getString(R.string.about_text)); + Linkify.addLinks(str, Linkify.WEB_URLS); + message.setText(str); + message.setMovementMethod(LinkMovementMethod.getInstance()); + int padding = (int) (10 * getResources().getDisplayMetrics().scaledDensity); + message.setPadding(padding, padding, padding, padding); + + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle(R.string.about_option); + dialog.setView(message); + dialog.setPositiveButton(R.string.about_close, null); + dialog.show(); + } + + private BroadcastReceiver serviceConfigReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + editAddress.setText(intent.getStringExtra(Traccar.EXTRA_ADDRESS)); + editPort.setText(String.valueOf(intent.getIntExtra(Traccar.EXTRA_PORT, 0))); + editPeriod.setText(String.valueOf(intent.getIntExtra(Traccar.EXTRA_PERIOD, 0))); + } + }; + + private BroadcastReceiver serviceStatusReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + editService.setText(intent.getStringExtra(Traccar.EXTRA_STATUS)); + } + }; + + private BroadcastReceiver connectionStatusReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + editConnection.setText(intent.getStringExtra(Traccar.EXTRA_STATUS)); + } + }; + + @Override + protected void onStart() { + super.onStart(); + loadSettings(); + + // Register receivers + registerReceiver(serviceConfigReceiver, new IntentFilter(Traccar.MSG_SERVICE_CONFIG)); + registerReceiver(serviceStatusReceiver, new IntentFilter(Traccar.MSG_SERVICE_STATUS)); + registerReceiver(connectionStatusReceiver, new IntentFilter(Traccar.MSG_CONNECTION_STATUS)); + + // Request update + sendBroadcast(new Intent(Traccar.MSG_UPDATE)); + } + + @Override + protected void onStop() { + super.onStop(); + saveSettings(); + + // Unregister receivers + unregisterReceiver(serviceConfigReceiver); + unregisterReceiver(serviceStatusReceiver); + unregisterReceiver(connectionStatusReceiver); + } + + public void onButtonClick(View v) { + if (button.isChecked()) { + try { + Intent serviceIntent = new Intent(this, TraccarService.class); + serviceIntent.putExtra(Traccar.EXTRA_ADDRESS, editAddress.getText().toString()); + serviceIntent.putExtra(Traccar.EXTRA_PORT, Integer.parseInt(editPort.getText().toString())); + serviceIntent.putExtra(Traccar.EXTRA_PERIOD, Integer.parseInt(editPeriod.getText().toString())); + startService(serviceIntent); + } catch (NumberFormatException e) { + Toast.makeText(this, R.string.error_input, Toast.LENGTH_SHORT).show(); + button.setChecked(false); + } + } else { + stopService(new Intent(this, TraccarService.class)); + } + } + + public void saveSettings() { + SharedPreferences settings = getSharedPreferences(Traccar.TRACCAR_SETTINGS, 0); + SharedPreferences.Editor editor = settings.edit(); + editor.putString(Traccar.EXTRA_ADDRESS, editAddress.getText().toString()); + editor.putString(Traccar.EXTRA_PORT, editPort.getText().toString()); + editor.putString(Traccar.EXTRA_PERIOD, editPeriod.getText().toString()); + editor.commit(); + } + + public void loadSettings() { + SharedPreferences settings = getSharedPreferences(Traccar.TRACCAR_SETTINGS, 0); + editAddress.setText(settings.getString(Traccar.EXTRA_ADDRESS, Traccar.DEFAULT_ADDRESS)); + editPort.setText(settings.getString(Traccar.EXTRA_PORT, Traccar.DEFAULT_PORT)); + editPeriod.setText(settings.getString(Traccar.EXTRA_PERIOD, Traccar.DEFAULT_PERIOD)); + } +} diff --git a/src/org/traccar/client/TraccarService.java b/src/org/traccar/client/TraccarService.java new file mode 100644 index 0000000..26fa7e3 --- /dev/null +++ b/src/org/traccar/client/TraccarService.java @@ -0,0 +1,141 @@ +/* + * Copyright 2012 Anton Tananaev (anton.tananaev@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.client; + +import java.io.*; +import java.net.*; +import java.util.*; +import android.app.Service; +import android.content.*; +import android.location.*; +import android.os.*; +import android.telephony.TelephonyManager; +import android.widget.Toast; + +/** + * Background service + */ +public class TraccarService extends Service implements LocationListener { + + private int period; + private String deviceId; + private Connection connection; + private LocationManager locationManager; + + /* + * Location methods + */ + + @Override + public void onLocationChanged(Location location) { + connection.send(Protocol.createNmea(location)); + } + + @Override + public void onProviderDisabled(String provider) { + } + + @Override + public void onProviderEnabled(String provider) { + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + /* + * Status methods + */ + + private String status; + + private void setStatus(String status) { + if (!status.equals(this.status)) { + this.status = status; + Intent intent = new Intent(Traccar.MSG_SERVICE_STATUS); + intent.putExtra(Traccar.EXTRA_STATUS, status); + sendBroadcast(intent); + } + } + + private BroadcastReceiver updateReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + // Send config + intent = new Intent(Traccar.MSG_SERVICE_CONFIG); + intent.putExtra(Traccar.EXTRA_ADDRESS, connection.getAddress()); + intent.putExtra(Traccar.EXTRA_PORT, connection.getPort()); + intent.putExtra(Traccar.EXTRA_PERIOD, period); + sendBroadcast(intent); + + // Send service status + intent = new Intent(Traccar.MSG_SERVICE_STATUS); + intent.putExtra(Traccar.EXTRA_STATUS, status); + sendBroadcast(intent); + + // Send connection status + intent = new Intent(Traccar.MSG_CONNECTION_STATUS); + intent.putExtra(Traccar.EXTRA_STATUS, connection.getStatus()); + sendBroadcast(intent); + } + }; + + /* + * Service methods + */ + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // Read device id + TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); + deviceId = telephonyManager.getDeviceId(); + + // Create connection + connection = new Connection( + intent.getStringExtra(Traccar.EXTRA_ADDRESS), + intent.getIntExtra(Traccar.EXTRA_PORT, 0), + Protocol.createId(deviceId)); + connection.setContext(this); + connection.connect(); + + // Request location updates + locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + period = intent.getIntExtra(Traccar.EXTRA_PERIOD, 0); + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, period * 1000, 0, this); + + // Report status + registerReceiver(updateReceiver, new IntentFilter(Traccar.MSG_UPDATE)); + setStatus(Traccar.STATUS_RUNNING); + return START_STICKY; + } + + @Override + public void onDestroy() { + // Close connection + connection.close(); + + // Stop location updates + locationManager.removeUpdates(this); + + // Report status + unregisterReceiver(updateReceiver); + setStatus(Traccar.STATUS_STOPPED); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +}