├── .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 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | --------------------------------------------------------------------------------