Commit d6e68f5c authored by Amit Davidi's avatar Amit Davidi

Notification opening (ProxyActivity)

parent 9aa75961
...@@ -5,7 +5,7 @@ android { ...@@ -5,7 +5,7 @@ android {
buildToolsVersion "24.0.1" buildToolsVersion "24.0.1"
defaultConfig { defaultConfig {
applicationId "com.wix.reactnativenotifications" applicationId "com.wix.reactnativenotifications.app"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 23 targetSdkVersion 23
versionCode 1 versionCode 1
......
...@@ -15,3 +15,6 @@ ...@@ -15,3 +15,6 @@
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *; # public *;
#} #}
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
...@@ -3,27 +3,33 @@ ...@@ -3,27 +3,33 @@
package="com.wix.reactnativenotifications.app"> package="com.wix.reactnativenotifications.app">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:name=".MainApplication"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<meta-data
<meta-data android:name="com.wix.reactnativenotifications.gcmSenderId" android:value="434691868895\0"/> android:name="com.wix.reactnativenotifications.gcmSenderId"
android:value="434691868895\0"/>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/app_name" android:label="@string/app_name">
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ChildActivity"
android:label="Child Activity">
<meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.wix.reactnativenotifications.app.MainActivity"/>
</activity>
</application> </application>
</manifest> </manifest>
\ No newline at end of file
package com.wix.reactnativenotifications.app;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class ChildActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_child);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public void onBackPressed() {
super.onBackPressed();
finish();
}
}
package com.wix.reactnativenotifications.app; package com.wix.reactnativenotifications.app;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.Toolbar;
import com.facebook.react.LifecycleState; import com.facebook.react.ReactActivity;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView; import com.facebook.react.ReactRootView;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.shell.MainReactPackage;
import com.wix.reactnativenotifications.RNNotificationsPackage;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { public class MainActivity extends ReactActivity {
private static final int OVERLAY_PERMISSION_REQ_CODE = 1234; private static final int OVERLAY_PERMISSION_REQ_CODE = 1234;
private ReactRootView mReactRootView; private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ViewGroup layout = (ViewGroup) getLayoutInflater().inflate(R.layout.activity_main, null);
Toolbar toolbar = (Toolbar) layout.findViewById(R.id.toolbar);
setActionBar(toolbar);
mReactRootView = new ReactRootView(this); mReactRootView = new ReactRootView(this);
mReactInstanceManager = ReactInstanceManager.builder() layout.addView(mReactRootView);
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle") setContentView(layout);
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.addPackage(new RNNotificationsPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
if (SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) { if (SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
...@@ -50,9 +43,22 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand ...@@ -50,9 +43,22 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
} }
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add("Child Activity").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
Intent intent = new Intent(MainActivity.this, ChildActivity.class);
MainActivity.this.startActivity(intent);
return true;
}
});
return super.onCreateOptionsMenu(menu);
}
@TargetApi(Build.VERSION_CODES.M) @TargetApi(Build.VERSION_CODES.M)
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) { if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (Settings.canDrawOverlays(this)) { if (Settings.canDrawOverlays(this)) {
startReactApplication(); startReactApplication();
...@@ -62,40 +68,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand ...@@ -62,40 +68,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
} }
} }
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
}
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
private void startReactApplication() { private void startReactApplication() {
mReactRootView.startReactApplication(mReactInstanceManager, "WixRNNotifications", null); mReactRootView.startReactApplication(getReactInstanceManager(), "WixRNNotifications", null);
setContentView(mReactRootView);
} }
} }
package com.wix.reactnativenotifications.app;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.wix.reactnativenotifications.RNNotificationsPackage;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.asList(
new MainReactPackage(),
new RNNotificationsPackage()
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_child"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.wix.reactnativenotifications.app.ChildActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="By default, when opening a notification, the entire activity back-stack will be restored as-is!\n(instead of launching or relaunching the main activity)"
android:layout_centerInParent="true"
/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.wix.reactnativenotifications.app.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
<resources> <resources>
<string name="app_name">Wix RN Notifications</string> <string name="app_name">Wix RN Notifications</string>
<string name="action_settings">Settings</string>
</resources> </resources>
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
......
...@@ -10,10 +10,16 @@ ...@@ -10,10 +10,16 @@
android:name="${applicationId}.permission.C2D_MESSAGE" android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" /> android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" /> <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<application> <application>
<!--
A proxy-activity that either launches the main activity or resumes the currently running app.
Required in order to keep the initial intent so it could be retrieved by the JS client.
Note: we declare it in its own private affinity so that a running app stack wouldn't shadow its creation.
-->
<activity android:name=".core.ProxyActivity" android:taskAffinity="com.wix.reactnativenotifications.core.ProxyActivity"/>
<!-- <!--
Google's ready-to-use GcmReceiver. Google's ready-to-use GcmReceiver.
1. Awaits actual GCM messages (e.g. push notifications) and invokes the GCM service with the concrete content. 1. Awaits actual GCM messages (e.g. push notifications) and invokes the GCM service with the concrete content.
...@@ -31,7 +37,7 @@ ...@@ -31,7 +37,7 @@
</receiver> </receiver>
<!-- Dispatched by the GcmReceiver when messages are received. --> <!-- Dispatched by the GcmReceiver when messages are received. -->
<service <service
android:name="com.wix.reactnativenotifications.GcmMessageHandlerService" android:name="com.wix.reactnativenotifications.gcm.GcmMessageHandlerService"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" /> <action android:name="com.google.android.c2dm.intent.RECEIVE" />
...@@ -39,14 +45,14 @@ ...@@ -39,14 +45,14 @@
</service> </service>
<!-- Dispatched by the GcmReceiver. Starts the designated refresh-handling service. --> <!-- Dispatched by the GcmReceiver. Starts the designated refresh-handling service. -->
<service <service
android:name="com.wix.reactnativenotifications.GcmInstanceIdListenerService" android:name=".gcm.GcmInstanceIdListenerService"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID" /> <action android:name="com.google.android.gms.iid.InstanceID" />
</intent-filter> </intent-filter>
</service> </service>
<service <service
android:name="com.wix.reactnativenotifications.GcmInstanceIdRefreshHandlerService" android:name=".gcm.GcmInstanceIdRefreshHandlerService"
android:exported="false" /> android:exported="false" />
</application> </application>
......
package com.wix.reactnativenotifications; package com.wix.reactnativenotifications;
/*package*/ interface Defs { public interface Defs {
String LOGTAG = "ReactNativeNotifs"; String LOGTAG = "ReactNativeNotifs";
String GCM_SENDER_ID_ATTR_NAME = "com.wix.reactnativenotifications.gcmSenderId"; String GCM_SENDER_ID_ATTR_NAME = "com.wix.reactnativenotifications.gcmSenderId";
String TOKEN_RECEIVED_EVENT_NAME = "remoteNotificationsRegistered"; String TOKEN_RECEIVED_EVENT_NAME = "remoteNotificationsRegistered";
String NOTIFICATION_RECEIVED_EVENT_NAME = "notificationReceived";
String NOTIFICATION_OPENED_EVENT_NAME = "notificationOpened";
} }
package com.wix.reactnativenotifications;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
import com.google.android.gms.gcm.GcmListenerService;
import static com.wix.reactnativenotifications.Defs.LOGTAG;
public class GcmMessageHandlerService extends GcmListenerService {
@Override
public void onMessageReceived(String s, Bundle bundle) {
Log.d(LOGTAG, "New message from GCM: " + bundle);
final Bundle notificationBundle = bundle.getBundle("notification");
final Bundle dataBundle = bundle.getBundle("data");
if (notificationBundle == null) {
return;
}
final String title = notificationBundle.getString("title");
final String body = notificationBundle.getString("body");
if (title == null || title.trim().isEmpty() || body == null || body.trim().isEmpty()) {
return;
}
final PendingIntent pendingIntent = getCTAPendingIntent(notificationBundle, dataBundle);
final Notification notification = buildNotification(title, body, pendingIntent);
postNotification((int) System.currentTimeMillis(), notification);
}
protected PendingIntent getCTAPendingIntent(Bundle notificationBundle, Bundle dataBundle) {
final Context appContext = getApplicationContext();
final Intent helperIntent = getPackageManager().getLaunchIntentForPackage(getPackageName());
final Intent intent;
try {
intent = new Intent(appContext, Class.forName(helperIntent.getComponent().getClassName()));
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("notification.meta", notificationBundle);
intent.putExtra("notification.data", dataBundle);
return PendingIntent.getActivity(appContext, 0, intent, 0);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
protected Notification buildNotification(String title, String body, PendingIntent intent) {
final Context appContext = getApplicationContext();
final Notification.Builder notificationBuilder = new Notification.Builder(appContext)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(R.drawable.notification)
.setContentIntent(intent)
.setDefaults(Notification.DEFAULT_ALL)
.setAutoCancel(true);
final Notification notification = notificationBuilder.build();
return notification;
}
protected void postNotification(int id, Notification notification) {
final Context appContext = getApplicationContext();
final NotificationManager notificationManager = (NotificationManager) appContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(id, notification);
}
protected Activity getCurrentActivity() {
final ReactApplicationContext reactContext = RNNotificationsContextHolder.getReactContext();
if (reactContext == null) {
return null;
}
return reactContext.getCurrentActivity();
}
}
package com.wix.reactnativenotifications;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
public class RNNotificationsContextHolder extends ReactContextBaseJavaModule {
private static RNNotificationsContextHolder sInstance;
public static RNNotificationsContextHolder createInstance(ReactApplicationContext reactContext) {
if (sInstance == null) {
sInstance = new RNNotificationsContextHolder(reactContext);
}
return sInstance;
}
public static ReactApplicationContext getReactContext() {
return (sInstance == null ? null : sInstance.getReactApplicationContext());
}
private RNNotificationsContextHolder(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "RNNotificationsContextHolder";
}
}
...@@ -5,6 +5,7 @@ import com.facebook.react.bridge.JavaScriptModule; ...@@ -5,6 +5,7 @@ import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.ViewManager;
import com.wix.reactnativenotifications.core.RNNotificationsModule;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -14,9 +15,7 @@ public class RNNotificationsPackage implements ReactPackage { ...@@ -14,9 +15,7 @@ public class RNNotificationsPackage implements ReactPackage {
@Override @Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList( return Arrays.<NativeModule>asList(new RNNotificationsModule(reactContext));
RNNotificationsContextHolder.createInstance(reactContext),
new RNNotificationsModule(reactContext));
} }
@Override @Override
......
package com.wix.reactnativenotifications.core;
public class InitialNotificationStore {
private static PushNotificationProps sInitialNotif;
public static void setInitialNotification(PushNotificationProps pushNotificationProps) {
sInitialNotif = pushNotificationProps;
}
public static PushNotificationProps getInitialNotification() {
return sInitialNotif;
}
}
package com.wix.reactnativenotifications.core;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class NotificationIntentAdapter {
private static final int PENDING_INTENT_CODE = 0;
private static final String PUSH_NOTIFICATION_EXTRA_NAME = "pushNotification";
public static PendingIntent createPendingNotificationIntent(Context appContext, Intent intent, PushNotificationProps notification) {
intent.putExtra(PUSH_NOTIFICATION_EXTRA_NAME, notification.asBundle());
return PendingIntent.getActivity(appContext, PENDING_INTENT_CODE, intent, 0);
}
public static Bundle extractPendingNotificationDataFromIntent(Intent intent) {
return intent.getBundleExtra(PUSH_NOTIFICATION_EXTRA_NAME);
}
}
package com.wix.reactnativenotifications.core;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import static com.wix.reactnativenotifications.Defs.LOGTAG;
/**
* The front-end (hidden) activity for handling notification <b>opening</b> actions triggered by the
* device user (i.e. upon tapping on them in the notifications drawer).
*/
public class ProxyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(LOGTAG, "ProxyActivity.onCreate, " + getIntent().getExtras());
final Bundle rawNotificationData = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(getIntent());
if (rawNotificationData != null) {
new PushNotification(this, rawNotificationData).onOpened(this);
}
finish();
}
}
package com.wix.reactnativenotifications.core;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import static com.wix.reactnativenotifications.Defs.LOGTAG;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_OPENED_EVENT_NAME;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
public class PushNotification {
public static class InvalidNotificationException extends Exception {
public InvalidNotificationException(String detailMessage) {
super(detailMessage);
}
}
private final Context mAppContext;
private PushNotificationProps mNotificationProps;
public PushNotification(Context context, Bundle bundle) {
mAppContext = context.getApplicationContext();
mNotificationProps = new PushNotificationProps(bundle);
}
public void onReceived() throws InvalidNotificationException {
postNotification();
notifyReceivedToJS();
}
public void onOpened(Activity hostActivity) {
digestNotification(hostActivity);
}
public PushNotificationProps asProps() {
return mNotificationProps.copy();
}
protected void postNotification() {
final PendingIntent pendingIntent = getCTAPendingIntent();
final Notification notification = buildNotification(pendingIntent);
postNotification((int) System.currentTimeMillis(), notification);
}
protected void digestNotification(Activity activity) {
final ReactContext reactContext = getRunningReactContext();
if (reactContext != null) {
launchOrResumeApp(activity);
notifyOpenedToJS(reactContext);
} else {
InitialNotificationStore.setInitialNotification(mNotificationProps);
launchOrResumeApp(activity);
}
}
protected PendingIntent getCTAPendingIntent() {
// Note: we launch the proxy activity, assuming it'll take it from there.
final Intent cta = new Intent(mAppContext, ProxyActivity.class);
return NotificationIntentAdapter.createPendingNotificationIntent(mAppContext, cta, mNotificationProps);
}
protected Notification buildNotification(PendingIntent intent) {
final Notification.Builder notificationBuilder = new Notification.Builder(mAppContext)
.setContentTitle(mNotificationProps.getTitle())
.setContentText(mNotificationProps.getBody())
.setSmallIcon(mAppContext.getApplicationInfo().icon)
.setContentIntent(intent)
.setDefaults(Notification.DEFAULT_ALL)
.setAutoCancel(true);
return notificationBuilder.build();
}
protected void postNotification(int id, Notification notification) {
final NotificationManager notificationManager = (NotificationManager) mAppContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(id, notification);
}
protected ReactContext getRunningReactContext() {
final ReactNativeHost rnHost = ((ReactApplication) mAppContext).getReactNativeHost();
if (!rnHost.hasInstance()) {
return null;
}
final ReactInstanceManager instanceManager = ((ReactApplication) mAppContext).getReactNativeHost().getReactInstanceManager();
final ReactContext reactContext = instanceManager.getCurrentReactContext();
if (reactContext == null || !reactContext.hasActiveCatalystInstance()) {
return null;
}
return reactContext;
}
private void notifyReceivedToJS() {
notifyJS(NOTIFICATION_RECEIVED_EVENT_NAME, null);
}
private void notifyOpenedToJS() {
notifyOpenedToJS(null);
}
private void notifyOpenedToJS(ReactContext reactContext) {
notifyJS(NOTIFICATION_OPENED_EVENT_NAME, reactContext);
}
private void notifyJS(String eventName, ReactContext reactContext) {
if (reactContext == null) {
reactContext = getRunningReactContext();
}
if (reactContext != null) {
final WritableMap notificationAsMap = Arguments.fromBundle(mNotificationProps.asBundle());
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, notificationAsMap);
}
}
protected void launchOrResumeApp(Activity activity) {
// The desired behavior upon notification opening is as follows:
// - If app is in foreground (and possibly has several activities in stack), simply keep it as-is in foreground.
// - If app is in background, bring it to foreground as-is (activity stack untampered).
// A distinction is made in this case such that if app went to back due to *back-button*, is should be recreated.
// - If app is dead, launch it through the main activity (as Android launchers do).
// THIS IS EXACTLY THE SAME AS ANDROID LAUNCHERS WORK. So, we use the same configuration (action, categories and
// flags) as they do.
final Intent helperIntent = mAppContext.getPackageManager().getLaunchIntentForPackage(mAppContext.getPackageName());
try {
final Intent intent = Intent.makeMainActivity(new ComponentName(mAppContext, Class.forName(helperIntent.getComponent().getClassName())));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
activity.startActivity(intent);
} catch (ClassNotFoundException e) {
Log.e(LOGTAG, "Failed to launch/resume app", e);
}
}
}
package com.wix.reactnativenotifications.core;
import android.os.Bundle;
/**
* @author amitd
*/
public class PushNotificationProps {
private Bundle mBundle;
public PushNotificationProps(Bundle bundle) {
final String title = bundle.getString("title");
final String body = bundle.getString("body");
if (title == null || title.trim().isEmpty() || body == null || body.trim().isEmpty()) {
throw new IllegalArgumentException("Invalid notification");
}
mBundle = bundle;
}
public String getTitle() {
return mBundle.getString("title");
}
public String getBody() {
return mBundle.getString("body");
}
public Bundle asBundle() {
return (Bundle) mBundle.clone();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(1024);
for (String key : mBundle.keySet()) {
sb.append(key).append("=").append(mBundle.get(key)).append(", ");
}
return sb.toString();
}
protected PushNotificationProps copy() {
return new PushNotificationProps((Bundle) mBundle.clone());
}
}
package com.wix.reactnativenotifications; package com.wix.reactnativenotifications.core;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log; import android.util.Log;
import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.wix.reactnativenotifications.gcm.GcmInstanceIdRefreshHandlerService;
import static com.wix.reactnativenotifications.Defs.LOGTAG; import static com.wix.reactnativenotifications.Defs.LOGTAG;
public class RNNotificationsModule extends ReactContextBaseJavaModule implements ActivityEventListener { public class RNNotificationsModule extends ReactContextBaseJavaModule {
public RNNotificationsModule(ReactApplicationContext reactContext) { public RNNotificationsModule(ReactApplicationContext reactContext) {
super(reactContext); super(reactContext);
...@@ -19,7 +21,7 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements ...@@ -19,7 +21,7 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
@Override @Override
public String getName() { public String getName() {
return "WixRNNotification"; return "WixRNNotifications";
} }
@Override @Override
...@@ -28,16 +30,21 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements ...@@ -28,16 +30,21 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
final Context appContext = getReactApplicationContext().getApplicationContext(); final Context appContext = getReactApplicationContext().getApplicationContext();
final Intent tokenFetchIntent = new Intent(appContext, GcmInstanceIdRefreshHandlerService.class); final Intent tokenFetchIntent = new Intent(appContext, GcmInstanceIdRefreshHandlerService.class);
appContext.startService(tokenFetchIntent); appContext.startService(tokenFetchIntent);
getReactApplicationContext().addActivityEventListener(this);
} }
@Override @ReactMethod
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { public void getInitialNotification(final Promise promise) {
} Object result = null;
@Override try {
public void onNewIntent(Intent intent) { final PushNotificationProps notification = InitialNotificationStore.getInitialNotification();
Log.e(LOGTAG, "New intent: "+intent); if (notification == null) {
return;
}
result = Arguments.fromBundle(notification.asBundle());
} finally {
promise.resolve(result);
}
} }
} }
package com.wix.reactnativenotifications; package com.wix.reactnativenotifications.gcm;
import android.app.IntentService; import android.app.IntentService;
import android.content.Intent; import android.content.Intent;
...@@ -6,7 +6,9 @@ import android.content.pm.ApplicationInfo; ...@@ -6,7 +6,9 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.util.Log; import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.google.android.gms.gcm.GoogleCloudMessaging; import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID; import com.google.android.gms.iid.InstanceID;
...@@ -66,10 +68,11 @@ public class GcmInstanceIdRefreshHandlerService extends IntentService { ...@@ -66,10 +68,11 @@ public class GcmInstanceIdRefreshHandlerService extends IntentService {
} }
protected void notifyTokenEvent(String registrationToken) { protected void notifyTokenEvent(String registrationToken) {
final ReactApplicationContext reactContext = RNNotificationsContextHolder.getReactContext(); final ReactInstanceManager instanceManager = ((ReactApplication) getApplication()).getReactNativeHost().getReactInstanceManager();
final ReactContext reactContext = instanceManager.getCurrentReactContext();
// Note: Cannot assume react-context exists cause this is an async dispatched service. // Note: Cannot assume react-context exists cause this is an async dispatched service.
if (reactContext != null) { if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(TOKEN_RECEIVED_EVENT_NAME, registrationToken); reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(TOKEN_RECEIVED_EVENT_NAME, registrationToken);
} }
} }
......
package com.wix.reactnativenotifications.gcm;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.gcm.GcmListenerService;
import com.wix.reactnativenotifications.core.PushNotification;
import static com.wix.reactnativenotifications.Defs.LOGTAG;
public class GcmMessageHandlerService extends GcmListenerService {
@Override
public void onMessageReceived(String s, Bundle bundle) {
Log.d(LOGTAG, "New message from GCM: " + bundle);
try {
final PushNotification notification = new PushNotification(getApplicationContext(), bundle);
notification.onReceived();
} catch (PushNotification.InvalidNotificationException e) {
// A GCM message, yes - but not the kind we know how to work with.
Log.v(LOGTAG, "GCM message handling aborted", e);
}
}
}
<resources>
<string name="app_name">lib</string>
</resources>
...@@ -4,11 +4,11 @@ import json ...@@ -4,11 +4,11 @@ import json
import sys import sys
API_KEY = "AIzaSyBVtqdO_SgPVhhXnyNGC_VXSbIX-fxk1YY" API_KEY = "AIzaSyBVtqdO_SgPVhhXnyNGC_VXSbIX-fxk1YY"
TOKEN = "eB6llQJLpw0:APA91bG3t07UNFyz_NPmGjgTZ8-tAUzwKmBJctKm2qpw973c-3vEINtTC3nVl89uJNv-l2LHzfd7fSmVPjeaVAQBE8tC9Pp2X5foteVMuBlHEiB5cznXqnP5RiCroGo1DdBIdWzBMwHW" TOKEN = "c70LL7cNR-0:APA91bFja9qHDqLFtKYGCKNZRBcgHAQrmcPE-XNIIctnFnsEKJml9tgbuQql15ORoJc4drw6fAA38_JFx1X3s3wF9FlArPO_laPTXmaCC4_ZFm1QzfgmoJ5hXUhDFsArJpTVFIonEOHR"
data={ data={
"to": TOKEN, "to": TOKEN,
"notification" : { "data" : {
"body": "A very tiny one", "body": "A very tiny one",
"title": "A tiny notification" "title": "A tiny notification"
} }
......
...@@ -5,9 +5,12 @@ import { ...@@ -5,9 +5,12 @@ import {
AppRegistry, AppRegistry,
StyleSheet, StyleSheet,
Text, Text,
View View,
TouchableHighlight
} from 'react-native'; } from 'react-native';
import {NotificationsAndroid, PendingNotifications} from './notifications';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
...@@ -21,13 +24,52 @@ const styles = StyleSheet.create({ ...@@ -21,13 +24,52 @@ const styles = StyleSheet.create({
}); });
class MainComponent extends Component { class MainComponent extends Component {
constructor(props) {
super(props);
this.state = {
elapsed: 0,
lastNotification: undefined
}
}
componentWillMount() {
NotificationsAndroid.setRegistrationTokenUpdateListener(this.onPushRegistered.bind(this));
NotificationsAndroid.setNotificationOpenedListener(this.onNotificationOpened.bind(this));
NotificationsAndroid.setNotificationReceivedListener(this.onNotificationReceived.bind(this));
setInterval(this.onTick.bind(this), 1000);
}
onTick() {
this.setState({elapsed: this.state.elapsed + 1});
}
render() { render() {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.mainText}>Wix React Native Notifications</Text> <TouchableHighlight onPress={() => {console.log("Touch")}}>
<Text style={styles.mainText}>Wix React Native Notifications</Text>
</TouchableHighlight>
<Text style={styles.mainText}>Last notification:</Text>
<Text style={styles.mainText}>{this.state.lastNotification ? this.state.lastNotification.body + ` (opened at ''${this.state.notificationRxTime})` : "N/A"}</Text>
<Text style={styles.mainText}>Time elapsed: {this.state.elapsed}</Text>
</View> </View>
) )
} }
onPushRegistered() {
}
onNotificationOpened(notification) {
console.log("onNotificationOpened: ", notification);
this.setState({lastNotification: notification.getData(), notificationRxTime: this.state.elapsed});
}
onNotificationReceived(notification) {
console.log("onNotificationReceived: ", notification);
}
} }
AppRegistry.registerComponent('WixRNNotifications', () => MainComponent); AppRegistry.registerComponent('WixRNNotifications', () => MainComponent);
require('react');
import {NativeModules, DeviceEventEmitter} from 'react-native';
const RNNotifications = NativeModules.WixRNNotification;
let notificationReceivedListener;
let notificationOpenedListener;
let registrationTokenUpdateListener;
/** A wrapper to align Android with iOS in terms on notification structure. */
class NotificationAndroid {
constructor(notification) {
this.data = notification;
}
getData() {
return this.data;
}
getTitle() {
return this.data.title;
}
getMessage() {
return this.data.body;
}
}
export class NotificationsAndroid {
static setRegistrationTokenUpdateListener(listener) {
registrationTokenUpdateListener = DeviceEventEmitter.addListener('remoteNotificationsRegistered', listener);
}
static clearRegistrationTokenUpdateListener() {
if (registrationTokenUpdateListener) {
registrationTokenUpdateListener.remove();
registrationTokenUpdateListener = null;
}
}
static setNotificationOpenedListener(listener) {
notificationOpenedListener = DeviceEventEmitter.addListener('notificationOpened', (notification) => listener(new NotificationAndroid(notification)));
}
static setNotificationReceivedListener(listener) {
notificationReceivedListener = DeviceEventEmitter.addListener('notificationReceived', (notification) => listener(new NotificationAndroid(notification)));
}
static clearNotificationOpenedListener() {
if (notificationOpenedListener) {
notificationOpenedListener.remove();
notificationOpenedListener = null;
}
}
static clearNotificationReceivedListener() {
if (notificationReceivedListener) {
notificationReceivedListener.remove();
notificationReceivedListener = null;
}
}
}
export class PendingNotifications {
static async getInitialNotification() {
return new NotificationAndroid(await RNNotifications.getInitialNotification());
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment