diff --git a/GPSTracker/.gitignore b/GPSTracker/.gitignore new file mode 100644 index 0000000..d794100 --- /dev/null +++ b/GPSTracker/.gitignore @@ -0,0 +1,29 @@ +# Created by .ignore support plugin (hsz.mobi) +### Android template + +# Generated files +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +# Log Files +# Android Studio Navigation editor temp files +# Android Studio captures folder +# Intellij +*.iml +.idea/workspace.xml +.idea/gradle.xml +.idea/dictionaries +.idea/libraries + +# Keystore files +# Uncomment the following line if you do not want to check your keystore files in. +#*.jks + +# External native build folder generated in Android Studio 2.2 and later +# Google Services (e.g. APIs or Firebase) +# Freeline diff --git a/GPSTracker/.idea/compiler.xml b/GPSTracker/.idea/compiler.xml new file mode 100644 index 0000000..290e589 --- /dev/null +++ b/GPSTracker/.idea/compiler.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GPSTracker/.idea/copyright/profiles_settings.xml b/GPSTracker/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/GPSTracker/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/GPSTracker/.idea/encodings.xml b/GPSTracker/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/GPSTracker/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/GPSTracker/.idea/inspectionProfiles/Project_Default.xml b/GPSTracker/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..f3f7314 --- /dev/null +++ b/GPSTracker/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,35 @@ + + + + \ No newline at end of file diff --git a/GPSTracker/.idea/inspectionProfiles/profiles_settings.xml b/GPSTracker/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/GPSTracker/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/GPSTracker/.idea/misc.xml b/GPSTracker/.idea/misc.xml new file mode 100644 index 0000000..c1129a9 --- /dev/null +++ b/GPSTracker/.idea/misc.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + Android + + + + + + + + + + + + + + + + + + + + + + C:\Users\Vadim\AppData\Roaming\Subversion + + + + + + + + + + + + + \ No newline at end of file diff --git a/GPSTracker/.idea/modules.xml b/GPSTracker/.idea/modules.xml new file mode 100644 index 0000000..11241f4 --- /dev/null +++ b/GPSTracker/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/GPSTracker/.idea/runConfigurations.xml b/GPSTracker/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/GPSTracker/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/GPSTracker/app/.gitignore b/GPSTracker/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/GPSTracker/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/GPSTracker/app/build.gradle b/GPSTracker/app/build.gradle new file mode 100644 index 0000000..35e5667 --- /dev/null +++ b/GPSTracker/app/build.gradle @@ -0,0 +1,46 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.2" + defaultConfig { + applicationId "ru.spbau.farutin_solikov.gpstracker" + minSdkVersion 16 + //noinspection OldTargetApi + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_1_7 + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + + compile 'com.android.support:appcompat-v7:26.0.0' + compile 'com.android.support:design:26.0.0' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.android.support:support-compat:26.0.0' + compile 'com.android.support:support-v4:26.0.0' + compile 'com.android.support:support-vector-drawable:26.0.0' + + compile 'com.google.android.gms:play-services-maps:11.0.4' + compile 'com.getbase:floatingactionbutton:1.10.1' + compile 'com.google.maps.android:android-maps-utils:0.4' + + testCompile 'junit:junit:4.12' +} diff --git a/GPSTracker/app/libs/mysql-connector-java-5.0.8-bin.jar b/GPSTracker/app/libs/mysql-connector-java-5.0.8-bin.jar new file mode 100644 index 0000000..0170c3e Binary files /dev/null and b/GPSTracker/app/libs/mysql-connector-java-5.0.8-bin.jar differ diff --git a/GPSTracker/app/proguard-rules.pro b/GPSTracker/app/proguard-rules.pro new file mode 100644 index 0000000..f68ab60 --- /dev/null +++ b/GPSTracker/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\Vadim\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# 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 *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/GPSTracker/app/src/androidTest/java/ru/spbau/farutin_solikov/gpstracker/ExampleInstrumentedTest.java b/GPSTracker/app/src/androidTest/java/ru/spbau/farutin_solikov/gpstracker/ExampleInstrumentedTest.java new file mode 100644 index 0000000..634da12 --- /dev/null +++ b/GPSTracker/app/src/androidTest/java/ru/spbau/farutin_solikov/gpstracker/ExampleInstrumentedTest.java @@ -0,0 +1,31 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("ru.spbau.farutin_solikov.gpstracker", appContext.getPackageName()); + } + + @Test + public void testCheckID() { + assertEquals(DBManager.isValidDeviceId("11111111"), true); + } +} diff --git a/GPSTracker/app/src/main/AndroidManifest.xml b/GPSTracker/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6c20df7 --- /dev/null +++ b/GPSTracker/app/src/main/AndroidManifest.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/AlarmActivity.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/AlarmActivity.java new file mode 100644 index 0000000..8945875 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/AlarmActivity.java @@ -0,0 +1,128 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.v4.app.NotificationCompat; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +/** + * Alarm mode - notifying user once vehicle has changed position. + */ +public class AlarmActivity extends DrawerActivity { + private static final int ALARM_NOTIFICATION_ID = 1; + + private Controller.AlarmCoordinatesReceiver receiver; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.content_alarm); + setUpButtons(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + try { + unregisterReceiver(receiver); + } catch (IllegalArgumentException ignored) { + // no API methods to tell if it is registered at the moment + } + } + + /** + * Notifies user and changes layout content. + * + * @param position position where vehicle was moved to + */ + public void positionChanged(final Coordinate position) { + notifyUser(); + + Button track = findViewById(R.id.alarm_button); + track.setText(R.string.title_button_track_position); + track.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + closeNotification(); + + Intent intent = new Intent(AlarmActivity.this, TrackerActivity.class); + intent.putExtra(getString(R.string.extra_position), position); + startActivity(intent); + } + }); + + TextView message = findViewById(R.id.alarm_message); + message.setVisibility(View.VISIBLE); + } + + private void notifyUser() { + if (Controller.notificationsOn(this)) { + NotificationCompat.Builder builder = + new NotificationCompat.Builder(AlarmActivity.this, getString(R.string.alarm_channel_id)) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle(getString(R.string.title_alarm_notification)); + + Intent resultIntent = new Intent(AlarmActivity.this, AlarmActivity.class); + PendingIntent resultPendingIntent = + PendingIntent.getActivity( + AlarmActivity.this, + 0, + resultIntent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + builder.setContentIntent(resultPendingIntent); + builder.setAutoCancel(true); + + NotificationManager notifyManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + assert notifyManager != null; + // IDE тут "жаловался" потому что теоретически `notifyManager` может содержать `null`, + // один из способов его успокоить это написать `assert`. + // Лучше, конечно, как-то обрабобтать эту ситуацию, но это не всегда возможно. + notifyManager.notify(ALARM_NOTIFICATION_ID, builder.build()); + } + } + + private void closeNotification() { + NotificationManager notifyManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + assert notifyManager != null; + notifyManager.cancel(ALARM_NOTIFICATION_ID); + } + + private void setUpButtons() { + final Button alarmButton = findViewById(R.id.alarm_button); + alarmButton.setText(R.string.title_button_turn_on); + alarmButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Controller.startCoordinatesService(AlarmActivity.this); + + alarmButton.setText(R.string.title_button_turn_off); + alarmButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + closeNotification(); + setUpButtons(); + + Controller.stopCoordinatesService(); + + try { + unregisterReceiver(receiver); + } catch (IllegalArgumentException ignored) { + // no API methods to tell if it is registered at the moment + } + } + }); + + receiver = new Controller.AlarmCoordinatesReceiver(AlarmActivity.this); + IntentFilter intentSFilter = new IntentFilter(getString(R.string.broadcast_content_coordinates)); + registerReceiver(receiver, intentSFilter); + } + }); + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/AppCompatPreferenceActivity.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/AppCompatPreferenceActivity.java new file mode 100644 index 0000000..ec9f4c7 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/AppCompatPreferenceActivity.java @@ -0,0 +1,105 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls + * to be used with AppCompat. + */ +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + @NonNull + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Controller.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Controller.java new file mode 100644 index 0000000..966ff77 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Controller.java @@ -0,0 +1,278 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.widget.Toast; + +import com.google.android.gms.maps.GoogleMap; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static android.content.Context.MODE_PRIVATE; + +/** + * Class with supporting methods. + */ +public class Controller { + /** + * SharedPreferences filename. + */ + public static final String PREF_FILE = "prefs"; + + /** + * Starts CoordinateService. + * + * @param context context to enqueue with + */ + public static void startCoordinatesService(Context context) { + CoordinatesService.enqueueWork(context, new Intent()); + } + + /** + * Stops CoordinateService. + */ + public static void stopCoordinatesService() { + CoordinatesService.stop(); + } + + /** + * Returns stored user id + * + * @param context context with SharedPreferences + * @return stored deviceId + */ + public static String getUserID(Context context) { + SharedPreferences sharedPreferences = context.getSharedPreferences(PREF_FILE, MODE_PRIVATE); + return sharedPreferences.getString(context.getString(R.string.preference_user_id), ""); + } + + /** + * Fetches new coordinates from the database. + * + * @param id id of the coordinate after which should take new coordinates + * @return new coordinates + */ + public static List fetchCoordinates(int id) { + return DBManager.fetchCoordinates(id); + } + + /** + * Fetches all displacements from the database. + * + * @return all displacements + */ + public static List fetchDisplacements() { + return DBManager.fetchDisplacements(); + } + + /** + * Deletes all rows from table. + */ + public static void clearTable() { + DBManager.clearTable(); + } + + /** + * Saves route to the database. + * + * @param route route to save + * @param name route name + */ + // (?) Почему данные получаете через сервис, а отправляете напрямую? + // + // Сервис нужен, чтобы данные считывались постоянно после запуска и до его отключения + // (причем не в главном потоке), а отправить нужно единожды. + public static void sendCoordinates(List route, String name) { + DBManager.sendCoordinates(route, name); + } + + /** + * Checks whether id is correct or not. + * + * @param deviceId input id + * @return true if id has correct format and exists, false otherwise + */ + public static boolean checkDeviceId(String deviceId) { + //For testing + //return deviceId.length() % 5 == 4 && DBManager.isValidDeviceId(deviceId); + return DBManager.isValidDeviceId(deviceId); + } + + /** + * Checks whether user has already logged in or not. + * + * @param context context with SharedPreferences + * @return true if user has already logged in, false otherwise + */ + public static boolean userLoggedIn(Context context) { + SharedPreferences sharedPreferences = context.getSharedPreferences(PREF_FILE, MODE_PRIVATE); + return sharedPreferences.getString(context.getString(R.string.preference_user_id), "").length() > 0; + } + + /** + * Checks whether notifications are turn on or not. + * + * @param context context with SharedPreferences + * @return true if notifications are turn on, false otherwise + */ + public static boolean notificationsOn(Context context) { + SharedPreferences sharedPreferences = context.getSharedPreferences(PREF_FILE, MODE_PRIVATE); + return sharedPreferences.getString(context.getString(R.string.preference_notifications_new_message), "true").equals("true"); + } + + /** + * Saves device id. + * + * @param context context with SharedPreferences + * @param deviceId id to save + */ + public static void saveUserDeviceId(Context context, String deviceId) { + DBManager.setDeviceId(deviceId); + + SharedPreferences sharedPreferences = context.getSharedPreferences(PREF_FILE, MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(context.getString(R.string.preference_user_id), deviceId); + editor.apply(); + } + + /** + * Makes snapshot of map with route and builds intent with it to send. + * + * @param instance activity from which snapshot is sending + * @param map map to send + * @param routeName route name + */ + public static void sendSnapshot(final RouteActivity instance, GoogleMap map, final String routeName) { + GoogleMap.SnapshotReadyCallback callback = new GoogleMap.SnapshotReadyCallback() { + // зачем заводить поле для этого? + //Bitmap bitmap; + // fixed + + @Override + public void onSnapshotReady(Bitmap snapshot) { + try { + File outputDir = new File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), + instance.getString(R.string.app_name)); + + if (!outputDir.exists()) { + outputDir.mkdir(); + } + + File outputFile = new File(outputDir, routeName + ".png"); + FileOutputStream fileOutputStream = new FileOutputStream(outputFile); + + snapshot.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream); + MediaScannerConnection.scanFile(instance, + new String[]{outputFile.getPath()}, + new String[]{"image/png"}, + null); + + Uri attachment = Uri.fromFile(outputFile); + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("image/*"); + intent.putExtra(Intent.EXTRA_STREAM, attachment); + + instance.startActivity(Intent.createChooser(intent, instance.getString(R.string.title_chooser))); + } catch (IOException e) { + Toast.makeText(instance, instance.getString(R.string.toast_route) + e.getMessage(), Toast.LENGTH_LONG).show(); + } + } + }; + + map.snapshot(callback); + } + + /** + * Fetches piece of a route. + * + * @param start start position + * @param stop stop position + * @return coordinates + */ + public static List fetchCoordinates(int start, int stop) { + return DBManager.fetchCoordinates(start, stop); + } + + /** + * Deletes corresponding route in the database + * + * @param name of route to be deleted + */ + public static void deleteDisplacement(String name){ + DBManager.deleteDisplacement(name); + } + + /** + * Class to receive coordinates broadcast by CoordinatorService. + */ + private static class CoordinatesReceiver extends BroadcastReceiver { + ArrayList coordinates = null; + + @Override + public void onReceive(Context context, Intent intent) { + Bundle notificationData = intent.getExtras(); + coordinates = notificationData.getParcelableArrayList(context.getString(R.string.extra_coordinates)); + } + } + + /** + * Once coordinates are received, notifies AlarmActivity. + */ + public static class AlarmCoordinatesReceiver extends CoordinatesReceiver { + // можно сделать final + // (*) но зачем? + // + // AlarmCoordinatesReceiver создается для конкретного экземпляра AlarmActivity, + // к которому он и должен быть привязан. Ключевым словом final показываем, что + // менять этот экземпляр на другой нельзя. + private final AlarmActivity alarmActivityInstance; + + public AlarmCoordinatesReceiver(AlarmActivity instance) { + alarmActivityInstance = instance; + } + + @Override + public void onReceive(Context context, Intent intent) { + stopCoordinatesService(); + + try { + alarmActivityInstance.unregisterReceiver(this); + } catch (IllegalArgumentException ignored) { + // no API methods to tell if it is registered at the moment + } + + super.onReceive(context, intent); + alarmActivityInstance.positionChanged(coordinates.get(coordinates.size() - 1)); + } + } + + /** + * Once coordinates are received, draws route in TrackerActivity. + */ + public static class TrackerCoordinatesReceiver extends CoordinatesReceiver { + private final TrackerActivity trackerActivityInstance; + + public TrackerCoordinatesReceiver(TrackerActivity instance) { + trackerActivityInstance = instance; + } + + @Override + public void onReceive(Context context, Intent intent) { + super.onReceive(context, intent); + trackerActivityInstance.drawRoute(coordinates); + } + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Coordinate.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Coordinate.java new file mode 100644 index 0000000..76e3c7e --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Coordinate.java @@ -0,0 +1,63 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Class for storing map coordinates. + */ +public class Coordinate implements Parcelable { + private final double lat; + private final double lng; + private final int id; + + Coordinate(double lat, double lng, int id) { + // [minor] неконсистентно: `this.foo`; `foo` + // почему то `x` то `lat`? + // fixed + this.lat = lat; + this.lng = lng; + this.id = id; + } + + public double getLat() { + return lat; + } + + public double getLng() { + return lng; + } + + public int getId() { + return id; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeDouble(lat); + parcel.writeDouble(lng); + parcel.writeInt(id); + } + + public static final Creator CREATOR + = new Creator() { + public Coordinate createFromParcel(Parcel in) { + return new Coordinate(in); + } + + public Coordinate[] newArray(int size) { + return new Coordinate[size]; + } + }; + + private Coordinate(Parcel in) { + lat = in.readDouble(); + lng = in.readDouble(); + id = in.readInt(); + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/CoordinatesService.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/CoordinatesService.java new file mode 100644 index 0000000..ea659e9 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/CoordinatesService.java @@ -0,0 +1,89 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.content.Context; +import android.content.Intent; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.v4.app.JobIntentService; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +import static java.lang.Math.abs; + +/** + * This class checks periodically whether position of the vehicle has been changed or not. + */ +public class CoordinatesService extends JobIntentService { + private static final String TAG = "CoordinatesService"; + public static final int JOB_ID = 1000; + private static final double EPS = 1.0E-06; + private static final int SLEEP = 1000; + private static boolean isActive; + + // coordinates можно сделать локальной переменной и передовать в broadcastCoordinates как параметр + //private ArrayList coordinates; + // fixed + + public static void enqueueWork(Context context, Intent work) { + isActive = true; + enqueueWork(context, CoordinatesService.class, JOB_ID, work); + } + + public static void stop() { + isActive = false; + } + + @Override + protected void onHandleWork(@NonNull Intent intent) { + List coordinates; + boolean positionChanged; + + double lat = 0; + double lng = 0; + int id = -1; + + // (?) Почему надо чистить таблицу? + // + // Там лежат координаты, оставшиеся от предыдущей поездки, сейчас они уже не нужны. + // Более того, иначе они будут считаться первыми координатами нового запуска. + Controller.clearTable(); + + while (isActive) { + coordinates = Controller.fetchCoordinates(id); + positionChanged = false; + + for (Coordinate pos : coordinates) { + if (abs(pos.getLat() - lat) > EPS || abs(pos.getLng() - lng) > EPS) { + positionChanged = true; + break; + } + } + + if (positionChanged) { + if (coordinates.size() != 0) { + Coordinate pos = coordinates.get(coordinates.size() - 1); + lat = pos.getLat(); + lng = pos.getLng(); + id = pos.getId(); + } + + broadcastCoordinates(coordinates); + } + + try { + Thread.sleep(SLEEP); + } catch (InterruptedException e) { + Log.w(TAG, e.getMessage()); + } + } + } + + private void broadcastCoordinates(List coordinates){ + Intent broadcastIntent = new Intent(); + broadcastIntent.setAction(getString(R.string.broadcast_content_coordinates)); + broadcastIntent.putParcelableArrayListExtra(getString(R.string.extra_coordinates), (ArrayList) coordinates); + sendBroadcast(broadcastIntent); + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/DBManager.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/DBManager.java new file mode 100644 index 0000000..44c5453 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/DBManager.java @@ -0,0 +1,389 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.util.Log; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +/** + * Class for interaction with the database. + */ +public class DBManager { + private static final String TAG = "DBManager"; + private static String deviceId = ""; + + private static final String driver = "com.mysql.jdbc.Driver"; + private static final String url = "jdbc:mysql://146.185.144.144:3306/gps?autoReconnect=true&useSSL=false"; + // На всякий случай напишу, что так делать не надо, но для прототипа не критично. + private static final String user = "android"; + private static final String password = "GPSTracker-MySQL123"; + + public static void setDeviceId(String deviceId) { + DBManager.deviceId = deviceId; + } + + /** + * Fetches new coordinates from the database. + * + * @param id id of the coordinate after which should take new coordinates + * @return new coordinates + */ + // Лучше возвращать List, а ингда даже Collection + // (*) почему? + // + // Более общий тип, чтобы методы, использующие возвращаемое значение, + // меньше зависели от реализации данного метода. + public static List fetchCoordinates(int id) { + Connection con = null; + PreparedStatement preparedStatement = null; + ResultSet rs = null; + + ArrayList coordinates = new ArrayList<>(); + + try { + Class.forName(driver); + + con = DriverManager.getConnection(url, user, password); + String sql = "SELECT * FROM " + deviceId + "_coordinates " + "where id > ?"; + + preparedStatement = con.prepareStatement(sql); + preparedStatement.setInt(1, id); + rs = preparedStatement.executeQuery(); + + double lat; + double lng; + int coordinate_id; + + while (rs.next()) { + lat = rs.getDouble("lat"); + lng = rs.getDouble("lng"); + coordinate_id = rs.getInt("id"); + coordinates.add(new Coordinate(lat, lng, coordinate_id)); + + // TODO: remove, demo only + break; + } + } catch (SQLException | ClassNotFoundException e) { + // лучше логировать https://developer.android.com/reference/android/util/Log.html + // fixed + Log.w(TAG, e.getMessage()); + } finally { + // было бы логичнее/правильнее закрывать ресурсы в обратном порядке + // (*) почему? + // + // Ресурсы, созданные позже, могут зависеть от ресурсов, созданных раньше. + try { + if (rs != null) { + rs.close(); + } + } catch (SQLException ignored) { + } + try { + if (preparedStatement != null) { + preparedStatement.close(); + } + } catch (SQLException ignored) { + } + try { + if (con != null) { + con.close(); + } + } catch (SQLException ignored) { + } + } + + return coordinates; + } + + public static List fetchCoordinates(int start, int stop) { + Connection con = null; + PreparedStatement preparedStatement = null; + ResultSet rs = null; + + ArrayList coordinates = new ArrayList<>(); + + try { + Class.forName(driver); + + con = DriverManager.getConnection(url, user, password); + String sql = "SELECT * FROM " + deviceId + "_all " + "where id > ? and id < ?"; + + preparedStatement = con.prepareStatement(sql); + preparedStatement.setInt(1, start - 1); + preparedStatement.setInt(2, stop + 1); + rs = preparedStatement.executeQuery(); + + double lat; + double lng; + int coordinate_id; + + System.err.println(start + " " + stop); + while (rs.next()) { + lat = rs.getDouble("lat"); + lng = rs.getDouble("lng"); + coordinate_id = rs.getInt("id"); + coordinates.add(new Coordinate(lat, lng, coordinate_id)); + } + } catch (SQLException | ClassNotFoundException e) { + Log.w(TAG, e.getMessage()); + } finally { + try { + if (rs != null) { + rs.close(); + } + } catch (SQLException ignored) { + } + try { + if (preparedStatement != null) { + preparedStatement.close(); + } + } catch (SQLException ignored) { + } + try { + if (con != null) { + con.close(); + } + } catch (SQLException ignored) { + } + } + + return coordinates; + } + + /** + * Fetches all displacements from the database. + * + * @return all displacement + */ + public static List fetchDisplacements() { + Connection con = null; + PreparedStatement preparedStatement = null; + ResultSet rs = null; + + ArrayList displacements = new ArrayList<>(); + + try { + Class.forName(driver); + + con = DriverManager.getConnection(url, user, password); + String sql = "SELECT * FROM " + deviceId + "_routes "; + + preparedStatement = con.prepareStatement(sql); + rs = preparedStatement.executeQuery(); + + int start, stop; + String name; + + while (rs.next()) { + name = rs.getString("name"); + start = rs.getInt("start"); + stop = rs.getInt("stop"); + displacements.add(new Displacement(start, stop, name)); + } + } catch (SQLException | ClassNotFoundException e) { + Log.w(TAG, e.getMessage()); + } finally { + try { + if (rs != null) { + rs.close(); + } + } catch (SQLException ignored) { + } + try { + if (preparedStatement != null) { + preparedStatement.close(); + } + } catch (SQLException ignored) { + } + try { + if (con != null) { + con.close(); + } + } catch (SQLException ignored) { + } + } + + return displacements; + } + + /** + * Deletes all rows from table. + */ + public static void clearTable() { + try { + Class.forName(driver); + + Connection con = DriverManager.getConnection(url, user, password); + + try { + String table = deviceId + "_coordinates"; + String query = "DELETE FROM " + table; + PreparedStatement preparedStatement = con.prepareStatement(query); + preparedStatement.executeUpdate(query); + } catch (SQLException e) { + Log.w(TAG, e.getMessage()); + } + + con.close(); + } catch (ClassNotFoundException | SQLException e) { + Log.w(TAG, e.getMessage()); + } + } + + /** + * Delete corresponding route in database + * + * @param name of route + */ + public static void deleteDisplacement(String name) { + try { + Class.forName(driver); + + Connection con = DriverManager.getConnection(url, user, password); + + try { + String table = deviceId + "_routes"; + String query = "delete from " + table + " where name = '" + name + "' limit 1"; + PreparedStatement preparedStatement = con.prepareStatement(query); + preparedStatement.executeUpdate(query); + } catch (SQLException e) { + Log.w(TAG, e.getMessage()); + } + + con.close(); + } catch (ClassNotFoundException | SQLException e) { + Log.w(TAG, e.getMessage()); + } + } + + /** + * Saves route to the database. + * + * @param route route to save + * @param name route name + */ + public static void sendCoordinates(List route, String name) { + Connection con = null; + Statement stmt = null; + + try { + Class.forName(driver); + + con = DriverManager.getConnection(url, user, password); + + stmt = con.createStatement(); + + int start = -1, stop = -1; + + String get_maxID = "select MAX(id) from " + deviceId + "_all"; + + ResultSet maxID = stmt.executeQuery(get_maxID); + + if (maxID.next()) { + start = maxID.getInt(1); + } + + // странное решение с созданием отдельной таблицы для каждого route + //fixed + + if (route != null) { + for (Coordinate coordinate : route) { + String sql = "INSERT INTO " + + deviceId + + "_all" + + " (lat, lng) VALUES (" + + String.valueOf(coordinate.getLat()) + ", " + + String.valueOf(coordinate.getLng()) + ")"; + stmt.executeUpdate(sql); + } + } + + maxID = stmt.executeQuery(get_maxID); + + if (maxID.next()) { + stop = maxID.getInt(1); + } + + String sql = "INSERT INTO " + + deviceId + + "_routes" + + " (name, start, stop) VALUES ('" + + name + "', " + + start + ", " + + stop + ")"; + stmt.executeUpdate(sql); + } catch (Exception e) { + Log.w(TAG, e.getMessage()); + } finally { + try { + if (stmt != null) + stmt.close(); + } catch (SQLException ignored) { + } + try { + if (con != null) + con.close(); + } catch (SQLException e) { + Log.w(TAG, e.getMessage()); + } + } + } + + /** + * Checks whether id is correct or not. + * + * @param deviceId input id + * @return true if id exists, false otherwise + */ + // возможно стоит переименовать, например в isExistingDeviceId или isValidDeviceId + // fixed + public static boolean isValidDeviceId(String deviceId) { + Connection con = null; + PreparedStatement preparedStatement; + ResultSet rs; + Statement stmt = null; + + int answer = 0; + + try { + Class.forName(driver); + + con = DriverManager.getConnection(url, user, password); + + stmt = con.createStatement(); + + String sql = "select count(*) from ids where id = ?;"; + + preparedStatement = con.prepareStatement(sql); + preparedStatement.setString(1, deviceId); + rs = preparedStatement.executeQuery(); + + if (rs.next()) { + answer = rs.getInt("count(*)"); + } + + } catch (Exception e) { + Log.w(TAG, e.getMessage()); + } finally { + try { + if (stmt != null) + con.close(); + } catch (SQLException ignored) { + } + try { + if (con != null) + con.close(); + } catch (SQLException e) { + Log.w(TAG, e.getMessage()); + } + } + + return answer == 1; + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Displacement.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Displacement.java new file mode 100644 index 0000000..9025a9c --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Displacement.java @@ -0,0 +1,29 @@ +package ru.spbau.farutin_solikov.gpstracker; + +/** + * Start and stop of a route with name. + */ +public class Displacement { + + public int getStart() { + return start; + } + + public int getStop() { + return stop; + } + + public String getName() { + return name; + } + + private int start; + private int stop; + private String name; + + Displacement(int start, int stop, String name) { + this.start = start; + this.stop = stop; + this.name = name; + } +} \ No newline at end of file diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/DrawerActivity.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/DrawerActivity.java new file mode 100644 index 0000000..126c2f5 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/DrawerActivity.java @@ -0,0 +1,87 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.design.widget.NavigationView; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; + +/** + * Basic DrawerActivity with navigation. + */ +// @SuppressLint("Registered") +// Все же, видимо, правильнее сделать так как предлагает IDE (в сплывающем сообщении можно нажать "more" и получить больше информации) +public abstract class DrawerActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { + + @Override + @SuppressLint("InflateParams") + public void setContentView(final int layoutResID) { + DrawerLayout drawerLayout = (DrawerLayout) getLayoutInflater().inflate(R.layout.activity_main, null); + FrameLayout contentFrame = drawerLayout.findViewById(R.id.content_frame); + getLayoutInflater().inflate(layoutResID, contentFrame, true); + super.setContentView(drawerLayout); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( + this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); + drawer.addDrawerListener(toggle); + toggle.syncState(); + + NavigationView navigationView = findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(this); + + ImageView settings = findViewById(R.id.settings); + settings.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(DrawerActivity.this, SettingsActivity.class); + startActivity(intent); + } + }); + } + + @Override + public void onBackPressed() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + } else { + Intent exitApp = new Intent(Intent.ACTION_MAIN); + exitApp.addCategory(Intent.CATEGORY_HOME); + exitApp.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(exitApp); + } + } + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + int id = item.getItemId(); + Class classToStart = TrackerActivity.class; + + if (id == R.id.nav_tracker) { + classToStart = TrackerActivity.class; + } else if (id == R.id.nav_alarm) { + classToStart = AlarmActivity.class; + } else if (id == R.id.nav_history) { + classToStart = HistoryActivity.class; + } + + Intent intent = new Intent(DrawerActivity.this, classToStart); + startActivity(intent); + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + return true; + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/HistoryActivity.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/HistoryActivity.java new file mode 100644 index 0000000..c85b9c9 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/HistoryActivity.java @@ -0,0 +1,152 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; + +/** + * Activity with saved routes. + */ +public class HistoryActivity extends DrawerActivity { + + private static final String TAG = "HistoryActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.content_history); + + final ListView historyList = findViewById(R.id.history_list); + final List displacements; + final ArrayList routesNames = new ArrayList<>(); + + try { + displacements = new FetchDisplacements().execute().get(); + for (Displacement displacement : displacements) { + routesNames.add(displacement.getName()); + } + + final ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, routesNames); + historyList.setAdapter(adapter); + historyList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Route route = null; + try { + route = new FetchRoutes().execute(displacements.get(position)).get(); + } catch (InterruptedException e) { + Log.w(TAG, e.getMessage()); + } catch (ExecutionException e) { + Log.w(TAG, e.getMessage()); + } + ArrayList coordinates = new ArrayList<>(); + coordinates.addAll(route.getRoute()); + System.err.println("ZHIII +" + coordinates.size()); + + Intent intent = new Intent(HistoryActivity.this, RouteActivity.class); + intent.putExtra(getString(R.string.extra_route_name), routesNames.get(position)); + intent.putParcelableArrayListExtra(getString(R.string.extra_coordinates), coordinates); + startActivity(intent); + } + }); + + historyList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView adapterView, View view, final int i, long l) { + AlertDialog.Builder builder = new AlertDialog.Builder(HistoryActivity.this); + + builder.setPositiveButton(getString(R.string.title_button_delete), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + String deleted = routesNames.remove(i); + adapter.notifyDataSetChanged(); + + if (routesNames.size() == 0) { + TextView emptyList = findViewById(R.id.empty_list); + emptyList.setVisibility(View.VISIBLE); + } + + new DeleteRoute().execute(deleted); + } + }); + + builder.setNegativeButton(getString(R.string.title_button_cancel), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + + } + }); + + builder.setTitle(getString(R.string.title_dialog_delete)); + builder.create().show(); + + return true; + } + }); + + if (routesNames.size() == 0) { + TextView emptyList = findViewById(R.id.empty_list); + emptyList.setVisibility(View.VISIBLE); + } + } catch (InterruptedException e) { + Log.w(TAG, e.getMessage()); + } catch (ExecutionException e) { + Log.w(TAG, e.getMessage()); + } + + } + + /** + * Fetches all routes from the database in separate thread. + */ + private static class FetchDisplacements extends AsyncTask> { + + @Override + protected List doInBackground(Void... voids) { + + return Controller.fetchDisplacements(); + } + } + + /** + * Fetches all coordinates for given displacement in separate thread. + */ + private static class FetchRoutes extends AsyncTask { + + @Override + protected Route doInBackground(Displacement... displacements) { + for (Displacement displacement : displacements) { + return new Route(Controller.fetchCoordinates(displacement.getStart(), displacement.getStop()), displacement.getName()); + } + + return null; + } + } + + /** + * Fetches all coordinates for given displacement in separate thread. + */ + private static class DeleteRoute extends AsyncTask { + + @Override + protected Void doInBackground(String... strings) { + for (String string : strings) { + Controller.deleteDisplacement(string); + return null; + } + + return null; + } + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/LoginActivity.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/LoginActivity.java new file mode 100644 index 0000000..ba9dd92 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/LoginActivity.java @@ -0,0 +1,120 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import java.util.concurrent.ExecutionException; + +/** + * Activity to log in. + */ +public class LoginActivity extends AppCompatActivity { + private static final String TAG = "LoginActivity"; + private EditText deviceId; + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (Controller.userLoggedIn(LoginActivity.this)) { + DBManager.setDeviceId(Controller.getUserID(LoginActivity.this).replaceAll("\\s+", "")); + loginSuccess(); + } + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + + deviceId = findViewById(R.id.device_id); + deviceId.addTextChangedListener(new FourLetterFormatWatcher()); + + Button login = findViewById(R.id.login_button); + login.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String sDeviceId = deviceId.getText().toString(); + try { + if (new CheckID().execute(sDeviceId.replaceAll("\\s+","")).get()){ + Controller.saveUserDeviceId(LoginActivity.this, sDeviceId); + loginSuccess(); + } else { + Toast.makeText(LoginActivity.this, getString(R.string.toast_login), Toast.LENGTH_SHORT).show(); + } + } catch (InterruptedException | ExecutionException e) { + Log.w(TAG, e.getMessage()); + } + } + }); + } + + private void loginSuccess() { + Intent intent = new Intent(LoginActivity.this, TrackerActivity.class); + startActivity(intent); + } + + /** + * Changes input id so that it corresponds following format: + * XXXX XXXX XXXX ... + */ + private class FourLetterFormatWatcher implements TextWatcher { + private static final char SPACE = ' '; + private boolean lock; + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void afterTextChanged(Editable s) { + if (lock) { + return; + } + + lock = true; + + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == SPACE) { + s.delete(i, i + 1); + } + } + + for (int i = 0; i < s.length(); i++) { + if (Character.isLowerCase(s.charAt(i))) { + s.replace(i, i + 1, String.valueOf(Character.toUpperCase(s.charAt(i)))); + } + } + + for (int i = 4; i < s.length(); i += 5) { + s.insert(i, String.valueOf(SPACE)); + } + + lock = false; + } + } + + /** + * Checks if given ID is in database in separate thread. + */ + private static class CheckID extends AsyncTask { + + @Override + protected Boolean doInBackground(String... strings) { + + for (String id : strings) { + return Controller.checkDeviceId(id); + } + + return false; + } + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Route.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Route.java new file mode 100644 index 0000000..7fee3be --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/Route.java @@ -0,0 +1,24 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import java.util.List; + +/** + * A route with name. + */ +public class Route { + public List getRoute() { + return route; + } + + public String getName() { + return name; + } + + private List route; + private String name; + + Route(List route, String name) { + this.route = route; + this.name = name; + } +} \ No newline at end of file diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/RouteActivity.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/RouteActivity.java new file mode 100644 index 0000000..e4997c9 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/RouteActivity.java @@ -0,0 +1,194 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.Manifest; +import android.animation.ObjectAnimator; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.getbase.floatingactionbutton.FloatingActionButton; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.PolylineOptions; +import com.google.maps.android.SphericalUtil; + +import java.util.ArrayList; +import static java.lang.Thread.sleep; + +/** + * Activity to display saved route on the map. + */ +public class RouteActivity extends DrawerActivity implements OnMapReadyCallback { + private static final String TAG = "RouteActivity"; + private static final int ZOOM = 15; + private static final int WRITE_EXTERNAL_STORAGE_REQUEST_CODE = 1; + + private GoogleMap map; + private String routeName; + private final ArrayList route = new ArrayList<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.content_route); + + SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() + .findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + + Intent intent = getIntent(); + routeName = intent.getStringExtra(getString(R.string.extra_route_name)); + ArrayList coordinates = intent.getParcelableArrayListExtra(getString(R.string.extra_coordinates)); + + for (Coordinate coordinate : coordinates) { + route.add(new LatLng(coordinate.getLat(), coordinate.getLng())); + } + + setUpUIElements(); + } + + // не должно ли это поведение общим для всех активити? + // + // Для основных трех активити (к которым есть доступ через меню слева в NavigationView) + // в этом месте хочется выходить из приложения (как и написано в соответствующем методе + // в DrawerActivity). Пользователь может какое-то количество раз между ними переключаться + // из меню, но по смыслу они не являются предками друг друга в плане иерархии активити. + @Override + public void onBackPressed() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + } else { + finish(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + switch (requestCode) { + case WRITE_EXTERNAL_STORAGE_REQUEST_CODE: { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Controller.sendSnapshot(this, map, routeName); + } + } + } + } + + @Override + public void onMapReady(GoogleMap googleMap) { + map = googleMap; + drawRoute(); + } + + private void setUpUIElements() { + Toolbar toolbar = findViewById(R.id.toolbar); + ImageView share = new ImageView(this); + share.setImageResource(R.drawable.ic_share_white_24dp); + Toolbar.LayoutParams params = new Toolbar.LayoutParams(Toolbar.LayoutParams.WRAP_CONTENT, Toolbar.LayoutParams.WRAP_CONTENT); + params.gravity = Gravity.END; + share.setLayoutParams(params); + toolbar.addView(share); + + share.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (ContextCompat.checkSelfPermission(RouteActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + + ActivityCompat.requestPermissions(RouteActivity.this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + WRITE_EXTERNAL_STORAGE_REQUEST_CODE); + } else { + Controller.sendSnapshot(RouteActivity.this, map, routeName); + } + } + }); + + final RelativeLayout infoLayout = findViewById(R.id.route_info); + infoLayout.post(new Runnable() { + @Override + public void run() { + RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) infoLayout.getLayoutParams(); + params.setMargins(0, 0, 0, -1 * infoLayout.getHeight()); + infoLayout.setLayoutParams(params); } + }); + + final FloatingActionButton find = findViewById(R.id.route_info_button); + find.setIcon(R.drawable.ic_info_outline_white_24dp); + find.setColorNormalResId(R.color.primaryColor); + find.setColorPressedResId(R.color.primaryLightColor); + find.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ObjectAnimator animX = ObjectAnimator.ofFloat(find, View.TRANSLATION_X, 0, find.getWidth()); + animX.setDuration(700); + animX.start(); + + try { + sleep(500); + } catch (InterruptedException e) { + Log.w(TAG, e.getMessage()); + } + + ObjectAnimator animY = ObjectAnimator.ofFloat(infoLayout, View.TRANSLATION_Y, 0, -1 * infoLayout.getHeight()); + animY.setDuration(1000); + animY.start(); + } + }); + + ImageView hide = findViewById(R.id.hide_info); + hide.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ObjectAnimator animY = ObjectAnimator.ofFloat(infoLayout, View.TRANSLATION_Y, -1 * infoLayout.getHeight(), 0); + animY.setDuration(700); + animY.start(); + + try { + sleep(500); + } catch (InterruptedException e) { + Log.w(TAG, e.getMessage()); + } + + ObjectAnimator animX = ObjectAnimator.ofFloat(find, View.TRANSLATION_X, find.getWidth(), 0); + animX.setDuration(1000); + animX.start(); + } + }); + + double length = SphericalUtil.computeLength(route); + TextView routeLength = findViewById(R.id.route_length); + routeLength.setText(getString(R.string.route_length_format, length)); + } + + private void drawRoute() { + PolylineOptions polylineOptions = new PolylineOptions().geodesic(true); + + for (LatLng position : route) { + polylineOptions.add(position); + } + + map.addPolyline(polylineOptions); + + if (route.size() != 0) { + LatLng position = route.get(route.size() - 1); + map.moveCamera(CameraUpdateFactory.newLatLngZoom(position, ZOOM)); + } + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/SettingsActivity.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/SettingsActivity.java new file mode 100644 index 0000000..39de059 --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/SettingsActivity.java @@ -0,0 +1,170 @@ +package ru.spbau.farutin_solikov.gpstracker; + + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.preference.Preference; +import android.support.v7.app.ActionBar; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.support.v7.app.AlertDialog; +import android.view.MenuItem; + +import java.util.List; + +public class SettingsActivity extends AppCompatPreferenceActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setupActionBar(); + } + + private void setupActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == android.R.id.home) { + finish(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + private static final Preference.OnPreferenceChangeListener onPreferenceChangeListener = new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + preference.setSummary(value.toString()); + + SharedPreferences sharedPreferences = preference.getContext().getSharedPreferences(Controller.PREF_FILE, MODE_PRIVATE); + SharedPreferences.Editor e = sharedPreferences.edit(); + e.putString(preference.getKey(), value.toString()); + e.apply(); + + return true; + } + }; + + private static boolean isXLargeTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; + } + + private static void setBooleanPreferenceChangeListener(Preference preference) { + preference.setOnPreferenceChangeListener(onPreferenceChangeListener); + + onPreferenceChangeListener.onPreferenceChange(preference, + PreferenceManager + .getDefaultSharedPreferences(preference.getContext()) + .getBoolean(preference.getKey(), true)); + } + + @Override + public boolean onIsMultiPane() { + return isXLargeTablet(this); + } + + @Override + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void onBuildHeaders(List
target) { + loadHeadersFromResource(R.xml.pref_headers, target); + } + + /** + * This method stops fragment injection in malicious applications. + * Make sure to deny any unknown fragments here. + */ + protected boolean isValidFragment(String fragmentName) { + return PreferenceFragment.class.getName().equals(fragmentName) + || UserIDPreferenceFragment.class.getName().equals(fragmentName) + || NotificationPreferenceFragment.class.getName().equals(fragmentName); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class NotificationPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_notification); + setHasOptionsMenu(true); + + setBooleanPreferenceChangeListener(findPreference(getString(R.string.preference_notifications_new_message))); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class UserIDPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_user_id); + setHasOptionsMenu(true); + + SharedPreferences sharedPreferences = getActivity().getSharedPreferences(Controller.PREF_FILE, MODE_PRIVATE); + findPreference(getString(R.string.preference_user_id)).setSummary(sharedPreferences.getString(getString(R.string.preference_user_id), "")); + + Preference quit = findPreference(getString(R.string.preference_quit)); + quit.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + builder.setPositiveButton(R.string.title_button_change, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + SharedPreferences sharedPreferences = getActivity().getSharedPreferences(Controller.PREF_FILE, MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove(getString(R.string.preference_user_id)); + editor.apply(); + + startActivity(new Intent(getActivity(), LoginActivity.class)); + } + }); + + builder.setNegativeButton(R.string.title_button_cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + + } + }); + + builder.setTitle(R.string.title_dialog_change); + builder.create().show(); + + return true; + } + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } +} diff --git a/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/TrackerActivity.java b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/TrackerActivity.java new file mode 100644 index 0000000..e8621bf --- /dev/null +++ b/GPSTracker/app/src/main/java/ru/spbau/farutin_solikov/gpstracker/TrackerActivity.java @@ -0,0 +1,249 @@ +package ru.spbau.farutin_solikov.gpstracker; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import com.getbase.floatingactionbutton.FloatingActionButton; +import com.getbase.floatingactionbutton.FloatingActionsMenu; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.PolylineOptions; + +import java.util.ArrayList; + +/** + * Activity to track current position and display route on the map. + */ +public class TrackerActivity extends DrawerActivity implements OnMapReadyCallback { + private static final int ZOOM = 20; + + private Controller.TrackerCoordinatesReceiver receiver; + private GoogleMap map; + private LatLng lastPosition; + private Coordinate startPosition; + private boolean startImmediately = false; + private static ArrayList route; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.content_tracker); + + SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() + .findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + + createMenu(); + + Intent intent = getIntent(); + if (intent.hasExtra(getString(R.string.extra_position))) { + startPosition = intent.getParcelableExtra(getString(R.string.extra_position)); + startImmediately = true; + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + try { + unregisterReceiver(receiver); + } catch (IllegalArgumentException ignored) { + // no API methods to tell if it is registered at the moment + } + } + + @Override + public void onMapReady(GoogleMap googleMap) { + map = googleMap; + + if (startImmediately) { + startTracking(startPosition); + } + } + + /** + * Draws route on Google Map. + * + * @param coordinates route to draw + */ + public void drawRoute(ArrayList coordinates) { + PolylineOptions polylineOptions = new PolylineOptions().geodesic(true); + + if (lastPosition == null) { + Coordinate position = coordinates.get(0); + lastPosition = new LatLng(position.getLat(), position.getLng()); + map.moveCamera(CameraUpdateFactory.newLatLngZoom(lastPosition, ZOOM)); + route.add(position); + } + + polylineOptions.add(lastPosition); + for (Coordinate position : coordinates) { + polylineOptions.add(new LatLng(position.getLat(), position.getLng())); + route.add(position); + } + + Coordinate position = coordinates.get(coordinates.size() - 1); + lastPosition = new LatLng(position.getLat(), position.getLng()); + + map.addPolyline(polylineOptions); + } + + private void createMenu() { + final FloatingActionsMenu menu = findViewById(R.id.menu_tracker); + + FloatingActionButton start = new FloatingActionButton(this); + start.setIcon(R.drawable.ic_play_arrow_white_24dp); + start.setColorNormalResId(R.color.primaryColor); + start.setColorPressedResId(R.color.primaryLightColor); + start.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + menu.collapse(); + + if (map != null) { + startTracking(null); + } + } + }); + + FloatingActionButton stop = new FloatingActionButton(this); + stop.setIcon(R.drawable.ic_stop_white_24dp); + stop.setColorNormalResId(R.color.primaryColor); + stop.setColorPressedResId(R.color.primaryLightColor); + stop.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + menu.collapse(); + Controller.stopCoordinatesService(); + + try { + unregisterReceiver(receiver); + } catch (IllegalArgumentException ignored) { + // no API methods to tell if it is registered at the moment + } + } + }); + + FloatingActionButton save = new FloatingActionButton(this); + save.setIcon(R.drawable.ic_save_white_24dp); + save.setColorNormalResId(R.color.primaryColor); + save.setColorPressedResId(R.color.primaryLightColor); + save.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + menu.collapse(); + saveRoute(); + } + }); + + menu.addButton(start); + menu.addButton(stop); + menu.addButton(save); + + FloatingActionButton find = findViewById(R.id.find_tracker); + find.setIcon(R.drawable.ic_my_location_white_24dp); + find.setColorNormalResId(R.color.primaryColor); + find.setColorPressedResId(R.color.primaryLightColor); + find.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (lastPosition != null) { + map.moveCamera(CameraUpdateFactory.newLatLngZoom(lastPosition, ZOOM)); + } + } + }); + } + + private void startTracking(Coordinate startPosition) { + map.clear(); + route = new ArrayList<>(); + + if (startPosition == null) { + lastPosition = null; + } else { + // кажется запись в lastPosition можно заменить на запись в локальную переменную + // + // Лишней была строчка в конце этого блока. + lastPosition = new LatLng(startPosition.getLat(), startPosition.getLng()); + map.moveCamera(CameraUpdateFactory.newLatLngZoom(lastPosition, ZOOM)); + map.addMarker(new MarkerOptions().position(lastPosition)); + route.add(startPosition); + + //lastPosition = null; + } + + Controller.startCoordinatesService(TrackerActivity.this); + + receiver = new Controller.TrackerCoordinatesReceiver(this); + IntentFilter intentSFilter = new IntentFilter(getString(R.string.broadcast_content_coordinates)); + registerReceiver(receiver, intentSFilter); + } + + private void saveRoute() { + DialogFragment saveDialogFragment = new SaveDialogFragment(); + saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.title_dialog_save)); + } + + public static class SaveDialogFragment extends DialogFragment { + @NonNull + @Override + @SuppressLint("InflateParams") + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final LayoutInflater inflater = getActivity().getLayoutInflater(); + final View view = inflater.inflate(R.layout.dialog_save, null); + + builder.setView(view) + .setTitle(R.string.title_dialog_save) + .setPositiveButton(R.string.title_button_save, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + EditText nameInput = view.findViewById(R.id.route_name); + String name = nameInput.getText().toString(); + + if (name.length() != 0) { + new SendRoute().execute(new Route(route, name)); +// Controller.sendCoordinates(route, name); + } + } + }) + .setNegativeButton(R.string.title_button_cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + SaveDialogFragment.this.getDialog().cancel(); + } + }); + + return builder.create(); + } + } + + /** + * Sends given route to the database in separate thread. + */ + private static class SendRoute extends AsyncTask { + @Override + protected Void doInBackground(Route... routes) { + for (Route route : routes) { + Controller.sendCoordinates(route.getRoute(), route.getName()); + } + + return null; + } + } +} diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..c7b1113 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_down_white_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_down_white_24dp.png new file mode 100644 index 0000000..bbb4fb4 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_down_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_map_black_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_map_black_24dp.png new file mode 100644 index 0000000..c81d0e0 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_map_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png new file mode 100644 index 0000000..67f07e4 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_my_location_white_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_my_location_white_24dp.png new file mode 100644 index 0000000..745db48 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_my_location_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_near_me_black_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_near_me_black_24dp.png new file mode 100644 index 0000000..de879d3 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_near_me_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png new file mode 100644 index 0000000..12969c5 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_perm_identity_black_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_perm_identity_black_24dp.png new file mode 100644 index 0000000..dc78670 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_perm_identity_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 0000000..57c9fa5 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_save_white_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_save_white_24dp.png new file mode 100644 index 0000000..dd3f106 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_save_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_share_white_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_share_white_24dp.png new file mode 100644 index 0000000..b09a692 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_share_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-hdpi/ic_stop_white_24dp.png b/GPSTracker/app/src/main/res/drawable-hdpi/ic_stop_white_24dp.png new file mode 100644 index 0000000..dfff26c Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-hdpi/ic_stop_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..353e064 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_down_white_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_down_white_24dp.png new file mode 100644 index 0000000..ef8a4b6 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_down_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_map_black_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_map_black_24dp.png new file mode 100644 index 0000000..bcc500e Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_map_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png new file mode 100644 index 0000000..017e45e Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_my_location_white_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_my_location_white_24dp.png new file mode 100644 index 0000000..d1c563c Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_my_location_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_near_me_black_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_near_me_black_24dp.png new file mode 100644 index 0000000..523222e Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_near_me_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png new file mode 100644 index 0000000..32562b0 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_perm_identity_black_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_perm_identity_black_24dp.png new file mode 100644 index 0000000..3c019ca Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_perm_identity_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 0000000..c61e948 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_save_white_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_save_white_24dp.png new file mode 100644 index 0000000..015062e Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_save_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_share_white_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_share_white_24dp.png new file mode 100644 index 0000000..e944fd7 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_share_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-mdpi/ic_stop_white_24dp.png b/GPSTracker/app/src/main/res/drawable-mdpi/ic_stop_white_24dp.png new file mode 100644 index 0000000..b84d2f6 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-mdpi/ic_stop_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..c571b2e Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_down_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_down_white_24dp.png new file mode 100644 index 0000000..058cebb Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_down_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_map_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_map_black_24dp.png new file mode 100644 index 0000000..a3ecc71 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_map_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png new file mode 100644 index 0000000..efab8a7 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_my_location_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_my_location_white_24dp.png new file mode 100644 index 0000000..ffab865 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_my_location_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_near_me_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_near_me_black_24dp.png new file mode 100644 index 0000000..df62429 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_near_me_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png new file mode 100644 index 0000000..98cbec6 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_perm_identity_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_perm_identity_black_24dp.png new file mode 100644 index 0000000..7a95c11 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_perm_identity_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 0000000..a3c80e7 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_save_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_save_white_24dp.png new file mode 100644 index 0000000..adda095 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_save_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png new file mode 100644 index 0000000..22a8783 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xhdpi/ic_stop_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_stop_white_24dp.png new file mode 100644 index 0000000..3ad2c9c Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xhdpi/ic_stop_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..c41a5fc Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_down_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_down_white_24dp.png new file mode 100644 index 0000000..f9622b7 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_down_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_map_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_map_black_24dp.png new file mode 100644 index 0000000..827c9fc Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_map_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png new file mode 100644 index 0000000..d322813 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_my_location_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_my_location_white_24dp.png new file mode 100644 index 0000000..387ecdf Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_my_location_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_near_me_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_near_me_black_24dp.png new file mode 100644 index 0000000..d5c3702 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_near_me_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png new file mode 100644 index 0000000..74c46cf Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_perm_identity_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_perm_identity_black_24dp.png new file mode 100644 index 0000000..d54de22 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_perm_identity_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 0000000..547ef30 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_save_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_save_white_24dp.png new file mode 100644 index 0000000..3e0ce1a Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_save_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png new file mode 100644 index 0000000..a35b3cd Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_stop_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_stop_white_24dp.png new file mode 100644 index 0000000..801d341 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxhdpi/ic_stop_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..3a82cab Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_down_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_down_white_24dp.png new file mode 100644 index 0000000..30948d9 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_down_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_map_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_map_black_24dp.png new file mode 100644 index 0000000..da75c65 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_map_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png new file mode 100644 index 0000000..2f2cb3d Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_my_location_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_my_location_white_24dp.png new file mode 100644 index 0000000..c55220a Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_my_location_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_near_me_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_near_me_black_24dp.png new file mode 100644 index 0000000..1e70e21 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_near_me_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png new file mode 100644 index 0000000..b06e6bc Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_perm_identity_black_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_perm_identity_black_24dp.png new file mode 100644 index 0000000..46ce7ef Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_perm_identity_black_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 0000000..be5c062 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_save_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_save_white_24dp.png new file mode 100644 index 0000000..bd80bf1 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_save_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png new file mode 100644 index 0000000..e351c7b Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_stop_white_24dp.png b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_stop_white_24dp.png new file mode 100644 index 0000000..5239336 Binary files /dev/null and b/GPSTracker/app/src/main/res/drawable-xxxhdpi/ic_stop_white_24dp.png differ diff --git a/GPSTracker/app/src/main/res/layout/activity_login.xml b/GPSTracker/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..4bf520a --- /dev/null +++ b/GPSTracker/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,53 @@ + + + + + + + + + + +