diff --git a/app/build.gradle b/app/build.gradle
index cadb5a5..46c9486 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ android {
defaultConfig {
applicationId "net.headlezz.notificationlogger"
- minSdkVersion 18
+ minSdkVersion 19
targetSdkVersion 23
versionCode 1
versionName "1.0"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 292c1b9..527db7a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,7 @@
package="net.headlezz.notificationlogger">
-
+
-
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/net/headlezz/notificationlogger/Application.java b/app/src/main/java/net/headlezz/notificationlogger/Application.java
new file mode 100644
index 0000000..50f51f6
--- /dev/null
+++ b/app/src/main/java/net/headlezz/notificationlogger/Application.java
@@ -0,0 +1,12 @@
+package net.headlezz.notificationlogger;
+
+import timber.log.Timber;
+
+public class Application extends android.app.Application {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Timber.plant(new Timber.DebugTree());
+ }
+}
diff --git a/app/src/main/java/net/headlezz/notificationlogger/MainActivity.java b/app/src/main/java/net/headlezz/notificationlogger/MainActivity.java
index e3f7d4b..0e0748c 100644
--- a/app/src/main/java/net/headlezz/notificationlogger/MainActivity.java
+++ b/app/src/main/java/net/headlezz/notificationlogger/MainActivity.java
@@ -1,21 +1,26 @@
package net.headlezz.notificationlogger;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
+import android.support.v7.preference.PreferenceManager;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import net.headlezz.notificationlogger.createnotification.NotificationCreationFragment;
+import net.headlezz.notificationlogger.logger.LoggerUtils;
+import net.headlezz.notificationlogger.presenter.LoggerWarningPresenter;
import butterknife.Bind;
import butterknife.ButterKnife;
-public class MainActivity extends AppCompatActivity {
+public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener, LoggerWarningPresenter.LoggerWarningView {
@Bind(R.id.toolbar)
Toolbar mToolbar;
@@ -23,14 +28,21 @@ public class MainActivity extends AppCompatActivity {
@Bind(R.id.main_menu_fab_add_notification)
FloatingActionButton mFAB;
+ SharedPreferences mSharedPrefs;
+
+ LoggerWarningPresenter mLoggerWarningPresenter;
+ Snackbar mLoggerWarningView;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
ButterKnife.bind(this);
-
setSupportActionBar(mToolbar);
+ mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mLoggerWarningPresenter = new LoggerWarningPresenter(this);
+
mFAB.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -38,7 +50,7 @@ public class MainActivity extends AppCompatActivity {
}
});
- if(getSupportFragmentManager().findFragmentById(R.id.main_menu_fragment_container) == null) {
+ if (getSupportFragmentManager().findFragmentById(R.id.main_menu_fragment_container) == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.main_menu_fragment_container, new NotificationListFragment())
.commit();
@@ -48,10 +60,25 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onResume() {
super.onResume();
+ if(!LoggerUtils.isLoggerRunning())
+ LoggerUtils.buildNotificationAccessDialog(this).show();
if(getSupportFragmentManager().getBackStackEntryCount() > 0)
mFAB.setVisibility(View.GONE);
}
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+ mLoggerWarningPresenter.setCurrentLoggerPreference(mSharedPrefs.getBoolean("pref_logging_enabled", true));
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ }
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_activity, menu);
@@ -60,11 +87,11 @@ public class MainActivity extends AppCompatActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if(item.getItemId() == R.id.menu_settings) {
+ if (item.getItemId() == R.id.menu_settings) {
Intent intent = new Intent(this, PreferenceActivity.class);
startActivity(intent);
return true;
- } else if(item.getItemId() == android.R.id.home) {
+ } else if (item.getItemId() == android.R.id.home) {
onBackPressed();
}
return super.onOptionsItemSelected(item);
@@ -86,11 +113,48 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onBackPressed() {
// make fab visible and change title if we come back to first fragment
- if(getSupportFragmentManager().getBackStackEntryCount() == 1) {
+ if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
mFAB.setVisibility(View.VISIBLE);
getSupportActionBar().setTitle(getString(R.string.app_name));
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
super.onBackPressed();
}
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals("pref_logging_enabled"))
+ mLoggerWarningPresenter.setCurrentLoggerPreference(sharedPreferences.getBoolean("pref_logging_enabled", true));
+ }
+
+ @Override
+ public void showLoggerWarning() {
+ if (mLoggerWarningView == null) {
+ mLoggerWarningView = Snackbar.make(findViewById(R.id.viewholder), R.string.main_snack_logging_disabled_warning, Snackbar.LENGTH_INDEFINITE);
+ mLoggerWarningView.setAction(R.string.main_snack_logging_disabled_warning_action, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSharedPrefs.edit().putBoolean("pref_logging_enabled", true).apply();
+ }
+ });
+ // fix for FAB not moving down after dismiss
+ mLoggerWarningView.getView().addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ mFAB.setTranslationY(0);
+ }
+ });
+ }
+ mLoggerWarningView.show();
+ }
+
+ @Override
+ public void hideLoggerWarning() {
+ if (mLoggerWarningView != null && mLoggerWarningView.isShownOrQueued())
+ mLoggerWarningView.dismiss();
+ }
}
diff --git a/app/src/main/java/net/headlezz/notificationlogger/PackageUtils.java b/app/src/main/java/net/headlezz/notificationlogger/PackageUtils.java
new file mode 100644
index 0000000..0d32c08
--- /dev/null
+++ b/app/src/main/java/net/headlezz/notificationlogger/PackageUtils.java
@@ -0,0 +1,16 @@
+package net.headlezz.notificationlogger;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+public class PackageUtils {
+
+ public static String getAppName(Context context, String packageName) throws PackageManager.NameNotFoundException {
+ PackageManager pm = context.getPackageManager();
+ ApplicationInfo appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
+ return (String) pm.getApplicationLabel(appInfo);
+ }
+
+
+}
diff --git a/app/src/main/java/net/headlezz/notificationlogger/PreferenceFragment.java b/app/src/main/java/net/headlezz/notificationlogger/PreferenceFragment.java
index c5a7036..625bcd0 100644
--- a/app/src/main/java/net/headlezz/notificationlogger/PreferenceFragment.java
+++ b/app/src/main/java/net/headlezz/notificationlogger/PreferenceFragment.java
@@ -1,17 +1,23 @@
package net.headlezz.notificationlogger;
import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.DialogInterface;
import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.AlertDialog;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
+import net.headlezz.notificationlogger.logger.Logged_notificationTable;
+
public class PreferenceFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceClickListener {
private PreferenceCallbacks mCallbacks;
final String PREF_ABOUT = "pref_about";
final String PREF_BLACKLIST = "pref_blacklist";
+ final String PREF_CLEAR_DATABASE = "pref_clear_database";
interface PreferenceCallbacks {
@@ -24,6 +30,7 @@ public class PreferenceFragment extends PreferenceFragmentCompat implements Pref
addPreferencesFromResource(R.xml.preferences);
findPreference(PREF_ABOUT).setOnPreferenceClickListener(this);
findPreference(PREF_BLACKLIST).setOnPreferenceClickListener(this);
+ findPreference(PREF_CLEAR_DATABASE).setOnPreferenceClickListener(this);
}
@Override
@@ -35,10 +42,29 @@ public class PreferenceFragment extends PreferenceFragmentCompat implements Pref
} else if(key.equals(PREF_BLACKLIST)) {
mCallbacks.showBlacklistScreen();
return true;
- }
+ } else if(key.equals(PREF_CLEAR_DATABASE))
+ askClearDatabase();
return false;
}
+ private void askClearDatabase() {
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.pref_clear_database_dialog_title)
+ .setMessage(R.string.pref_clear_database_dialog_message)
+ .setNegativeButton(R.string.pref_clear_database_dialog_negative, null)
+ .setPositiveButton(R.string.pref_clear_database_dialog_positive, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ContentResolver resolver = getContext().getContentResolver();
+ // there are no notifications with negative ids, so we use this to select all rows
+ String whereClause = Logged_notificationTable.FIELD_NOTIFICATION_ID + " != -1";
+ resolver.delete(Logged_notificationTable.CONTENT_URI, whereClause, new String[0]);
+ Snackbar.make(getActivity().findViewById(android.R.id.content), R.string.pref_clear_database_snackbar_cleared, Snackbar.LENGTH_SHORT).show();
+ }
+ })
+ .create().show();
+ }
+
@SuppressWarnings("ConstantConditions")
@Override
public void onAttach(Activity activity) {
diff --git a/app/src/main/java/net/headlezz/notificationlogger/logger/LoggedNotification.java b/app/src/main/java/net/headlezz/notificationlogger/logger/LoggedNotification.java
new file mode 100644
index 0000000..ac2d56c
--- /dev/null
+++ b/app/src/main/java/net/headlezz/notificationlogger/logger/LoggedNotification.java
@@ -0,0 +1,36 @@
+package net.headlezz.notificationlogger.logger;
+
+import java.util.Date;
+
+import ckm.simple.sql_provider.annotation.SimpleSQLColumn;
+import ckm.simple.sql_provider.annotation.SimpleSQLTable;
+
+@SimpleSQLTable(table="logged_notification", provider="NotificationProvider")
+public class LoggedNotification {
+
+
+ @SimpleSQLColumn("title")
+ public String title;
+
+ @SimpleSQLColumn("message")
+ public String message;
+
+ @SimpleSQLColumn("date")
+ public Date date;
+
+ @SimpleSQLColumn("app_name")
+ public String appName;
+
+ @SimpleSQLColumn("package_name")
+ public String packageName;
+
+ @SimpleSQLColumn("notification_id")
+ public int notificationId;
+
+ @SimpleSQLColumn("user_id")
+ public int userId;
+
+ @SimpleSQLColumn("small_icon_id")
+ public int smallIconId;
+
+}
diff --git a/app/src/main/java/net/headlezz/notificationlogger/logger/LoggerUtils.java b/app/src/main/java/net/headlezz/notificationlogger/logger/LoggerUtils.java
new file mode 100644
index 0000000..1f64635
--- /dev/null
+++ b/app/src/main/java/net/headlezz/notificationlogger/logger/LoggerUtils.java
@@ -0,0 +1,36 @@
+package net.headlezz.notificationlogger.logger;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.support.v7.app.AlertDialog;
+
+import net.headlezz.notificationlogger.R;
+
+public class LoggerUtils {
+
+ public static void openNotificationSystemSettings(Context context) {
+ Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ context.startActivity(intent);
+ }
+
+ public static boolean isLoggerRunning() {
+ return NotificationLoggerService.isRunning;
+ }
+
+ public static AlertDialog buildNotificationAccessDialog(final Context context) {
+ return new AlertDialog.Builder(context)
+ .setTitle(context.getString(R.string.main_dialog_enable_notification_access_title))
+ .setMessage(context.getString(R.string.main_dialog_enable_notification_access_message))
+ .setPositiveButton(R.string.main_dialog_enable_notification_access_positive, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ openNotificationSystemSettings(context);
+ }
+ })
+ .setNegativeButton(R.string.main_dialog_enable_notification_access_negative, null)
+ .create();
+ }
+
+}
diff --git a/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationLoggerService.java b/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationLoggerService.java
new file mode 100644
index 0000000..73d5238
--- /dev/null
+++ b/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationLoggerService.java
@@ -0,0 +1,114 @@
+package net.headlezz.notificationlogger.logger;
+
+import android.app.Notification;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+import net.headlezz.notificationlogger.PackageUtils;
+
+import java.util.Date;
+
+import timber.log.Timber;
+
+public class NotificationLoggerService extends NotificationListenerService implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ /**
+ * true if logger is enabled in system settings
+ */
+ public static boolean isRunning = false;
+
+ /**
+ * true if logger is enabled in app settings
+ */
+ public static boolean isEnabled = true;
+
+ SharedPreferences mSharedPrefs;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ isRunning = true;
+ mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ isEnabled = mSharedPrefs.getBoolean("pref_logging_enabled", true);
+ mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+ Timber.d(String.format("Logger started (running: %s, enabled: %s)", isRunning, isEnabled));
+ return super.onBind(intent);
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ Timber.d("Logger stopped");
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn) {
+ super.onNotificationPosted(sbn);
+
+ if(!isEnabled) {
+ Timber.d("Notification posted but logger is disabled");
+ return;
+ }
+
+
+ LoggedNotification ln = buildLoggedNotification(sbn);
+ getApplicationContext().getContentResolver().insert(
+ Logged_notificationTable.CONTENT_URI,
+ Logged_notificationTable.getContentValues(ln, false)
+ );
+ Timber.d(String.format("Notificaton logged (id: %d)", ln.notificationId));
+ }
+
+ private LoggedNotification buildLoggedNotification(StatusBarNotification sbn) {
+ int notificationId = sbn.getId();
+
+ int userId = sbn.getUserId();
+ Date date = new Date(sbn.getPostTime());
+ String packageName = sbn.getPackageName();
+ int iconId = sbn.getNotification().icon;
+
+ String appName;
+ try {
+ appName = PackageUtils.getAppName(getApplicationContext(), packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ Timber.w("App name not found for " + packageName);
+ appName = "";
+ }
+
+ Notification notification = sbn.getNotification();
+ Bundle extras = notification.extras;
+
+ String title = extras.getString("android.title", "");
+ String message = extras.getCharSequence("android.text", "").toString();
+
+ if(message.isEmpty() && notification.tickerText != null)
+ message = notification.tickerText.toString();
+ if(message.isEmpty() && extras.containsKey("android.infoText"))
+ message = extras.get("android.infoText").toString();
+
+ LoggedNotification ln = new LoggedNotification();
+ ln.title = title;
+ ln.message = message;
+ ln.date = date;
+ ln.appName = appName;
+ ln.packageName = packageName;
+ ln.notificationId = notificationId;
+ ln.userId = userId;
+ ln.smallIconId = iconId;
+ return ln;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if(key.equals("pref_logging_enabled")) {
+ isEnabled = sharedPreferences.getBoolean("pref_logging_enabled", true);
+ Timber.d("Logging is now " + isEnabled);
+ }
+ }
+}
diff --git a/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationProviderConfig.java b/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationProviderConfig.java
new file mode 100644
index 0000000..ffd6084
--- /dev/null
+++ b/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationProviderConfig.java
@@ -0,0 +1,18 @@
+package net.headlezz.notificationlogger.logger;
+
+import ckm.simple.sql_provider.UpgradeScript;
+import ckm.simple.sql_provider.annotation.ProviderConfig;
+import ckm.simple.sql_provider.annotation.SimpleSQLConfig;
+
+@SimpleSQLConfig(
+ name="NotificationProvider",
+ authority = "net.headlezz.notificationlogger.logger.authority",
+ database = "notifications.db",
+ version = 1
+)
+public class NotificationProviderConfig implements ProviderConfig {
+ @Override
+ public UpgradeScript[] getUpdateScripts() {
+ return new UpgradeScript[0];
+ }
+}
diff --git a/app/src/main/java/net/headlezz/notificationlogger/presenter/LoggerWarningPresenter.java b/app/src/main/java/net/headlezz/notificationlogger/presenter/LoggerWarningPresenter.java
new file mode 100644
index 0000000..3cfdd17
--- /dev/null
+++ b/app/src/main/java/net/headlezz/notificationlogger/presenter/LoggerWarningPresenter.java
@@ -0,0 +1,23 @@
+package net.headlezz.notificationlogger.presenter;
+
+public class LoggerWarningPresenter {
+
+ public interface LoggerWarningView {
+ void showLoggerWarning();
+ void hideLoggerWarning();
+ }
+
+ LoggerWarningView mView;
+
+ public LoggerWarningPresenter(LoggerWarningView lwv) {
+ mView = lwv;
+ }
+
+ public void setCurrentLoggerPreference(boolean loggingEnabled) {
+ if(loggingEnabled)
+ mView.hideLoggerWarning();
+ else
+ mView.showLoggerWarning();
+ }
+
+}
diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml
index 12ae9a6..82e5d94 100644
--- a/app/src/main/res/layout/main_activity.xml
+++ b/app/src/main/res/layout/main_activity.xml
@@ -1,5 +1,6 @@
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1c84b09..501025c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3,6 +3,13 @@
Settings
Settings
+ Logging is disabled.
+ Enable
+ Enable Notification Access
+ In order to log notifcations, this app needs access to your notification system.
+ Open settings
+ Cancel
+
Notification Settings
Support
Notification Logging
@@ -13,6 +20,12 @@
Remove all logged notifications
Export to sdcard
About
+ Clear database
+ Delete all logged notifications?
+ Cancel
+ Yes
+ Database cleared.
+
FAB: Create notification
Create notification
Blacklist