├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── android-remote-notifications
├── build.gradle
├── gradle.properties
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ ├── github
│ │ └── kaiwinter
│ │ │ └── androidremotenotifications
│ │ │ ├── RemoteNotifications.java
│ │ │ ├── json
│ │ │ └── UnMarshaller.java
│ │ │ ├── model
│ │ │ ├── ExecutionPolicy.java
│ │ │ ├── UpdatePolicy.java
│ │ │ ├── UserNotification.java
│ │ │ ├── buttonaction
│ │ │ │ ├── ButtonAction.java
│ │ │ │ └── impl
│ │ │ │ │ ├── ExitAppButtonAction.java
│ │ │ │ │ ├── OpenStoreButtonAction.java
│ │ │ │ │ └── OpenUrlButtonAction.java
│ │ │ └── impl
│ │ │ │ ├── AbstractUserNotification.java
│ │ │ │ ├── AlertDialogNotification.java
│ │ │ │ ├── NotificationConfiguration.java
│ │ │ │ ├── PersistentNotification.java
│ │ │ │ ├── ToastNotification.java
│ │ │ │ └── VersionCodePolicy.java
│ │ │ ├── network
│ │ │ ├── NotificationLoaderFinishListener.java
│ │ │ └── NotificationLoaderTask.java
│ │ │ └── persistence
│ │ │ ├── NotificationStore.java
│ │ │ └── impl
│ │ │ └── SharedPreferencesStore.java
│ │ └── google
│ │ └── gson
│ │ └── typeadapters
│ │ └── RuntimeTypeAdapterFactory.java
│ └── test
│ ├── java
│ └── com
│ │ └── github
│ │ └── kaiwinter
│ │ └── androidremotenotifications
│ │ ├── RemoteNotificationsTest.java
│ │ ├── json
│ │ └── Notification2Json2NotificationTest.java
│ │ └── model
│ │ ├── PersistentNotificationTest.java
│ │ ├── UpdatePolicyTest.java
│ │ └── VersionCodePolicyTest.java
│ └── resources
│ └── com
│ └── github
│ └── kaiwinter
│ └── androidremotenotifications
│ ├── empty_notification.json
│ ├── invalid_answer
│ └── one_notification.json
├── build.gradle
├── example
├── build.gradle
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── github
│ │ │ └── kaiwinter
│ │ │ └── androidremotenotifications
│ │ │ └── example
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-v14
│ │ └── styles.xml
│ │ ├── values-v21
│ │ └── styles.xml
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── resources
│ └── notifications.json
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── notification-creator-util
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── github
│ └── kaiwinter
│ └── androidremotenotifications
│ └── util
│ └── NotificationCreatorUtil.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | local.properties
2 | .idea
3 | .gradle
4 | build
5 | *.iml
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | Version 1.1.5 *(2018-06-23)*
5 | ----------------------------
6 | * Improved error handling on error while reading json file
7 |
8 | Version 1.1.4 *(2018-05-17)*
9 | ----------------------------
10 | * Improved error handling on missing/erroneous json file
11 |
12 | Version 1.1.3 *(2018-05-05)*
13 | ----------------------------
14 | * Switched from jackson to gson. This reduces the method count by ~4500 methods.
15 |
16 | Version 1.1.0 *(2015-07-05)*
17 | ----------------------------
18 | * Fix: Last update time wasn't saved which results in too many updates
19 | * New: Bi-weekly updates in UpdatePolicy
20 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://android-arsenal.com/details/1/2058)
2 | # android-remote-notifications
3 | A Google GCM/Amazon SNS alternative using pull instead of push.
4 | ## Main features
5 | - Independent user notifications (no Google GCM or Amazon SNS), just put a JSON file in the cloud
6 | - Framework will update available notifications automatically in defined time intervals (`now`, `daily`, `weekly`, `bi-weekly`, `monthly`)
7 | - Flexible parameters:
8 | - start date for notification (distribute early, show later)
9 | - amount of times the notification should be shown
10 | - interval between showing notifications (`always`, `every day`, `every week`, `every month`)
11 | - specify on which app version (`versionCode`) the notification should be shown
12 | - Show notifications as `AlertDialog` or `Toast` message (defined by the remote JSON file)
13 | - `AlertDialog`: Specify button captions and actions: `Open Store`-Action, `Open URL`-Action, `Exit App`-Action, all defined by a JSON file on your server
14 | - perfect to inform your users about discounts on one of your other apps or premium version
15 | - pro tip: as some users don't always update to the latest app versions you should integrate this framework in an early phase if you plan to use it at a later time.
16 |
17 | ## Download
18 | Gradle:
19 | ```groovy
20 | implementation ('com.github.kaiwinter:android-remote-notifications:1.1.5@aar') {
21 | transitive = true
22 | }
23 | ```
24 |
25 | ## Demo:
26 | - [Demo App](https://github.com/kaiwinter/android-remote-notifications/releases/download/v1.1.0/remotenotifications-example-1.1.0.apk)
27 | - Live Demo: [Appetize.io](https://appetize.io/app/efkur5jaxuztahp6wtc5n65x74)
28 |
29 | ## How to integrate
30 | You can find the source code of the example app here: [MainActivity](https://github.com/kaiwinter/android-remote-notifications/blob/master/example/src/main/java/com/github/kaiwinter/androidremotenotifications/example/MainActivity.java)
31 | ### The easy way
32 | - Call `RemoteNotifications.start(context, url, UpdatePolicy.NOW);` in your `onCreate()` or `onResume()` method
33 |
34 | this will
35 | - start an update of the notifications (use `UpdatePolicy.WEEKLY` in production environment to update once a week)
36 | - show due notifications to the user
37 |
38 | ### For more control
39 | ```java
40 | RemoteNotifications rn = new RemoteNotifications(context, url);
41 | rn.updateNotifications(UpdatePolicy.NOW);
42 | rn.showNotifications(); // if update still runs, event will be queued and carried out later
43 |
44 | // -or, register a listener on update completion-
45 | rn.updateNotifications(UpdatePolicy.NOW, finishListener);
46 | ```
47 |
48 | ## How to build a JSON notification file
49 | First: You don´t have to write the JSON file by hand. Just use the API to initialize a Notification object and then create JSON from it: [NotificationCreatorUtil ](https://github.com/kaiwinter/android-remote-notifications/blob/master/notification-creator-util/src/main/java/com/github/kaiwinter/androidremotenotifications/util/NotificationCreatorUtil.java)
50 | ### Example of a Toast Notification
51 | ```
52 | [
53 | {
54 | "type": "ToastNotification",
55 | "notificationConfiguration": {
56 | "startShowingDate": null,
57 | "executionPolicy": "ALWAYS",
58 | "numberOfTotalViews": null,
59 | "versionCodePolicy": null
60 | },
61 | "message": "This is a Toast Notification",
62 | "duration": 1
63 | }
64 | ]
65 | ```
66 | ### Example of an AlertDialog Notification
67 | ```
68 | [
69 | {
70 | "type": "AlertDialogNotification",
71 | "notificationConfiguration": {
72 | "startShowingDate": null,
73 | "executionPolicy": "ALWAYS",
74 | "numberOfTotalViews": null,
75 | "versionCodePolicy": null
76 | },
77 | "title": "Title",
78 | "message": "This is an AlertDialog notification",
79 | "negativeButtonText": "Exit App",
80 | "neutralButtonText": "Open web page",
81 | "positiveButtonText": "Open Play Store",
82 | "positiveButtonAction": {
83 | "type": "OpenStoreButtonAction",
84 | "packageName": "de.vorlesungsfrei.taekwondo.ads"
85 | },
86 | "negativeButtonAction": {
87 | "type": "ExitAppButtonAction"
88 | },
89 | "neutralButtonAction": {
90 | "type": "OpenUrlButtonAction",
91 | "link": "https://github.com/kaiwinter/android-remote-notifications"
92 | },
93 | "modal": false
94 | }
95 | ]
96 | ```
97 |
98 | ## Screenshots
99 | 
100 |
101 | ## License
102 | Copyright 2018 Kai Winter
103 |
104 | Licensed under the Apache License, Version 2.0 (the "License");
105 | you may not use this file except in compliance with the License.
106 | You may obtain a copy of the License at
107 |
108 | http://www.apache.org/licenses/LICENSE-2.0
109 |
110 | Unless required by applicable law or agreed to in writing, software
111 | distributed under the License is distributed on an "AS IS" BASIS,
112 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
113 | See the License for the specific language governing permissions and
114 | limitations under the License.
115 |
--------------------------------------------------------------------------------
/android-remote-notifications/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 27
5 | buildToolsVersion '27.0.3'
6 | defaultConfig {
7 | minSdkVersion 9
8 | targetSdkVersion 26
9 | versionCode 4
10 | versionName version
11 | }
12 | buildTypes {
13 | release {
14 | minifyEnabled false
15 | }
16 | }
17 |
18 | compileOptions {
19 | encoding "UTF-8"
20 | }
21 | testOptions {
22 | unitTests.returnDefaultValues = true
23 | }
24 | }
25 |
26 | dependencies {
27 | compile 'com.google.code.gson:gson:2.8.4'
28 | testCompile 'junit:junit:4.12'
29 | testCompile 'org.robolectric:robolectric:3.8'
30 | }
31 |
32 | ext {
33 | bintrayRepo = 'maven'
34 | bintrayName = 'com.github.kaiwinter:android-remote-notifications'
35 |
36 | publishedGroupId = 'com.github.kaiwinter'
37 | libraryName = 'android-remote-notifications'
38 | artifact = 'android-remote-notifications'
39 |
40 | libraryDescription = 'Pulls notifications from a remote JSON file and shows them in your app.'
41 |
42 | siteUrl = 'https://github.com/kaiwinter/android-remote-notifications'
43 | gitUrl = 'https://github.com/kaiwinter/android-remote-notifications.git'
44 |
45 | libraryVersion = version
46 |
47 | developerId = 'kaiwinter'
48 | developerName = 'Kai Winter'
49 | developerEmail = 'kaiwinter@gmx.de'
50 |
51 | licenseName = 'The Apache Software License, Version 2.0'
52 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
53 | allLicenses = ["Apache-2.0"]
54 | }
55 | // https://gist.github.com/kellyfj/9666950
56 | android.libraryVariants.all { variant ->
57 | def name = variant.buildType.name
58 | if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
59 | return; // Skip debug builds.
60 | }
61 | def task = project.tasks.create "jar${name.capitalize()}", Jar
62 | task.dependsOn variant.javaCompile
63 | task.from variant.javaCompile.destinationDir
64 | task.from configurations.compile.findAll {
65 | it.getName() != 'android.jar' && !it.getName().startsWith('junit') && !it.getName().startsWith('hamcrest')
66 | }.collect {
67 | it.isDirectory() ? it : zipTree(it)
68 | }
69 | artifacts.add('archives', task);
70 | }
71 |
72 | apply from: 'https://raw.githubusercontent.com/kaiwinter/JCenter/master/installv1.gradle'
73 | apply from: 'https://raw.githubusercontent.com/kaiwinter/JCenter/master/bintrayv1.gradle'
--------------------------------------------------------------------------------
/android-remote-notifications/gradle.properties:
--------------------------------------------------------------------------------
1 | version=1.1.6-SNAPSHOT
2 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/RemoteNotifications.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 | import android.content.pm.PackageManager.NameNotFoundException;
7 | import android.util.Log;
8 |
9 | import com.github.kaiwinter.androidremotenotifications.model.UpdatePolicy;
10 | import com.github.kaiwinter.androidremotenotifications.model.UserNotification;
11 | import com.github.kaiwinter.androidremotenotifications.model.impl.PersistentNotification;
12 | import com.github.kaiwinter.androidremotenotifications.network.NotificationLoaderFinishListener;
13 | import com.github.kaiwinter.androidremotenotifications.network.NotificationLoaderTask;
14 | import com.github.kaiwinter.androidremotenotifications.persistence.NotificationStore;
15 | import com.github.kaiwinter.androidremotenotifications.persistence.impl.SharedPreferencesStore;
16 |
17 | import java.net.URL;
18 | import java.util.Date;
19 | import java.util.HashSet;
20 | import java.util.Iterator;
21 | import java.util.Set;
22 | import java.util.concurrent.atomic.AtomicBoolean;
23 |
24 | /**
25 | * The starting class of this library. Construct an instance with the URL to your JSON file on a server and call
26 | * {@link #updateNotificationsFromServer(UpdatePolicy)}. This will load the configured notifications and store it in the
27 | * libraries store. Call {@link #showPendingNotificationsToUser(boolean)} to automatically show notifications to the
28 | * user by an {@link AlertDialog}.
29 | */
30 | public final class RemoteNotifications {
31 |
32 | public static final String TAG = "RemoteNotifications";
33 |
34 | private static final String ARN_PREFERENCES_NAME = "arn";
35 |
36 | private final URL serverUrl;
37 | private final Context context;
38 | private final NotificationStore preferenceStore;
39 |
40 | private final Integer appVersionCode;
41 |
42 | /**
43 | * This guard is used for two things:
44 | *
45 | *
doing the notification update only once at a time
46 | *
don't try to show notifications when update is running. In this case the show event is scheduled and {@link #scheduledShowEvent} is set to true.
47 | *
48 | * Guard to run the notification only one at a time.
49 | */
50 | private AtomicBoolean notificationUpdateRunning = new AtomicBoolean(false);
51 |
52 | /**
53 | * This is set to true if {@link #showPendingNotificationsToUser} was called then an update of the notification was running.
54 | * It is used to show the notifications after the update has finished.
55 | */
56 | private AtomicBoolean scheduledShowEvent = new AtomicBoolean(false);
57 | private boolean scheduledShowEventParameter;
58 |
59 | /**
60 | * Constructs a new {@link RemoteNotifications}. The Notifications from the server and the last server update timestamp will be
61 | * stored in a Shared Preferences file with the name {@value #ARN_PREFERENCES_NAME}. To change the name use
62 | * {@link #RemoteNotifications(Context, URL, String)}.
63 | *
64 | * @param context The application context
65 | * @param serverUrl The absolute URL to the notification JSON on a server (or file system), not null.
66 | */
67 | public RemoteNotifications(Context context, URL serverUrl) {
68 | this(context, serverUrl, ARN_PREFERENCES_NAME);
69 | }
70 |
71 | /**
72 | * Constructs a new {@link RemoteNotifications}.
73 | *
74 | * @param context The application context
75 | * @param serverUrl The absolute URL to the notification JSON on a server (or file system), not null.
76 | * @param sharedPreferenceName The file name to use for the Shared Preferences file.
77 | */
78 | public RemoteNotifications(Context context, URL serverUrl, String sharedPreferenceName) {
79 | if (serverUrl == null) {
80 | throw new IllegalArgumentException("serverUrl must not be null");
81 | }
82 | if (context == null) {
83 | throw new IllegalArgumentException("context must not be null");
84 | }
85 | if (sharedPreferenceName == null) {
86 | throw new IllegalArgumentException("sharedPreferenceName must not be null");
87 | }
88 | this.serverUrl = serverUrl;
89 | this.context = context;
90 | SharedPreferences sharedPreferences = context.getSharedPreferences(sharedPreferenceName, Context.MODE_PRIVATE);
91 | this.preferenceStore = new SharedPreferencesStore(sharedPreferences);
92 | this.appVersionCode = getAppVersionCode(context);
93 | }
94 |
95 | private Integer getAppVersionCode(Context context) {
96 | int versionCode;
97 | try {
98 | versionCode = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
99 | } catch (NameNotFoundException e1) {
100 | versionCode = -1;
101 | }
102 | return versionCode;
103 | }
104 |
105 | /**
106 | * Shows all due notifications. If a server update of the notifications is running this call gets scheduled and is
107 | * executed automatically after the update has finished.
108 | *
109 | * @see #showPendingNotificationsToUser(boolean)
110 | */
111 | public void showPendingNotificationsToUser() {
112 | showPendingNotificationsToUser(true);
113 | }
114 |
115 | /**
116 | * Shows due notifications. If a server update of the notifications is running this call gets scheduled and is
117 | * executed automatically after the update has finished.
118 | *
119 | * @param showAll if true all pending notification dialogs will be shown (may result in bad user experience
120 | * if several dialogs are popping up). If false only one dialog will be shown on each call
121 | * of this method.
122 | */
123 | public void showPendingNotificationsToUser(boolean showAll) {
124 |
125 | if (notificationUpdateRunning.get()) {
126 | Log.w(TAG, "Notification update running, scheduling show event");
127 | scheduledShowEvent.set(true);
128 | // store passed parameter
129 | scheduledShowEventParameter = showAll;
130 | return;
131 | }
132 |
133 | Set persistentNotifications = preferenceStore.getPersistentNotifications();
134 |
135 | Set updateNotifications = new HashSet<>();
136 | for (PersistentNotification persistentNotification : persistentNotifications) {
137 | if (persistentNotification.hasToBeShown(appVersionCode)) {
138 | //notificationConsumer.show(context, persistentNotification.getNotification());
139 | persistentNotification.getNotification().show(context);
140 | persistentNotification.setLastShown(new Date());
141 | persistentNotification.setShownCounter(persistentNotification.getShownCounter() + 1);
142 | updateNotifications.add(persistentNotification);
143 | if (!showAll) {
144 | break;
145 | }
146 | }
147 | }
148 |
149 | if (!updateNotifications.isEmpty()) {
150 | preferenceStore.updatePersistentNotification(updateNotifications);
151 | }
152 | }
153 |
154 | /**
155 | * Starts an AsyncTask which loads the notifications from the server, removes local notifications which doesn't exist anymore and adds new
156 | * one.
157 | *
158 | * @param updatePolicy Use this parameter to reduce the number of server calls, not null.
159 | * @param listener a {@link NotificationLoaderFinishListener} which is called after the update of the notifications finished.
160 | */
161 | public void updateNotificationsFromServer(UpdatePolicy updatePolicy, final NotificationLoaderFinishListener listener) {
162 | if (updatePolicy == null) {
163 | throw new IllegalArgumentException("updatePolicy must not be null");
164 | }
165 |
166 | Date date = preferenceStore.getLastServerUpdate();
167 | boolean shouldUpdate = updatePolicy.shouldUpdate(date);
168 | Log.v(TAG, "UpdatePolicy: " + updatePolicy.toString() + ", shouldUpdate: " + shouldUpdate);
169 | if (!shouldUpdate) {
170 | return;
171 | }
172 |
173 | boolean updateRunning = notificationUpdateRunning.getAndSet(true);
174 | if (updateRunning) {
175 | Log.w(TAG, "Notification update already running, skipping this update");
176 | return;
177 | }
178 |
179 | NotificationLoaderFinishListener internalListener = new NotificationLoaderFinishListener() {
180 | @Override
181 | public void onDownloadFinished(Set notifications) {
182 | Log.v(TAG, "Received " + notifications.size() + " notifications");
183 | handleNotificationsFromServer(notifications);
184 | notificationUpdateRunning.set(false);
185 | preferenceStore.saveLastServerUpdate(new Date());
186 | if (scheduledShowEvent.getAndSet(false)) {
187 | Log.v(TAG, "executing scheduled show event");
188 | showPendingNotificationsToUser(scheduledShowEventParameter);
189 | }
190 |
191 | // Call custom listener
192 | if (listener != null) {
193 | listener.onDownloadFinished(notifications);
194 | }
195 | }
196 | };
197 | NotificationLoaderTask notificationLoaderTask = new NotificationLoaderTask(serverUrl, internalListener);
198 | notificationLoaderTask.execute();
199 | }
200 |
201 | /**
202 | * Starts an AsyncTask which loads the notifications from the server, removes local notifications which doesn't exist anymore and adds new
203 | * one.
204 | *
205 | * @param updatePolicy Use this parameter to reduce the number of server calls, not null.
206 | */
207 | public void updateNotificationsFromServer(UpdatePolicy updatePolicy) {
208 | updateNotificationsFromServer(updatePolicy, null);
209 | }
210 |
211 | private void handleNotificationsFromServer(Set notifications) {
212 | // Remove notifications from app which aren't anymore in the server notifications
213 | Set persistentNotifications = preferenceStore.getPersistentNotifications();
214 |
215 | boolean save = false;
216 | for (Iterator iterator = persistentNotifications.iterator(); iterator.hasNext(); ) {
217 | PersistentNotification persistentNotification = iterator.next();
218 | if (!notifications.contains(persistentNotification.getNotification())) {
219 | // Remove notification
220 | iterator.remove();
221 | save = true;
222 | Log.v(TAG, "Removed Persistent Notification " + persistentNotification.toString());
223 | }
224 | }
225 |
226 | // Add notifications from server which aren't in the app notification
227 | for (UserNotification notification : notifications) {
228 | PersistentNotification persistentNotification = new PersistentNotification(notification);
229 | if (!persistentNotifications.contains(persistentNotification)) {
230 | // Add new notification
231 | persistentNotifications.add(persistentNotification);
232 | save = true;
233 | Log.v(TAG, "Added Persistent Notification " + persistentNotification.toString());
234 | }
235 | }
236 |
237 | if (save) {
238 | preferenceStore.replacePersistentNotifications(persistentNotifications);
239 | }
240 | }
241 |
242 | /**
243 | * @return the internally used store of the notifications
244 | */
245 | public NotificationStore getPreferenceStore() {
246 | return preferenceStore;
247 | }
248 |
249 | /**
250 | * Convenience method to update and show notifications.
251 | *
252 | * @param context the application context
253 | * @param url the URL of the JSON file
254 | * @param updatePolicy the time between server updates
255 | */
256 | public static void start(Context context, URL url, UpdatePolicy updatePolicy) {
257 | RemoteNotifications remoteNotifications = new RemoteNotifications(context, url);
258 | remoteNotifications.updateNotificationsFromServer(updatePolicy);
259 | remoteNotifications.showPendingNotificationsToUser();
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/json/UnMarshaller.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.json;
2 |
3 | import android.util.Log;
4 |
5 | import com.github.kaiwinter.androidremotenotifications.RemoteNotifications;
6 | import com.github.kaiwinter.androidremotenotifications.model.UserNotification;
7 | import com.github.kaiwinter.androidremotenotifications.model.buttonaction.ButtonAction;
8 | import com.github.kaiwinter.androidremotenotifications.model.buttonaction.impl.ExitAppButtonAction;
9 | import com.github.kaiwinter.androidremotenotifications.model.buttonaction.impl.OpenStoreButtonAction;
10 | import com.github.kaiwinter.androidremotenotifications.model.buttonaction.impl.OpenUrlButtonAction;
11 | import com.github.kaiwinter.androidremotenotifications.model.impl.AlertDialogNotification;
12 | import com.github.kaiwinter.androidremotenotifications.model.impl.PersistentNotification;
13 | import com.github.kaiwinter.androidremotenotifications.model.impl.ToastNotification;
14 | import com.google.gson.Gson;
15 | import com.google.gson.GsonBuilder;
16 | import com.google.gson.JsonIOException;
17 | import com.google.gson.JsonSyntaxException;
18 | import com.google.gson.reflect.TypeToken;
19 | import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
20 |
21 | import java.io.IOException;
22 | import java.io.InputStreamReader;
23 | import java.io.Reader;
24 | import java.lang.reflect.Type;
25 | import java.net.URL;
26 | import java.util.Collections;
27 | import java.util.HashSet;
28 | import java.util.Set;
29 |
30 | /**
31 | * Utility class for marshalling and unmarshalling JSON and Notification objects.
32 | */
33 | public final class UnMarshaller {
34 |
35 | /**
36 | * Creates a JSON string from a Set of {@link PersistentNotification}s.
37 | *
38 | * @param notifications the notifications
39 | * @return the JSON string
40 | */
41 | public static String getJsonFromPersistentNotifications(Set notifications) {
42 | Type listType = new TypeToken>() {
43 | }.getType();
44 | return createGson().toJson(notifications, listType);
45 | }
46 |
47 | /**
48 | * Creates a JSON string from a Set of {@link UserNotification}s.
49 | *
50 | * @param notifications the notifications
51 | * @return the JSON string
52 | */
53 | public static String getJsonFromNotifications(Set notifications) {
54 | Type listType = new TypeToken>() {
55 | }.getType();
56 | return createGson().toJson(notifications, listType);
57 | }
58 |
59 | /**
60 | * Creates a Set of {@link UserNotification}s from JSON string which is queried from the given URL.
61 | *
62 | * @param url the URL which is parsed as JSON
63 | * @return Set of {@link UserNotification}s
64 | * @throws IOException if reading the JSON from the URL fails
65 | */
66 | public static Set getNotificationsFromJson(URL url) throws IOException {
67 |
68 | Reader reader = null;
69 | try {
70 | reader = new InputStreamReader(url.openStream());
71 |
72 | Type listType = new TypeToken>() {
73 | }.getType();
74 | Set notifications = createGson().fromJson(reader, listType);
75 | if (notifications == null) {
76 | notifications = new HashSet<>();
77 | }
78 | return notifications;
79 | } catch (JsonSyntaxException | JsonIOException e) {
80 | Log.e(RemoteNotifications.TAG, e.getMessage() + ": " + url);
81 | return Collections.emptySet();
82 | } finally {
83 | if (reader != null) {
84 | reader.close();
85 | }
86 | }
87 | }
88 |
89 | /**
90 | * Creates a Set of {@link PersistentNotification}s from a JSON string.
91 | *
92 | * @param json the JSON string.
93 | * @return Set of {@link PersistentNotification}
94 | */
95 | public static Set getPersistentNotificationsFromJson(String json) {
96 | Type listType = new TypeToken>() {
97 | }.getType();
98 | Set notifications = createGson().fromJson(json, listType);
99 | if (notifications == null) {
100 | notifications = new HashSet<>();
101 | }
102 | return notifications;
103 | }
104 |
105 | private static Gson createGson() {
106 | RuntimeTypeAdapterFactory runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
107 | .of(UserNotification.class, "type")
108 | .registerSubtype(AlertDialogNotification.class, "AlertDialogNotification")
109 | .registerSubtype(ToastNotification.class, "ToastNotification");
110 |
111 | RuntimeTypeAdapterFactory runtimeTypeAdapterButtonFactory = RuntimeTypeAdapterFactory
112 | .of(ButtonAction.class, "type")
113 | .registerSubtype(OpenUrlButtonAction.class, "OpenUrlButtonAction")
114 | .registerSubtype(ExitAppButtonAction.class, "ExitAppButtonAction")
115 | .registerSubtype(OpenStoreButtonAction.class, "OpenStoreButtonAction");
116 |
117 | return new GsonBuilder()
118 | .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
119 | .registerTypeAdapterFactory(runtimeTypeAdapterButtonFactory)
120 | .create();
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/ExecutionPolicy.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model;
2 |
3 | import com.github.kaiwinter.androidremotenotifications.model.impl.NotificationConfiguration;
4 |
5 | /**
6 | * Defines when the notification is shown. Does not define the number of times the notification is shown. E.g. for a
7 | * notification which is shown once only: Use {@link #EVERY_DAY} and set {@link NotificationConfiguration#numberOfTotalViews} to 1.
8 | */
9 | public enum ExecutionPolicy {
10 |
11 | /**
12 | * The notification should be shown always when API gets called. Use with caution!
13 | */
14 | ALWAYS,
15 |
16 | /**
17 | * The notification should be shown on every day.
18 | */
19 | EVERY_DAY,
20 |
21 | /**
22 | * The notification should be shown on every Monday.
23 | */
24 | EVERY_MONDAY,
25 |
26 | /**
27 | * The notification should be shown on every Sunday.
28 | */
29 | EVERY_SUNDAY,
30 |
31 | /**
32 | * The notification should be shown on 1st of every month.
33 | */
34 | EVERY_FIRST_OF_MONTH
35 | }
36 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/UpdatePolicy.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model;
2 |
3 | import com.github.kaiwinter.androidremotenotifications.RemoteNotifications;
4 |
5 | import java.util.Date;
6 |
7 | /**
8 | * Defines how often the notifications are updated from the server. Doing this on each start of your app ({@link #NOW})
9 | * might lead to too many server calls. You can reduce them by using {@link #WEEKLY} or {@link #MONTHLY}.
10 | */
11 | public enum UpdatePolicy {
12 |
13 | /**
14 | * The update is made now, regardless when the last one was.
15 | */
16 | NOW(0),
17 |
18 | /**
19 | * The update is made once a week. {@link RemoteNotifications} uses an internal shared preference to track when the last update
20 | * was.
21 | */
22 | WEEKLY(7 * 24 * 60 * 60 * 1000),
23 |
24 | /**
25 | * The update is made every second week. {@link RemoteNotifications} uses an internal shared preference to track when the last update
26 | * was.
27 | */
28 | BI_WEEKLY(WEEKLY.getInterval() * 2),
29 |
30 | /**
31 | * The update is made once a month. {@link RemoteNotifications} uses an internal shared preference to track when the last
32 | * update was.
33 | */
34 | MONTHLY(WEEKLY.getInterval() * 4);
35 |
36 | private final long interval;
37 |
38 | UpdatePolicy(long interval) {
39 | this.interval = interval;
40 | }
41 |
42 | private long getInterval() {
43 | return interval;
44 | }
45 |
46 | /**
47 | * Checks if the interval of this {@link UpdatePolicy} is over in regard to the lastUpdate.
48 | *
49 | * @param lastUpdate The {@link Date} of the last update.
50 | * @return true if an update should be done, else false.
51 | */
52 | public boolean shouldUpdate(Date lastUpdate) {
53 | if (lastUpdate == null) {
54 | return true;
55 | }
56 | // be robust against small time differences when using NOW
57 | return System.currentTimeMillis() - interval >= lastUpdate.getTime();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/UserNotification.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model;
2 |
3 | import android.content.Context;
4 |
5 | import com.github.kaiwinter.androidremotenotifications.model.impl.NotificationConfiguration;
6 |
7 | /**
8 | * Interface for different types of Notifications.
9 | */
10 | public interface UserNotification {
11 |
12 | /**
13 | * Returns the {@link NotificationConfiguration} of this Notification.
14 | *
15 | * @return the {@link NotificationConfiguration} of this Notification.
16 | */
17 | NotificationConfiguration getNotificationConfiguration();
18 |
19 | /**
20 | * Shows the Notification to the user.
21 | *
22 | * @param context the {@link Context} of the app.
23 | */
24 | void show(Context context);
25 | }
26 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/buttonaction/ButtonAction.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.buttonaction;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * Provides an action which will be carried out by the selection of a button.
7 | */
8 | public interface ButtonAction {
9 |
10 | /**
11 | * Executes the action of this button.
12 | *
13 | * @param context the application context
14 | */
15 | void execute(Context context);
16 |
17 | @Override
18 | int hashCode();
19 |
20 | @Override
21 | boolean equals(Object obj);
22 | }
23 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/buttonaction/impl/ExitAppButtonAction.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.buttonaction.impl;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import com.github.kaiwinter.androidremotenotifications.model.buttonaction.ButtonAction;
7 |
8 | /**
9 | * Ends the app.
10 | */
11 | public final class ExitAppButtonAction implements ButtonAction {
12 |
13 | @Override
14 | public void execute(Context context) {
15 | //android.os.Process.killProcess(android.os.Process.myPid());
16 |
17 | Intent intent = new Intent(Intent.ACTION_MAIN);
18 | intent.addCategory(Intent.CATEGORY_HOME);
19 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
20 | context.startActivity(intent);
21 | }
22 |
23 | // Make sure all of this actions are equal so that no change is detected when updating an existing notification.
24 | @Override
25 | public boolean equals(Object o) {
26 | if (this == o) return true;
27 | if (o == null || getClass() != o.getClass()) return false;
28 |
29 | return true;
30 | }
31 |
32 | @Override
33 | public int hashCode() {
34 | return 17;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/buttonaction/impl/OpenStoreButtonAction.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.buttonaction.impl;
2 |
3 | import android.content.ActivityNotFoundException;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 |
8 | import com.github.kaiwinter.androidremotenotifications.model.buttonaction.ButtonAction;
9 |
10 | /**
11 | * Opens an available store app. Currently the following are supported:
12 | *
13 | *
Google Play
14 | *
Google Play (Web)
15 | *
16 | * If none of those can be opened as a fallback the web address of Google Play is opened.
17 | */
18 | public final class OpenStoreButtonAction implements ButtonAction {
19 |
20 |
21 | private static final String MARKET_LINK_GOOGLE = "market://details?id=";
22 | private static final String MARKET_LINK_GOOGLE_WEB = "http://play.google.com/store/apps/details?id=";
23 |
24 | private final String packageName;
25 |
26 | /**
27 | * @param packageName The package name which should be opened in the Store.
28 | */
29 | // @JsonCreator
30 | public OpenStoreButtonAction(String packageName) {
31 | this.packageName = packageName;
32 | }
33 |
34 | @Override
35 | public void execute(Context context) {
36 | Intent intent = new Intent(Intent.ACTION_VIEW);
37 | try {
38 | intent.setData(Uri.parse(MARKET_LINK_GOOGLE + packageName));
39 | context.startActivity(intent);
40 | } catch (ActivityNotFoundException e) {
41 | intent.setData(Uri.parse(MARKET_LINK_GOOGLE_WEB + packageName));
42 | context.startActivity(intent);
43 | }
44 | }
45 |
46 | public String getPackageName() {
47 | return packageName;
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return "OpenStoreButtonAction [packageName=" + packageName + "]";
53 | }
54 |
55 | @Override
56 | public int hashCode() {
57 | final int prime = 31;
58 | int result = 1;
59 | result = prime * result + ((packageName == null) ? 0 : packageName.hashCode());
60 | return result;
61 | }
62 |
63 | @Override
64 | public boolean equals(Object obj) {
65 | if (this == obj)
66 | return true;
67 | if (obj == null)
68 | return false;
69 | if (getClass() != obj.getClass())
70 | return false;
71 | OpenStoreButtonAction other = (OpenStoreButtonAction) obj;
72 | if (packageName == null) {
73 | if (other.packageName != null)
74 | return false;
75 | } else if (!packageName.equals(other.packageName))
76 | return false;
77 | return true;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/buttonaction/impl/OpenUrlButtonAction.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.buttonaction.impl;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.util.Log;
7 |
8 | import com.github.kaiwinter.androidremotenotifications.model.buttonaction.ButtonAction;
9 |
10 | import static com.github.kaiwinter.androidremotenotifications.RemoteNotifications.TAG;
11 |
12 | /**
13 | * Opens the link with the platform browser.
14 | */
15 | public final class OpenUrlButtonAction implements ButtonAction {
16 | private final String link;
17 |
18 | /**
19 | * @param link the link to open.
20 | */
21 | public OpenUrlButtonAction(String link) {
22 | this.link = link;
23 | }
24 |
25 | @Override
26 | public void execute(Context context) {
27 | Log.v(TAG, "Opening URL: " + link);
28 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
29 | context.startActivity(browserIntent);
30 | }
31 |
32 | public String getLink() {
33 | return link;
34 | }
35 |
36 | @Override
37 | public String toString() {
38 | return "OpenURLButtonAction [link=" + link + "]";
39 | }
40 |
41 | @Override
42 | public int hashCode() {
43 | final int prime = 31;
44 | int result = 1;
45 | result = prime * result + ((link == null) ? 0 : link.hashCode());
46 | return result;
47 | }
48 |
49 | @Override
50 | public boolean equals(Object obj) {
51 | if (this == obj)
52 | return true;
53 | if (obj == null)
54 | return false;
55 | if (getClass() != obj.getClass())
56 | return false;
57 | OpenUrlButtonAction other = (OpenUrlButtonAction) obj;
58 | if (link == null) {
59 | if (other.link != null)
60 | return false;
61 | } else if (!link.equals(other.link))
62 | return false;
63 | return true;
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/impl/AbstractUserNotification.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.impl;
2 |
3 | import com.github.kaiwinter.androidremotenotifications.model.UserNotification;
4 |
5 | /**
6 | * Abstract implementation of {@link UserNotification} which implements common functionality.
7 | */
8 | public abstract class AbstractUserNotification implements UserNotification {
9 |
10 | private static final NotificationConfiguration DEFAULT_CONFIGURATION = new NotificationConfiguration();
11 |
12 | /**
13 | * The configuration about when to show this Notification.
14 | */
15 | private final NotificationConfiguration notificationConfiguration;
16 |
17 | public AbstractUserNotification() {
18 | this(DEFAULT_CONFIGURATION);
19 | }
20 |
21 | public AbstractUserNotification(NotificationConfiguration notificationConfiguration) {
22 | if (notificationConfiguration == null) {
23 | this.notificationConfiguration = DEFAULT_CONFIGURATION;
24 | } else {
25 | this.notificationConfiguration = notificationConfiguration;
26 | }
27 | }
28 |
29 | @Override
30 | public NotificationConfiguration getNotificationConfiguration() {
31 | return notificationConfiguration;
32 | }
33 |
34 | /**
35 | * hashCode/equals must be implemented to synchronized the notifications with the ones from the server.
36 | */
37 | @Override
38 | public boolean equals(Object o) {
39 | if (this == o) return true;
40 | if (o == null || getClass() != o.getClass()) return false;
41 |
42 | AbstractUserNotification that = (AbstractUserNotification) o;
43 | return !(notificationConfiguration != null ? !notificationConfiguration.equals(that.notificationConfiguration) : that.notificationConfiguration != null);
44 | }
45 |
46 | /**
47 | * hashCode/equals must be implemented to synchronized the notifications with the ones from the server.
48 | */
49 | @Override
50 | public int hashCode() {
51 | return notificationConfiguration != null ? notificationConfiguration.hashCode() : 0;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/impl/AlertDialogNotification.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.impl;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 |
7 | import com.github.kaiwinter.androidremotenotifications.model.buttonaction.ButtonAction;
8 |
9 | /**
10 | * Shows a Notification in an {@link AlertDialog}.
11 | */
12 | public final class AlertDialogNotification extends AbstractUserNotification {
13 |
14 | /**
15 | * Title of the notification.
16 | */
17 | private String title;
18 |
19 | /**
20 | * The message in the notification.
21 | */
22 | private String message;
23 |
24 | /**
25 | * The caption of the negative button. If null the button won't be shown.
26 | */
27 | private String negativeButtonText;
28 |
29 | /**
30 | * The caption of the neutral button. If null the button won't be shown.
31 | */
32 | private String neutralButtonText;
33 |
34 | /**
35 | * The caption of the positive button. If null the button won't be shown.
36 | */
37 | private String positiveButtonText;
38 |
39 | /**
40 | * Action to carry out when the user selects the positive button. If null the dialog just gets closed.
41 | */
42 | private ButtonAction positiveButtonAction;
43 |
44 | /**
45 | * Action to carry out when the user selects the negative button. If null the dialog just gets closed.
46 | */
47 | private ButtonAction negativeButtonAction;
48 |
49 | /**
50 | * Action to carry out when the user selects the neutral button. If null the dialog just gets closed.
51 | */
52 | private ButtonAction neutralButtonAction;
53 |
54 | /**
55 | * If true, the user must push one of the buttons. The back button and tapping the app in the
56 | * background won't close the dialog.
57 | */
58 | private boolean modal;
59 |
60 | public AlertDialogNotification() {
61 | super();
62 | }
63 |
64 | public AlertDialogNotification(NotificationConfiguration notificationConfiguration) {
65 | super(notificationConfiguration);
66 | }
67 |
68 | @Override
69 | public void show(final Context context) {
70 | AlertDialog.Builder builder = new AlertDialog.Builder(context);
71 | builder.setMessage(message);
72 | builder.setTitle(title);
73 |
74 | if (negativeButtonText != null) {
75 | DialogInterface.OnClickListener negativeListener = new DialogInterface.OnClickListener() {
76 |
77 | @Override
78 | public void onClick(DialogInterface dialog, int which) {
79 | if (negativeButtonAction != null) {
80 | negativeButtonAction.execute(context);
81 | }
82 | dialog.dismiss();
83 | }
84 | };
85 | builder.setNegativeButton(negativeButtonText, negativeListener);
86 | }
87 |
88 | if (neutralButtonText != null) {
89 | DialogInterface.OnClickListener neutralListener = new DialogInterface.OnClickListener() {
90 | @Override
91 | public void onClick(DialogInterface dialog, int which) {
92 | if (neutralButtonAction != null) {
93 | neutralButtonAction.execute(context);
94 | }
95 | dialog.dismiss();
96 | }
97 | };
98 | builder.setNeutralButton(neutralButtonText, neutralListener);
99 | }
100 |
101 | if (positiveButtonText != null) {
102 | DialogInterface.OnClickListener positiveListener = new DialogInterface.OnClickListener() {
103 |
104 | @Override
105 | public void onClick(DialogInterface dialog, int which) {
106 | if (positiveButtonAction != null) {
107 | positiveButtonAction.execute(context);
108 | }
109 | dialog.dismiss();
110 | }
111 | };
112 | builder.setPositiveButton(positiveButtonText, positiveListener);
113 | }
114 |
115 | if (modal) {
116 | builder.setCancelable(false);
117 | }
118 |
119 | builder.create().show();
120 | }
121 |
122 | /**
123 | * Returns the title of the AlertDialog.
124 | *
125 | * @return the title of the AlertDialog.
126 | */
127 | public String getTitle() {
128 | return title;
129 | }
130 |
131 | /**
132 | * Sets the title of the AlertDialog.
133 | *
134 | * @param title the title of the AlertDialog.
135 | */
136 | public void setTitle(String title) {
137 | this.title = title;
138 | }
139 |
140 | /**
141 | * Returns the message of the AlertDialog.
142 | *
143 | * @return the message of the AlertDialog
144 | */
145 | public String getMessage() {
146 | return message;
147 | }
148 |
149 | /**
150 | * Sets the message of the AlertDialog.
151 | *
152 | * @param message the message of the AlertDialog
153 | */
154 | public void setMessage(String message) {
155 | this.message = message;
156 | }
157 |
158 | /**
159 | * Returns the caption of the positive button.
160 | *
161 | * @return the caption of the positive button
162 | */
163 | public String getPositiveButtonText() {
164 | return positiveButtonText;
165 | }
166 |
167 | /**
168 | * Sets the caption of the positive button. If null the button won't be shown.
169 | *
170 | * @param positiveButtonText the caption of the positive button
171 | */
172 | public void setPositiveButtonText(String positiveButtonText) {
173 | this.positiveButtonText = positiveButtonText;
174 | }
175 |
176 | /**
177 | * Returns the caption of the negative button.
178 | *
179 | * @return the caption of the negative button
180 | */
181 | public String getNegativeButtonText() {
182 | return negativeButtonText;
183 | }
184 |
185 | /**
186 | * Sets the caption of the negative button. If null the button won't be shown.
187 | *
188 | * @param negativeButtonText the caption of the negative button
189 | */
190 | public void setNegativeButtonText(String negativeButtonText) {
191 | this.negativeButtonText = negativeButtonText;
192 | }
193 |
194 | /**
195 | * Returns the Action to carry out when the user selects the positive button.
196 | *
197 | * @return the Action to carry out when the user selects the positive button.
198 | */
199 | public ButtonAction getPositiveButtonAction() {
200 | return positiveButtonAction;
201 | }
202 |
203 | /**
204 | * Sets the Action to carry out when the user selects the positive button.
205 | *
206 | * @param positiveButtonAction the Action to carry out when the user selects the positive button
207 | */
208 | public void setPositiveButtonAction(ButtonAction positiveButtonAction) {
209 | this.positiveButtonAction = positiveButtonAction;
210 | }
211 |
212 | /**
213 | * Returns the Action to carry out when the user selects the negative button.
214 | *
215 | * @return the Action to carry out when the user selects the negative button.
216 | */
217 | public ButtonAction getNegativeButtonAction() {
218 | return negativeButtonAction;
219 | }
220 |
221 | /**
222 | * Sets the Action to carry out when the user selects the negative button.
223 | *
224 | * @param negativeButtonAction the Action to carry out when the user selects the negative button
225 | */
226 | public void setNegativeButtonAction(ButtonAction negativeButtonAction) {
227 | this.negativeButtonAction = negativeButtonAction;
228 | }
229 |
230 | /**
231 | * Returns if the AlertDialog is modal.
232 | *
233 | * @return true if the AlertDialog is modal, else false
234 | */
235 | public boolean isModal() {
236 | return modal;
237 | }
238 |
239 | /**
240 | * Sets if the AlertDialog is modal. If true, the user must push one of the buttons. The back button and tapping the app in the
241 | * background won't close the dialog.
242 | *
243 | * @param modal true if the AlertDialog is modal, else false
244 | */
245 | public void setModal(boolean modal) {
246 | this.modal = modal;
247 | }
248 |
249 | /**
250 | * Returns the caption of the neutral button.
251 | *
252 | * @return the caption of the neutral button
253 | */
254 | public String getNeutralButtonText() {
255 | return neutralButtonText;
256 | }
257 |
258 | /**
259 | * Sets the caption of the neutral button. If null the button won't be shown.
260 | *
261 | * @param neutralButtonText the caption of the neutral button
262 | */
263 | public void setNeutralButtonText(String neutralButtonText) {
264 | this.neutralButtonText = neutralButtonText;
265 | }
266 |
267 | /**
268 | * Returns the Action to carry out when the user selects the neutral button.
269 | *
270 | * @return the Action to carry out when the user selects the neutral button.
271 | */
272 | public ButtonAction getNeutralButtonAction() {
273 | return neutralButtonAction;
274 | }
275 |
276 | /**
277 | * Sets the Action to carry out when the user selects the neutral button.
278 | *
279 | * @param neutralButtonAction the Action to carry out when the user selects the neutral button
280 | */
281 | public void setNeutralButtonAction(ButtonAction neutralButtonAction) {
282 | this.neutralButtonAction = neutralButtonAction;
283 | }
284 |
285 | @Override
286 | public String toString() {
287 | return "AlertDialogNotification{" +
288 | "title='" + title + '\'' +
289 | ", message='" + message + '\'' +
290 | '}';
291 | }
292 |
293 | @Override
294 | public boolean equals(Object o) {
295 | if (this == o) return true;
296 | if (o == null || getClass() != o.getClass()) return false;
297 | if (!super.equals(o)) return false;
298 |
299 | AlertDialogNotification that = (AlertDialogNotification) o;
300 |
301 | if (modal != that.modal) return false;
302 | if (title != null ? !title.equals(that.title) : that.title != null) return false;
303 | if (message != null ? !message.equals(that.message) : that.message != null) return false;
304 | if (negativeButtonText != null ? !negativeButtonText.equals(that.negativeButtonText) : that.negativeButtonText != null)
305 | return false;
306 | if (neutralButtonText != null ? !neutralButtonText.equals(that.neutralButtonText) : that.neutralButtonText != null)
307 | return false;
308 | if (positiveButtonText != null ? !positiveButtonText.equals(that.positiveButtonText) : that.positiveButtonText != null)
309 | return false;
310 | if (positiveButtonAction != null ? !positiveButtonAction.equals(that.positiveButtonAction) : that.positiveButtonAction != null)
311 | return false;
312 | if (negativeButtonAction != null ? !negativeButtonAction.equals(that.negativeButtonAction) : that.negativeButtonAction != null)
313 | return false;
314 | return !(neutralButtonAction != null ? !neutralButtonAction.equals(that.neutralButtonAction) : that.neutralButtonAction != null);
315 |
316 | }
317 |
318 | @Override
319 | public int hashCode() {
320 | int result = super.hashCode();
321 | result = 31 * result + (title != null ? title.hashCode() : 0);
322 | result = 31 * result + (message != null ? message.hashCode() : 0);
323 | result = 31 * result + (negativeButtonText != null ? negativeButtonText.hashCode() : 0);
324 | result = 31 * result + (neutralButtonText != null ? neutralButtonText.hashCode() : 0);
325 | result = 31 * result + (positiveButtonText != null ? positiveButtonText.hashCode() : 0);
326 | result = 31 * result + (positiveButtonAction != null ? positiveButtonAction.hashCode() : 0);
327 | result = 31 * result + (negativeButtonAction != null ? negativeButtonAction.hashCode() : 0);
328 | result = 31 * result + (neutralButtonAction != null ? neutralButtonAction.hashCode() : 0);
329 | result = 31 * result + (modal ? 1 : 0);
330 | return result;
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/impl/NotificationConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.impl;
2 |
3 | import com.github.kaiwinter.androidremotenotifications.model.ExecutionPolicy;
4 |
5 | import java.util.Date;
6 |
7 | /**
8 | * Stores configurations for a Notification e.g. when and how often it is shown and on which app versions.
9 | */
10 | public final class NotificationConfiguration {
11 |
12 | /**
13 | * From this date on the notification will be shown. If null notification will be shown immediately (if
14 | * {@link ExecutionPolicy} strikes).
15 | */
16 | private Date startShowingDate;
17 |
18 | /**
19 | * Defines when to show the notification. If null the notification will be shown
20 | * {@link ExecutionPolicy#ALWAYS}.
21 | *
22 | * This will be evaluated only after {@link #startShowingDate} (if not null).
23 | */
24 | private ExecutionPolicy executionPolicy;
25 |
26 | /**
27 | * Defines how many times the notification is shown in regards to executionPolicy (null=infinite).
28 | */
29 | private Integer numberOfTotalViews;
30 |
31 | /**
32 | * The app version on which the notification is shown. If null notification will be shown on all
33 | * versions.
34 | */
35 | private VersionCodePolicy versionCodePolicy;
36 |
37 | /**
38 | * @return the date to start showing the notification
39 | */
40 | public Date getStartShowingDate() {
41 | return startShowingDate;
42 | }
43 |
44 | /**
45 | * Set the date from which the notification will be shown. If null notification will be shown immediately (if
46 | * {@link ExecutionPolicy} strikes).
47 | *
48 | * @param startShowingDate the date to start showing the notification
49 | */
50 | public void setStartShowingDate(Date startShowingDate) {
51 | this.startShowingDate = startShowingDate;
52 | }
53 |
54 | /**
55 | * Returns when to show the notification.
56 | *
57 | * @return when to show the notification
58 | */
59 | public ExecutionPolicy getExecutionPolicy() {
60 | return executionPolicy;
61 | }
62 |
63 | /**
64 | * Sets the {@link ExecutionPolicy} when to show the notification. If null the notification will be shown
65 | * {@link ExecutionPolicy#ALWAYS}. This will be evaluated only after {@link #startShowingDate} (if not null).
66 | *
67 | * @param executionPolicy when to show the notification
68 | */
69 | public void setExecutionPolicy(ExecutionPolicy executionPolicy) {
70 | this.executionPolicy = executionPolicy;
71 | }
72 |
73 | /**
74 | * Returns the number of times the notification is shown in regards to executionPolicy (null=infinite).
75 | *
76 | * @return the number of times the notification is shown.
77 | */
78 | public Integer getNumberOfTotalViews() {
79 | return numberOfTotalViews;
80 | }
81 |
82 | /**
83 | * Sets the number of times the notification is shown in regards to executionPolicy (null=infinite).
84 | *
85 | * @param numberOfTotalViews the number of times the notification is shown in regards to executionPolicy (null=infinite).
86 | */
87 | public void setNumberOfTotalViews(Integer numberOfTotalViews) {
88 | this.numberOfTotalViews = numberOfTotalViews;
89 | }
90 |
91 | /**
92 | * Returns the app version on which the notification is shown. If null notification will be shown on all
93 | * versions.
94 | *
95 | * @return the {@link VersionCodePolicy}
96 | */
97 | public VersionCodePolicy getVersionCodePolicy() {
98 | return versionCodePolicy;
99 | }
100 |
101 | /**
102 | * Sets the app version on which the notification is shown. If null notification will be shown on all
103 | * versions.
104 | *
105 | * @param versionCodePolicy the {@link VersionCodePolicy} to restrict the app versions
106 | */
107 | public void setVersionCodePolicy(VersionCodePolicy versionCodePolicy) {
108 | this.versionCodePolicy = versionCodePolicy;
109 | }
110 |
111 | @Override
112 | public boolean equals(Object o) {
113 | if (this == o) return true;
114 | if (o == null || getClass() != o.getClass()) return false;
115 |
116 | NotificationConfiguration that = (NotificationConfiguration) o;
117 |
118 | if (startShowingDate != null ? !startShowingDate.equals(that.startShowingDate) : that.startShowingDate != null)
119 | return false;
120 | if (executionPolicy != that.executionPolicy) return false;
121 | if (numberOfTotalViews != null ? !numberOfTotalViews.equals(that.numberOfTotalViews) : that.numberOfTotalViews != null)
122 | return false;
123 | return !(versionCodePolicy != null ? !versionCodePolicy.equals(that.versionCodePolicy) : that.versionCodePolicy != null);
124 |
125 | }
126 |
127 | @Override
128 | public int hashCode() {
129 | int result = startShowingDate != null ? startShowingDate.hashCode() : 0;
130 | result = 31 * result + (executionPolicy != null ? executionPolicy.hashCode() : 0);
131 | result = 31 * result + (numberOfTotalViews != null ? numberOfTotalViews.hashCode() : 0);
132 | result = 31 * result + (versionCodePolicy != null ? versionCodePolicy.hashCode() : 0);
133 | return result;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/impl/PersistentNotification.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.impl;
2 |
3 | import com.github.kaiwinter.androidremotenotifications.model.UserNotification;
4 |
5 | import java.util.Calendar;
6 | import java.util.Date;
7 |
8 | /**
9 | * A {@link PersistentNotification} is a {@link UserNotification} which can be stored in the shared preferences. It stores
10 | * metadata about the {@link UserNotification} such as last shown date etc.
11 | */
12 | public final class PersistentNotification {
13 |
14 | /**
15 | * The notification to show
16 | */
17 | private final UserNotification notification;
18 |
19 | /**
20 | * The time when the notification was shown the last time
21 | */
22 | private Date lastShown;
23 |
24 | /**
25 | * The number of times the notification was shown already
26 | */
27 | private int shownCounter = 0;
28 |
29 | /**
30 | * @param notification The {@link AlertDialogNotification} to persist, not null.
31 | */
32 | public PersistentNotification(UserNotification notification) {
33 | if (notification == null) {
34 | throw new IllegalArgumentException("Notification must not be null");
35 | }
36 | this.notification = notification;
37 | }
38 |
39 | /**
40 | * Checks whether the {@link UserNotification} has to be shown considering the {@link NotificationConfiguration}.
41 | *
42 | * @param appVersionCode the app´s versionCode
43 | * @return true if the notification has to be shown, else false
44 | */
45 | public boolean hasToBeShown(int appVersionCode) {
46 | NotificationConfiguration notificationConfiguration = notification.getNotificationConfiguration();
47 |
48 | Date now = new Date();
49 | if (notificationConfiguration.getStartShowingDate() != null && now.before(notificationConfiguration.getStartShowingDate())) {
50 | // Should not be shown yet
51 | return false;
52 | }
53 |
54 | // if notification was shown configured times return immediately
55 | if (notificationConfiguration.getNumberOfTotalViews() != null && shownCounter >= notificationConfiguration.getNumberOfTotalViews()) {
56 | //Log.v(TAG, "Not showing notification, max number of views reached.");
57 | return false;
58 | }
59 |
60 | if (notificationConfiguration.getVersionCodePolicy() != null) {
61 | boolean hasBeShownForThisVersion = notificationConfiguration.getVersionCodePolicy().hasBeShownForThisVersion(
62 | appVersionCode);
63 | if (!hasBeShownForThisVersion) {
64 | return false;
65 | }
66 | }
67 |
68 | if (lastShown == null) {
69 | return true;
70 | }
71 |
72 | if (notificationConfiguration.getExecutionPolicy() == null) {
73 | return true;
74 | }
75 |
76 | Calendar calendar = Calendar.getInstance();
77 | calendar.setTime(lastShown);
78 | calendar.set(Calendar.HOUR_OF_DAY, 0);
79 | calendar.set(Calendar.MINUTE, 0);
80 | switch (notificationConfiguration.getExecutionPolicy()) {
81 | case ALWAYS:
82 | return true;
83 |
84 | case EVERY_DAY:
85 | // Calculate date for next midnight
86 | calendar.add(Calendar.DATE, 1);
87 | Date nextDayMidnight = calendar.getTime();
88 | return now.after(nextDayMidnight);
89 |
90 | case EVERY_FIRST_OF_MONTH:
91 | // Calculate 1st of next month
92 | calendar.add(Calendar.MONTH, 1);
93 | calendar.set(Calendar.DAY_OF_MONTH, 1);
94 | Date nextFirstOfMonthMidnight = calendar.getTime();
95 | return now.after(nextFirstOfMonthMidnight);
96 |
97 | case EVERY_MONDAY:
98 | Date nextMondayMidnight = nextDayOfWeek(calendar, Calendar.MONDAY);
99 | return now.after(nextMondayMidnight);
100 |
101 | case EVERY_SUNDAY:
102 | Date nextSundayMidnight = nextDayOfWeek(calendar, Calendar.SUNDAY);
103 | return now.after(nextSundayMidnight);
104 |
105 | default:
106 | throw new UnsupportedOperationException("Unknown execution mode: "
107 | + notificationConfiguration.getExecutionPolicy().name());
108 | }
109 | }
110 |
111 | private static Date nextDayOfWeek(Calendar calendar, int dow) {
112 | int diff = dow - calendar.get(Calendar.DAY_OF_WEEK);
113 | if (!(diff > 0)) {
114 | diff += 7;
115 | }
116 | calendar.add(Calendar.DAY_OF_MONTH, diff);
117 | return calendar.getTime();
118 | }
119 |
120 | /**
121 | * Returns the time when the notification was shown the last time.
122 | *
123 | * @return the time when the notification was shown the last time
124 | */
125 | public Date getLastShown() {
126 | return lastShown;
127 | }
128 |
129 | /**
130 | * Sets the time when the notification was shown the last time.
131 | *
132 | * @param lastShown the time when the notification was shown the last time
133 | */
134 | public void setLastShown(Date lastShown) {
135 | this.lastShown = lastShown;
136 | }
137 |
138 | /**
139 | * Returns the number of times the notification was shown already.
140 | *
141 | * @return the number of times the notification was shown already.
142 | */
143 | public int getShownCounter() {
144 | return shownCounter;
145 | }
146 |
147 | /**
148 | * Sets the number of times the notification was shown already.
149 | *
150 | * @param shownCounter the number of times the notification was shown already.
151 | */
152 | public void setShownCounter(int shownCounter) {
153 | this.shownCounter = shownCounter;
154 | }
155 |
156 | /**
157 | * Returns the notification to show.
158 | *
159 | * @return the notification to show
160 | */
161 | public UserNotification getNotification() {
162 | return notification;
163 | }
164 |
165 | @Override
166 | public String toString() {
167 | return "PersistentNotification [lastShown=" + lastShown + ", shownCounter=" + shownCounter + ", notification="
168 | + notification + "]";
169 | }
170 |
171 | @Override
172 | public int hashCode() {
173 | final int prime = 31;
174 | int result = 1;
175 | result = prime * result + ((notification == null) ? 0 : notification.hashCode());
176 | return result;
177 | }
178 |
179 | @Override
180 | public boolean equals(Object obj) {
181 | if (this == obj)
182 | return true;
183 | if (obj == null)
184 | return false;
185 | if (getClass() != obj.getClass())
186 | return false;
187 | PersistentNotification other = (PersistentNotification) obj;
188 | if (notification == null) {
189 | if (other.notification != null)
190 | return false;
191 | } else if (!notification.equals(other.notification))
192 | return false;
193 | return true;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/impl/ToastNotification.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.impl;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * A Notification which is shown as a {@link Toast}. Set a message and a duration of {@link Toast#LENGTH_LONG} or {@link Toast#LENGTH_LONG}.
8 | */
9 | public final class ToastNotification extends AbstractUserNotification {
10 |
11 | /**
12 | * The message in the notification.
13 | */
14 | private String message;
15 |
16 | /**
17 | * Either {@link Toast#LENGTH_SHORT} or {@link Toast#LENGTH_LONG}.
18 | */
19 | private int duration;
20 |
21 | public ToastNotification() {
22 | super();
23 | }
24 |
25 | public ToastNotification(NotificationConfiguration notificationConfiguration) {
26 | super(notificationConfiguration);
27 | }
28 |
29 | @Override
30 | public void show(final Context context) {
31 | Toast.makeText(context, message, duration).show();
32 | }
33 |
34 | /**
35 | * @return the Toast message
36 | */
37 | public String getMessage() {
38 | return message;
39 | }
40 |
41 | /**
42 | * Sets the Toast message.
43 | *
44 | * @param message the Toast message
45 | */
46 | public void setMessage(String message) {
47 | this.message = message;
48 | }
49 |
50 | /**
51 | * @return the Toast duration (either {@link Toast#LENGTH_SHORT} or {@link Toast#LENGTH_LONG}
52 | */
53 | public int getDuration() {
54 | return duration;
55 | }
56 |
57 | /**
58 | * Sets the Toast duration (either {@link Toast#LENGTH_SHORT} (0) or {@link Toast#LENGTH_LONG} (1).
59 | *
60 | * @param duration the Toast duration
61 | */
62 | public void setDuration(int duration) {
63 | this.duration = duration;
64 | }
65 |
66 | @Override
67 | public String toString() {
68 | return "ToastNotification{" +
69 | "message='" + message + '\'' +
70 | '}';
71 | }
72 |
73 | @Override
74 | public boolean equals(Object o) {
75 | if (this == o) return true;
76 | if (o == null || getClass() != o.getClass()) return false;
77 | if (!super.equals(o)) return false;
78 |
79 | ToastNotification that = (ToastNotification) o;
80 |
81 | if (duration != that.duration) return false;
82 | return !(message != null ? !message.equals(that.message) : that.message != null);
83 | }
84 |
85 | @Override
86 | public int hashCode() {
87 | int result = super.hashCode();
88 | result = 31 * result + (message != null ? message.hashCode() : 0);
89 | result = 31 * result + duration;
90 | return result;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/model/impl/VersionCodePolicy.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.model.impl;
2 |
3 | import java.util.Collection;
4 |
5 | /**
6 | * Defines the app version codes on which a notification should be shown. If the current app install doesn't match this
7 | * {@link VersionCodePolicy} the notification will not be shown.
8 | * There are three ways to define on which versions of your app the notification should be shown: onAllBefore,
9 | * onAllAfter and onSpecific. You can also mix them but have to keep them sane. Only one of the previous named
10 | * parameters have to match to let the notification show. They are evaluated in the previous named order.
11 | */
12 | public final class VersionCodePolicy {
13 |
14 | /**
15 | * The notification will be shown on all versions < onAllBefore.
16 | */
17 | private Integer onAllBefore;
18 |
19 | /**
20 | * The notification will be shown on all versions > onAllAfter.
21 | */
22 | private Integer onAllAfter;
23 |
24 | /**
25 | * The notification will be shown on all versions which are in onSpecific.
26 | */
27 | private Collection onSpecific;
28 |
29 | /**
30 | * Checks if one of onAllAfter, onAllBefore or onSpecific matches the given
31 | * versionCode. If one matches true is returned, else false.
32 | *
33 | * @param appVersionCode The version code to match against this {@link VersionCodePolicy}.
34 | * @return true if the notification should be shown, else false.
35 | */
36 | public boolean hasBeShownForThisVersion(int appVersionCode) {
37 |
38 | if (onAllAfter != null) {
39 | if (appVersionCode > onAllAfter) {
40 | return true;
41 | }
42 | }
43 |
44 | if (onAllBefore != null) {
45 | if (appVersionCode < onAllBefore) {
46 | return true;
47 | }
48 | }
49 |
50 | if (onSpecific != null) {
51 | if (onSpecific.contains(appVersionCode)) {
52 | return true;
53 | }
54 | }
55 | return false;
56 | }
57 |
58 | /**
59 | * @return the versionCode before which the notification should be shown
60 | */
61 | public Integer getOnAllBefore() {
62 | return onAllBefore;
63 | }
64 |
65 | /**
66 | * Sets the versionCode before which the notification should be shown.
67 | *
68 | * @param onAllBefore the versionCode before which the notification should be shown
69 | */
70 | public void setOnAllBefore(Integer onAllBefore) {
71 | this.onAllBefore = onAllBefore;
72 | }
73 |
74 | /**
75 | * @return the versionCode after which the notification should be shown
76 | */
77 | public Integer getOnAllAfter() {
78 | return onAllAfter;
79 | }
80 |
81 | /**
82 | * Set the versionCode after which the notification should be shown.
83 | *
84 | * @param onAllAfter the versionCode after which the notification should be shown
85 | */
86 | public void setOnAllAfter(Integer onAllAfter) {
87 | this.onAllAfter = onAllAfter;
88 | }
89 |
90 | /**
91 | * @return the specific app versionCodes on which the notification should be shown
92 | */
93 | public Collection getOnSpecific() {
94 | return onSpecific;
95 | }
96 |
97 | /**
98 | * Sets specific app versionCodes on which the notification should be shown.
99 | *
100 | * @param onSpecific the app versionCodes on which the notification should be shown
101 | */
102 | public void setOnSpecific(Collection onSpecific) {
103 | this.onSpecific = onSpecific;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/network/NotificationLoaderFinishListener.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.network;
2 |
3 | import com.github.kaiwinter.androidremotenotifications.RemoteNotifications;
4 | import com.github.kaiwinter.androidremotenotifications.model.UpdatePolicy;
5 | import com.github.kaiwinter.androidremotenotifications.model.UserNotification;
6 |
7 | import java.util.Set;
8 |
9 | /**
10 | * Listener to attach to {@link RemoteNotifications#updateNotificationsFromServer(UpdatePolicy, NotificationLoaderFinishListener)}
11 | * to be informed when the update of the Notifications is finished.
12 | */
13 | public interface NotificationLoaderFinishListener {
14 |
15 | /**
16 | * Listener to attach to {@link RemoteNotifications#updateNotificationsFromServer(UpdatePolicy, NotificationLoaderFinishListener)}
17 | * to be informed when the update of the Notifications is finished.
18 | *
19 | * @param notifications the notifications
20 | */
21 | void onDownloadFinished(Set notifications);
22 | }
23 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/network/NotificationLoaderTask.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.network;
2 |
3 | import android.os.AsyncTask;
4 | import android.util.Log;
5 |
6 | import com.github.kaiwinter.androidremotenotifications.json.UnMarshaller;
7 | import com.github.kaiwinter.androidremotenotifications.model.UserNotification;
8 |
9 | import java.io.IOException;
10 | import java.net.URL;
11 | import java.util.Collections;
12 | import java.util.Set;
13 |
14 | import static com.github.kaiwinter.androidremotenotifications.RemoteNotifications.TAG;
15 |
16 | /**
17 | * AsyncTask to load notifications from a server.
18 | */
19 | public final class NotificationLoaderTask extends AsyncTask> {
20 |
21 | private final URL serverUrl;
22 | private final NotificationLoaderFinishListener listener;
23 |
24 | /**
25 | * Constructs a new NotificationLoaderTask.
26 | *
27 | * @param serverUrl the URL of the JSON file to load
28 | * @param listener the NotificationLoaderFinishListener which is called after the JSON file has been loaded and parsed.
29 | */
30 | public NotificationLoaderTask(URL serverUrl, NotificationLoaderFinishListener listener) {
31 | this.serverUrl = serverUrl;
32 | this.listener = listener;
33 | }
34 |
35 | @Override
36 | protected Set doInBackground(Void... params) {
37 | try {
38 | return UnMarshaller.getNotificationsFromJson(serverUrl);
39 | } catch (IOException e) {
40 | Log.e(TAG, "Error on requesting JSON from server", e);
41 | return Collections.emptySet();
42 | }
43 | }
44 |
45 | @Override
46 | protected void onPostExecute(Set notifications) {
47 | super.onPostExecute(notifications);
48 | if (listener != null) {
49 | listener.onDownloadFinished(notifications);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/persistence/NotificationStore.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.persistence;
2 |
3 | import com.github.kaiwinter.androidremotenotifications.model.impl.PersistentNotification;
4 |
5 | import java.util.Date;
6 | import java.util.Set;
7 |
8 | /**
9 | * Interface for persisting notifications and configurations.
10 | */
11 | public interface NotificationStore {
12 |
13 | /**
14 | * Loads the date when the notifications was updated from the server the last time.
15 | *
16 | * @return The last update date.
17 | */
18 | Date getLastServerUpdate();
19 |
20 | /**
21 | * Saves the date when the notifications was updated from the server the last time.
22 | *
23 | * @param date The last update date.
24 | */
25 | void saveLastServerUpdate(Date date);
26 |
27 | /**
28 | * Loads all saved {@link PersistentNotification}s.
29 | *
30 | * @return The saved {@link PersistentNotification}s
31 | */
32 | Set getPersistentNotifications();
33 |
34 | /**
35 | * Updates the {@link PersistentNotification} in the {@link NotificationStore} with the given
36 | * updatePersistentNotifications. The notifications to update are identified by their hashCode/equals
37 | * methods.
38 | *
39 | * @param updatePersistentNotifications The {@link PersistentNotification} to update.
40 | */
41 | void updatePersistentNotification(Set updatePersistentNotifications);
42 |
43 | /**
44 | * Replaces the currently stored {@link PersistentNotification}s by the passed ones.
45 | *
46 | * @param persistentNotifications The {@link PersistentNotification}s to store.
47 | */
48 | void replacePersistentNotifications(Set persistentNotifications);
49 |
50 | /**
51 | * Removes all notifications from the store.
52 | */
53 | void clearPersistentNotifications();
54 | }
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/github/kaiwinter/androidremotenotifications/persistence/impl/SharedPreferencesStore.java:
--------------------------------------------------------------------------------
1 | package com.github.kaiwinter.androidremotenotifications.persistence.impl;
2 |
3 | import android.content.SharedPreferences;
4 | import android.content.SharedPreferences.Editor;
5 | import android.util.Log;
6 |
7 | import com.github.kaiwinter.androidremotenotifications.json.UnMarshaller;
8 | import com.github.kaiwinter.androidremotenotifications.model.impl.AlertDialogNotification;
9 | import com.github.kaiwinter.androidremotenotifications.model.impl.PersistentNotification;
10 | import com.github.kaiwinter.androidremotenotifications.persistence.NotificationStore;
11 |
12 | import java.util.Date;
13 | import java.util.HashSet;
14 | import java.util.Set;
15 |
16 | import static com.github.kaiwinter.androidremotenotifications.RemoteNotifications.TAG;
17 |
18 | /**
19 | * Implementation of {@link NotificationStore} which utilizes Androids Shared Preferences.
20 | */
21 | public final class SharedPreferencesStore implements NotificationStore {
22 |
23 | /**
24 | * Key in the Shared Preferences of the date of the last server update.
25 | */
26 | private static final String LAST_SERVER_UPDATE = "LAST_SERVER_UPDATE";
27 |
28 | /**
29 | * Key in the Shared Preferences of the Persistent Notifications.
30 | */
31 | private static final String PERSISTENT_NOTIFICATIONS = "PERSISTENT_NOTIFICATIONS";
32 |
33 | private final SharedPreferences sharedPreferences;
34 |
35 | /**
36 | * Constructs a new {@link SharedPreferencesStore} and initializes it with the underlying {@link SharedPreferences}
37 | * object.
38 | *
39 | * @param sharedPreferences The {@link SharedPreferences} instance which is used to store {@link AlertDialogNotification}s.
40 | */
41 | public SharedPreferencesStore(SharedPreferences sharedPreferences) {
42 | this.sharedPreferences = sharedPreferences;
43 | }
44 |
45 | @Override
46 | public Date getLastServerUpdate() {
47 | if (sharedPreferences.contains(LAST_SERVER_UPDATE)) {
48 | return new Date(sharedPreferences.getLong(LAST_SERVER_UPDATE, 0));
49 | }
50 | return null;
51 | }
52 |
53 | @Override
54 | public void saveLastServerUpdate(Date date) {
55 | Log.v(TAG, date.toString());
56 | Editor edit = sharedPreferences.edit();
57 | edit.putLong(LAST_SERVER_UPDATE, date.getTime());
58 | edit.apply();
59 | }
60 |
61 | @Override
62 | public Set getPersistentNotifications() {
63 | Set persistentNotifications = new HashSet<>();
64 |
65 | if (sharedPreferences.contains(PERSISTENT_NOTIFICATIONS)) {
66 | String json = sharedPreferences.getString(PERSISTENT_NOTIFICATIONS, null);
67 | persistentNotifications = UnMarshaller.getPersistentNotificationsFromJson(json);
68 | }
69 |
70 | Log.v(TAG, "Loaded " + persistentNotifications.size() + " Persistent Notifications");
71 | return persistentNotifications;
72 | }
73 |
74 | @Override
75 | public void updatePersistentNotification(Set updatePersistentNotifications) {
76 | Set persistentNotifications = getPersistentNotifications();
77 | for (PersistentNotification updatePersistentNotification : updatePersistentNotifications) {
78 | Log.v(TAG, "Updating " + updatePersistentNotification.toString());
79 | persistentNotifications.remove(updatePersistentNotification);
80 | persistentNotifications.add(updatePersistentNotification);
81 | }
82 | replacePersistentNotifications(persistentNotifications);
83 | }
84 |
85 | @Override
86 | public void replacePersistentNotifications(Set persistentNotifications) {
87 | String json = UnMarshaller.getJsonFromPersistentNotifications(persistentNotifications);
88 | Editor editor = sharedPreferences.edit();
89 | editor.putString(PERSISTENT_NOTIFICATIONS, json);
90 | editor.apply();
91 |
92 | Log.v(TAG, "Saved " + persistentNotifications.size() + " Persistent Notifications");
93 | }
94 |
95 | @Override
96 | public void clearPersistentNotifications() {
97 | Editor editor = sharedPreferences.edit();
98 | editor.clear();
99 | editor.apply();
100 | Log.v(TAG, "Persistent Notifications removed");
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/android-remote-notifications/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.gson.typeadapters;
18 |
19 | import java.io.IOException;
20 | import java.util.LinkedHashMap;
21 | import java.util.Map;
22 |
23 | import com.google.gson.Gson;
24 | import com.google.gson.JsonElement;
25 | import com.google.gson.JsonObject;
26 | import com.google.gson.JsonParseException;
27 | import com.google.gson.JsonPrimitive;
28 | import com.google.gson.TypeAdapter;
29 | import com.google.gson.TypeAdapterFactory;
30 | import com.google.gson.internal.Streams;
31 | import com.google.gson.reflect.TypeToken;
32 | import com.google.gson.stream.JsonReader;
33 | import com.google.gson.stream.JsonWriter;
34 |
35 | /**
36 | * Adapts values whose runtime type may differ from their declaration type. This
37 | * is necessary when a field's type is not the same type that GSON should create
38 | * when deserializing that field. For example, consider these types:
39 | *
{@code
40 | * abstract class Shape {
41 | * int x;
42 | * int y;
43 | * }
44 | * class Circle extends Shape {
45 | * int radius;
46 | * }
47 | * class Rectangle extends Shape {
48 | * int width;
49 | * int height;
50 | * }
51 | * class Diamond extends Shape {
52 | * int width;
53 | * int height;
54 | * }
55 | * class Drawing {
56 | * Shape bottomShape;
57 | * Shape topShape;
58 | * }
59 | * }
60 | *
Without additional type information, the serialized JSON is ambiguous. Is
61 | * the bottom shape in this drawing a rectangle or a diamond?
75 | * This class addresses this problem by adding type information to the
76 | * serialized JSON and honoring that type information when the JSON is
77 | * deserialized:
93 | * Both the type field name ({@code "type"}) and the type labels ({@code
94 | * "Rectangle"}) are configurable.
95 | *
96 | *
Registering Types
97 | * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field
98 | * name to the {@link #of} factory method. If you don't supply an explicit type
99 | * field name, {@code "type"} will be used.
103 | * Next register all of your subtypes. Every subtype must be explicitly
104 | * registered. This protects your application from injection attacks. If you
105 | * don't supply an explicit type label, the type's simple name will be used.
106 | *