diff --git a/.gitignore b/.gitignore index a8b0d1d..d3c6c1b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,45 @@ google-services.json # Android Profiling *.hprof +## Suggested ^^ + +## From TicTacToe project for good measure. vv + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Log/OS Files +*.log + +# Android Studio generated files and folders +captures/ +.externalNativeBuild/ +.cxx/ +*.apk +output.json + +# IntelliJ +*.iml +.idea/ +misc.xml +deploymentTargetDropDown.xml +render.experimental.xml + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Android Profiling +*.hprof +/app/debug/output-metadata.json + +# Ha! +keystore/* +release diff --git a/app/.gitignore b/app/.gitignore new file mode 100755 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100755 index 0000000..a32c8c4 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 24 + buildToolsVersion "24.0.2" + defaultConfig { + applicationId "com.hyperling.apps.wheretheriverfrowns" + minSdkVersion 15 + targetSdkVersion 24 + versionCode 3 + versionName "1.02" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + 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:24.2.1' + compile 'com.android.support:support-v4:24.2.1' + compile 'com.android.support:support-vector-drawable:24.2.1' + testCompile 'junit:junit:4.12' + compile 'com.google.android.gms:play-services-appindexing:8.4.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100755 index 0000000..2141ffd --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/ling/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 *; +#} diff --git a/app/src/androidTest/java/com/hyperling/apps/wheretheriverfrowns/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/hyperling/apps/wheretheriverfrowns/ExampleInstrumentedTest.java new file mode 100755 index 0000000..b63ec57 --- /dev/null +++ b/app/src/androidTest/java/com/hyperling/apps/wheretheriverfrowns/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.hyperling.apps.wheretheriverfrowns; + +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() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.hyperling.apps.wheretheriverfrowns", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000..9c4f4d5 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/DebugActivity.java b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/DebugActivity.java new file mode 100755 index 0000000..57ca0cd --- /dev/null +++ b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/DebugActivity.java @@ -0,0 +1,158 @@ +package com.hyperling.apps.wheretheriverfrowns; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.os.Handler; +import android.os.Message; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.Map; +import java.util.TimerTask; + +public class DebugActivity extends AppCompatActivity { + + private ActionBar actionBar; + + private SharedPreferences sharedPreferences; + + private String sharedPreferencesKey, debugKey, + TAG; + + private LinearLayout debugLayout; + + private Button btnDebug, btnResetPreferences; + + private boolean DEBUG; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_debug); + + // Load Strings + sharedPreferencesKey = getString(R.string.sharedPreferencesKey); + debugKey = getString(R.string.debugKey); + + TAG = getString(R.string.TAG); + + // Get Preferences + sharedPreferences = getSharedPreferences(sharedPreferencesKey, MODE_PRIVATE); + DEBUG = sharedPreferences.getBoolean(debugKey, false); + + Map sharedPreferencesAll = sharedPreferences.getAll(); + + // Initialize UI + actionBar = DebugActivity.this.getSupportActionBar(); + actionBar.setTitle(getString(R.string.debugActivityTitle)); + actionBar.setDisplayHomeAsUpEnabled(true); + + btnDebug = (Button) findViewById(R.id.btnDebugExit); + btnResetPreferences = (Button) findViewById(R.id.btnResetPreferences); + + // Disable buttons so they aren't accidentally clicked + setButtonsEnabled(false); + + if (DEBUG){ + btnDebug.setBackgroundColor(Color.GREEN); + } + else{ + btnDebug.setBackgroundColor(Color.RED); + } + btnDebug.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(debugKey, !DEBUG); + editor.apply(); + + resetScreen(); + } + }); + + btnResetPreferences.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.clear(); + editor.apply(); + + resetScreen(); + } + }); + + debugLayout = (LinearLayout) findViewById(R.id.debugLayout); + + for (String key : sharedPreferencesAll.keySet()){ + if (DEBUG){ + Log.d(TAG, "DebugActivity: key=" + key + " value=" + sharedPreferencesAll.get(key)); + } + TextView textView = new TextView(btnDebug.getContext()); + textView.setText(key + "=" + sharedPreferencesAll.get(key)); + + debugLayout.addView(textView); + } + + Runnable runnable = new TimerTask() { + @Override + public void run() { + if (DEBUG) Log.d(TAG, "DebugActivity: Waiting 2 seconds to enable buttons."); + synchronized (this){ + try{ + wait(2000); + if (DEBUG) Log.d(TAG, "DebugActivity: Done waiting."); + } + catch (Exception e){ + e.printStackTrace(); + if (DEBUG) Log.d(TAG, "DebugActivity: Failed to wait."); + } + } + enableButtonsHandler.sendEmptyMessage(0); + } + }; + + Thread thread = new Thread(runnable); + thread.start(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + int id = item.getItemId(); + + switch (id){ + case android.R.id.home: + finish(); + } + + return super.onOptionsItemSelected(item); + } + + private void resetScreen(){ + finish(); + Intent refreshDebugActivity = new Intent(DebugActivity.this, DebugActivity.class); + startActivity(refreshDebugActivity); + } + + private void setButtonsEnabled(boolean enabled){ + btnDebug.setEnabled(enabled); + btnResetPreferences.setEnabled(enabled); + } + + private Handler enableButtonsHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + setButtonsEnabled(true); + } + }; +} diff --git a/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/MainActivity.java b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/MainActivity.java new file mode 100755 index 0000000..e580367 --- /dev/null +++ b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/MainActivity.java @@ -0,0 +1,394 @@ +package com.hyperling.apps.wheretheriverfrowns; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.StrictMode; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.gms.appindexing.Action; +import com.google.android.gms.appindexing.AppIndex; +import com.google.android.gms.appindexing.Thing; +import com.google.android.gms.common.api.GoogleApiClient; + +public class MainActivity extends AppCompatActivity { + private SharedPreferences sharedPreferences; + + private String sharedPreferencesKey, topArticleKey, externalBrowserKey, checkForNewArticlesKey, debugKey, + networkConnected, networkRoaming, networkDisconnected, + TAG, + action, url; + + private ActionBar actionBar; + + private ProgressBar progressBar; + + private LinearLayout layout; + private Button btnExample; + + private boolean homePage, externalBrowser; + + private boolean DEBUG; + + private WebPage webPage; + + private int debugCount, debugMax = 13; + /** + * ATTENTION: This was auto-generated to implement the App Indexing API. + * See https://g.co/AppIndexing/AndroidStudio for more information. + */ + private GoogleApiClient client; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + /*** Initialize Strings ***/ + // Keys + sharedPreferencesKey = getResources().getString(R.string.sharedPreferencesKey); + topArticleKey = getResources().getString(R.string.topArticleKey); + externalBrowserKey = getResources().getString(R.string.externalBrowserKey); + checkForNewArticlesKey = getResources().getString(R.string.checkForNewArticlesKey); + debugKey = getString(R.string.debugKey); + + // Strings + networkConnected = getResources().getString(R.string.networkConnected); + networkRoaming = getResources().getString(R.string.networkRoaming); + networkDisconnected = getResources().getString(R.string.networkDisconnected); + TAG = getResources().getString(R.string.TAG); + + /*** Get Preferences ***/ + sharedPreferences = getSharedPreferences(sharedPreferencesKey, MODE_PRIVATE); + externalBrowser = sharedPreferences.getBoolean(externalBrowserKey, true); + DEBUG = sharedPreferences.getBoolean(debugKey, false); + + /*** Initialize UI Objects ***/ + actionBar = MainActivity.this.getSupportActionBar(); + actionBar.setTitle(getString(R.string.app_title)); + actionBar.setHomeButtonEnabled(true); + + progressBar = (ProgressBar) findViewById(R.id.progressBar); + + layout = (LinearLayout) findViewById(R.id.layout); + layout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + debugCount++; + if (debugCount >= debugMax) { + Intent debugIntent = new Intent(MainActivity.this, DebugActivity.class); + startActivity(debugIntent); + } + if (DEBUG) { + Log.d(TAG, "MainActivity: debugCount=" + debugCount); + } + } + }); + + btnExample = (Button) findViewById(R.id.btnExample); + + // Data checks + action = MainActivity.this.getIntent().getAction(); + if (action.isEmpty() || !action.contains("http")) { + url = getResources().getString(R.string.allStories); + homePage = true; + } else { + url = action; + homePage = false; + } + if (DEBUG) Log.d(TAG, "MainActivity: action=" + action + " url=" + url); + + // Allow webpage download + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(policy); + + // Start the fun + this.refreshWebPage(); + + // ATTENTION: This was auto-generated to implement the App Indexing API. + // See https://g.co/AppIndexing/AndroidStudio for more information. + client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build(); + } + + private String isNetworkAvailable() { + NetworkInfo info = ((ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo(); + + try { + if (info.isConnected()) { + return networkConnected; + } else if (info.isRoaming()) { + return networkRoaming; + } + } catch (Exception e) { + e.printStackTrace(); + } + + return networkDisconnected; + } + + private void refreshWebPage() { + String status = isNetworkAvailable(); + + if (status.equals(networkConnected)) { + Runnable getWebsiteData = new Runnable() { + @Override + public void run() { + webPage = new WebPage(getBaseContext(), url); + loadArticles.sendEmptyMessage(0); + } + }; + + Thread t = new Thread(getWebsiteData); + progressBar.setIndeterminate(true); + progressBar.setVisibility(View.VISIBLE); + t.start(); + } else if (status.equals(networkRoaming)) { + Toast.makeText(this, "Currently Roaming -- Not Updating", Toast.LENGTH_LONG).show(); + } else if (status.equals(networkDisconnected)) { + Toast.makeText(this, "Network Connection Not Found", Toast.LENGTH_LONG).show(); + } + } + + private Handler loadArticles = new Handler() { + @Override + public void handleMessage(Message msg) { + + layout.removeAllViews(); + + progressBar.setMax(webPage.getTitles().size()); + + int index; + + for (String title : webPage.getTitles()) { + index = webPage.getTitles().indexOf(title); + + progressBar.setProgress(index); + progressBar.invalidate(); + + String date = webPage.getDates().get(index); + if (DEBUG) Log.d(TAG, "MainActivity: date=" + date + " title=" + title); + String text = date + '\n' + title; + + // If we are on all-stories and on the first article + if (homePage && webPage.getTitles().indexOf(title) == 0) { + // Only update the Top Article if necessary + String oldTitle = sharedPreferences.getString(topArticleKey, ""); + if (!oldTitle.equals(title)) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(topArticleKey, title); + editor.apply(); + if (DEBUG) Log.d(TAG, "MainActivity: Top Article updated."); + } + + if (DEBUG) { + Log.d(TAG, "MainActivity: title=" + title); + Log.d(TAG, "MainActivity: oldTitle=" + oldTitle); + Log.d(TAG, "MainActivity: sharedPreferences.TopArticle=" + sharedPreferences.getString(topArticleKey, "")); + } + } + + // Display all articles + if (!action.equals(url)) { + TextView spacer = new TextView(btnExample.getContext()); + Button btn = new Button(btnExample.getContext()); + btn.setText(text); + if (sharedPreferences.getBoolean(title, false)) { + btn.setBackgroundColor(getResources().getColor(R.color.articleRead)); + } else { + btn.setBackgroundColor(getResources().getColor(R.color.colorPrimary)); + } + btn.setTextColor(Color.WHITE); + btn.setVisibility(View.VISIBLE); + btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Button b = (Button) v; + + String text = b.getText().toString(); + int newline = text.indexOf('\n'); + + String articleTitle = text.substring(newline + 1); + String articleLink = webPage.getLinks().get(webPage.getTitles().indexOf(articleTitle)); + + if (externalBrowser) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(articleLink)); + startActivity(browserIntent); + } else { + Intent openArticle = new Intent(MainActivity.this, MainActivity.class); + openArticle.setAction(articleLink); + MainActivity.this.startActivity(openArticle); + } + + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(articleTitle, true); + editor.apply(); + + b.setBackgroundColor(getResources().getColor(R.color.articleRead)); + } + }); + layout.addView(spacer); + layout.addView(btn); + } + // Internal browser + else { + TextView btn = new TextView(btnExample.getContext()); + btn.setText(date + '\n' + title); + btn.setTextColor(getResources().getColor(R.color.colorPrimary)); + btn.setTextSize(20); + btn.setVisibility(View.VISIBLE); + btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TextView b = (TextView) v; + + String text = b.getText().toString(); + int newline = text.indexOf('\n'); + + String articleTitle = text.substring(newline + 1); + String articleLink = webPage.getLinks().get(webPage.getTitles().indexOf(articleTitle)); + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(articleLink)); + startActivity(browserIntent); + } + }); + + layout.addView(btn); + } + } + + if (webPage.getTitles().size() == 1) { + layout.addView(webPage.getContent()); + } + + progressBar.setVisibility(View.GONE); + + // Reschedule the service + Intent restartService = new Intent(MainActivity.this, MyService.class); + startService(restartService); + + super.handleMessage(msg); + } + }; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + + MenuItem refresh = menu.findItem(R.id.refresh); + refresh.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + refreshWebPage(); + return true; + } + }); + + MenuItem checkForNewArticles = menu.findItem(R.id.checkForNewArticles); + checkForNewArticles.setChecked(sharedPreferences.getBoolean(checkForNewArticlesKey, true)); + + MenuItem externalBrowser = menu.findItem(R.id.externalBrowser); + externalBrowser.setChecked(sharedPreferences.getBoolean(externalBrowserKey, true)); + + return super.onCreateOptionsMenu(menu); + } +/* + @Override + public void onOptionsMenuClosed(Menu menu) { + super.onOptionsMenuClosed(menu); + } +*/ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + int id = item.getItemId(); + + String menuItemKey; + + switch (id) { + case R.id.externalBrowser: + menuItemKey = externalBrowserKey; + break; + case R.id.checkForNewArticles: + menuItemKey = checkForNewArticlesKey; + break; + default: + return super.onOptionsItemSelected(item); + } + + boolean newValue = !sharedPreferences.getBoolean(menuItemKey, true); + + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(menuItemKey, newValue); + editor.apply(); + + if (id == R.id.externalBrowser) { + finish(); + startActivity(this.getIntent()); + } + + item.setChecked(newValue); + + Toast.makeText(this, "Settings saved.", Toast.LENGTH_LONG).show(); + return true; + } + + @Override + protected void onResume() { + debugCount = 0; + + super.onResume(); + } + + /** + * ATTENTION: This was auto-generated to implement the App Indexing API. + * See https://g.co/AppIndexing/AndroidStudio for more information. + */ + public Action getIndexApiAction() { + Thing object = new Thing.Builder() + .setName("Main Page") // TODO: Define a title for the content shown. + // TODO: Make sure this auto-generated URL is correct. + .setUrl(Uri.parse("http://[ENTER-YOUR-URL-HERE]")) + .build(); + return new Action.Builder(Action.TYPE_VIEW) + .setObject(object) + .setActionStatus(Action.STATUS_TYPE_COMPLETED) + .build(); + } + + @Override + public void onStart() { + super.onStart(); + + // ATTENTION: This was auto-generated to implement the App Indexing API. + // See https://g.co/AppIndexing/AndroidStudio for more information. + client.connect(); + AppIndex.AppIndexApi.start(client, getIndexApiAction()); + } + + @Override + public void onStop() { + super.onStop(); + + // ATTENTION: This was auto-generated to implement the App Indexing API. + // See https://g.co/AppIndexing/AndroidStudio for more information. + AppIndex.AppIndexApi.end(client, getIndexApiAction()); + client.disconnect(); + } +} diff --git a/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/MyService.java b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/MyService.java new file mode 100755 index 0000000..b4eaafd --- /dev/null +++ b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/MyService.java @@ -0,0 +1,164 @@ +package com.hyperling.apps.wheretheriverfrowns; + +import android.app.AlarmManager; +import android.app.IntentService; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.PowerManager; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.TaskStackBuilder; +import android.util.Log; + +import java.util.Date; + +/** + * Created by ling on 10/2/16. + */ + +public class MyService extends IntentService { + + private String sharedPreferencesKey, topArticleKey, checkForNewArticlesKey, debugKey, lastServiceRunKey, nextServiceRunKey, + TAG, + url; + + private SharedPreferences sharedPreferences; + + private PowerManager.WakeLock wakeLock; + + private AlarmManager alarmManager; + private PendingIntent reschedule; + + private boolean DEBUG; + + public MyService(){ + super("MyService"); + } + + @Override + protected void onHandleIntent(Intent intent) { + // Keys + sharedPreferencesKey = getString(R.string.sharedPreferencesKey); + topArticleKey = getString(R.string.topArticleKey); + checkForNewArticlesKey = getString(R.string.checkForNewArticlesKey); + debugKey = getString(R.string.debugKey); + lastServiceRunKey = getString(R.string.lastServiceRunKey); + nextServiceRunKey = getString(R.string.nextServiceRunKey); + + // Strings + TAG = getString(R.string.TAG); + url = getString(R.string.allStories); + + // Preferences + sharedPreferences = getSharedPreferences(sharedPreferencesKey, MODE_PRIVATE); + DEBUG = sharedPreferences.getBoolean(debugKey, false); + + // Wake Lock + wakeLock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + wakeLock.acquire(); + + if (DEBUG) Log.d(TAG, "MyService: Service started."); + + // Check if we have a connection + NetworkInfo info = ((ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE)).getActiveNetworkInfo(); + if (info == null || !info.isConnected() || info.isRoaming()){ + if (DEBUG) Log.d(TAG, "MyService: Not polling due to network."); + rescheduleService(1); + stopSelf(); + return; + } + + if (sharedPreferences.getBoolean(checkForNewArticlesKey, true)) { + wakeLock.acquire(); + WebPage webPage = new WebPage(this, url); + if (!webPage.getTitles().isEmpty()) { + String newTopArticle = webPage.getTitles().get(0); + String newDate = webPage.getDates().get(0); + + String lastTopArticle = sharedPreferences.getString("TopArticle", newTopArticle); + + if (DEBUG){ + Log.d(TAG, "MyService: lastTopArticle=" + lastTopArticle); + Log.d(TAG, "MyService: newTopArticle=" + newTopArticle); + } + + if (!newTopArticle.equals(lastTopArticle)) { + createNotification(newDate, newTopArticle); + } else { + rescheduleService(1); + } + + // Release lock and resources + wakeLock.release(); + stopSelf(); + } + } + } + + private void createNotification(String articleDate, String articleTitle){ + if (DEBUG){ + Log.d(TAG, "MyService: createNotification"); + } + + // Create notification + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.wtrf_anarchy) + .setColor(getResources().getColor(R.color.colorPrimary)) + .setContentTitle(getResources().getString(R.string.newArticleNotificationTitle) + " - " + articleDate) + .setContentText(articleTitle) + .setAutoCancel(true); + + // Link so the notification opens WTRF + Intent notificationLink = new Intent(this, MainActivity.class); + notificationLink.setAction(""); + + // Allow using back to leave app + TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); + stackBuilder.addParentStack(MainActivity.class); + stackBuilder.addNextIntent(notificationLink); + PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + + // Add it all to the notification + notificationBuilder.setContentIntent(pendingIntent); + + // Display notification + NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + manager.notify(TAG, 0, notificationBuilder.build()); + } + + private void rescheduleService(int hours){ + // Schedule next run after an hour or so + alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + reschedule = PendingIntent.getBroadcast(this, 0, new Intent("com.hyperling.wheretheriverfrowns.ALARM_STARTED"), 0); + + long convertedHours = 1000*60*60*hours; + + long currentTime = System.currentTimeMillis(); + + alarmManager.cancel(reschedule); + alarmManager.set(AlarmManager.RTC_WAKEUP, currentTime + convertedHours, reschedule); + + if (DEBUG) { + Log.d(TAG, "MyService: rescheduleService: Rescheduled for " + hours + " hour(s)."); + Log.d(TAG, "MyService: rescheduleService: Current time= " + currentTime + ", reschedule time=" + currentTime + convertedHours); + + Date date = new Date(); + String prettyDate; + SharedPreferences.Editor editor = sharedPreferences.edit(); + + date.setTime(currentTime); + prettyDate = date.toString(); + editor.putString(lastServiceRunKey, prettyDate); + + date.setTime(currentTime + convertedHours); + prettyDate = date.toString(); + editor.putString(nextServiceRunKey, prettyDate); + + editor.apply(); + + } + } +} diff --git a/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/ServiceReceiver.java b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/ServiceReceiver.java new file mode 100755 index 0000000..0f34165 --- /dev/null +++ b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/ServiceReceiver.java @@ -0,0 +1,44 @@ +package com.hyperling.apps.wheretheriverfrowns; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.support.v4.content.WakefulBroadcastReceiver; +import android.util.Log; + +import static android.content.Context.MODE_PRIVATE; + +/** + * Created by ling on 10/11/16. + */ + +public class ServiceReceiver extends WakefulBroadcastReceiver { + private SharedPreferences sharedPreferences; + + private String sharedPreferencesKey, debugKey, TAG; + + private static boolean DEBUG; + + @Override + public void onReceive(Context context, Intent intent) { + + // Keys + sharedPreferencesKey = context.getString(R.string.sharedPreferencesKey); + debugKey = context.getString(R.string.debugKey); + + // Strings + TAG = context.getResources().getString(R.string.TAG); + + // Preferences + sharedPreferences = context.getSharedPreferences(sharedPreferencesKey, MODE_PRIVATE); + DEBUG = sharedPreferences.getBoolean(debugKey, false); + + if (DEBUG) { + Log.d(TAG, "ServiceReceiver: onReceive: intent.getAction()=" + intent.getAction()); + } + + Intent serviceIntent = new Intent(context, MyService.class); + context.startService(serviceIntent); + } +} diff --git a/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/WebPage.java b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/WebPage.java new file mode 100755 index 0000000..a0a89fe --- /dev/null +++ b/app/src/main/java/com/hyperling/apps/wheretheriverfrowns/WebPage.java @@ -0,0 +1,234 @@ +package com.hyperling.apps.wheretheriverfrowns; + +import android.content.Context; +import android.graphics.Color; +import android.util.Log; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; + +/** + * Created by ling on 9/24/16. + */ + +public class WebPage { + private Context context; + + private String link; + private String html = ""; + + private int index; + + private ArrayList titles; + private ArrayList links; + private ArrayList dates; + + private LinearLayout content; + + private boolean DEBUG = false; + private String TAG; + + public WebPage(Context context, String link) { + TAG = context.getResources().getString(R.string.TAG); + + this.context = context; + this.link = link; + URL url; + InputStream is = null; + BufferedReader br; + String line; + + titles = new ArrayList<>(); + links = new ArrayList<>(); + dates = new ArrayList<>(); + + try { + url = new URL(link); + is = url.openStream(); // throws an IOException + br = new BufferedReader(new InputStreamReader(is)); + + while ((line = br.readLine()) != null) { + html = html.concat(line); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (is != null) is.close(); + } catch (IOException ioe) { + // nothing to see here + } + } + + if (html.length() > 0) { + html = html.replace(""", "\""); + + int nextArticle; + index = 0; + while (findArticle() > 0) { + // Move to the next article + nextArticle = findArticle(); + html = html.substring(nextArticle); + + // Get the url + html = html.substring(html.indexOf("href=") + 6); + String articleLink = html.substring(0, html.indexOf('\"')); + links.add(index, articleLink); + + // Get the title + html = html.substring(html.indexOf('>') + 1); + String articleTitle = html.substring(0, html.indexOf("")); + articleTitle = removeSpaces(articleTitle); + titles.add(index, articleTitle); + + // Get the date + /* Date looks like: +

+ 9/21/2016 +

+ */ + html = html.substring(html.indexOf("

")); + html = html.substring(html.indexOf('>') + 1); + html = html.substring(html.indexOf('>') + 1); + String articleDate = html.substring(0, html.indexOf("")); + articleDate = removeSpaces(articleDate); + dates.add(index, articleDate); + + index += 1; + } + + if (context != null) { + if (index == 1) { + // Page is an article, get text + /* Text starts at: +

+ */ + content = new LinearLayout(context); + content.setOrientation(LinearLayout.VERTICAL); + + html = html.substring(html.indexOf("
")); + html = html.substring(html.indexOf('>') + 1); + + if (DEBUG) Log.d(TAG, "DEBUG: WebPage: html=" + html); + + int level = 1; + String contentText = ""; + while (level > 0) { + /* + Paragraphs looks like: +
+ + New TextViews at: +

+ + level += 1 at < + level -= 2 at < + */ + + while (html.indexOf('&') < html.indexOf('<')) { + contentText = contentText.concat(html.substring(0, html.indexOf('&'))); + html = html.substring(html.indexOf(';') + 1); + } + + contentText = contentText.concat(html.substring(0, html.indexOf('<'))); + contentText = removeSpaces(contentText); + + + html = html.substring(html.indexOf('<')); + + // TODO: Testing only + if (level > 0) { + //System.out.println("DEBUG: level=" + level + " html=" + html); + //System.out.println("DEBUG: contentText=" + contentText); + } + + if (html.indexOf("
") == 0) { + + TextView newView = new TextView(context); + newView.setText(contentText); + newView.setTextColor(Color.BLACK); + content.addView(newView); + + contentText = ""; + } else if (html.indexOf("
") == 0 || html.indexOf("
") == 0) { + contentText = contentText.concat("\n"); + level -= 1; + } else if (html.indexOf("") < html.indexOf(">")) { + level -= 1; + } + + html = html.substring(html.indexOf('>') + 1); + level += 1; + } + + TextView newView = new TextView(context); + newView.setText(contentText); + newView.setTextColor(Color.BLACK); + content.addView(newView); + } + } + } + } + + private int findArticle(){ + /* Example of an article title: + Kite Line – September 16, 2016: National Prison Strike Updates + */ + return html.indexOf(" getTitles(){ + return titles; + } + + public ArrayList getLinks(){ + return links; + } + + public ArrayList getDates(){ + return dates; + } + + public LinearLayout getContent(){ + return content; + } + + private String removeSpaces(String string){ + while(string.contains(" ")) { + string = string.replace(" ", " "); + } + //string = string.replace("\n", ""); + string = string.replace("\t", ""); + return string; + } + + public void setDEBUG(boolean debug){ + this.DEBUG = debug; + } +} diff --git a/app/src/main/res/drawable/ic_info_black_24dp.xml b/app/src/main/res/drawable/ic_info_black_24dp.xml new file mode 100755 index 0000000..34b8202 --- /dev/null +++ b/app/src/main/res/drawable/ic_info_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100755 index 0000000..e3400cf --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sync_black_24dp.xml b/app/src/main/res/drawable/ic_sync_black_24dp.xml new file mode 100755 index 0000000..5a283aa --- /dev/null +++ b/app/src/main/res/drawable/ic_sync_black_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/wtrf.png b/app/src/main/res/drawable/wtrf.png new file mode 100755 index 0000000..86a33b4 Binary files /dev/null and b/app/src/main/res/drawable/wtrf.png differ diff --git a/app/src/main/res/drawable/wtrf_anarchy.png b/app/src/main/res/drawable/wtrf_anarchy.png new file mode 100755 index 0000000..d411559 Binary files /dev/null and b/app/src/main/res/drawable/wtrf_anarchy.png differ diff --git a/app/src/main/res/layout/activity_debug.xml b/app/src/main/res/layout/activity_debug.xml new file mode 100755 index 0000000..a19690b --- /dev/null +++ b/app/src/main/res/layout/activity_debug.xml @@ -0,0 +1,26 @@ + + + +