Merge branch 'notification_logger'

This commit is contained in:
danijoo
2016-01-02 15:02:04 +01:00
13 changed files with 388 additions and 12 deletions

View File

@@ -9,7 +9,7 @@ android {
defaultConfig {
applicationId "net.headlezz.notificationlogger"
minSdkVersion 18
minSdkVersion 19
targetSdkVersion 23
versionCode 1
versionName "1.0"

View File

@@ -2,6 +2,7 @@
package="net.headlezz.notificationlogger">
<application
android:name=".Application"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -14,15 +15,31 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".PreferenceActivity"
android:parentActivityName=".MainActivity"/>
<activity
android:name=".PreferenceActivity"
android:parentActivityName=".MainActivity" />
<receiver android:name=".createnotification.NotificationAlarmReceiver"
<receiver
android:name=".createnotification.NotificationAlarmReceiver"
android:enabled="true">
<intent-filter>
<action android:name="net.headlezz.notificationbroadcast" />
</intent-filter>
</receiver>
<service
android:name=".logger.NotificationLoggerService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<provider
android:authorities="net.headlezz.notificationlogger.logger.authority"
android:name="net.headlezz.notificationlogger.logger.NotificationProvider"
android:exported="true"
/>
</application>
</manifest>

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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];
}
}

View File

@@ -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();
}
}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/viewholder"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

View File

@@ -3,6 +3,13 @@
<string name="menu_item_settings">Settings</string>
<string name="title_settings">Settings</string>
<string name="main_snack_logging_disabled_warning">Logging is disabled.</string>
<string name="main_snack_logging_disabled_warning_action">Enable</string>
<string name="main_dialog_enable_notification_access_title">Enable Notification Access</string>
<string name="main_dialog_enable_notification_access_message">In order to log notifcations, this app needs access to your notification system.</string>
<string name="main_dialog_enable_notification_access_positive">Open settings</string>
<string name="main_dialog_enable_notification_access_negative">Cancel</string>
<string name="pref_category_notification_settings">Notification Settings</string>
<string name="pref_category_support">Support</string>
<string name="pref_logging_enabled_title">Notification Logging</string>
@@ -13,6 +20,12 @@
<string name="pref_clear_database_summary">Remove all logged notifications</string>
<string name="pref_export_title">Export to sdcard</string>
<string name="pref_about_title">About</string>
<string name="pref_clear_database_dialog_title">Clear database</string>
<string name="pref_clear_database_dialog_message">Delete all logged notifications?</string>
<string name="pref_clear_database_dialog_negative">Cancel</string>
<string name="pref_clear_database_dialog_positive">Yes</string>
<string name="pref_clear_database_snackbar_cleared">Database cleared.</string>
<string name="fab_cd_create_notification">FAB: Create notification</string>
<string name="title_create_notification">Create notification</string>
<string name="title_blacklist">Blacklist</string>