diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0f32531..0b37224 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,10 +7,6 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > - - diff --git a/app/src/main/java/ru/vfilippov/electronreminder/ElectronReminderService.java b/app/src/main/java/ru/vfilippov/electronreminder/ElectronReminderService.java index 27fe1e6..0ab8b08 100644 --- a/app/src/main/java/ru/vfilippov/electronreminder/ElectronReminderService.java +++ b/app/src/main/java/ru/vfilippov/electronreminder/ElectronReminderService.java @@ -6,14 +6,28 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.support.v4.app.NotificationCompat; +import android.support.v4.content.LocalBroadcastManager; import android.util.Log; +import android.widget.Toast; -import java.io.BufferedReader; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +import simple.SimpleXml; +import simple.SimpleXmlNode; /** * This {@code IntentService} does the app's actual work. @@ -30,80 +44,207 @@ public class ElectronReminderService extends IntentService } public static final String TAG = "Electron Reminder"; - // An ID used to post the notification. - public static final int NOTIFICATION_ID = 1; + private String token = null; private NotificationManager mNotificationManager; NotificationCompat.Builder builder; @Override protected void onHandleIntent(Intent intent) { - String urlString = "https://www.google.com"; - String result = ""; - // Try to connect to the Google homepage and download content. + String action = intent.getAction(); + FileOutputStream fs = null; + InputStream is = null; try { - result = loadFromNetwork(urlString); + if (action.equals("load_cities")) + { + File file = new File(getFilesDir(), "suburban_cities.xml"); + if (!file.exists() || file.lastModified() < System.currentTimeMillis()-86400*7*1000) + { + is = downloadUrl("http://static.rasp.yandex.net/data/export/suburban_cities.xml"); + fs = new FileOutputStream(file); + copyStream(is, fs); + } + Intent i = new Intent("cities_loaded"); + LocalBroadcastManager.getInstance(this).sendBroadcast(i); + } + else if (action == "load_stations") + { + int cityId = intent.getIntExtra("city_id", 0); + is = downloadUrl("http://mobile.rasp.yandex.net/export/suburban/city/"+cityId+"/stations?uuid="+token); + File file = new File(getFilesDir(), "stations_"+cityId+".xml"); + fs = new FileOutputStream(file); + copyStream(is, fs); + Intent i = new Intent("stations_loaded"); + LocalBroadcastManager.getInstance(this).sendBroadcast(i); + } + else if (action == "load_trips") + { + int from = intent.getIntExtra("from", 0); + int to = intent.getIntExtra("to", 0); + String date = intent.getStringExtra("date"); + getTrips(from, to, date, false); + Intent i = new Intent("trips_loaded"); + LocalBroadcastManager.getInstance(this).sendBroadcast(i); + } + else if (action == "refresh_all") + { + File file = new File(getFilesDir(), "reminders.xml"); + if (file.exists()) + { + FileInputStream fis = new FileInputStream(file); + SimpleXmlNode remindersRoot = SimpleXml.parse(fis); + fis.close(); + int i = 0; + Date d = new Date(); + String curTime = new SimpleDateFormat("hh:mm").format(d); + SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd"); + for (SimpleXmlNode reminder: remindersRoot.childrenByName.get("reminder")) + { + int from = Integer.valueOf(reminder.attributes.get("from")); + int to = Integer.valueOf(reminder.attributes.get("to")); + String time = reminder.attributes.get("time"); + String date; + // FIXME Check for different date? + if (time.compareTo(curTime) >= 0) + date = ymd.format(new Date(d.getTime()+86400000)); + else + date = ymd.format(d); + List segments = getTrips(from, to, date, true); + boolean found = false; + for (SimpleXmlNode seg: segments) + { + if (String.valueOf(from).equals(seg.attributes.get("from")) && + String.valueOf(to).equals(seg.attributes.get("to")) && + (date+" "+time).equals(seg.attributes.get("departure"))) + { + found = true; + break; + } + } + if (!found) + sendNotification(i, from, to, reminder.attributes.get("fromName"), reminder.attributes.get("toName"), time); + i++; + } + } + } } - catch (IOException e) + catch(IOException e) { - Log.i(TAG, "Connection error"); + Toast.makeText(getApplicationContext(), "Network error: "+e.getMessage(), Toast.LENGTH_LONG).show(); } - - // If the app finds the string "doodle" in the Google home page content, it - // indicates the presence of a doodle. Post a "Doodle Alert" notification. - if (result.indexOf("doodle") != -1) + catch (XmlPullParserException e) { - sendNotification(/*getString(R.string.doodle_found)*/"doodle found"); - Log.i(TAG, "Found doodle!!"); + Toast.makeText(getApplicationContext(), "Error parsing XML: "+e.getMessage(), Toast.LENGTH_LONG).show(); } - else + finally { - sendNotification("no doodle"); - Log.i(TAG, "No doodle found. :-("); + try + { + if (is != null) + is.close(); + if (fs != null) + fs.close(); + } + catch(IOException e) + { + } } // Release the wake lock provided by the BroadcastReceiver. RecheckAlarmReceiver.completeWakefulIntent(intent); } + private List getTrips(int from, int to, String date, boolean decode) throws IOException, XmlPullParserException + { + if (token == null) + initToken(); + FileOutputStream fs = null; + try + { + byte[] trips = loadFromNetwork( + "http://mobile.rasp.yandex.net/export/suburban/trip/"+from+"/"+to+ + "/?date="+date+"&tomorrow_upto=12&uuid="+token + ); + File file = new File(getFilesDir(), "trips_"+from+"_"+to+".xml"); + fs = new FileOutputStream(file); + fs.write(trips); + if (decode) + { + ByteArrayInputStream bis = new ByteArrayInputStream(trips); + SimpleXmlNode node = SimpleXml.parse(bis); + bis.close(); + return node.childrenByName.get("segment"); + } + } + finally + { + if (fs != null) + fs.close(); + } + return null; + } + + private void copyStream(InputStream is, OutputStream os) throws IOException + { + byte[] buf = new byte[0x10000]; + int r; + while ((r = is.read(buf)) > 0) + os.write(buf, 0, r); + } + + private boolean initToken() throws IOException, XmlPullParserException + { + // FIXME Store token? + SimpleXmlNode node = SimpleXml.parse(downloadUrl("http://mobile.rasp.yandex.net/rasp/startup?app_platform=android&app_version=200")); + List l = node.childrenByName.get("uuid"); + if (l != null) + { + token = l.get(0).value; + return true; + } + return false; + } + // Post a notification indicating suburban changes - private void sendNotification(String msg) + private void sendNotification(int notificationId, int from, int to, String fromName, String toName, String time) { mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); + Intent i = new Intent(this, MainActivity.class); + i.putExtra("from", from); + i.putExtra("to", to); + i.putExtra("time", time); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0); + String msg = getString(R.string.changes_details, fromName, toName, time); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) - .setContentTitle("doodle alert") - .setStyle(new NotificationCompat.BigTextStyle() - .bigText(msg)) + .setContentTitle(getString(R.string.changes_detected)) + .setStyle(new NotificationCompat.BigTextStyle().bigText(msg)) .setContentText(msg); mBuilder.setContentIntent(contentIntent); - mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); + mNotificationManager.notify(notificationId, mBuilder.build()); } // Given a URL string, initiate a fetch operation. - private String loadFromNetwork(String urlString) throws IOException + private byte[] loadFromNetwork(String urlString) throws IOException { InputStream stream = null; - String str = ""; try { stream = downloadUrl(urlString); - str = readIt(stream); + return readStream(stream); } finally { if (stream != null) stream.close(); } - return str; } /** @@ -127,19 +268,10 @@ public class ElectronReminderService extends IntentService return stream; } - /** - * Reads an InputStream and converts it to a String. - * @param stream InputStream containing HTML from www.google.com. - * @return String version of InputStream. - * @throws IOException - */ - private String readIt(InputStream stream) throws IOException + private byte[] readStream(InputStream is) throws IOException { - StringBuilder builder = new StringBuilder(); - BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); - for (String line = reader.readLine(); line != null; line = reader.readLine()) - builder.append(line); - reader.close(); - return builder.toString(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + copyStream(is, bos); + return bos.toByteArray(); } } diff --git a/app/src/main/java/ru/vfilippov/electronreminder/MainActivity.java b/app/src/main/java/ru/vfilippov/electronreminder/MainActivity.java index 5ab4b3a..0a59c24 100644 --- a/app/src/main/java/ru/vfilippov/electronreminder/MainActivity.java +++ b/app/src/main/java/ru/vfilippov/electronreminder/MainActivity.java @@ -1,6 +1,10 @@ package ru.vfilippov.electronreminder; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.Menu; @@ -8,6 +12,29 @@ import android.view.MenuItem; public class MainActivity extends AppCompatActivity { + private BroadcastReceiver srvReceiver = new BroadcastReceiver() + { + @Override + public void onReceive(Context context, Intent intent) + { + // put here whaterver you want your activity to do with the intent received + } + }; + + protected void onResume() + { + super.onResume(); + IntentFilter flt = new IntentFilter("cities_loaded"); + flt.addAction("trips_loaded"); + flt.addAction("cities_loaded"); + LocalBroadcastManager.getInstance(this).registerReceiver(srvReceiver, flt); + } + + protected void onPause() + { + super.onPause(); + LocalBroadcastManager.getInstance(this).unregisterReceiver(srvReceiver); + } @Override protected void onCreate(Bundle savedInstanceState) @@ -32,11 +59,10 @@ public class MainActivity extends AppCompatActivity // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); - // noinspection SimplifiableIfStatement if (id == R.id.action_settings) { - Intent intent = new Intent(this, SettingsActivity.class); - startActivity(intent); + //Intent intent = new Intent(this, SettingsActivity.class); + //startActivity(intent); return true; } diff --git a/app/src/main/java/ru/vfilippov/electronreminder/RecheckAlarmReceiver.java b/app/src/main/java/ru/vfilippov/electronreminder/RecheckAlarmReceiver.java index acf9468..ec7faa4 100644 --- a/app/src/main/java/ru/vfilippov/electronreminder/RecheckAlarmReceiver.java +++ b/app/src/main/java/ru/vfilippov/electronreminder/RecheckAlarmReceiver.java @@ -41,6 +41,7 @@ public class RecheckAlarmReceiver extends WakefulBroadcastReceiver { alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, RecheckAlarmReceiver.class); + intent.setAction("refresh_all"); alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0); Calendar calendar = Calendar.getInstance(); diff --git a/app/src/main/java/ru/vfilippov/electronreminder/SettingsActivity.java b/app/src/main/java/ru/vfilippov/electronreminder/SettingsActivity.java deleted file mode 100644 index 9248e3a..0000000 --- a/app/src/main/java/ru/vfilippov/electronreminder/SettingsActivity.java +++ /dev/null @@ -1,194 +0,0 @@ -package ru.vfilippov.electronreminder; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.Configuration; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceActivity; -import android.preference.PreferenceCategory; -import android.preference.PreferenceFragment; -import android.preference.PreferenceManager; -import android.preference.RingtonePreference; -import android.text.TextUtils; - -import java.util.List; - - -/** - * A {@link PreferenceActivity} that presents a set of application settings. On - * handset devices, settings are presented as a single list. On tablets, - * settings are split by category, with category headers shown to the left of - * the list of settings. - *

- * See - * Android Design: Settings for design guidelines and the Settings - * API Guide for more information on developing a Settings UI. - */ -public class SettingsActivity extends PreferenceActivity { - - - /** - * {@inheritDoc} - */ - @Override - public boolean onIsMultiPane() { - return isXLargeTablet(this); - } - - /** - * Helper method to determine if the device has an extra-large screen. For - * example, 10" tablets are extra-large. - */ - private static boolean isXLargeTablet(Context context) { - return (context.getResources().getConfiguration().screenLayout - & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; - } - - - /** - * {@inheritDoc} - */ - @Override - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public void onBuildHeaders(List

target) { - loadHeadersFromResource(R.xml.pref_headers, target); - } - - /** - * A preference value change listener that updates the preference's summary - * to reflect its new value. - */ - private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object value) { - String stringValue = value.toString(); - - if (preference instanceof ListPreference) { - // For list preferences, look up the correct display value in - // the preference's 'entries' list. - ListPreference listPreference = (ListPreference) preference; - int index = listPreference.findIndexOfValue(stringValue); - - // Set the summary to reflect the new value. - preference.setSummary( - index >= 0 - ? listPreference.getEntries()[index] - : null); - - } else if (preference instanceof RingtonePreference) { - // For ringtone preferences, look up the correct display value - // using RingtoneManager. - if (TextUtils.isEmpty(stringValue)) { - // Empty values correspond to 'silent' (no ringtone). - preference.setSummary(R.string.pref_ringtone_silent); - - } else { - Ringtone ringtone = RingtoneManager.getRingtone( - preference.getContext(), Uri.parse(stringValue)); - - if (ringtone == null) { - // Clear the summary if there was a lookup error. - preference.setSummary(null); - } else { - // Set the summary to reflect the new ringtone display - // name. - String name = ringtone.getTitle(preference.getContext()); - preference.setSummary(name); - } - } - - } else { - // For all other preferences, set the summary to the value's - // simple string representation. - preference.setSummary(stringValue); - } - return true; - } - }; - - /** - * Binds a preference's summary to its value. More specifically, when the - * preference's value is changed, its summary (line of text below the - * preference title) is updated to reflect the value. The summary is also - * immediately updated upon calling this method. The exact display format is - * dependent on the type of preference. - * - * @see #sBindPreferenceSummaryToValueListener - */ - private static void bindPreferenceSummaryToValue(Preference preference) { - // Set the listener to watch for value changes. - preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); - - // Trigger the listener immediately with the preference's - // current value. - sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, - PreferenceManager - .getDefaultSharedPreferences(preference.getContext()) - .getString(preference.getKey(), "")); - } - - /** - * This fragment shows general preferences only. It is used when the - * activity is showing a two-pane settings UI. - */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public static class GeneralPreferenceFragment extends PreferenceFragment { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pref_general); - - // Bind the summaries of EditText/List/Dialog/Ringtone preferences - // to their values. When their values change, their summaries are - // updated to reflect the new value, per the Android Design - // guidelines. - bindPreferenceSummaryToValue(findPreference("example_text")); - bindPreferenceSummaryToValue(findPreference("example_list")); - } - } - - /** - * This fragment shows notification preferences only. It is used when the - * activity is showing a two-pane settings UI. - */ - @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); - - // Bind the summaries of EditText/List/Dialog/Ringtone preferences - // to their values. When their values change, their summaries are - // updated to reflect the new value, per the Android Design - // guidelines. - bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone")); - } - } - - /** - * This fragment shows data and sync preferences only. It is used when the - * activity is showing a two-pane settings UI. - */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public static class DataSyncPreferenceFragment extends PreferenceFragment { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pref_data_sync); - - // Bind the summaries of EditText/List/Dialog/Ringtone preferences - // to their values. When their values change, their summaries are - // updated to reflect the new value, per the Android Design - // guidelines. - bindPreferenceSummaryToValue(findPreference("sync_frequency")); - } - } -} diff --git a/app/src/main/java/simple/SimpleXml.java b/app/src/main/java/simple/SimpleXml.java index cf42858..c13aac9 100644 --- a/app/src/main/java/simple/SimpleXml.java +++ b/app/src/main/java/simple/SimpleXml.java @@ -11,7 +11,7 @@ import java.util.ArrayList; public class SimpleXml { - public SimpleXmlNode parse(InputStream in) throws IOException, XmlPullParserException + public static SimpleXmlNode parse(InputStream in) throws IOException, XmlPullParserException { try { @@ -24,9 +24,26 @@ public class SimpleXml { in.close(); } + /** + import javax.xml.parsers.DocumentBuilder; + import javax.xml.parsers.DocumentBuilderFactory; + import javax.xml.parsers.ParserConfigurationException; + + Document d = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(bis); + NodeList segments = d.getElementsByTagName("segment"); + segments.item(0).getAttributes().getNamedItem("from") + catch (SAXException e) + { + e.printStackTrace(); + } + catch (ParserConfigurationException e) + { + e.printStackTrace(); + } + */ } - public SimpleXmlNode readFeed(XmlPullParser parser) throws IOException, XmlPullParserException + public static SimpleXmlNode readFeed(XmlPullParser parser) throws IOException, XmlPullParserException { ArrayList stack = new ArrayList(); while (parser.next() != XmlPullParser.START_TAG) {} @@ -54,6 +71,8 @@ public class SimpleXml else return stack.get(0); } + else if (parser.getEventType() == XmlPullParser.TEXT) + stack.get(stack.size() - 1).value += parser.getText(); } return null; } diff --git a/app/src/main/java/simple/SimpleXmlNode.java b/app/src/main/java/simple/SimpleXmlNode.java index d61f359..35f06b3 100644 --- a/app/src/main/java/simple/SimpleXmlNode.java +++ b/app/src/main/java/simple/SimpleXmlNode.java @@ -7,7 +7,7 @@ import java.util.Map; public class SimpleXmlNode { - public String name; + public String name, value; public Map attributes; public List children; public Map> childrenByName; diff --git a/app/src/main/res/layout/tripitem.xml b/app/src/main/res/layout/tripitem.xml new file mode 100644 index 0000000..895dcba --- /dev/null +++ b/app/src/main/res/layout/tripitem.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f445de4..8e06a20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,7 @@ Electron Reminder - MainActivity + Electron Reminder Settings + Suburban Changes Detected + Timetable changes: %1$s to %2$s, %3$s diff --git a/app/src/main/res/values/strings_activity_settings.xml b/app/src/main/res/values/strings_activity_settings.xml deleted file mode 100644 index 2686b04..0000000 --- a/app/src/main/res/values/strings_activity_settings.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - General - - Enable social recommendations - Recommendations for people to contact - based on your message history - - - Display name - John Smith - - Add friends to messages - - Always - When possible - Never - - - 1 - 0 - -1 - - - - Data & sync - - Sync frequency - - 15 minutes - 30 minutes - 1 hour - 3 hours - 6 hours - Never - - - 15 - 30 - 60 - 180 - 360 - -1 - - - System sync settings - - - Notifications - - New message notifications - - Ringtone - Silent - - Vibrate - diff --git a/app/src/main/res/xml/pref_data_sync.xml b/app/src/main/res/xml/pref_data_sync.xml deleted file mode 100644 index ffda831..0000000 --- a/app/src/main/res/xml/pref_data_sync.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml deleted file mode 100644 index c49cbed..0000000 --- a/app/src/main/res/xml/pref_general.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/xml/pref_headers.xml b/app/src/main/res/xml/pref_headers.xml deleted file mode 100644 index bc7aed5..0000000 --- a/app/src/main/res/xml/pref_headers.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - -
- -
- -
- - diff --git a/app/src/main/res/xml/pref_notification.xml b/app/src/main/res/xml/pref_notification.xml deleted file mode 100644 index b4b8cae..0000000 --- a/app/src/main/res/xml/pref_notification.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - -