diff --git a/AndroidPushTutorial/README.md b/AndroidPushTutorial/README.md
new file mode 100644
index 00000000..c0c02d41
--- /dev/null
+++ b/AndroidPushTutorial/README.md
@@ -0,0 +1,24 @@
+[](https://www.ably.io)
+
+---
+
+# Android Push Tutorials
+
+This tutorial explains how to use Ably to send Push notification from your private server.
+
+There are some pre-requisites before you can run the app.
+
+1. Register FCM Token from Google Firebase Console
+2. Install nodejs to run the local server insider `Server/` folder
+3. Setup *.ngrok.io to make the local server accessible via Internet
+4. Register an account with Ably and create required App keys.
+
+
+## Tutorials Setup
+1. After the above steps are done, open the `push-demo-server-registration` folder as a project in Android Studio
+2. The project might take some time to sync.
+3. Replace `google-services.json` into `push-tutorial-one` and `push-demo-server-registration` folder.
+4. For this tutorial we have used `io.ably.tutorial.push_tutorial_two` as applicationId for the app respectively. So ensure your FCM Keys have same applicationId. Feel free to modify the tutorials to suit your purpose.
+5. Edit `local.properties` file and add the keys, `ably.key` (retrieved from dashboard), `ably.env` (either production or sanbox), `base.url` (pointing to your ngrok.io domain)
+
+
diff --git a/AndroidPushTutorial/Server/main.js b/AndroidPushTutorial/Server/main.js
new file mode 100644
index 00000000..ab6d019a
--- /dev/null
+++ b/AndroidPushTutorial/Server/main.js
@@ -0,0 +1,109 @@
+var Ably = require('ably')
+//replace with actual API key or token
+var client = Ably.Realtime(API_KEY)
+var express = require('express')
+var app = express()
+
+app.get('/auth', (req, res) => {
+ var tokenParams = {
+ 'clientId': req.query.clientId
+ };
+ console.log("Authenticating client:", JSON.stringify(tokenParams));
+ client.auth.createTokenRequest(tokenParams, function(err, tokenRequest) {
+ if (err) {
+ res.status(500).send('Error requesting token: ' + JSON.stringify(err));
+ } else {
+ res.setHeader('Content-Type', 'application/json');
+ res.send(JSON.stringify(tokenRequest));
+ }
+ });
+})
+
+app.get('/register', (req, res) => {
+
+ console.log('Registering device')
+ var deviceId = req.query.deviceId;
+ var registrationToken = req.query.registrationToken;
+ var clientId = req.query.clientId;
+ //var deviceId and deviceToken to be received in the request object
+ var recipientDetails = {
+ //It's "fcm" for Android.
+ transportType: 'fcm',
+ //replace with actual device token
+ registrationToken:registrationToken
+ }
+
+ var myDevice = {
+ id: deviceId,
+ clientId:clientId,
+ formFactor: 'phone',
+ metadata: 'PST',
+ platform: 'android',
+ push: {
+ recipient: recipientDetails
+ }
+ }
+ client.push.admin.deviceRegistrations.save(myDevice, (err, device) => {
+ if(err){
+ console.log(err);
+ } else{
+ console.log(device);
+ subscribeDevice(device);
+ res.setHeader('Content-Type', 'application/json');
+ res.send(device);
+ }
+
+ })
+
+})
+
+app.get('/push/device', function (req, res) {
+ var deviceId = req.query.deviceId;
+
+ var recipient = {
+ deviceId: deviceId
+
+ };
+ var notification = {
+ notification: {
+ title: 'Hello from Ably!'
+ }
+
+ };
+ realtime.push.publish(recipient, notification, function(err) {
+ if (err) {
+ console.log('Unable to publish push notification; err = ' + err.message);
+ return;
+ }
+ console.log('Push notification published');
+ res.send("Push Sent");
+ });
+})
+
+function subscribeDevice (device){
+ var channelSub = {
+ channel: 'test_push_channel',
+ deviceId: device.id
+ }
+ client.push.admin.channelSubscriptions.save(channelSub, (err, channelSub) => {
+ if(err){
+ console.log(err);
+ } else{
+ console.log('Device subscribed to push channel with deviceId' + device.id)
+ }
+ })
+}
+
+app.get('/', (req, res) => {
+ console.log('Push Notifications tutorial with Ably')
+})
+
+app.listen(3000, () => {
+ console.log('APP LISTENING ON PORT 3000')
+})
+
+
+/*Steps to run this server */
+// 1. Download the ably-js 1.1 node library using `npm install ably`
+// 2. Replace the 'API_KEY' string with an actual key
+// 3. Run the server using `node main.js`
\ No newline at end of file
diff --git a/AndroidPushTutorial/push-demo-server-registration/.gitignore b/AndroidPushTutorial/push-demo-server-registration/.gitignore
new file mode 100644
index 00000000..40e59569
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.idea
+google-services.json
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/.gitignore b/AndroidPushTutorial/push-demo-server-registration/app/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/build.gradle b/AndroidPushTutorial/push-demo-server-registration/app/build.gradle
new file mode 100644
index 00000000..87a64d01
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/build.gradle
@@ -0,0 +1,49 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "io.ably.tutorial.push_tutorial_two"
+ minSdkVersion 23
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ Properties properties = new Properties()
+ properties.load(project.rootProject.file('local.properties').newDataInputStream())
+
+ buildConfigField "String", "ABLY_KEY", "\"${properties.getProperty('ably.key')}\""
+ buildConfigField "String", "ABLY_ENV", "\"${properties.getProperty('ably.env')}\""
+ buildConfigField "String", "SERVER_BASE_URL", "\"${properties.getProperty('base.url')}\""
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ //noinspection GradleCompatible
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.android.support:design:28.0.0'
+ implementation 'com.google.code.gson:gson:2.5'
+
+ implementation 'com.google.firebase:firebase-messaging:17.3.4'
+ implementation 'com.google.firebase:firebase-core:16.0.1'
+
+ implementation 'io.ably:ably-android:1.1.0'
+ implementation 'com.squareup.retrofit2:retrofit:2.5.0'
+ implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
+
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+}
+
+/**
+ * Please ensure to add google-services.json file from your FCM Console.
+ */
+apply plugin: 'com.google.gms.google-services'
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/proguard-rules.pro b/AndroidPushTutorial/push-demo-server-registration/app/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/AndroidManifest.xml b/AndroidPushTutorial/push-demo-server-registration/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..c8981e95
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/MainActivity.java b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/MainActivity.java
new file mode 100644
index 00000000..96c85d84
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/MainActivity.java
@@ -0,0 +1,216 @@
+package io.ably.tutorial.push_tutorial_two;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.support.annotation.Nullable;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.google.gson.Gson;
+
+import io.ably.lib.push.LocalDevice;
+import io.ably.lib.realtime.AblyRealtime;
+import io.ably.lib.realtime.ConnectionStateListener;
+import io.ably.lib.types.AblyException;
+import io.ably.lib.types.ClientOptions;
+import io.ably.lib.types.ErrorInfo;
+import io.ably.lib.types.Param;
+import io.ably.lib.util.IntentUtils;
+import io.ably.tutorial.push_tutorial_two.receivers.AblyPushMessagingService;
+import io.ably.tutorial.push_tutorial_two.server.NetResponse;
+import io.ably.tutorial.push_tutorial_two.server.ServerAPI;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+/**
+ * Created by Amit S.
+ */
+public class MainActivity extends AppCompatActivity {
+
+ public static final int SUCCESS = 0;
+ public static final int FAILURE = 1;
+ public static final int UPDATE_LOGS = 2;
+
+ public static final String STEP_1 = "Initialize Ably";
+ public static final String STEP_2 = "Register & Subscribe Channels via Server";
+
+ private static final String PRIVATE_SERVER_AUTH_URL = BuildConfig.SERVER_BASE_URL + "/auth";
+
+ //Broadcast receiver actions
+ public static final String ABLY_PUSH_ACTIVATE_ACTION = "io.ably.broadcast.PUSH_ACTIVATE";
+
+ private TextView rollingLogs;
+ private Button stepsButton;
+ private StringBuilder logs = new StringBuilder();
+ private AblyRealtime ablyRealtime;
+
+
+ private Handler handler = new Handler(new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case SUCCESS:
+ stepsButton.setText((String) msg.obj);
+ stepsButton.setEnabled(true);
+ break;
+ case FAILURE:
+ stepsButton.setEnabled(true);
+ break;
+ case UPDATE_LOGS:
+ rollingLogs.setText(logs.toString());
+ break;
+ }
+ return false;
+ }
+ });
+
+ private BroadcastReceiver pushReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ABLY_PUSH_ACTIVATE_ACTION.equalsIgnoreCase(intent.getAction())) {
+ ErrorInfo error = IntentUtils.getErrorInfo(intent);
+ if (error != null) {
+ logMessage("Error activating push service: " + error);
+ handler.sendMessage(handler.obtainMessage(FAILURE));
+ return;
+ }
+ return;
+ }
+
+ }
+ };
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ rollingLogs = findViewById(R.id.rolling_logs);
+ stepsButton = findViewById(R.id.steps);
+ LocalBroadcastManager.getInstance(this).registerReceiver(pushReceiver, new IntentFilter(ABLY_PUSH_ACTIVATE_ACTION));
+ LocalBroadcastManager.getInstance(this).registerReceiver(pushReceiver, new IntentFilter(AblyPushMessagingService.PUSH_NOTIFICATION_ACTION));
+ }
+
+ public void performAction(View view) {
+ try {
+ Button button = (Button) view;
+ button.setEnabled(false);
+ String step = button.getText().toString();
+ logMessage("Performing Step: " + step);
+ switch (step) {
+ case STEP_1:
+ initAblyRuntime();
+ break;
+ case STEP_2:
+ initAblyPush();
+ break;
+ }
+ } catch (AblyException e) {
+ logMessage("AblyException " + e.getMessage());
+ handler.sendMessage(handler.obtainMessage(FAILURE));
+ }
+ }
+
+ /**
+ * By default we set client ID as the androidID.
+ *
+ * @return
+ */
+ private String getClientId() {
+ String clientId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
+ return clientId;
+ }
+
+ /**
+ * Step 1: Initialize Ably library and perform authentication with private server
+ *
+ * @throws AblyException
+ */
+ private void initAblyRuntime() throws AblyException {
+
+ ClientOptions options = new ClientOptions();
+ options.environment = BuildConfig.ABLY_ENV;
+ options.key = BuildConfig.ABLY_KEY;
+ options.authUrl = PRIVATE_SERVER_AUTH_URL;
+ options.authParams = new Param[]{new Param("clientId", getClientId())};
+
+ ablyRealtime = new AblyRealtime(options);
+ ablyRealtime.setAndroidContext(getApplicationContext());
+ ablyRealtime.connect();
+
+ ablyRealtime.connection.on(new ConnectionStateListener() {
+ @Override
+ public void onConnectionStateChanged(ConnectionStateChange state) {
+ logMessage("Connection state changed to : " + state.current.name());
+ switch (state.current) {
+ case connected:
+ //Go to step 2
+ handler.sendMessage(handler.obtainMessage(SUCCESS, STEP_2));
+ break;
+ case disconnected:
+ case failed:
+ handler.sendMessage(handler.obtainMessage(FAILURE));
+ break;
+ }
+ }
+ });
+ }
+
+ /**
+ * Step 2: Register for Push notification through private server.
+ * We also subscribe for the required channels on Server for the relevant device Id.
+ *
+ * @throws AblyException
+ */
+ private void initAblyPush() throws AblyException {
+ LocalDevice device = ablyRealtime.push.getActivationContext().getLocalDevice();
+ if (device.push.recipient == null) {
+ logMessage("Push not initialized. Please check Firebase settings");
+ return;
+ }
+ String registrationToken = device.push.recipient.get("registrationToken").getAsString();
+ if (registrationToken == null || registrationToken.length() == 0) {
+ logMessage("Registration token cannot be null. Please check Firebase settings");
+ return;
+ }
+ String deviceId = device.id;
+ String clientId = getClientId();
+
+ logMessage("Sending registration Token: " + registrationToken);
+ logMessage("Device ID: " + deviceId);
+ logMessage("Client ID: " + clientId);
+ logMessage("Registering device via Server");
+ logMessage("Subscribing channels");
+
+ ServerAPI.getInstance().api().register(deviceId, registrationToken, clientId).enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ //This is where device is successfully registered via Server.
+ logMessage("Successfully registered: " + new Gson().toJson(response.body()));
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ logMessage("Error registering with server: " + t.getMessage());
+ handler.sendMessage(handler.obtainMessage(FAILURE));
+ }
+ });
+ }
+
+ private void logMessage(String message) {
+ Log.i(MainActivity.class.getSimpleName(), message);
+ logs.append(message);
+ logs.append("\n");
+ handler.sendMessage(handler.obtainMessage(UPDATE_LOGS));
+ }
+}
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/receivers/AblyPushMessagingService.java b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/receivers/AblyPushMessagingService.java
new file mode 100644
index 00000000..09b4a68f
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/receivers/AblyPushMessagingService.java
@@ -0,0 +1,23 @@
+package io.ably.tutorial.push_tutorial_two.receivers;
+
+import android.content.Intent;
+import android.support.v4.content.LocalBroadcastManager;
+
+import com.google.firebase.messaging.FirebaseMessagingService;
+import com.google.firebase.messaging.RemoteMessage;
+
+/**
+ *
+ */
+public class AblyPushMessagingService extends FirebaseMessagingService {
+ public static final String PUSH_NOTIFICATION_ACTION = AblyPushMessagingService.class.getName() + ".PUSH_NOTIFICATION_MESSAGE";
+
+ @Override
+ public void onMessageReceived(RemoteMessage message) {
+ //FCM data is received here.
+
+ Intent intent = new Intent(PUSH_NOTIFICATION_ACTION);
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
+ }
+
+}
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/receivers/AblyPushRegistrationService.java b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/receivers/AblyPushRegistrationService.java
new file mode 100644
index 00000000..78df9262
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/receivers/AblyPushRegistrationService.java
@@ -0,0 +1,15 @@
+package io.ably.tutorial.push_tutorial_two.receivers;
+
+import io.ably.lib.push.AblyFirebaseInstanceIdService;
+
+/**
+ * Leave this empty as the base class AblyFirebaseInstanceIdService does the FCM token registration.
+ * In case your app requires access to FCM token as well, then override io.ably.lib.push.AblyFirebaseInstanceIdService#onTokenRefresh()
+ */
+public class AblyPushRegistrationService extends AblyFirebaseInstanceIdService {
+ @Override
+ public void onTokenRefresh() {
+ //Make sure to call super.onTokenRefresh to initialize Ably push environment.
+ super.onTokenRefresh();
+ }
+}
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/server/NetResponse.java b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/server/NetResponse.java
new file mode 100644
index 00000000..987013fa
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/server/NetResponse.java
@@ -0,0 +1,18 @@
+package io.ably.tutorial.push_tutorial_two.server;
+
+import io.ably.lib.rest.DeviceDetails;
+
+/**
+ * Created by Amit S.
+ *
+ * {"id":"123","platform":"android","formFactor":"phone","clientId":"123",
+ * "metadata":"PST",
+ * "deviceIdentityToken":{"token":"TOKEN","keyName":"KEY_NAME","issued":1551196978541,"expires":1582732978541,"capability":"{}","clientId":"123","deviceId":"123"},"push":{"recipient":{"transportType":"fcm","registrationToken":"123"},"state":"ACTIVE"}}
+ */
+public class NetResponse {
+ public String id;
+ public String platform;
+ public String formFactor;
+ public String clientId;
+ DeviceDetails.Push push;
+}
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/server/ServerAPI.java b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/server/ServerAPI.java
new file mode 100644
index 00000000..d64fccac
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/java/io/ably/tutorial/push_tutorial_two/server/ServerAPI.java
@@ -0,0 +1,45 @@
+package io.ably.tutorial.push_tutorial_two.server;
+
+import io.ably.tutorial.push_tutorial_two.BuildConfig;
+import retrofit2.Call;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+import retrofit2.http.GET;
+import retrofit2.http.Query;
+
+/**
+ * Created by Amit S.
+ */
+public class ServerAPI {
+
+
+ public interface API {
+ @GET("/register")
+ Call register(@Query("deviceId") String deviceId, @Query("registrationToken") String registrationToken, @Query("clientId") String clientId);
+ }
+
+
+ private static ServerAPI instance;
+
+ public static ServerAPI getInstance() {
+ if (instance == null) {
+ instance = new ServerAPI();
+ }
+ return instance;
+ }
+
+ private final API api;
+
+ private ServerAPI() {
+ Retrofit.Builder builder = new Retrofit.Builder();
+ builder.baseUrl(BuildConfig.SERVER_BASE_URL);
+ builder.addConverterFactory(GsonConverterFactory.create());
+
+ Retrofit retrofit = builder.build();
+ api = retrofit.create(API.class);
+ }
+
+ public API api() {
+ return api;
+ }
+}
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..1f6bb290
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/drawable/ic_launcher_background.xml b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..0d025f9b
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/layout/activity_main.xml b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..828ac425
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-hdpi/ic_launcher.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..898f3ed5
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..dffca360
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-mdpi/ic_launcher.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..64ba76f7
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..dae5e082
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..e5ed4659
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..14ed0af3
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..b0907cac
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..d8ae0315
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..2c18de9e
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..beed3cdd
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/values/colors.xml b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..69b22338
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/values/strings.xml b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..6d8003fb
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Ably Push Tutorial
+
diff --git a/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/values/styles.xml b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..5885930d
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/AndroidPushTutorial/push-demo-server-registration/build.gradle b/AndroidPushTutorial/push-demo-server-registration/build.gradle
new file mode 100644
index 00000000..af6d850f
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.3.0'
+ classpath 'com.google.gms:google-services:4.2.0'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/AndroidPushTutorial/push-demo-server-registration/gradle.properties b/AndroidPushTutorial/push-demo-server-registration/gradle.properties
new file mode 100644
index 00000000..82618cec
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/gradle.properties
@@ -0,0 +1,15 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+
diff --git a/AndroidPushTutorial/push-demo-server-registration/gradle/wrapper/gradle-wrapper.jar b/AndroidPushTutorial/push-demo-server-registration/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..f6b961fd
Binary files /dev/null and b/AndroidPushTutorial/push-demo-server-registration/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/AndroidPushTutorial/push-demo-server-registration/gradle/wrapper/gradle-wrapper.properties b/AndroidPushTutorial/push-demo-server-registration/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..26def355
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Apr 04 10:35:22 IST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
diff --git a/AndroidPushTutorial/push-demo-server-registration/gradlew b/AndroidPushTutorial/push-demo-server-registration/gradlew
new file mode 100755
index 00000000..cccdd3d5
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/AndroidPushTutorial/push-demo-server-registration/gradlew.bat b/AndroidPushTutorial/push-demo-server-registration/gradlew.bat
new file mode 100644
index 00000000..e95643d6
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/AndroidPushTutorial/push-demo-server-registration/settings.gradle b/AndroidPushTutorial/push-demo-server-registration/settings.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/AndroidPushTutorial/push-demo-server-registration/settings.gradle
@@ -0,0 +1 @@
+include ':app'