├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── notifLib
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── robj
│ │ └── notificationhelperlibrary
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ ├── com
│ │ │ └── robj
│ │ │ │ └── notificationhelperlibrary
│ │ │ │ └── utils
│ │ │ │ ├── NotificationContentUtils.java
│ │ │ │ ├── NotificationDebugUtils.java
│ │ │ │ ├── NotificationListenerUtils.java
│ │ │ │ ├── NotificationUtils.java
│ │ │ │ └── VersionUtils.java
│ │ ├── models
│ │ │ ├── Action.java
│ │ │ ├── NotificationIds.java
│ │ │ ├── PendingNotification.java
│ │ │ └── RemoteInputParcel.java
│ │ └── services
│ │ │ └── BaseNotificationListener.java
│ └── res
│ │ ├── drawable
│ │ └── dummy_icon.png
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── robj
│ └── notificationhelperlibrary
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' // Add this line
9 | classpath 'com.jakewharton:butterknife-gradle-plugin:8.7.0'
10 | classpath 'com.android.tools.build:gradle:2.3.3'
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamrobj/NotificationHelperLibrary/f92d43b2fda97f4afe573d45da91ec10c1250b1c/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jul 13 11:06:16 BST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save ( ) {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/notifLib/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/notifLib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion '26.0.1'
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "2.0.5"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 |
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(include: ['*.jar'], dir: 'libs')
26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
27 | exclude group: 'com.android.support', module: 'support-annotations'
28 | })
29 | testCompile 'junit:junit:4.12'
30 | compile 'com.android.support:appcompat-v7:25.0.1'
31 | }
32 |
--------------------------------------------------------------------------------
/notifLib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /sharedHome/jj/android_sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/notifLib/src/androidTest/java/com/robj/notificationhelperlibrary/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.robj.notificationhelperlibrary;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.robj.notificationhelperlibrary.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/notifLib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/com/robj/notificationhelperlibrary/utils/NotificationContentUtils.java:
--------------------------------------------------------------------------------
1 | package com.robj.notificationhelperlibrary.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.annotation.TargetApi;
5 | import android.app.Notification;
6 | import android.content.Context;
7 | import android.os.Bundle;
8 | import android.os.Build.VERSION;
9 | import android.text.TextUtils;
10 | import android.util.Log;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 | import android.widget.RemoteViews;
15 | import android.widget.TextView;
16 |
17 | import models.NotificationIds;
18 |
19 | public class NotificationContentUtils {
20 |
21 | private static final String TAG = NotificationContentUtils.class.getSimpleName();
22 |
23 | @TargetApi(19)
24 | public static String getTitle(Bundle extras) {
25 | Log.d(TAG, "Getting title from extras..");
26 | String msg = extras.getString("android.title");
27 | Log.d("Title Big", "" + extras.getString("android.title.big"));
28 | return msg;
29 | }
30 |
31 | public static String getTitle(ViewGroup localView) {
32 | Log.d(TAG, "Getting title..");
33 | String msg = null;
34 | TextView tv = (TextView)localView.findViewById(NotificationIds.getInstance(localView.getContext()).TITLE);
35 | if(tv != null) {
36 | msg = tv.getText().toString();
37 | }
38 |
39 | return msg;
40 | }
41 |
42 | @TargetApi(19)
43 | public static String getMessage(Bundle extras) {
44 | Log.d(TAG, "Getting message from extras..");
45 | Log.d("Text", "" + extras.getCharSequence("android.text"));
46 | Log.d("Big Text", "" + extras.getCharSequence("android.bigText"));
47 | Log.d("Title Big", "" + extras.getCharSequence("android.title.big"));
48 | Log.d("Info text", "" + extras.getCharSequence("android.infoText"));
49 | Log.d("Info text", "" + extras.getCharSequence("android.infoText"));
50 | Log.d("Subtext", "" + extras.getCharSequence("android.subText"));
51 | Log.d("Summary", "" + extras.getString("android.summaryText"));
52 | CharSequence chars = extras.getCharSequence("android.text");
53 | String chars1;
54 | return !TextUtils.isEmpty(chars)?chars.toString():(!TextUtils.isEmpty(chars1 = extras.getString("android.summaryText"))?chars1.toString():null);
55 | }
56 |
57 | public static String getMessage(ViewGroup localView) {
58 | Log.d(TAG, "Getting message..");
59 | String msg = null;
60 | TextView tv = (TextView)localView.findViewById(NotificationIds.getInstance(localView.getContext()).BIG_TEXT);
61 | if(tv != null && !TextUtils.isEmpty(tv.getText())) {
62 | msg = tv.getText().toString();
63 | }
64 |
65 | if(TextUtils.isEmpty(msg)) {
66 | tv = (TextView)localView.findViewById(NotificationIds.getInstance(localView.getContext()).TEXT);
67 | if(tv != null) {
68 | msg = tv.getText().toString();
69 | }
70 | }
71 |
72 | return msg;
73 | }
74 |
75 | @TargetApi(19)
76 | public static String getExtended(Bundle extras, ViewGroup v) {
77 | Log.d(TAG, "Getting message from extras..");
78 | CharSequence[] lines = extras.getCharSequenceArray("android.textLines");
79 | if(lines != null && lines.length > 0) {
80 | StringBuilder var8 = new StringBuilder();
81 | CharSequence[] var4 = lines;
82 | int var5 = lines.length;
83 |
84 | for(int var6 = 0; var6 < var5; ++var6) {
85 | CharSequence msg = var4[var6];
86 | if(!TextUtils.isEmpty(msg)) {
87 | var8.append(msg.toString());
88 | var8.append('\n');
89 | }
90 | }
91 |
92 | return var8.toString().trim();
93 | } else {
94 | CharSequence chars = extras.getCharSequence("android.bigText");
95 | return !TextUtils.isEmpty(chars)?chars.toString():(!VersionUtils.isJellyBeanMR2()?getExtended(v):getMessage(extras));
96 | }
97 | }
98 |
99 | public static String getExtended(ViewGroup localView) {
100 | Log.d(TAG, "Getting extended message..");
101 | String msg = "";
102 | NotificationIds notificationIds = NotificationIds.getInstance(localView.getContext());
103 | TextView tv = (TextView)localView.findViewById(notificationIds.EMAIL_0);
104 | if(tv != null && !TextUtils.isEmpty(tv.getText())) {
105 | msg = msg + tv.getText().toString() + '\n';
106 | }
107 |
108 | tv = (TextView)localView.findViewById(notificationIds.EMAIL_1);
109 | if(tv != null && !TextUtils.isEmpty(tv.getText())) {
110 | msg = msg + tv.getText().toString() + '\n';
111 | }
112 |
113 | tv = (TextView)localView.findViewById(notificationIds.EMAIL_2);
114 | if(tv != null && !TextUtils.isEmpty(tv.getText())) {
115 | msg = msg + tv.getText().toString() + '\n';
116 | }
117 |
118 | tv = (TextView)localView.findViewById(notificationIds.EMAIL_3);
119 | if(tv != null && !TextUtils.isEmpty(tv.getText())) {
120 | msg = msg + tv.getText().toString() + '\n';
121 | }
122 |
123 | tv = (TextView)localView.findViewById(notificationIds.EMAIL_4);
124 | if(tv != null && !TextUtils.isEmpty(tv.getText())) {
125 | msg = msg + tv.getText().toString() + '\n';
126 | }
127 |
128 | tv = (TextView)localView.findViewById(notificationIds.EMAIL_5);
129 | if(tv != null && !TextUtils.isEmpty(tv.getText())) {
130 | msg = msg + tv.getText().toString() + '\n';
131 | }
132 |
133 | tv = (TextView)localView.findViewById(notificationIds.EMAIL_6);
134 | if(tv != null && !TextUtils.isEmpty(tv.getText())) {
135 | msg = msg + tv.getText().toString() + '\n';
136 | }
137 |
138 | if(msg.isEmpty()) {
139 | msg = getExpandedText(localView);
140 | }
141 |
142 | if(msg.isEmpty()) {
143 | msg = getMessage(localView);
144 | }
145 |
146 | return msg.trim();
147 | }
148 |
149 | @SuppressLint({"NewApi"})
150 | public static ViewGroup getLocalView(Context context, Notification n) {
151 | RemoteViews view = null;
152 | if(VERSION.SDK_INT >= 16) {
153 | view = n.bigContentView;
154 | }
155 |
156 | if(view == null) {
157 | view = n.contentView;
158 | }
159 |
160 | ViewGroup localView = null;
161 |
162 | try {
163 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
164 | localView = (ViewGroup)inflater.inflate(view.getLayoutId(), (ViewGroup)null);
165 | view.reapply(context, localView);
166 | } catch (Exception var5) {
167 | ;
168 | }
169 |
170 | return localView;
171 | }
172 |
173 | public static String getExpandedText(ViewGroup localView) {
174 | NotificationIds notificationIds = NotificationIds.getInstance(localView.getContext());
175 | String text = "";
176 | if(localView != null) {
177 | View v = localView.findViewById(notificationIds.big_notification_content_text);
178 | View bigTitleView;
179 | if(v != null && v instanceof TextView) {
180 | String titleView = ((TextView)v).getText().toString();
181 | if(!titleView.equals("")) {
182 | bigTitleView = localView.findViewById(android.R.id.title);
183 | if(v != null && v instanceof TextView) {
184 | String inboxTitleView = ((TextView)bigTitleView).getText().toString();
185 | if(!inboxTitleView.equals("")) {
186 | text = inboxTitleView + " " + titleView;
187 | } else {
188 | text = titleView;
189 | }
190 | } else {
191 | text = titleView;
192 | }
193 | }
194 | }
195 |
196 | v = localView.findViewById(notificationIds.inbox_notification_event_10_id);
197 | CharSequence titleView1;
198 | if(v != null && v instanceof TextView) {
199 | titleView1 = ((TextView)v).getText();
200 | if(!titleView1.equals("") && !titleView1.equals("")) {
201 | text = text + titleView1.toString();
202 | }
203 | }
204 |
205 | v = localView.findViewById(notificationIds.inbox_notification_event_9_id);
206 | if(v != null && v instanceof TextView) {
207 | titleView1 = ((TextView)v).getText();
208 | if(!titleView1.equals("")) {
209 | text = text + "\n" + titleView1.toString();
210 | }
211 | }
212 |
213 | v = localView.findViewById(notificationIds.inbox_notification_event_8_id);
214 | if(v != null && v instanceof TextView) {
215 | titleView1 = ((TextView)v).getText();
216 | if(!titleView1.equals("")) {
217 | text = text + "\n" + titleView1.toString();
218 | }
219 | }
220 |
221 | v = localView.findViewById(notificationIds.inbox_notification_event_7_id);
222 | if(v != null && v instanceof TextView) {
223 | titleView1 = ((TextView)v).getText();
224 | if(!titleView1.equals("")) {
225 | text = text + "\n" + titleView1.toString();
226 | }
227 | }
228 |
229 | v = localView.findViewById(notificationIds.inbox_notification_event_6_id);
230 | if(v != null && v instanceof TextView) {
231 | titleView1 = ((TextView)v).getText();
232 | if(!titleView1.equals("")) {
233 | text = text + "\n" + titleView1.toString();
234 | }
235 | }
236 |
237 | v = localView.findViewById(notificationIds.inbox_notification_event_5_id);
238 | if(v != null && v instanceof TextView) {
239 | titleView1 = ((TextView)v).getText();
240 | if(!titleView1.equals("")) {
241 | text = text + "\n" + titleView1.toString();
242 | }
243 | }
244 |
245 | v = localView.findViewById(notificationIds.inbox_notification_event_4_id);
246 | if(v != null && v instanceof TextView) {
247 | titleView1 = ((TextView)v).getText();
248 | if(!titleView1.equals("")) {
249 | text = text + "\n" + titleView1.toString();
250 | }
251 | }
252 |
253 | v = localView.findViewById(notificationIds.inbox_notification_event_3_id);
254 | if(v != null && v instanceof TextView) {
255 | titleView1 = ((TextView)v).getText();
256 | if(!titleView1.equals("")) {
257 | text = text + "\n" + titleView1.toString();
258 | }
259 | }
260 |
261 | v = localView.findViewById(notificationIds.inbox_notification_event_2_id);
262 | if(v != null && v instanceof TextView) {
263 | titleView1 = ((TextView)v).getText();
264 | if(!titleView1.equals("")) {
265 | text = text + "\n" + titleView1.toString();
266 | }
267 | }
268 |
269 | v = localView.findViewById(notificationIds.inbox_notification_event_1_id);
270 | if(v != null && v instanceof TextView) {
271 | titleView1 = ((TextView)v).getText();
272 | if(!titleView1.equals("")) {
273 | text = text + "\n" + titleView1.toString();
274 | }
275 | }
276 |
277 | if(text.equals("")) {
278 | View titleView2 = localView.findViewById(notificationIds.notification_title_id);
279 | bigTitleView = localView.findViewById(notificationIds.big_notification_title_id);
280 | View inboxTitleView1 = localView.findViewById(notificationIds.inbox_notification_title_id);
281 | if(titleView2 != null && titleView2 instanceof TextView) {
282 | text = text + ((TextView)titleView2).getText() + " - ";
283 | } else if(bigTitleView != null && bigTitleView instanceof TextView) {
284 | text = text + ((TextView)titleView2).getText();
285 | } else if(inboxTitleView1 != null && inboxTitleView1 instanceof TextView) {
286 | text = text + ((TextView)titleView2).getText();
287 | }
288 |
289 | v = localView.findViewById(notificationIds.notification_subtext_id);
290 | if(v != null && v instanceof TextView) {
291 | CharSequence s = ((TextView)v).getText();
292 | if(!s.equals("")) {
293 | text = text + s.toString();
294 | }
295 | }
296 | }
297 | }
298 |
299 | return text.trim();
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/com/robj/notificationhelperlibrary/utils/NotificationDebugUtils.java:
--------------------------------------------------------------------------------
1 | package com.robj.notificationhelperlibrary.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.annotation.TargetApi;
5 | import android.app.Notification;
6 | import android.os.Build;
7 | import android.os.Bundle;
8 | import android.service.notification.StatusBarNotification;
9 | import android.support.v7.app.NotificationCompat;
10 | import android.util.Log;
11 |
12 | /**
13 | * Created by jj on 03/09/16.
14 | */
15 | public class NotificationDebugUtils {
16 |
17 | @SuppressLint("NewApi")
18 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
19 | public static void printNotification(StatusBarNotification sbn) {
20 | final String TAG = NotificationDebugUtils.class.getSimpleName();
21 | Log.d(TAG, getTitle(sbn.getNotification()) + ", " +
22 | getMessageContent(sbn.getNotification()) + ", " +
23 | "Sbn id: " + sbn.getId() + ", " +
24 | "Sbn tag: " + sbn.getTag() + ", " +
25 | "Sbn key: " + (Build.VERSION.SDK_INT >= 20 ? sbn.getKey() : "Unknown") + ", " +
26 | "Post time: " + sbn.getPostTime() + ", " +
27 | "When time: " + sbn.getNotification().when + ", ");
28 | }
29 |
30 | public static String getTitle(Notification n) {
31 | Bundle bundle = NotificationCompat.getExtras(n);
32 | return bundle.getString(NotificationCompat.EXTRA_TITLE);
33 | }
34 |
35 | public static String getMessageContent(Notification n) {
36 | Bundle bundle = NotificationCompat.getExtras(n);
37 | return bundle.getString(NotificationCompat.EXTRA_TEXT);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/com/robj/notificationhelperlibrary/utils/NotificationListenerUtils.java:
--------------------------------------------------------------------------------
1 | package com.robj.notificationhelperlibrary.utils;
2 |
3 | import android.app.Activity;
4 | import android.content.ComponentName;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.SharedPreferences;
8 | import android.provider.Settings;
9 | import android.support.v4.app.Fragment;
10 |
11 | /**
12 | * Created by jj on 06/10/16.
13 | */
14 |
15 | public class NotificationListenerUtils {
16 |
17 | public static final int REQ_NOTIFICATION_LISTENER = 123;
18 | private final static String LISTENER_SERVICE_CONNECTED = "LISTENER_SERVICE_CONNECTED";
19 | private static final String TAG = "NotificationListener";
20 |
21 | //TODO: Multi process prefs was depreciated as of 23, no longer works
22 |
23 | public static void setListenerConnected(Context context, boolean listenerConnected) {
24 | SharedPreferences.Editor editor = context.getSharedPreferences(TAG, Context.MODE_MULTI_PROCESS).edit();
25 | editor.putBoolean(LISTENER_SERVICE_CONNECTED, listenerConnected);
26 | editor.commit();
27 | }
28 |
29 | public static boolean isListenerConnected(Context context) {
30 | SharedPreferences sp = context.getSharedPreferences(TAG, Context.MODE_MULTI_PROCESS);
31 | return sp.getBoolean(LISTENER_SERVICE_CONNECTED, false);
32 | }
33 |
34 | //https://stackoverflow.com/questions/20141727/check-if-user-has-granted-notificationlistener-access-to-my-app/28160115
35 | //TODO: Use in UI to verify if it needs enabling or restarting
36 | public static boolean isListenerEnabled(Context context, Class notificationListenerCls) {
37 | ComponentName cn = new ComponentName(context, notificationListenerCls);
38 | String flat = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
39 | return flat != null && flat.contains(cn.flattenToString());
40 | }
41 |
42 | public static void launchNotificationAccessSettings(Activity activity) {
43 | Intent i = new Intent(VersionUtils.isJellyBeanMR2()
44 | ? "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"
45 | : android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS);
46 | activity.startActivityForResult(i, REQ_NOTIFICATION_LISTENER);
47 | }
48 |
49 | public static void launchNotificationAccessSettings(Fragment fragment) {
50 | Intent i = new Intent(VersionUtils.isJellyBeanMR2()
51 | ? "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"
52 | : android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS);
53 | fragment.startActivityForResult(i, REQ_NOTIFICATION_LISTENER);
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/com/robj/notificationhelperlibrary/utils/NotificationUtils.java:
--------------------------------------------------------------------------------
1 | package com.robj.notificationhelperlibrary.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.annotation.TargetApi;
5 | import android.app.Notification;
6 | import android.content.Context;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.service.notification.NotificationListenerService;
10 | import android.service.notification.StatusBarNotification;
11 | import android.support.annotation.RequiresApi;
12 | import android.support.v4.app.NotificationCompat;
13 | import android.support.v4.app.RemoteInput;
14 | import android.text.TextUtils;
15 | import android.util.Log;
16 | import android.view.LayoutInflater;
17 | import android.view.View;
18 | import android.view.ViewGroup;
19 | import android.widget.RemoteViews;
20 | import android.widget.TextView;
21 |
22 | import com.robj.notificationhelperlibrary.R;
23 |
24 | import java.util.ArrayList;
25 | import java.util.concurrent.TimeUnit;
26 |
27 | import models.Action;
28 | import models.NotificationIds;
29 |
30 | public class NotificationUtils {
31 |
32 | private static final String[] REPLY_KEYWORDS = {"reply", "android.intent.extra.text"};
33 | private static final CharSequence REPLY_KEYWORD = "reply";
34 | private static final CharSequence INPUT_KEYWORD = "input";
35 |
36 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
37 | public static boolean isRecent(StatusBarNotification sbn, long recentTimeframeInSecs) {
38 | return sbn.getNotification().when > 0 && //Checks against real time to make sure its new
39 | System.currentTimeMillis() - sbn.getNotification().when <= TimeUnit.SECONDS.toMillis(recentTimeframeInSecs);
40 | }
41 |
42 | /**
43 | * http://stackoverflow.com/questions/9292032/extract-notification-text-from-parcelable-contentview-or-contentintent *
44 | */
45 |
46 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
47 | public static boolean notificationMatchesFilter(StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) {
48 | NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
49 | if (rankingMap.getRanking(sbn.getKey(), ranking))
50 | if (ranking.matchesInterruptionFilter())
51 | return true;
52 | return false;
53 | }
54 |
55 | @TargetApi(Build.VERSION_CODES.KITKAT)
56 | public static String getMessage(Bundle extras) {
57 | Log.d("NOTIFICATIONUTILS", "Getting message from extras..");
58 | Log.d("Text", "" + extras.getCharSequence(Notification.EXTRA_TEXT));
59 | Log.d("Big Text", "" + extras.getCharSequence(Notification.EXTRA_BIG_TEXT));
60 | Log.d("Title Big", "" + extras.getCharSequence(Notification.EXTRA_TITLE_BIG));
61 | // Log.d("Text lines", "" + extras.getCharSequence(Notification.EXTRA_TEXT_LINES));
62 | Log.d("Info text", "" + extras.getCharSequence(Notification.EXTRA_INFO_TEXT));
63 | Log.d("Info text", "" + extras.getCharSequence(Notification.EXTRA_INFO_TEXT));
64 | Log.d("Subtext", "" + extras.getCharSequence(Notification.EXTRA_SUB_TEXT));
65 | Log.d("Summary", "" + extras.getString(Notification.EXTRA_SUMMARY_TEXT));
66 | CharSequence chars = extras.getCharSequence(Notification.EXTRA_TEXT);
67 | if(!TextUtils.isEmpty(chars))
68 | return chars.toString();
69 | else if(!TextUtils.isEmpty((chars = extras.getString(Notification.EXTRA_SUMMARY_TEXT))))
70 | return chars.toString();
71 | else
72 | return null;
73 | }
74 |
75 | @TargetApi(Build.VERSION_CODES.KITKAT)
76 | public static String getExtended(Bundle extras, ViewGroup v) {
77 | Log.d("NOTIFICATIONUTILS", "Getting message from extras..");
78 |
79 | CharSequence[] lines = extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
80 | if(lines != null && lines.length > 0) {
81 | StringBuilder sb = new StringBuilder();
82 | for (CharSequence msg : lines)
83 | // msg = msg.toString();//.replaceAll("(\\s+$|^\\s+)", "").replaceAll("\n+", "\n");
84 | if (!TextUtils.isEmpty(msg)) {
85 | sb.append(msg.toString());
86 | sb.append('\n');
87 | }
88 | return sb.toString().trim();
89 | }
90 | CharSequence chars = extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
91 | if(!TextUtils.isEmpty(chars))
92 | return chars.toString();
93 | else if(!VersionUtils.isJellyBeanMR2())
94 | return getExtended(v);
95 | else
96 | return getMessage(extras);
97 | }
98 |
99 | @SuppressLint("NewApi")
100 | public static ViewGroup getMessageView(Context context, Notification n) {
101 | Log.d("NOTIFICATIONUTILS", "Getting message view..");
102 | RemoteViews views = null;
103 | if (Build.VERSION.SDK_INT >= 16)
104 | views = n.bigContentView;
105 | if (views == null)
106 | views = n.contentView;
107 | if (views == null)
108 | return null;
109 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
110 | ViewGroup localView = (ViewGroup) inflater.inflate(views.getLayoutId(), null);
111 | views.reapply(context.getApplicationContext(), localView);
112 | return localView;
113 |
114 | }
115 |
116 | public static String getTitle(ViewGroup localView) {
117 | Log.d("NOTIFICATIONUTILS", "Getting title..");
118 | String msg = null;
119 | Context context = localView.getContext();
120 | TextView tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).TITLE);
121 | if (tv != null)
122 | msg = tv.getText().toString();
123 | return msg;
124 | }
125 |
126 | public static String getMessage(ViewGroup localView) {
127 | Log.d("NOTIFICATIONUTILS", "Getting message..");
128 | String msg = null;
129 | Context context = localView.getContext();
130 | TextView tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).BIG_TEXT);
131 | if (tv != null && !TextUtils.isEmpty(tv.getText()))
132 | msg = tv.getText().toString();
133 | if (TextUtils.isEmpty(msg)) {
134 | tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).TEXT);
135 | if (tv != null)
136 | msg = tv.getText().toString();
137 | }
138 | return msg;
139 | }
140 |
141 | public static String getExtended(ViewGroup localView) {
142 | Log.d("NOTIFICATIONUTILS", "Getting extended message..");
143 | String msg = "";
144 | Context context = localView.getContext();
145 | TextView tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).EMAIL_0);
146 | if (tv != null && !TextUtils.isEmpty(tv.getText()))
147 | msg += tv.getText().toString() + '\n';
148 | tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).EMAIL_1);
149 | if (tv != null && !TextUtils.isEmpty(tv.getText()))
150 | msg += tv.getText().toString() + '\n';
151 | tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).EMAIL_2);
152 | if (tv != null && !TextUtils.isEmpty(tv.getText()))
153 | msg += tv.getText().toString() + '\n';
154 | tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).EMAIL_3);
155 | if (tv != null && !TextUtils.isEmpty(tv.getText()))
156 | msg += tv.getText().toString() + '\n';
157 | tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).EMAIL_4);
158 | if (tv != null && !TextUtils.isEmpty(tv.getText()))
159 | msg += tv.getText().toString() + '\n';
160 | tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).EMAIL_5);
161 | if (tv != null && !TextUtils.isEmpty(tv.getText()))
162 | msg += tv.getText().toString() + '\n';
163 | tv = (TextView) localView.findViewById(NotificationIds.getInstance(context).EMAIL_6);
164 | if (tv != null && !TextUtils.isEmpty(tv.getText()))
165 | msg += tv.getText().toString() + '\n';
166 | // tv = (TextView) localView.findViewById(NotificationIds.getInstance().INBOX_MORE);
167 | // if (tv != null && !TextUtils.isEmpty(tv.getText()))
168 | // msg += tv.getText().toString() + '\n';
169 | if (msg.isEmpty())
170 | msg = getExpandedText(localView);
171 | if (msg.isEmpty())
172 | msg = getMessage(localView);
173 | return msg.trim();
174 | }
175 |
176 | @TargetApi(Build.VERSION_CODES.KITKAT)
177 | public static String getTitle(Bundle extras) {
178 | Log.d("NOTIFICATIONUTILS", "Getting title from extras..");
179 | String msg = extras.getString(Notification.EXTRA_TITLE);
180 | Log.d("Title Big", "" + extras.getString(Notification.EXTRA_TITLE_BIG));
181 | return msg;
182 | }
183 |
184 | /** OLD/CURRENT METHODS **/
185 |
186 | public static ViewGroup getView(Context context, RemoteViews view)
187 | {
188 | ViewGroup localView = null;
189 | try
190 | {
191 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
192 | localView = (ViewGroup) inflater.inflate(view.getLayoutId(), null);
193 | view.reapply(context, localView);
194 | }
195 | catch (Exception exp)
196 | {
197 | }
198 | return localView;
199 | }
200 |
201 | @SuppressLint("NewApi")
202 | public static ViewGroup getLocalView(Context context, Notification n)
203 | {
204 | RemoteViews view = null;
205 | if(Build.VERSION.SDK_INT >= 16) { view = n.bigContentView; }
206 |
207 | if (view == null)
208 | {
209 | view = n.contentView;
210 | }
211 | ViewGroup localView = null;
212 | try
213 | {
214 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
215 | localView = (ViewGroup) inflater.inflate(view.getLayoutId(), null);
216 | view.reapply(context, localView);
217 | } catch (Exception exp) { }
218 | return localView;
219 | }
220 |
221 | public static ArrayList getActions(Notification n, String packageName, ArrayList actions) {
222 | NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(n);
223 | if (wearableExtender.getActions().size() > 0) {
224 | for (NotificationCompat.Action action : wearableExtender.getActions())
225 | actions.add(new Action(action, packageName, action.title.toString().toLowerCase().contains(REPLY_KEYWORD)));
226 | }
227 | return actions;
228 | }
229 |
230 | public static Action getQuickReplyAction(Notification n, String packageName) {
231 | NotificationCompat.Action action = null;
232 | if(Build.VERSION.SDK_INT >= 24)
233 | action = getQuickReplyAction(n);
234 | if(action == null)
235 | action = getWearReplyAction(n);
236 | if(action == null)
237 | return null;
238 | return new Action(action, packageName, true);
239 | }
240 |
241 | private static NotificationCompat.Action getQuickReplyAction(Notification n) {
242 | for(int i = 0; i < NotificationCompat.getActionCount(n); i++) {
243 | NotificationCompat.Action action = NotificationCompat.getAction(n, i);
244 | if(action.getRemoteInputs() != null) {
245 | for (int x = 0; x < action.getRemoteInputs().length; x++) {
246 | RemoteInput remoteInput = action.getRemoteInputs()[x];
247 | if (isKnownReplyKey(remoteInput.getResultKey()))
248 | return action;
249 | }
250 | }
251 | }
252 | return null;
253 | }
254 |
255 | private static NotificationCompat.Action getWearReplyAction(Notification n) {
256 | NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(n);
257 | for (NotificationCompat.Action action : wearableExtender.getActions()) {
258 | if(action.getRemoteInputs() != null) {
259 | for (int x = 0; x < action.getRemoteInputs().length; x++) {
260 | RemoteInput remoteInput = action.getRemoteInputs()[x];
261 | if (isKnownReplyKey(remoteInput.getResultKey()))
262 | return action;
263 | else if (remoteInput.getResultKey().toLowerCase().contains(INPUT_KEYWORD))
264 | return action;
265 | }
266 | }
267 | }
268 | return null;
269 | }
270 |
271 | private static boolean isKnownReplyKey(String resultKey) {
272 | if(TextUtils.isEmpty(resultKey))
273 | return false;
274 |
275 | resultKey = resultKey.toLowerCase();
276 | for(String keyword : REPLY_KEYWORDS)
277 | if(resultKey.contains(keyword))
278 | return true;
279 |
280 | return false;
281 | }
282 |
283 | //OLD METHOD
284 | public static String getExpandedText(ViewGroup localView)
285 | {
286 | String text = "";
287 | if (localView != null)
288 | {
289 | Context context = localView.getContext();
290 | View v;
291 | // try to get big text
292 | v = localView.findViewById(NotificationIds.getInstance(context).big_notification_content_text);
293 | if (v != null && v instanceof TextView)
294 | {
295 | String s = ((TextView)v).getText().toString();
296 | if (!s.equals(""))
297 | {
298 | // add title string if available
299 | View titleView = localView.findViewById(android.R.id.title);
300 | if (v != null && v instanceof TextView)
301 | {
302 | String title = ((TextView)titleView).getText().toString();
303 | if (!title.equals(""))
304 | text = title + " " + s;
305 | else
306 | text = s;
307 | }
308 | else
309 | text = s;
310 | }
311 | }
312 |
313 | // try to extract details lines
314 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_10_id);
315 | if (v != null && v instanceof TextView)
316 | {
317 | CharSequence s = ((TextView)v).getText();
318 | if (!s.equals(""))
319 | if (!s.equals(""))
320 | text += s.toString();
321 | }
322 |
323 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_9_id);
324 | if (v != null && v instanceof TextView)
325 | {
326 | CharSequence s = ((TextView)v).getText();
327 | if (!s.equals(""))
328 | text += "\n" + s.toString();
329 | }
330 |
331 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_8_id);
332 | if (v != null && v instanceof TextView)
333 | {
334 | CharSequence s = ((TextView)v).getText();
335 | if (!s.equals(""))
336 | text += "\n" + s.toString();
337 | }
338 |
339 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_7_id);
340 | if (v != null && v instanceof TextView)
341 | {
342 | CharSequence s = ((TextView)v).getText();
343 | if (!s.equals(""))
344 | text += "\n" + s.toString();
345 | }
346 |
347 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_6_id);
348 | if (v != null && v instanceof TextView)
349 | {
350 | CharSequence s = ((TextView)v).getText();
351 | if (!s.equals(""))
352 | text += "\n" + s.toString();
353 | }
354 |
355 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_5_id);
356 | if (v != null && v instanceof TextView)
357 | {
358 | CharSequence s = ((TextView)v).getText();
359 | if (!s.equals(""))
360 | text += "\n" + s.toString();
361 | }
362 |
363 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_4_id);
364 | if (v != null && v instanceof TextView)
365 | {
366 | CharSequence s = ((TextView)v).getText();
367 | if (!s.equals(""))
368 | text += "\n" + s.toString();
369 | }
370 |
371 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_3_id);
372 | if (v != null && v instanceof TextView)
373 | {
374 | CharSequence s = ((TextView)v).getText();
375 | if (!s.equals(""))
376 | text += "\n" + s.toString();
377 | }
378 |
379 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_2_id);
380 | if (v != null && v instanceof TextView)
381 | {
382 | CharSequence s = ((TextView)v).getText();
383 | if (!s.equals(""))
384 | text += "\n" + s.toString();
385 | }
386 |
387 | v = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_event_1_id);
388 | if (v != null && v instanceof TextView)
389 | {
390 | CharSequence s = ((TextView)v).getText();
391 | if (!s.equals(""))
392 | text += "\n" + s.toString();
393 | }
394 |
395 | if (text.equals("")) //Last resort for Kik
396 | {
397 | // get title string if available
398 | View titleView = localView.findViewById(NotificationIds.getInstance(context).notification_title_id );
399 | View bigTitleView = localView.findViewById(NotificationIds.getInstance(context).big_notification_title_id );
400 | View inboxTitleView = localView.findViewById(NotificationIds.getInstance(context).inbox_notification_title_id );
401 | if (titleView != null && titleView instanceof TextView)
402 | {
403 | text += ((TextView)titleView).getText() + " - ";
404 | } else if (bigTitleView != null && bigTitleView instanceof TextView)
405 | {
406 | text += ((TextView)titleView).getText();
407 | } else if (inboxTitleView != null && inboxTitleView instanceof TextView)
408 | {
409 | text += ((TextView)titleView).getText();
410 | }
411 |
412 | v = localView.findViewById(NotificationIds.getInstance(context).notification_subtext_id);
413 | if (v != null && v instanceof TextView)
414 | {
415 | CharSequence s = ((TextView)v).getText();
416 | if (!s.equals(""))
417 | {
418 | text += s.toString();
419 | }
420 | }
421 | }
422 |
423 | }
424 | return text.trim();
425 | }
426 |
427 | public static boolean isAPriorityMode(int interruptionFilter) {
428 | if(interruptionFilter == NotificationListenerService.INTERRUPTION_FILTER_NONE ||
429 | interruptionFilter == NotificationListenerService.INTERRUPTION_FILTER_UNKNOWN)
430 | return false;
431 | return true;
432 | }
433 |
434 | }
435 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/com/robj/notificationhelperlibrary/utils/VersionUtils.java:
--------------------------------------------------------------------------------
1 | package com.robj.notificationhelperlibrary.utils;
2 |
3 | import android.os.Build;
4 |
5 | /**
6 | * Created by jj on 26/05/17.
7 | */
8 |
9 | public class VersionUtils {
10 |
11 | public static boolean isKitKat() {
12 | return Build.VERSION.SDK_INT >= 19;
13 | }
14 |
15 | public static boolean isJellyBean() {
16 | return Build.VERSION.SDK_INT >= 68;
17 | }
18 |
19 | public static boolean isJellyBeanMR2() {
20 | return Build.VERSION.SDK_INT >= 18;
21 | }
22 |
23 | public static boolean isLollipop() {
24 | return Build.VERSION.SDK_INT >= 21;
25 | }
26 |
27 | public static boolean isMarshmallow() {
28 | return Build.VERSION.SDK_INT >= 23;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/models/Action.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import android.app.PendingIntent;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.os.Parcel;
8 | import android.os.Parcelable;
9 | import android.support.v4.app.NotificationCompat;
10 | import android.support.v4.app.RemoteInput;
11 | import android.util.Log;
12 |
13 | import java.util.ArrayList;
14 |
15 | public class Action implements Parcelable {
16 |
17 | private final String text;
18 | private final String packageName;
19 | private final PendingIntent p;
20 | private final boolean isQuickReply;
21 | private final ArrayList remoteInputs = new ArrayList<>();
22 |
23 | public Action(Parcel in) {
24 | text = in.readString();
25 | packageName = in.readString();
26 | p = in.readParcelable(PendingIntent.class.getClassLoader());
27 | isQuickReply = in.readByte() != 0;
28 | in.readTypedList(remoteInputs, RemoteInputParcel.CREATOR);
29 | }
30 |
31 | @Override
32 | public void writeToParcel(Parcel dest, int flags) {
33 | dest.writeString(text);
34 | dest.writeString(packageName);
35 | dest.writeParcelable(p, flags);
36 | dest.writeByte((byte) (isQuickReply ? 1 : 0));
37 | dest.writeTypedList(remoteInputs);
38 | }
39 |
40 | public Action(String text, String packageName, PendingIntent p, RemoteInput remoteInput, boolean isQuickReply) {
41 | this.text = text;
42 | this.packageName = packageName;
43 | this.p = p;
44 | this.isQuickReply = isQuickReply;
45 | remoteInputs.add(new RemoteInputParcel(remoteInput));
46 | }
47 |
48 | public Action(NotificationCompat.Action action, String packageName, boolean isQuickReply) {
49 | this.text = action.title.toString();
50 | this.packageName = packageName;
51 | this.p = action.actionIntent;
52 | if(action.getRemoteInputs() != null) {
53 | int size = action.getRemoteInputs().length;
54 | for(int i = 0; i < size; i++)
55 | remoteInputs.add(new RemoteInputParcel(action.getRemoteInputs()[i]));
56 | }
57 | this.isQuickReply = isQuickReply;
58 | }
59 |
60 | public void sendReply(Context context, String msg) throws PendingIntent.CanceledException {
61 | Intent intent = new Intent();
62 | Bundle bundle = new Bundle();
63 | ArrayList actualInputs = new ArrayList<>();
64 |
65 | for (RemoteInputParcel input : remoteInputs) {
66 | Log.i("", "RemoteInput: " + input.getLabel());
67 | bundle.putCharSequence(input.getResultKey(), msg);
68 | RemoteInput.Builder builder = new RemoteInput.Builder(input.getResultKey());
69 | builder.setLabel(input.getLabel());
70 | builder.setChoices(input.getChoices());
71 | builder.setAllowFreeFormInput(input.isAllowFreeFormInput());
72 | builder.addExtras(input.getExtras());
73 | actualInputs.add(builder.build());
74 | }
75 |
76 | RemoteInput[] inputs = actualInputs.toArray(new RemoteInput[actualInputs.size()]);
77 | RemoteInput.addResultsToIntent(inputs, intent, bundle);
78 | p.send(context, 0, intent);
79 | }
80 |
81 | public ArrayList getRemoteInputs() {
82 | return remoteInputs;
83 | }
84 |
85 | public boolean isQuickReply() {
86 | return isQuickReply;
87 | }
88 |
89 | public String getText() {
90 | return text;
91 | }
92 |
93 | public PendingIntent getQuickReplyIntent() {
94 | return isQuickReply ? p : null;
95 | }
96 |
97 | public String getPackageName() {
98 | return packageName;
99 | }
100 |
101 | @Override
102 | public int describeContents() {
103 | return 0;
104 | }
105 |
106 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
107 | public Action createFromParcel(Parcel in) {
108 | return new Action(in);
109 | }
110 | public Action[] newArray(int size) {
111 | return new Action[size];
112 | }
113 | };
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/models/NotificationIds.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.res.Resources;
6 | import android.graphics.drawable.Drawable;
7 | import android.os.AsyncTask;
8 | import android.os.Build;
9 | import android.support.v4.app.NotificationCompat;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.ImageView;
14 | import android.widget.TextView;
15 |
16 | import com.robj.notificationhelperlibrary.R;
17 |
18 | /**
19 | * Created by JJ on 22/05/15.
20 | */
21 | public class NotificationIds {
22 |
23 | public int ICON;
24 | public int TITLE;
25 | public int BIG_TEXT;
26 | public int TEXT;
27 | public int BIG_PIC;
28 | public int EMAIL_0;
29 | public int EMAIL_1;
30 | public int EMAIL_2;
31 | public int EMAIL_3;
32 | public int EMAIL_4;
33 | public int EMAIL_5;
34 | public int EMAIL_6;
35 | public int INBOX_MORE;
36 |
37 | private static NotificationIds singleton;
38 |
39 | public static NotificationIds getInstance(Context context) {
40 | if (singleton == null)
41 | singleton = new NotificationIds(context);
42 | return singleton;
43 | }
44 |
45 | public NotificationIds(final Context context) {
46 | Resources r = context.getResources();
47 | ICON = r.getIdentifier("android:id/icon", null, null);
48 | TITLE = r.getIdentifier("android:id/title", null, null);
49 | BIG_TEXT = r.getIdentifier("android:id/big_text", null, null);
50 | TEXT = r.getIdentifier("android:id/text", null, null);
51 | BIG_PIC = r.getIdentifier("android:id/big_picture", null, null);
52 | EMAIL_0 = r.getIdentifier("android:id/inbox_text0", null, null);
53 | EMAIL_1 = r.getIdentifier("android:id/inbox_text1", null, null);
54 | EMAIL_2 = r.getIdentifier("android:id/inbox_text2", null, null);
55 | EMAIL_3 = r.getIdentifier("android:id/inbox_text3", null, null);
56 | EMAIL_4 = r.getIdentifier("android:id/inbox_text4", null, null);
57 | EMAIL_5 = r.getIdentifier("android:id/inbox_text5", null, null);
58 | EMAIL_6 = r.getIdentifier("android:id/inbox_text6", null, null);
59 | INBOX_MORE = r.getIdentifier("android:id/inbox_more", null, null);
60 | new AsyncTask() {
61 | @Override
62 | protected Object doInBackground(Object[] params) {
63 | return null;
64 | }
65 | @Override
66 | protected void onPostExecute(Object o) {
67 | detectNotificationIds(context);
68 | }
69 | }.execute();
70 |
71 | }
72 |
73 | public int notification_title_id = 0;
74 | public int big_notification_summary_id = 0;
75 | public int big_notification_content_title = 0;
76 | public int big_notification_content_text = 0;
77 | public int notification_image_id = 0;
78 |
79 | public int inbox_notification_title_id = 0;
80 | public int big_notification_title_id = 0;
81 |
82 | public int notification_subtext_id = 0;
83 | public int inbox_notification_event_1_id = 0;
84 | public int inbox_notification_event_2_id = 0;
85 | public int inbox_notification_event_3_id = 0;
86 | public int inbox_notification_event_4_id = 0;
87 | public int inbox_notification_event_5_id = 0;
88 | public int inbox_notification_event_6_id = 0;
89 | public int inbox_notification_event_7_id = 0;
90 | public int inbox_notification_event_8_id = 0;
91 | public int inbox_notification_event_9_id = 0;
92 | public int inbox_notification_event_10_id = 0;
93 |
94 | private void recursiveDetectNotificationsIds(ViewGroup v)
95 | {
96 | for(int i=0; i= Build.VERSION_CODES.JELLY_BEAN)
158 | //{
159 | NotificationCompat.BigTextStyle bigtextstyle = new NotificationCompat.BigTextStyle();
160 | bigtextstyle.setSummaryText("5");
161 | bigtextstyle.setBigContentTitle("6");
162 | bigtextstyle.bigText("7");
163 | mBuilder.setContentTitle("8");
164 | mBuilder.setStyle(bigtextstyle);
165 | detectExpandedNotificationsIds(mBuilder.build(), context);
166 |
167 | NotificationCompat.InboxStyle inboxStyle =
168 | new NotificationCompat.InboxStyle();
169 | String[] events = {"10","11","12","13","14","15","16","17","18","19"};
170 | inboxStyle.setBigContentTitle("6");
171 | mBuilder.setContentTitle("9");
172 | inboxStyle.setSummaryText("5");
173 |
174 | for (int i=0; i < events.length; i++)
175 | {
176 | inboxStyle.addLine(events[i]);
177 | }
178 | mBuilder.setStyle(inboxStyle);
179 |
180 | detectExpandedNotificationsIds(mBuilder.build(), context);
181 | //}
182 | }
183 |
184 | @SuppressLint("NewApi")
185 | private void detectExpandedNotificationsIds(android.app.Notification n, Context context)
186 | {
187 | if(Build.VERSION.SDK_INT >= 16)
188 | {
189 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
190 | ViewGroup localView = (ViewGroup) inflater.inflate(n.bigContentView.getLayoutId(), null);
191 | n.bigContentView.reapply(context, localView);
192 | recursiveDetectNotificationsIds(localView);
193 | }
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/models/PendingNotification.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.service.notification.StatusBarNotification;
5 | import android.support.v4.app.NotificationCompat;
6 |
7 | import com.robj.notificationhelperlibrary.utils.VersionUtils;
8 |
9 | import java.util.concurrent.ScheduledFuture;
10 |
11 | /**
12 | * Created by JJ on 05/06/15.
13 | */
14 | public class PendingNotification {
15 |
16 | private ScheduledFuture> scheduledFuture;
17 | private StatusBarNotification sbn;
18 | private String key;
19 |
20 | @SuppressLint("NewApi")
21 | public PendingNotification(StatusBarNotification sbn) {
22 | this.sbn = sbn;
23 | this.key = VersionUtils.isLollipop() ? sbn.getKey() : null;
24 | }
25 |
26 | public void setDismissKey(String key) {
27 | this.key = key;
28 | }
29 |
30 | public String getDismissKey() {
31 | return key;
32 | }
33 |
34 | public StatusBarNotification getSbn() {
35 | return sbn;
36 | }
37 |
38 | public void setScheduledFuture(ScheduledFuture> scheduledFuture) {
39 | this.scheduledFuture = scheduledFuture;
40 | }
41 |
42 | public ScheduledFuture> getScheduledFuture() {
43 | return scheduledFuture;
44 | }
45 |
46 | @Override
47 | public boolean equals(Object o) {
48 | if(VersionUtils.isJellyBean()) {
49 | String group = NotificationCompat.getGroup(((PendingNotification) o).getSbn().getNotification());
50 | String thisGroup = NotificationCompat.getGroup(sbn.getNotification());
51 | if(group == null || thisGroup == null)
52 | return false;
53 | return group.equals(thisGroup);
54 | } else
55 | return ((PendingNotification) o).getSbn().getPackageName().equals(sbn.getPackageName());
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/models/RemoteInputParcel.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import android.os.Bundle;
4 | import android.os.Parcel;
5 | import android.os.Parcelable;
6 | import android.support.v4.app.RemoteInput;
7 |
8 | /**
9 | * Created by JJ on 05/08/15.
10 | */
11 | public class RemoteInputParcel implements Parcelable {
12 |
13 | private String label;
14 | private String resultKey;
15 | private String[] choices = new String[0];
16 | private boolean allowFreeFormInput;
17 | private Bundle extras;
18 |
19 |
20 | public RemoteInputParcel(RemoteInput input) {
21 | label = input.getLabel().toString();
22 | resultKey = input.getResultKey();
23 | charSequenceToStringArray(input.getChoices());
24 | allowFreeFormInput = input.getAllowFreeFormInput();
25 | extras = input.getExtras();
26 | }
27 |
28 | public RemoteInputParcel(Parcel in) {
29 | label = in.readString();
30 | resultKey = in.readString();
31 | choices = in.createStringArray();
32 | allowFreeFormInput = in.readByte() != 0;
33 | extras = in.readParcelable(Bundle.class.getClassLoader());
34 | }
35 |
36 | public void charSequenceToStringArray(CharSequence[] charSequence) {
37 | if(charSequence != null) {
38 | int size = charSequence.length;
39 | choices = new String[charSequence.length];
40 | for (int i = 0; i < size; i++)
41 | choices[i] = charSequence[i].toString();
42 | }
43 | }
44 |
45 | public String getResultKey() {
46 | return resultKey;
47 | }
48 |
49 | public String getLabel() {
50 | return label;
51 | }
52 |
53 | public CharSequence[] getChoices() {
54 | return choices;
55 | }
56 |
57 | public boolean isAllowFreeFormInput() {
58 | return allowFreeFormInput;
59 | }
60 |
61 | public Bundle getExtras() {
62 | return extras;
63 | }
64 |
65 | @Override
66 | public void writeToParcel(Parcel dest, int flags) {
67 | dest.writeString(label);
68 | dest.writeString(resultKey);
69 | dest.writeStringArray(choices);
70 | dest.writeByte((byte) (allowFreeFormInput ? 1 : 0));
71 | dest.writeParcelable(extras, flags);
72 | }
73 |
74 | @Override
75 | public int describeContents() {
76 | return 0;
77 | }
78 |
79 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
80 | public RemoteInputParcel createFromParcel(Parcel in) {
81 | return new RemoteInputParcel(in);
82 | }
83 | public RemoteInputParcel[] newArray(int size) {
84 | return new RemoteInputParcel[size];
85 | }
86 | };
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/notifLib/src/main/java/services/BaseNotificationListener.java:
--------------------------------------------------------------------------------
1 | package services;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.os.IBinder;
7 | import android.service.notification.NotificationListenerService;
8 | import android.service.notification.StatusBarNotification;
9 | import android.support.v4.app.NotificationCompat;
10 | import android.text.TextUtils;
11 | import android.util.Log;
12 |
13 | import com.robj.notificationhelperlibrary.BuildConfig;
14 | import com.robj.notificationhelperlibrary.utils.NotificationContentUtils;
15 | import com.robj.notificationhelperlibrary.utils.NotificationListenerUtils;
16 | import com.robj.notificationhelperlibrary.utils.NotificationUtils;
17 | import com.robj.notificationhelperlibrary.utils.VersionUtils;
18 |
19 | import java.util.ArrayList;
20 | import java.util.HashMap;
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.concurrent.Executors;
24 | import java.util.concurrent.ScheduledExecutorService;
25 | import java.util.concurrent.ScheduledFuture;
26 | import java.util.concurrent.TimeUnit;
27 |
28 | import models.PendingNotification;
29 |
30 | @SuppressLint("NewApi")
31 | public abstract class BaseNotificationListener extends NotificationListenerService {
32 |
33 | protected final String TAG = getClass().getSimpleName();
34 |
35 | private ArrayList pending = new ArrayList<>();
36 | private ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
37 | private List notifHandled = new ArrayList<>();
38 | private Map previouslyDismissed = new HashMap<>();
39 |
40 | private ArrayList duplicateReplyPackages = new ArrayList<>();
41 | private String lastRemovedKey = "";
42 |
43 | protected void onEnabled(boolean enabled) {
44 | NotificationListenerUtils.setListenerConnected(this, enabled);
45 | Log.d(TAG, "Listener enabled: " + enabled + "..");
46 | }
47 |
48 | private void initDupeReplyList() {
49 | // duplicateReplyPackages.add("com.google.android.talk3");
50 | // duplicateReplyPackages.add("com.google.android.apps.messaging");
51 | duplicateReplyPackages.add("com.whatsapp");
52 | }
53 |
54 | @Override
55 | public void onCreate() {
56 | super.onCreate();
57 | Log.d(TAG, "Listener created..");
58 | initDupeReplyList();
59 | onEnabled(false);
60 | }
61 |
62 | @Override
63 | public void onDestroy() {
64 | super.onDestroy();
65 | Log.d(TAG, "Listener destroyed..");
66 | onEnabled(false);
67 | }
68 |
69 | @Override
70 | public void onListenerConnected() {
71 | super.onListenerConnected();
72 | Log.d(TAG, "Listener connected..");
73 | onEnabled(true);
74 | }
75 |
76 | @Override
77 | public void onListenerDisconnected() {
78 | super.onListenerDisconnected();
79 | Log.d(TAG, "Listener disconnected..");
80 | onEnabled(false);
81 | }
82 |
83 | @Override
84 | public IBinder onBind(Intent mIntent) {
85 | IBinder mIBinder = super.onBind(mIntent);
86 | onEnabled(true);
87 | return mIBinder;
88 | }
89 |
90 | @Override
91 | public boolean onUnbind(Intent mIntent) {
92 | boolean mOnUnbind = super.onUnbind(mIntent);
93 | onEnabled(false);
94 | return mOnUnbind;
95 | }
96 |
97 | @Override
98 | public int onStartCommand(Intent intent, int flags, int startId) {
99 | super.onStartCommand(intent, flags, startId);
100 | return START_STICKY;
101 | }
102 |
103 | @Override
104 | public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
105 | if(shouldBeIgnored(sbn)) {
106 | if(BuildConfig.DEBUG) {
107 | Bundle extras = NotificationCompat.getExtras(sbn.getNotification());
108 | String title = NotificationUtils.getTitle(extras);
109 | String msg = NotificationUtils.getMessage(extras);
110 | Log.d(TAG, "Ignoring potential duplicate from " + sbn.getPackageName() + ":\n" + title + "\n" + msg);
111 | }
112 | return;
113 | }
114 |
115 | if(shouldAppBeAnnounced(sbn, rankingMap))
116 | handleSbn(sbn);
117 | }
118 |
119 | private boolean shouldBeIgnored(StatusBarNotification sbn) {
120 | if(!duplicateReplyPackages.contains(sbn.getPackageName()))
121 | return false;
122 | int hashCode = getHashCode(sbn);
123 | return notifHandled.indexOf(hashCode) > -1 || previouslyDismissed.containsValue(hashCode);
124 | }
125 |
126 | @Override
127 | public void onNotificationPosted(StatusBarNotification sbn) {
128 | if(shouldBeIgnored(sbn))
129 | return;
130 |
131 | if (shouldAppBeAnnounced(sbn))
132 | handleSbn(sbn);
133 | }
134 |
135 | private int getHashCode(StatusBarNotification sbn) {
136 | Bundle extras = NotificationCompat.getExtras(sbn.getNotification());
137 | String title = NotificationUtils.getTitle(extras);
138 | String msg = NotificationUtils.getMessage(extras);
139 | return (title + msg + sbn.getPackageName()).hashCode();
140 | }
141 |
142 | private void handleSbn(StatusBarNotification sbn) {
143 | if(!lastRemovedKey.equals(getKey(sbn)))
144 | postDelayed(sbn);
145 | lastRemovedKey = "";
146 | }
147 |
148 | private void postDelayed(StatusBarNotification sbn) {
149 | PendingNotification pn = new PendingNotification(sbn);
150 | int index = pending.indexOf(pn);
151 | if (index >= 0) {
152 | boolean remove = false;
153 | try {
154 | if (NotificationCompat.isGroupSummary(sbn.getNotification())) {
155 | pending.get(index).setDismissKey(pn.getDismissKey());
156 | return;
157 | } else if ((index = pending.indexOf(pn)) >= 0 && NotificationCompat.isGroupSummary(pending.get(index).getSbn().getNotification())) { //Fix for bug where by now the list could be empty so we need to re-evaluate the index
158 | pn.setDismissKey(pending.get(index).getDismissKey());
159 | remove = true;
160 | }
161 | } catch (IndexOutOfBoundsException e) {
162 | e.printStackTrace();
163 | }
164 | if (remove && pending.size() > index) {
165 | pending.get(index).getScheduledFuture().cancel(false);
166 | pending.remove(index);
167 | }
168 | }
169 |
170 | Runnable task = new Runnable() {
171 | public void run() {
172 | if (pending.size() > 0) {
173 | PendingNotification pn = pending.get(0);
174 | pending.remove(0);
175 | if(duplicateReplyPackages.contains(pn.getSbn().getPackageName()))
176 | notifHandled.add(getHashCode(pn.getSbn()));
177 | onNotificationPosted(pn.getSbn(), pn.getDismissKey());
178 | }
179 | }
180 | };
181 | ScheduledFuture> scheduledFuture = worker.schedule(task, 200, TimeUnit.MILLISECONDS);
182 | pn.setScheduledFuture(scheduledFuture);
183 | pending.add(pn);
184 | }
185 |
186 | @Override
187 | public void onNotificationRemoved(final StatusBarNotification sbn) {
188 | if(sbn != null && duplicateReplyPackages.contains(sbn.getPackageName())) {
189 | int hashCode = getHashCode(sbn);
190 | int indexOf = notifHandled.indexOf(hashCode);
191 | if (indexOf > -1) {
192 | notifHandled.remove(indexOf);
193 | Bundle extras = NotificationCompat.getExtras(sbn.getNotification());
194 | String title = NotificationContentUtils.getTitle(extras);
195 | if(TextUtils.isEmpty(title))
196 | return;
197 | int titleHashcode = title.hashCode();
198 | if(previouslyDismissed.containsKey(titleHashcode))
199 | previouslyDismissed.put(titleHashcode, hashCode);
200 | else {
201 | if (previouslyDismissed.size() >= 5)
202 | previouslyDismissed.remove(previouslyDismissed.size() - 1);
203 | previouslyDismissed.put(titleHashcode, hashCode);
204 | }
205 | }
206 | }
207 | }
208 |
209 | private String getKey(StatusBarNotification sbn) {
210 | return VersionUtils.isLollipop() ? sbn.getKey() : String.valueOf(sbn.getId());
211 | }
212 |
213 | protected boolean shouldAppBeAnnounced(StatusBarNotification sbn, RankingMap rankingMap) {
214 | return shouldAppBeAnnounced(sbn);
215 | }
216 | protected abstract boolean shouldAppBeAnnounced(StatusBarNotification sbn);
217 | protected abstract void onNotificationPosted(StatusBarNotification sbn, String dismissKey);
218 |
219 | }
--------------------------------------------------------------------------------
/notifLib/src/main/res/drawable/dummy_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamrobj/NotificationHelperLibrary/f92d43b2fda97f4afe573d45da91ec10c1250b1c/notifLib/src/main/res/drawable/dummy_icon.png
--------------------------------------------------------------------------------
/notifLib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | NotificationHelperLibrary
3 |
4 |
--------------------------------------------------------------------------------
/notifLib/src/test/java/com/robj/notificationhelperlibrary/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.robj.notificationhelperlibrary;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':notifLib'
2 |
--------------------------------------------------------------------------------