├── .gitignore ├── LICENSE ├── README.md ├── RELEASE-NOTES.md ├── RELEASE.md ├── ROADMAP.md ├── breadcast-annotation ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── dreiklang │ └── breadcast │ └── annotation │ ├── Receive.java │ └── ThreadModus.java ├── breadcast-base ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── dreiklang │ │ └── breadcast │ │ └── base │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── dreiklang │ │ │ └── breadcast │ │ │ └── base │ │ │ ├── BaseBreadcast.java │ │ │ ├── Breadcaster.java │ │ │ ├── exception │ │ │ └── NoAnnotatedMethodException.java │ │ │ ├── exec │ │ │ ├── Execution.java │ │ │ ├── ExecutiveManager.java │ │ │ ├── TypedExecution.java │ │ │ ├── TypedExecutive.java │ │ │ └── TypedMultiExecution.java │ │ │ ├── statics │ │ │ ├── ManifestBreadcast.java │ │ │ └── SingletonBreadcast.java │ │ │ └── thread │ │ │ └── ThreadUtil.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── io │ └── dreiklang │ └── breadcast │ └── base │ └── ThreadUtilUnitTest.java ├── breadcast-processor ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── dreiklang │ │ └── breadcast │ │ └── processor │ │ └── BreadcastProcessor.java │ └── resources │ └── META-INF │ └── services │ └── javax.annotation.processing.Processor ├── breadcast-test-integration ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── dreiklang │ │ └── breadcast │ │ └── test │ │ └── integration │ │ ├── AnnotationBreadcastTest.java │ │ ├── StandaloneBreadcasterTest.java │ │ └── receiver │ │ ├── TestReceiver.java │ │ └── TestThreadedReceiver.java │ ├── main │ ├── AndroidManifest.xml │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── io │ └── dreiklang │ └── breadcast │ └── test │ └── integration │ └── ExampleUnitTest.java └── lib.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | 43 | # Keystore files 44 | *.jks 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | 49 | # Google Services (e.g. APIs or Firebase) 50 | google-services.json 51 | 52 | # Freeline 53 | freeline.py 54 | freeline/ 55 | freeline_project_description.json 56 | 57 | *.asc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ###### Base: [ ![Download](https://api.bintray.com/packages/dreiklang/Breadcast/breadcast-base/images/download.svg) ](https://bintray.com/dreiklang/Breadcast/breadcast-base/_latestVersion) Processor: [ ![Download](https://api.bintray.com/packages/dreiklang/Breadcast/breadcast-processor/images/download.svg) ](https://bintray.com/dreiklang/Breadcast/breadcast-processor/_latestVersion) Annotation: [ ![Download](https://api.bintray.com/packages/dreiklang/Breadcast/breadcast-annotation/images/download.svg) ](https://bintray.com/dreiklang/Breadcast/breadcast-annotation/_latestVersion) 2 | 3 | # ![bread](https://png.icons8.com/metro/50/000000/bread.png) Breadcast 4 | Small Broadcast Receiver Library for Android 5 | 6 | > ... simplifies listening to broadcasts by hiding what would be boilerplate code. 7 | 8 | - __One for All__: Single BroadcastReceiver instance for every action 9 | - __Clean look__: Generates the code behind the curtains 10 | - __Thread-Modes__: Multithreaded callback via annotation option 11 | - __Simple__: Broadcast listening possible in one statement 12 | 13 | #### Before: 14 | ```java 15 | class MyReceiver extends BroadcastReceiver { 16 | @Override 17 | public void onReceive(Context context, Intent intent) { 18 | switch (intent.getAction()) { 19 | case Intent.ACTION_SCREEN_OFF: 20 | ... 21 | case Intent.ACTION_SCREEN_ON: 22 | ... 23 | } 24 | } 25 | } 26 | ``` 27 | ```java 28 | IntentFilter filter = new IntentFilter(); 29 | filter.addAction(Intent.ACTION_SCREEN_OFF); 30 | filter.addAction(Intent.ACTION_SCREEN_ON); 31 | context.registerBroadcast(new MyReceiver(), filter); 32 | ``` 33 | 34 | ### With Breadcast you can choose between 2 ways: 35 | 36 | ### 1. Breadcast Standalone (Base package) 37 | ```java 38 | new Breadcaster(context) 39 | .action(Intent.ACTION_SCREEN_OFF, (context, intent) -> ... ) 40 | .action(Intent.ACTION_SCREEN_ON, (context, intent) -> ... ) 41 | .register(); 42 | ``` 43 | 44 | ### 2. Breadcast Annotation 45 | ```java 46 | Breadcast.init(context); 47 | ``` 48 | ```java 49 | class MyClass { // extends ... 50 | MyClass() { 51 | Breadcast.instance().register(this); // required for non-static methods 52 | } 53 | 54 | @Receive(action = Intent.ACTION_SCREEN_OFF) 55 | void onScreenOff() { ... } 56 | 57 | @Receive(action = Intent.ACTION_SCREEN_ON) 58 |    void onScreenOn() { ... } 59 | } 60 | ``` 61 | 62 | #### More examples 63 | ```java 64 | @Receive(action = {Intent.ACTION_SCREEN_ON, Intent.ACTION_SCREEN_OFF}) 65 | onScreenChange(Context context, Intent intent) { ... } // multiple 66 | 67 | @Receive(action = "custom1", threadMode = ThreadModus.ASYNC) 68 | onCustomAsync() { ... } // asynchronous 69 | 70 | @Receive(action = Intent.ACTION_SHUTDOWN) 71 | static withoutRegister() { ... } // static - called regardless of object registration 72 | ``` 73 | 74 | #### To define actions via manifest (implicit/static), use _ManifestBreadcast_ 75 | ```xml 76 | 77 | 78 | 79 | 80 | 81 | ``` 82 | ``` 83 | @Receive(action = Intent.ACTION_BOOT_COMPLETED) 84 | static onBoot() { ... } // must be static 85 | ``` 86 | __Important__: You must init() Breadcast nevertheless! 87 | 88 | ## Notes 89 | - Standalone: Remember to call release() if not needed anymore (memory leaks) 90 | - Watch out for Android O's [implicit broadcast exceptions](https://developer.android.com/guide/components/broadcast-exceptions.html) 91 | 92 | ## Download 93 | #### Gradle 94 | ```java 95 | dependencies { 96 | // required 97 | implementation 'io.dreiklang:breadcast-base:1.1.0' 98 | 99 | // if you use the annotation 100 | implementation 'io.dreiklang:breadcast-annotation:1.1.0' 101 | annotationProcessor 'io.dreiklang:breadcast-processor:1.1.0' 102 | } 103 | ``` 104 | 105 | Please open an issue case if you find one. It's merely 1-2 minutes against literally tons of neurons roasted for debunking them. :) 106 | 107 | __Thank you and have fun!__ 108 | 109 | License 110 | ------- 111 | 112 | Copyright 2018 Nhu Huy Le 113 | 114 | Licensed under the Apache License, Version 2.0 (the "License"); 115 | you may not use this file except in compliance with the License. 116 | You may obtain a copy of the License at 117 | 118 | http://www.apache.org/licenses/LICENSE-2.0 119 | 120 | Unless required by applicable law or agreed to in writing, software 121 | distributed under the License is distributed on an "AS IS" BASIS, 122 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 123 | See the License for the specific language governing permissions and 124 | limitations under the License. 125 | 126 | 127 | Logo icon by [Icons8](https://icons8.com) 128 | -------------------------------------------------------------------------------- /RELEASE-NOTES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## 1.1.0 (7. Feb 2018) 4 | - Support for __multiple actions__ in an annotation (action string array) 5 | - Support for __implicit registration__ (_ManifestBreadcast_ e. g. BOOT_COMPLETED) 6 | - Annotated __static methods__ without previous registration of an instance 7 | 8 | ## 1.0.1 (5. Feb 2018) 9 | - Critital Bugfix: Annotating multiple methods in the same class with the same action resulted to duplicated callbacks. 10 | - Added Integration Test. 11 | 12 | ## 1.0.0 (3. Feb 2018) 13 | - Initial Release 14 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | - change library version in parent lib.gradle 2 | - run gradle tasks _install_ and _bintrayUpload_ on each module with cmd (instead with android studio) -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | - Logging 3 | - Configuration (on Initialization) 4 | - Log Level 5 | - Exception Handling 6 | - Thread Modes POSTING possibly obsolete (default: MAIN) 7 | - Annotation param: multiple actions with string array 8 | 9 | ##### Help me if you can :) 10 | 11 | > "We must all suffer one of two things: The pain of discipline or the pain of regret." (Jim Rohn) 12 | -------------------------------------------------------------------------------- /breadcast-annotation/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | } 6 | 7 | sourceCompatibility = "1.7" 8 | targetCompatibility = "1.7" 9 | 10 | ext { 11 | bintrayName = 'breadcast-annotation' 12 | libraryName = 'breadcast-annotation' 13 | artifact = 'breadcast-annotation' 14 | libraryDescription = 'Broadcast Receiver Library for Android: Annotation' 15 | } 16 | 17 | apply from: '../lib.gradle' -------------------------------------------------------------------------------- /breadcast-annotation/src/main/java/io/dreiklang/breadcast/annotation/Receive.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import static io.dreiklang.breadcast.annotation.ThreadModus.POSTING; 9 | 10 | /** 11 | * Annotates a method to be called by Breadcast when the respective broadcast action occurs. 12 | * Object must be previously registered under the generated and initialized static Breadcast instance. 13 | * 14 | * @author Nhu Huy Le, mail@huy-le.de 15 | */ 16 | 17 | @Retention(RetentionPolicy.SOURCE) 18 | @Target(ElementType.METHOD) 19 | public @interface Receive { 20 | 21 | ThreadModus threadMode() default POSTING; 22 | 23 | String[] action(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /breadcast-annotation/src/main/java/io/dreiklang/breadcast/annotation/ThreadModus.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.annotation; 2 | 3 | /** 4 | * Defines the thread the receiving method should run in. 5 | * 6 | * @author Nhu Huy Le, mail@huy-le.de 7 | */ 8 | 9 | public enum ThreadModus { 10 | 11 | /** 12 | * Runs in the thread the broadcast is sent with android.Context#sendBroadcast(Intent, String, Handler). 13 | * If no handler-thread is defined, the receiving method will be called in the UI Thread aka Main Thread. 14 | */ 15 | POSTING, 16 | 17 | /** 18 | * Runs in the UI thread of the Android framework. 19 | */ 20 | MAIN, 21 | 22 | /** 23 | * Ensures that the receiving method will be called in the background, either as the posting thread or 24 | * by starting a separate thread on its own. 25 | */ 26 | BACKGROUND, 27 | 28 | /** 29 | * Starts a separate thread the method is called in. 30 | */ 31 | ASYNC, 32 | 33 | } 34 | -------------------------------------------------------------------------------- /breadcast-base/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | defaultConfig { 7 | minSdkVersion 15 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | configurations { 25 | javadocDeps 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | 31 | implementation 'com.android.support:appcompat-v7:26.1.0' 32 | testImplementation 'junit:junit:4.12' 33 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 34 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 35 | 36 | implementation project(path: ':breadcast-annotation') 37 | } 38 | 39 | ext { 40 | bintrayName = 'breadcast-base' 41 | libraryName = 'breadcast-base' 42 | artifact = 'breadcast-base' 43 | libraryDescription = 'Broadcast Receiver Library for Android: Base' 44 | } 45 | 46 | apply from: '../lib.gradle' -------------------------------------------------------------------------------- /breadcast-base/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /breadcast-base/src/androidTest/java/io/dreiklang/breadcast/base/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base; 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 | /** 11 | * Instrumented test, which will execute on an Android device. 12 | * 13 | * @see Testing documentation 14 | */ 15 | @RunWith(AndroidJUnit4.class) 16 | public class ExampleInstrumentedTest { 17 | 18 | @Test 19 | public void useAppContext() throws Exception { 20 | // Context of the app under test. 21 | Context appContext = InstrumentationRegistry.getTargetContext(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /breadcast-base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/BaseBreadcast.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import java.util.Collection; 7 | 8 | import io.dreiklang.breadcast.annotation.ThreadModus; 9 | import io.dreiklang.breadcast.base.exception.NoAnnotatedMethodException; 10 | import io.dreiklang.breadcast.base.exec.Execution; 11 | import io.dreiklang.breadcast.base.exec.ExecutiveManager; 12 | import io.dreiklang.breadcast.base.exec.TypedExecution; 13 | import io.dreiklang.breadcast.base.exec.TypedMultiExecution; 14 | import io.dreiklang.breadcast.base.thread.ThreadUtil; 15 | 16 | /** 17 | * Base class of the custom generated Breadcast. 18 | * @author Nhu Huy Le, mail@huy-le.de 19 | */ 20 | // TODO cant resolve jdoc dependencies 21 | // see https://stackoverflow.com/questions/10895032/javadoc-with-gradle-dont-get-the-libraries-while-running-javadoc-task 22 | 23 | public abstract class BaseBreadcast { 24 | 25 | private final ExecutiveManager manager = new ExecutiveManager(); 26 | 27 | private final Breadcaster caster; 28 | 29 | private boolean NamExceptionOpt = true; 30 | 31 | protected BaseBreadcast(Context context) { 32 | caster = new Breadcaster(context); 33 | initExecutions(); 34 | caster.register(); 35 | } 36 | 37 | /** 38 | * Manually run all callbacks/executions listening to the intent action. 39 | * @param intent intent with action the executions are mapped to. 40 | * @return if mapping exists and executions ran. 41 | */ 42 | public boolean exec(Intent intent) { 43 | return caster.exec(intent); 44 | } 45 | 46 | /** 47 | * Registers an object with annotated methods of {@link io.dreiklang.breadcast.annotation.Receive}. Throws an exception if no annotated method is found. */ 48 | public void register(Object object) { 49 | if (!manager.addInstance(object)) { 50 | if (NamExceptionOpt) { 51 | throw new NoAnnotatedMethodException(); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Unregisters an object with annotated methods of {@link io.dreiklang.breadcast.annotation.Receive}. Throws an exception if no annotated method is found. */ 58 | public void unregister(Object object) { 59 | if (!manager.removeInstance(object)) { 60 | if (NamExceptionOpt) { 61 | throw new NoAnnotatedMethodException(); 62 | } 63 | } 64 | } 65 | 66 | protected void map(final Class clazz, final String action, final String method, final boolean isStatic, final ThreadModus modus, final TypedExecution execution) { 67 | mapMethod(clazz, method, isStatic, execution); 68 | mapAction(clazz, method, modus, action); 69 | } 70 | 71 | protected void map(final Class clazz, final String[] actions, final String method, final boolean isStatic, final ThreadModus modus, final TypedExecution execution) { 72 | mapMethod(clazz, method, isStatic, execution); 73 | for (String action : actions) { 74 | mapAction(clazz, method, modus, action); 75 | } 76 | } 77 | 78 | private void mapMethod(Class clazz, String method, final boolean isStatic, final TypedExecution execution) { 79 | manager.defineExecutive(clazz, method, new TypedMultiExecution() { 80 | @Override 81 | public void exec(Context context, Intent intent, Collection instances) { 82 | if (isStatic) { 83 | execution.exec(context, intent, null); 84 | } 85 | else if (instances != null) { 86 | for (T instance: instances) { 87 | execution.exec(context, intent, instance); 88 | } 89 | } 90 | } 91 | }); 92 | } 93 | 94 | private void mapAction(final Class clazz, final String method, final ThreadModus modus, String action) { 95 | caster.action(action, new Execution() { 96 | @Override 97 | public void exec(final Context context, final Intent intent) { 98 | ThreadUtil.runOnThread(modus, new Runnable() { 99 | @Override 100 | public void run() { 101 | manager.exec(clazz, method, context, intent); 102 | } 103 | }); 104 | } 105 | }); 106 | } 107 | 108 | protected abstract void initExecutions(); 109 | 110 | } 111 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/Breadcaster.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import io.dreiklang.breadcast.base.exec.Execution; 14 | 15 | /** 16 | * Standalone broadcast receiver object. 17 | * - Registered breadcasters must not be modified (unregister to add more actions). 18 | * - Changes to this breadcaster object are not thread-safe. 19 | * - Remember to {@link #release()} resources if not needed anymore to avoid memory leaks. 20 | */ 21 | public class Breadcaster extends BroadcastReceiver { 22 | 23 | private Map> executions = new HashMap<>(); 24 | 25 | private Context context; 26 | 27 | private boolean isRegistered = false; 28 | 29 | /** 30 | * Initializes Breadcaster with a context. Breadcaster does not know about lifecycles. 31 | * @param context 32 | */ 33 | public Breadcaster(Context context) { 34 | this.context = context; 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | @Override 41 | public void onReceive(Context context, Intent intent) { 42 | exec(context, intent); 43 | } 44 | 45 | /** 46 | * Must be called after adding actions to start listening to broadcasts. 47 | * Equals to {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)} 48 | * @return 49 | */ 50 | public Breadcaster register() { 51 | if (context == null) { 52 | throw new IllegalStateException("breadcaster already released"); 53 | } 54 | context.registerReceiver(this, getIntentFilter()); 55 | isRegistered = true; 56 | return this; 57 | } 58 | 59 | /** 60 | * Breadcaster stops to listen. Equals to {@link Context#unregisterReceiver(BroadcastReceiver)} 61 | * @return 62 | */ 63 | public Breadcaster unregister() { 64 | if (context == null) { 65 | throw new IllegalStateException("breadcaster already released"); 66 | } 67 | if (isRegistered) { 68 | context.unregisterReceiver(this); 69 | } 70 | isRegistered = false; 71 | return this; 72 | } 73 | 74 | /** 75 | * Removes all action callbacks. Breadcaster must be unregistered. 76 | */ 77 | public Breadcaster clear() { 78 | if (isRegistered) { 79 | throw new IllegalStateException("breadcaster is registered - unregister to clear actions"); 80 | } 81 | executions.clear(); 82 | return this; 83 | } 84 | 85 | /** 86 | * Releases resources (e.g. context). After that, this Breadcaster object is not useable anymore. 87 | */ 88 | public void release() { 89 | unregister(); 90 | context = null; 91 | executions = null; 92 | } 93 | 94 | /** 95 | * Defines an execution to run on a broadcast action. 96 | * @param action broadcasted action to listen to. 97 | * @param execution interface to run on action. 98 | * @return 99 | */ 100 | public Breadcaster action(String action, Execution execution) { 101 | if (isRegistered) { 102 | throw new IllegalStateException("breadcaster is registered - unregister to add action"); 103 | } 104 | List list = executions.get(action); 105 | if (list == null) { 106 | list = new ArrayList<>(); 107 | executions.put(action, list); 108 | } 109 | 110 | list.add(execution); 111 | return this; 112 | } 113 | 114 | /** 115 | * @return If breadcaster object is currently registered. 116 | */ 117 | public boolean isRegistered() { 118 | return isRegistered; 119 | } 120 | 121 | /** 122 | * @return Underlying context. 123 | */ 124 | public Context getContext() { 125 | if (context == null) { 126 | throw new IllegalStateException("breadcaster already released"); 127 | } 128 | return context; 129 | } 130 | 131 | /** 132 | * Manually run all callbacks/executions listening to the intent action on the context initialized with. 133 | * @param intent intent with action the executions are mapped to. 134 | * if mapping exists and executions ran. 135 | */ 136 | public boolean exec(Intent intent) { 137 | return exec(context, intent); 138 | } 139 | 140 | private boolean exec(Context context, Intent intent) { 141 | if (intent == null) { 142 | return false; 143 | } 144 | 145 | List list = executions.get(intent.getAction()); 146 | if (list == null) { 147 | return false; 148 | } 149 | 150 | for (Execution execution : list) { 151 | execution.exec(context, intent); 152 | } 153 | 154 | return true; 155 | } 156 | 157 | private IntentFilter getIntentFilter() { 158 | IntentFilter intentFilter = new IntentFilter(); 159 | for (String action : executions.keySet()) { 160 | intentFilter.addAction(action); 161 | } 162 | return intentFilter; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/exception/NoAnnotatedMethodException.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base.exception; 2 | 3 | /** 4 | * The attempted registering of an object to Breadcast fails because no method or supermethod is 5 | * annotated with io.dreiklang.breadcast.annotation.Receive. 6 | * 7 | * @author Nhu Huy Le, mail@huy-le.de 8 | */ 9 | 10 | public class NoAnnotatedMethodException extends RuntimeException { 11 | 12 | public NoAnnotatedMethodException() { 13 | } 14 | 15 | public NoAnnotatedMethodException(String s) { 16 | super(s); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/exec/Execution.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base.exec; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | /** 7 | * Callback functional interface on receiving a broadcast in Breadcast. 8 | * @author Nhu Huy Le, mail@huy-le.de 9 | */ 10 | 11 | @FunctionalInterface 12 | public interface Execution { 13 | 14 | /** 15 | * Runs on receiving respective broadcasts. 16 | * @param context Context parameter from {@link android.content.BroadcastReceiver#onReceive(Context, Intent)} 17 | * @param intent Intent parameter from {@link android.content.BroadcastReceiver#onReceive(Context, Intent)} 18 | */ 19 | void exec(Context context, Intent intent); 20 | } 21 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/exec/ExecutiveManager.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base.exec; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * Holds executives mapped with their classes and ensures type safety. 11 | * 12 | * @author Nhu Huy Le, mail@huy-le.de 13 | */ 14 | 15 | public class ExecutiveManager { 16 | 17 | private final Map executives = new ConcurrentHashMap<>(); 18 | 19 | /** 20 | * Sets or overrides the execution run on all instances according to class type and listening method. 21 | * @param clazz class object as the primary key. 22 | * @param method method as secondary key. 23 | * @param execution execution to run on {@link #exec(Class, String, Context, Intent)} call. 24 | * @param generic class type to keep both class and execution in fit. 25 | */ 26 | public void defineExecutive(Class clazz, String method, TypedMultiExecution execution) { 27 | TypedExecutive executive = executives.get(clazz); 28 | if (executive == null) { 29 | executive = new TypedExecutive(); 30 | executives.put(clazz, executive); 31 | } 32 | 33 | executive.put(method, execution); 34 | } 35 | 36 | /** 37 | * Adds an instance to execute with, previously defined with {@link #defineExecutive(Class, String, TypedMultiExecution)}. 38 | * @param object object of type T defined in {@link #defineExecutive(Class, String, TypedMultiExecution)} 39 | * @return if there is a defined class to map the object execution with. 40 | */ 41 | public boolean addInstance(Object object) { 42 | Class clazz = object.getClass(); 43 | if (clazz.isAnonymousClass()) { 44 | clazz = clazz.getSuperclass(); 45 | } 46 | 47 | TypedExecutive executive = executives.get(clazz); 48 | if (executive == null) { 49 | return false; 50 | } 51 | 52 | return executive.addInstance(object); 53 | } 54 | 55 | /** 56 | * Removes an instance to execute with, previously defined with {@link #defineExecutive(Class, String, TypedMultiExecution)}. 57 | * @param object object of type T defined in {@link #defineExecutive(Class, String, TypedMultiExecution)} 58 | * @return if there is a defined class to map the object execution with. 59 | */ 60 | public boolean removeInstance(Object object) { 61 | Class clazz = object.getClass(); 62 | if (clazz.isAnonymousClass()) { 63 | clazz = clazz.getSuperclass(); 64 | } 65 | 66 | TypedExecutive executive = executives.get(clazz); 67 | if (executive == null) { 68 | return false; 69 | } 70 | 71 | return executive.removeInstance(object); 72 | } 73 | 74 | /** 75 | * Runs the execution specified by class and method name on all instances of the respective class type. 76 | * @param clazz class type to execute on. 77 | * @param method method specifier. 78 | * @param context context of #onReceive. 79 | * @param intent intent parameter from {@link android.content.BroadcastReceiver#onReceive(Context, Intent)} 80 | * @param 81 | */ 82 | public void exec(Class clazz, String method, Context context, Intent intent) { 83 | executives.get(clazz).exec(method, context, intent); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/exec/TypedExecution.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base.exec; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | /** 7 | * Callback functional interface on a typed object on receiving a broadcast in Breadcast. 8 | * @author Nhu Huy Le, mail@huy-le.de 9 | */ 10 | 11 | @FunctionalInterface 12 | public interface TypedExecution { 13 | 14 | /** 15 | * Runs on receiving respective broadcasts. 16 | * @param context Context parameter from {@link android.content.BroadcastReceiver#onReceive(Context, Intent)} 17 | * @param intent Intent parameter from {@link android.content.BroadcastReceiver#onReceive(Context, Intent)} 18 | * @param instance instance of T to exec on 19 | */ 20 | void exec(Context context, Intent intent, T instance); 21 | } 22 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/exec/TypedExecutive.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base.exec; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | /** 13 | * Responsible for executions in instances of one distinct class. 14 | * @author Nhu Huy Le, mail@huy-le.de 15 | */ 16 | 17 | public class TypedExecutive { 18 | 19 | private final List instances = Collections.synchronizedList(new ArrayList()); 20 | 21 | private final Map>> executionsMap = new ConcurrentHashMap<>(); 22 | 23 | /** 24 | * Run the method callback defined by {@link #put(String, TypedMultiExecution)}. 25 | * @param method method name to run the callback on 26 | * @param context context under which broadcast was sent with {@link Context#sendBroadcast(Intent)} 27 | * @param intent intent with which broadcast was sent with {@link Context#sendBroadcast(Intent)} 28 | */ 29 | public void exec(String method, Context context, Intent intent) { 30 | List> executions = executionsMap.get(method); 31 | if (executions == null) { 32 | throw new IllegalArgumentException("no executions found for method: " + method); 33 | } 34 | for (TypedMultiExecution execution : executions) { 35 | execution.exec(context, intent, instances); 36 | } 37 | } 38 | 39 | /** 40 | * Defines an execution mapped by the method name. 41 | * @param method method name, on which the execution will run 42 | * @param execution typed callback to run on method 43 | */ 44 | public void put(String method, TypedMultiExecution execution) { 45 | List> executions = executionsMap.get(method); 46 | if (executions == null) { 47 | executions = Collections.synchronizedList(new ArrayList>()); 48 | executionsMap.put(method, executions); 49 | } 50 | executions.add(execution); 51 | } 52 | 53 | /** 54 | * Adds an instance on which the execution will run. 55 | * @param object instance of T to add. 56 | * @return result of {@link List#add(Object)} 57 | */ 58 | public boolean addInstance(T object) { 59 | return instances.add(object); 60 | } 61 | 62 | /** 63 | * Removes an instance on which the execution will run. 64 | * @param object instance of T to remove. 65 | * @return result of {@link List#remove(Object)} 66 | */ 67 | public boolean removeInstance(T object) { 68 | return instances.remove(object); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/exec/TypedMultiExecution.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base.exec; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import java.util.Collection; 7 | 8 | /** 9 | * Callback functional interface on a list of typed objects on receiving a broadcast in Breadcast. 10 | * @author Nhu Huy Le, mail@huy-le.de 11 | */ 12 | 13 | @FunctionalInterface 14 | public interface TypedMultiExecution { 15 | 16 | /** 17 | * Runs on receiving respective broadcasts. 18 | * @param context Context parameter from {@link android.content.BroadcastReceiver#onReceive(Context, Intent)} 19 | * @param intent Intent parameter from {@link android.content.BroadcastReceiver#onReceive(Context, Intent)} 20 | * @param instances List of instances of T to exec on 21 | */ 22 | void exec(Context context, Intent intent, Collection instances); 23 | } 24 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/statics/ManifestBreadcast.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base.statics; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | /** 8 | * @author Nhu Huy Le (mail@huy-le.de), on 06/02/2018 9 | */ 10 | 11 | public class ManifestBreadcast extends BroadcastReceiver { 12 | 13 | @Override 14 | public void onReceive(Context context, Intent intent) { 15 | SingletonBreadcast.instance().exec(intent); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/statics/SingletonBreadcast.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base.statics; 2 | 3 | import android.content.Context; 4 | 5 | import io.dreiklang.breadcast.base.BaseBreadcast; 6 | 7 | /** 8 | * Breadcast implementing the static singleton pattern. 9 | * @author Nhu Huy Le (mail@huy-le.de), on 06/02/2018 10 | */ 11 | 12 | public abstract class SingletonBreadcast extends BaseBreadcast { 13 | 14 | private static SingletonBreadcast instance; 15 | 16 | protected SingletonBreadcast(Context context) { 17 | super(context); 18 | if(instance != null) { 19 | throw new IllegalStateException("Breadcast already initialized."); 20 | } 21 | instance = this; 22 | } 23 | 24 | public static BaseBreadcast instance() { 25 | if(instance == null) { 26 | throw new IllegalStateException("Breadcast not yet initialized."); 27 | } 28 | return instance; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /breadcast-base/src/main/java/io/dreiklang/breadcast/base/thread/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base.thread; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import io.dreiklang.breadcast.annotation.ThreadModus; 7 | 8 | /** 9 | * Util class for threading in Breadcast. 10 | * @author Nhu Huy Le, mail@huy-le.de 11 | */ 12 | 13 | public final class ThreadUtil { 14 | 15 | private ThreadUtil() { 16 | } 17 | 18 | /** 19 | * Runs on thread defined by ThreadModus 20 | * @param modus ThreadModus 21 | * @param runnable functional interface to run 22 | */ 23 | public static void runOnThread(ThreadModus modus, Runnable runnable) { 24 | switch (modus) { 25 | 26 | case POSTING: // TODO obsolet as posting thread only configurable on registerBroadcast? 27 | runnable.run(); 28 | break; 29 | 30 | case MAIN: 31 | runOnUiThread(runnable); 32 | break; 33 | 34 | case ASYNC: 35 | runAsync(runnable); 36 | break; 37 | 38 | case BACKGROUND: 39 | runOnBackgroundThread(runnable); 40 | break; 41 | } 42 | } 43 | 44 | /** 45 | * Runs on the android UI thread aka main thread. See ThreadModus#MAIN 46 | * @param runnable 47 | */ 48 | public static void runOnUiThread(Runnable runnable) { 49 | if (Looper.myLooper() == Looper.getMainLooper()) { 50 | runnable.run(); 51 | } 52 | else { 53 | new Handler(Looper.getMainLooper()).post(runnable); 54 | } 55 | } 56 | 57 | /** 58 | * Runs on a background thread. See ThreadModus#BACKGROUND 59 | * @param runnable 60 | */ 61 | public static void runOnBackgroundThread(Runnable runnable) { 62 | if (Looper.myLooper() == Looper.getMainLooper()) { 63 | runAsync(runnable); 64 | } 65 | else { 66 | runnable.run(); 67 | } 68 | } 69 | 70 | /** 71 | * Runs asynchronous. See ThreadModus#ASYNC 72 | * @param runnable 73 | */ 74 | // TODO replace by threadpool/executor? 75 | public static void runAsync(Runnable runnable) { 76 | new Thread(runnable).start(); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /breadcast-base/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | breadcast-base 3 | 4 | -------------------------------------------------------------------------------- /breadcast-base/src/test/java/io/dreiklang/breadcast/base/ThreadUtilUnitTest.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.base; 2 | 3 | import org.junit.Test; 4 | 5 | import io.dreiklang.breadcast.base.thread.ThreadUtil; 6 | 7 | import static junit.framework.Assert.assertTrue; 8 | 9 | public class ThreadUtilUnitTest { 10 | 11 | @Test 12 | public void testAsync() throws Exception { 13 | final long mainThread = Thread.currentThread().getId(); 14 | ThreadUtil.runAsync(new Runnable() { 15 | @Override 16 | public void run() { 17 | final long asyncThread = Thread.currentThread().getId(); 18 | assertTrue("is async thread", mainThread != asyncThread); 19 | } 20 | }); 21 | Thread.sleep(1000); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /breadcast-processor/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(include: ['*.jar'], dir: 'libs') 5 | compile 'com.squareup:javapoet:1.9.0' 6 | 7 | implementation project(path: ':breadcast-annotation') 8 | } 9 | 10 | sourceCompatibility = "1.8" 11 | targetCompatibility = "1.8" 12 | 13 | ext { 14 | bintrayName = 'breadcast-processor' 15 | libraryName = 'breadcast-processor' 16 | artifact = 'breadcast-processor' 17 | libraryDescription = 'Broadcast Receiver Library for Android: Processor' 18 | } 19 | 20 | apply from: '../lib.gradle' -------------------------------------------------------------------------------- /breadcast-processor/src/main/java/io/dreiklang/breadcast/processor/BreadcastProcessor.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.processor; 2 | 3 | import com.squareup.javapoet.ClassName; 4 | import com.squareup.javapoet.JavaFile; 5 | import com.squareup.javapoet.MethodSpec; 6 | import com.squareup.javapoet.ParameterizedTypeName; 7 | import com.squareup.javapoet.TypeName; 8 | import com.squareup.javapoet.TypeSpec; 9 | 10 | import java.io.IOException; 11 | import java.time.Duration; 12 | import java.time.Instant; 13 | import java.util.ArrayList; 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Objects; 17 | import java.util.Set; 18 | import java.util.stream.Collectors; 19 | import java.util.stream.Stream; 20 | 21 | import javax.annotation.processing.AbstractProcessor; 22 | import javax.annotation.processing.Filer; 23 | import javax.annotation.processing.Messager; 24 | import javax.annotation.processing.ProcessingEnvironment; 25 | import javax.annotation.processing.RoundEnvironment; 26 | import javax.annotation.processing.SupportedSourceVersion; 27 | import javax.lang.model.SourceVersion; 28 | import javax.lang.model.element.Element; 29 | import javax.lang.model.element.ElementKind; 30 | import javax.lang.model.element.ExecutableElement; 31 | import javax.lang.model.element.TypeElement; 32 | import javax.lang.model.element.VariableElement; 33 | import javax.lang.model.type.NoType; 34 | import javax.lang.model.util.Elements; 35 | import javax.lang.model.util.Types; 36 | import javax.tools.Diagnostic; 37 | 38 | import io.dreiklang.breadcast.annotation.Receive; 39 | import io.dreiklang.breadcast.annotation.ThreadModus; 40 | 41 | import static javax.lang.model.element.Modifier.ABSTRACT; 42 | import static javax.lang.model.element.Modifier.PRIVATE; 43 | import static javax.lang.model.element.Modifier.PROTECTED; 44 | import static javax.lang.model.element.Modifier.PUBLIC; 45 | import static javax.lang.model.element.Modifier.STATIC; 46 | 47 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 48 | public class BreadcastProcessor extends AbstractProcessor { 49 | 50 | private Types typeUtils; 51 | 52 | private Elements elementUtils; 53 | 54 | private Filer filer; 55 | 56 | private Messager messager; 57 | 58 | private List receives = new ArrayList<>(); 59 | 60 | @Override 61 | public Set getSupportedAnnotationTypes() { 62 | HashSet supported = new HashSet<>(); 63 | supported.add(Receive.class.getCanonicalName()); 64 | return supported; 65 | } 66 | 67 | @Override 68 | public synchronized void init(ProcessingEnvironment processingEnv) { 69 | super.init(processingEnv); 70 | typeUtils = processingEnv.getTypeUtils(); 71 | elementUtils = processingEnv.getElementUtils(); 72 | filer = processingEnv.getFiler(); 73 | messager = processingEnv.getMessager(); 74 | messager.printMessage(Diagnostic.Kind.NOTE, "breadcast:\t init ..."); 75 | } 76 | 77 | @Override 78 | public boolean process(Set annotations, RoundEnvironment roundEnv) { 79 | if (annotations.isEmpty()) { 80 | return true; 81 | } 82 | 83 | messager.printMessage(Diagnostic.Kind.NOTE, "breadcast:\t processing annotation(s): " + annotations); 84 | Instant startTime = Instant.now(); 85 | if (roundEnv.getElementsAnnotatedWith(Receive.class).isEmpty()) { 86 | messager.printMessage(Diagnostic.Kind.ERROR, "breadcast:\t no elements annotated with: " + annotations); 87 | return true; 88 | } 89 | 90 | if (!extractReceives(roundEnv)) { 91 | messager.printMessage(Diagnostic.Kind.ERROR, "breadcast:\t extracting items failed."); 92 | return true; 93 | } 94 | 95 | TypeSpec breadcastSpec = generateBreadcast(); 96 | try { 97 | JavaFile.builder("io.dreiklang.breadcast", breadcastSpec).build().writeTo(filer); 98 | } 99 | catch (IOException ex) { 100 | ex.printStackTrace(); 101 | } 102 | 103 | messager.printMessage(Diagnostic.Kind.NOTE, "breadcast:\t processing duration: " + Duration.between(startTime, Instant.now())); 104 | return true; 105 | } 106 | 107 | private TypeSpec generateBreadcast() { 108 | ClassName breadcastName = ClassName.get("io.dreiklang.breadcast", "Breadcast"); 109 | ClassName singletonBCName = ClassName.get("io.dreiklang.breadcast.base.statics", "SingletonBreadcast"); 110 | ClassName execName = ClassName.get("io.dreiklang.breadcast.base.exec", "TypedExecution"); 111 | ClassName contextName = ClassName.get("android.content", "Context"); 112 | ClassName intentName = ClassName.get("android.content", "Intent"); 113 | 114 | TypeSpec.Builder breadcastBuilder = TypeSpec 115 | .classBuilder(breadcastName.simpleName()) 116 | .addJavadoc("Breadcast listens to broadcasted actions and executes the respective methods annotated" + 117 | "with {@link io.dreiklang.breadcast.annotation.Receive} of objects registered by {@link #register(Object)}.") 118 | .addModifiers(PUBLIC) 119 | .superclass(singletonBCName) 120 | .addMethod(MethodSpec 121 | .constructorBuilder() 122 | .addModifiers(PRIVATE) 123 | .addParameter(contextName, "context") 124 | .addStatement("super(context)") 125 | .build()) 126 | .addMethod(MethodSpec 127 | .methodBuilder("init") 128 | .addJavadoc("Installs Breadcast with a context, preferably the application context. (Baking the bread)") 129 | .addModifiers(PUBLIC, STATIC) 130 | .addParameter(contextName, "context") 131 | .addStatement("new $T(context)", breadcastName) 132 | .build()); 133 | 134 | MethodSpec.Builder initExecutionSpec = MethodSpec 135 | .methodBuilder("initExecutions") 136 | .addAnnotation(Override.class) 137 | .addModifiers(PROTECTED); 138 | 139 | receives.forEach(receive -> { 140 | String params = Stream.of(receive.contextParam, receive.intentParam) 141 | .filter(Objects::nonNull) 142 | .collect(Collectors.joining(", ")); 143 | String actions = Stream.of(receive.actions) 144 | .filter(Objects::nonNull) 145 | .collect(Collectors.joining("\", \"","\"", "\"")); 146 | TypeSpec execution = TypeSpec 147 | .anonymousClassBuilder("") 148 | .addJavadoc("item") 149 | .addSuperinterface(ParameterizedTypeName.get(execName, receive.type)) 150 | .addMethod(MethodSpec 151 | .methodBuilder("exec") 152 | .addModifiers(PUBLIC) 153 | .addParameter(contextName, "context") 154 | .addParameter(intentName, "intent") 155 | .addParameter(receive.type, "object") 156 | .addStatement("object.$L($L)", receive.methodName, params) 157 | .build()) 158 | .build(); 159 | 160 | switch (receive.actions.length) { 161 | case 0: 162 | break; 163 | 164 | case 1: 165 | initExecutionSpec 166 | .addStatement("map($T.class, $S, $S, $L, $T.$L, $L)", 167 | receive.type, receive.actions[0], receive.methodName, receive.isStatic, ThreadModus.class, receive.threadModus, execution); 168 | break; 169 | 170 | default: // mutliple actions 171 | initExecutionSpec 172 | .addStatement("map($T.class, new String[] {$L}, $S, $L, $T.$L, $L)", 173 | receive.type, actions, receive.methodName, receive.isStatic, ThreadModus.class, receive.threadModus, execution); 174 | break; 175 | } 176 | }); 177 | 178 | return breadcastBuilder 179 | .addMethod(initExecutionSpec.build()) 180 | .build(); 181 | } 182 | 183 | private boolean extractReceives(RoundEnvironment roundEnv) { 184 | for (Element annotated : roundEnv.getElementsAnnotatedWith(Receive.class)) { 185 | 186 | if (annotated.getKind() != ElementKind.METHOD) { 187 | messager.printMessage(Diagnostic.Kind.ERROR, "breadcast:\t annotated element must be a method.", annotated); 188 | return false; 189 | } 190 | 191 | ExecutableElement method = (ExecutableElement) annotated; 192 | 193 | if (method.getModifiers().contains(ABSTRACT)) { 194 | messager.printMessage(Diagnostic.Kind.ERROR, "breadcast:\t annotated method must not be abstract.", annotated); 195 | return false; 196 | } 197 | 198 | if (!method.getModifiers().contains(PUBLIC)) { 199 | messager.printMessage(Diagnostic.Kind.ERROR, "breadcast:\t annotated method must not be public.", annotated); 200 | return false; 201 | } 202 | 203 | if (!(method.getReturnType() instanceof NoType)) { 204 | messager.printMessage(Diagnostic.Kind.ERROR, "breadcast:\t annotated method must be void.", annotated); 205 | return false; 206 | } 207 | 208 | List params = method.getParameters(); 209 | Receive annotation = annotated.getAnnotation(Receive.class); 210 | ReceiveHolder receive = new ReceiveHolder(); 211 | 212 | if (method.getModifiers().contains(STATIC)) { 213 | receive.isStatic = true; 214 | } 215 | 216 | for (VariableElement param : params) { 217 | switch (param.asType().toString()) { 218 | case "android.content.Context": 219 | receive.contextParam = "context"; 220 | break; 221 | 222 | case "android.content.Intent": 223 | receive.intentParam = "intent"; 224 | break; 225 | 226 | default: 227 | messager.printMessage(Diagnostic.Kind.ERROR, "breadcast:\t annotated method must have either parameter(s) android.content.Context and/or android.content.Intent or none.", annotated); 228 | return false; 229 | } 230 | } 231 | 232 | receive.type = ClassName.get((TypeElement) method.getEnclosingElement()); 233 | receive.methodName = method.getSimpleName().toString(); 234 | receive.actions = annotation.action(); 235 | receive.threadModus = annotation.threadMode(); 236 | 237 | receives.add(receive); 238 | } 239 | 240 | return true; 241 | } 242 | 243 | private class ReceiveHolder { 244 | TypeName type; 245 | String methodName; 246 | String[] actions; 247 | String contextParam; 248 | String intentParam; 249 | ThreadModus threadModus; 250 | boolean isStatic = false; 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /breadcast-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | io.dreiklang.breadcast.processor.BreadcastProcessor -------------------------------------------------------------------------------- /breadcast-test-integration/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /breadcast-test-integration/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | defaultConfig { 7 | minSdkVersion 15 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | 22 | } 23 | 24 | dependencies { 25 | implementation fileTree(dir: 'libs', include: ['*.jar']) 26 | 27 | implementation 'com.android.support:appcompat-v7:26.1.0' 28 | testImplementation 'junit:junit:4.12' 29 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 30 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 31 | 32 | androidTestImplementation project(':breadcast-base') 33 | androidTestImplementation project(':breadcast-annotation') 34 | androidTestAnnotationProcessor project(':breadcast-processor') 35 | 36 | // androidTestImplementation 'io.dreiklang:breadcast-annotation:1.0.0' 37 | // androidTestImplementation 'io.dreiklang:breadcast-base:1.0.0' 38 | // androidTestAnnotationProcessor 'io.dreiklang:breadcast-processor:1.0.0' 39 | } 40 | -------------------------------------------------------------------------------- /breadcast-test-integration/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /breadcast-test-integration/src/androidTest/java/io/dreiklang/breadcast/test/integration/AnnotationBreadcastTest.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.test.integration; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Looper; 6 | import android.support.test.InstrumentationRegistry; 7 | import android.support.test.runner.AndroidJUnit4; 8 | 9 | import org.junit.BeforeClass; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | import java.util.concurrent.CountDownLatch; 14 | 15 | import io.dreiklang.breadcast.Breadcast; 16 | import io.dreiklang.breadcast.test.integration.receiver.TestReceiver; 17 | import io.dreiklang.breadcast.test.integration.receiver.TestThreadedReceiver; 18 | 19 | import static junit.framework.Assert.assertFalse; 20 | import static junit.framework.Assert.assertTrue; 21 | 22 | /** 23 | * Instrumented test, which will execute on an Android device. 24 | * 25 | * @see Testing documentation 26 | */ 27 | @RunWith(AndroidJUnit4.class) 28 | public class AnnotationBreadcastTest { 29 | 30 | @BeforeClass 31 | public static void beforeClass() { 32 | Context appContext = InstrumentationRegistry.getTargetContext(); 33 | Breadcast.init(appContext); 34 | } 35 | 36 | @Test 37 | public void testCast() throws Exception { 38 | Context appContext = InstrumentationRegistry.getTargetContext(); 39 | TestReceiver receiver = new TestReceiver(); 40 | Breadcast.instance().register(receiver); 41 | 42 | appContext.sendBroadcast(new Intent("1")); 43 | appContext.sendBroadcast(new Intent("2")); 44 | appContext.sendBroadcast(new Intent("3")); 45 | appContext.sendBroadcast(new Intent("4")); 46 | appContext.sendBroadcast(new Intent("static")); 47 | Thread.sleep(2000); 48 | 49 | assertTrue("hasRun1", receiver.isRun1()); 50 | assertTrue("hasRun2", receiver.isRun2()); 51 | assertTrue("hasRun3", receiver.isRun3()); 52 | assertTrue("hasRunMultiA", receiver.isRunMultiA()); 53 | assertTrue("hasRunMultiB", receiver.isRunMultiB()); 54 | assertTrue("hasRunStatic", receiver.isRunStatic()); 55 | assertTrue("notRun", receiver.isNoRun()); 56 | Breadcast.instance().unregister(receiver); 57 | } 58 | 59 | private final CountDownLatch latch = new CountDownLatch(4); 60 | 61 | @Test(timeout = 5000) 62 | public void testThreaded() throws Exception { 63 | boolean isMainThread = Looper.myLooper() == Looper.getMainLooper(); 64 | assertFalse("isNotMainThread", isMainThread); 65 | 66 | final Context appContext = InstrumentationRegistry.getTargetContext(); 67 | TestThreadedReceiver receiver = new TestThreadedReceiver(latch); 68 | Breadcast.instance().register(receiver); 69 | 70 | // receives in main thread 71 | appContext.sendBroadcast(new Intent("thread")); 72 | latch.await(); 73 | Breadcast.instance().unregister(receiver); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /breadcast-test-integration/src/androidTest/java/io/dreiklang/breadcast/test/integration/StandaloneBreadcasterTest.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.test.integration; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.support.test.InstrumentationRegistry; 6 | import android.support.test.runner.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | 14 | import io.dreiklang.breadcast.base.Breadcaster; 15 | import io.dreiklang.breadcast.base.exec.Execution; 16 | 17 | import static junit.framework.Assert.assertEquals; 18 | import static junit.framework.Assert.assertTrue; 19 | 20 | /** 21 | * Instrumented test, which will execute on an Android device. 22 | * 23 | * @see Testing documentation 24 | */ 25 | @RunWith(AndroidJUnit4.class) 26 | public class StandaloneBreadcasterTest { 27 | 28 | @Test 29 | public void testCast() throws Exception { 30 | // Context of the app under test. 31 | Context appContext = InstrumentationRegistry.getTargetContext(); 32 | 33 | final AtomicBoolean hasRun1 = new AtomicBoolean(false); 34 | final AtomicBoolean hasRun2 = new AtomicBoolean(false); 35 | final AtomicBoolean hasRun3 = new AtomicBoolean(false); 36 | final AtomicBoolean notRun = new AtomicBoolean(true); 37 | final AtomicInteger times = new AtomicInteger(0); 38 | 39 | Breadcaster caster = new Breadcaster(appContext) 40 | .action("1", new Execution() { 41 | @Override 42 | public void exec(Context context, Intent intent) { 43 | hasRun1.set(true); 44 | times.incrementAndGet(); 45 | } 46 | }) 47 | .action("2", new Execution() { 48 | @Override 49 | public void exec(Context context, Intent intent) { 50 | hasRun2.set(true); 51 | } 52 | }) 53 | .action("1", new Execution() { 54 | @Override 55 | public void exec(Context context, Intent intent) { 56 | hasRun3.set(true); 57 | times.incrementAndGet(); 58 | } 59 | }) 60 | .register(); 61 | 62 | assertTrue("isRegistered", caster.isRegistered()); 63 | 64 | appContext.sendBroadcast(new Intent("1")); 65 | appContext.sendBroadcast(new Intent("2")); 66 | Thread.sleep(1000); 67 | assertTrue("hasRun1", hasRun1.get()); 68 | assertTrue("hasRun2", hasRun2.get()); 69 | assertTrue("hasRun3", hasRun3.get()); 70 | assertTrue("notRun", notRun.get()); 71 | assertEquals(times.get(), 2); 72 | caster.unregister(); 73 | caster.release(); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /breadcast-test-integration/src/androidTest/java/io/dreiklang/breadcast/test/integration/receiver/TestReceiver.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.test.integration.receiver; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import io.dreiklang.breadcast.annotation.Receive; 7 | 8 | import static junit.framework.Assert.assertEquals; 9 | import static junit.framework.Assert.assertFalse; 10 | import static junit.framework.Assert.assertNotNull; 11 | 12 | /** 13 | * @author Nhu Huy Le (mail@huy-le.de), on 05/02/2018 14 | */ 15 | 16 | public class TestReceiver { 17 | 18 | private boolean run1 = false; 19 | private boolean run2 = false; 20 | private boolean run3 = false; 21 | private boolean runMultiA = false; 22 | private boolean runMultiB = false; 23 | private static boolean runStatic = false; 24 | private boolean noRun = true; 25 | 26 | @Receive(action = "1") 27 | public void onAction1(Context context, Intent intent) { 28 | System.out.println("action1"); 29 | assertFalse(run1); 30 | run1 = true; 31 | assertNotNull(context); 32 | assertNotNull(intent); 33 | } 34 | 35 | @Receive(action = "2") 36 | public void onAction2() { 37 | System.out.println("action2"); 38 | assertFalse(run2); 39 | run2 = true; 40 | } 41 | 42 | @Receive(action = "1") 43 | public void onAction3(Intent intent) { 44 | System.out.println("action3"); 45 | assertEquals("1", intent.getAction()); 46 | assertFalse(run3); 47 | run3 = true; 48 | } 49 | 50 | @Receive(action = {"4", "3"}) 51 | public void onMultiAction(Intent intent) { 52 | String action = intent.getAction(); 53 | System.out.println("multiAction: " + intent.getAction()); 54 | if (action.equals("3")) { 55 | assertFalse(runMultiA); 56 | runMultiA = true; 57 | } 58 | else if (action.equals("4")) { 59 | assertFalse(runMultiB); 60 | runMultiB = true; 61 | } 62 | } 63 | 64 | @Receive(action = "static") 65 | public static void onStaticAction(Context context, Intent intent) { 66 | System.out.println("staticAction"); 67 | assertNotNull(context); 68 | assertNotNull(intent); 69 | assertFalse(runStatic); 70 | runStatic = true; 71 | } 72 | 73 | @Receive(action = "0") 74 | public void onNoAction() { 75 | System.out.println("noAction"); 76 | noRun = false; 77 | throw new IllegalStateException("should never run"); 78 | } 79 | 80 | public boolean isRun1() { 81 | return run1; 82 | } 83 | 84 | public boolean isRun2() { 85 | return run2; 86 | } 87 | 88 | public boolean isRun3() { 89 | return run3; 90 | } 91 | 92 | public boolean isRunMultiA() { 93 | return runMultiA; 94 | } 95 | 96 | public boolean isRunMultiB() { 97 | return runMultiB; 98 | } 99 | 100 | public static boolean isRunStatic() { 101 | return runStatic; 102 | } 103 | 104 | public boolean isNoRun() { 105 | return noRun; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /breadcast-test-integration/src/androidTest/java/io/dreiklang/breadcast/test/integration/receiver/TestThreadedReceiver.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.test.integration.receiver; 2 | 3 | import android.os.Looper; 4 | 5 | import java.util.concurrent.CountDownLatch; 6 | 7 | import io.dreiklang.breadcast.annotation.Receive; 8 | import io.dreiklang.breadcast.annotation.ThreadModus; 9 | 10 | import static junit.framework.Assert.assertFalse; 11 | import static junit.framework.Assert.assertTrue; 12 | 13 | /** 14 | * @author Nhu Huy Le (mail@huy-le.de), on 05/02/2018 15 | */ 16 | 17 | public class TestThreadedReceiver { 18 | 19 | private final CountDownLatch latch; 20 | 21 | public TestThreadedReceiver(CountDownLatch latch) { 22 | this.latch = latch; 23 | } 24 | 25 | @Receive(action = "thread", threadMode = ThreadModus.ASYNC) 26 | public void onAsync() { 27 | System.out.println("onAsync"); 28 | boolean isMainThread = Looper.myLooper() == Looper.getMainLooper(); 29 | assertFalse(isMainThread); 30 | latch.countDown(); 31 | } 32 | 33 | @Receive(action = "thread", threadMode = ThreadModus.BACKGROUND) 34 | public void onBackground() { 35 | System.out.println("onBackground"); 36 | boolean isMainThread = Looper.myLooper() == Looper.getMainLooper(); 37 | assertFalse(isMainThread); 38 | latch.countDown(); 39 | } 40 | 41 | @Receive(action = "thread", threadMode = ThreadModus.MAIN) 42 | public void onMain() { 43 | System.out.println("onMain"); 44 | boolean isMainThread = Looper.myLooper() == Looper.getMainLooper(); 45 | assertTrue(isMainThread); 46 | latch.countDown(); 47 | } 48 | 49 | @Receive(action = "thread", threadMode = ThreadModus.POSTING) 50 | public void onPosting() { 51 | System.out.println("onPosting"); 52 | boolean isMainThread = Looper.myLooper() == Looper.getMainLooper(); 53 | assertTrue(isMainThread); 54 | latch.countDown(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /breadcast-test-integration/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /breadcast-test-integration/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | breadcast-test-integration 3 | 4 | -------------------------------------------------------------------------------- /breadcast-test-integration/src/test/java/io/dreiklang/breadcast/test/integration/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package io.dreiklang.breadcast.test.integration; 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 | } -------------------------------------------------------------------------------- /lib.gradle: -------------------------------------------------------------------------------- 1 | 2 | ext { 3 | bintrayRepo = 'Breadcast' 4 | 5 | publishedGroupId = 'io.dreiklang' 6 | 7 | siteUrl = 'https://github.com/dreiklangdev/Breadcast' 8 | gitUrl = 'https://github.com/dreiklangdev/Breadcast.git' 9 | 10 | libraryVersion = '1.0.1' 11 | 12 | developerId = 'dreiklang' 13 | developerName = 'Nhu Huy Le' 14 | developerEmail = 'mail@huy-le.de' 15 | 16 | licenseName = 'The Apache Software License, Version 2.0' 17 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 18 | allLicenses = ["Apache-2.0"] 19 | } 20 | 21 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' 22 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' --------------------------------------------------------------------------------