Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android overhaul (includes important bug fixes and new functionality) #71

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 134 additions & 74 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ android {

dependencies {
// Google's GCM.
compile 'com.google.android.gms:play-services-gcm:10.0.1'
compile "com.google.firebase:firebase-messaging:10.2.6"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which classpath are you using to match this with 10.2.6?

classpath "com.google.gms:google-services:3.0.0" ? its crashing when using 3.1.0 (unsure why yet)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah saw your note. Mine was crashing unless I included as follows:

    compile 'com.google.android.gms:play-services-base:11.0.4'
    compile(project(':react-native-notifications')) {
        exclude group: 'com.google.firebase', module: 'firebase-messaging'
    }
    compile 'com.google.firebase:firebase-messaging:11.0.4'


compile 'com.facebook.react:react-native:+'

Expand Down
37 changes: 6 additions & 31 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wix.reactnativenotifications">

<!--
Permissions required for enabling GCM.
-->
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />

<!-- Ref: http://stackoverflow.com/questions/13602190/java-lang-securityexception-requires-vibrate-permission-on-jelly-bean-4-2 -->
<uses-permission android:name="android.permission.VIBRATE" android:maxSdkVersion="18" />

Expand All @@ -19,41 +11,24 @@
<!--
A proxy-service that gives the library an opportunity to do some work before launching/resuming the actual application task.
-->
<service android:name=".core.ProxyService"/>
<service android:name=".core.LocalNotificationService"/>

<!--
Google's ready-to-use GcmReceiver.
1. Awaits actual GCM messages (e.g. push notifications) and invokes the GCM service with the concrete content.
2. Awaits instance-ID/token refresh requests from the GCM and invokes the Instance-ID listener service.
-->
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
<!-- Dispatched by the GcmReceiver when messages are received. -->
<service
android:name="com.wix.reactnativenotifications.gcm.GcmMessageHandlerService"
android:exported="false">
android:name="com.wix.reactnativenotifications.fcm.FcmMessageHandlerService">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- Dispatched by the GcmReceiver. Starts the designated refresh-handling service. -->
<service
android:name=".gcm.GcmInstanceIdListenerService"
android:exported="false">
android:name=".fcm.FcmInstanceIdListenerService">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID" />
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service
android:name=".gcm.GcmInstanceIdRefreshHandlerService"
android:name=".fcm.FcmTokenService"
android:exported="false" />
</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

public interface Defs {
String LOGTAG = "ReactNativeNotifs";
String GCM_SENDER_ID_ATTR_NAME = "com.wix.reactnativenotifications.gcmSenderId";

String TOKEN_RECEIVED_EVENT_NAME = "remoteNotificationsRegistered";
String TOKEN_RECEIVED_EVENT_NAME = "com.wix.reactnativenotifications.remoteNotificationsRegistered";

String NOTIFICATION_RECEIVED_EVENT_NAME = "notificationReceived";
String NOTIFICATION_OPENED_EVENT_NAME = "notificationOpened";
String NOTIFICATION_RECEIVED_EVENT_NAME = "com.wix.reactnativenotifications.notificationReceived";
String NOTIFICATION_OPENED_EVENT_NAME = "com.wix.reactnativenotifications.notificationOpened";
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;

import com.facebook.react.bridge.Arguments;
Expand All @@ -16,13 +17,14 @@
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.AppLifecycleFacadeHolder;
import com.wix.reactnativenotifications.core.InitialNotificationHolder;
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.wix.reactnativenotifications.core.ReactAppLifecycleFacade;
import com.wix.reactnativenotifications.core.notification.IPushNotification;
import com.wix.reactnativenotifications.core.notification.PushNotification;
import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer;
import com.wix.reactnativenotifications.core.notificationdrawer.PushNotificationsDrawer;
import com.wix.reactnativenotifications.gcm.GcmInstanceIdRefreshHandlerService;
import com.wix.reactnativenotifications.core.notificationdrawer.INotificationDrawer;
import com.wix.reactnativenotifications.core.notificationdrawer.NotificationDrawer;
import com.wix.reactnativenotifications.core.notifications.ILocalNotification;
import com.wix.reactnativenotifications.core.notifications.LocalNotification;
import com.wix.reactnativenotifications.core.notifications.NotificationProps;
import com.wix.reactnativenotifications.fcm.FcmTokenService;

import static com.wix.reactnativenotifications.Defs.LOGTAG;

Expand All @@ -46,16 +48,20 @@ public String getName() {
@Override
public void initialize() {
Log.d(LOGTAG, "Native module init");
startGcmIntentService(GcmInstanceIdRefreshHandlerService.EXTRA_IS_APP_INIT);

final IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
final INotificationDrawer notificationsDrawer = NotificationDrawer.get(getReactApplicationContext().getApplicationContext());
notificationsDrawer.onAppInit();
}

@ReactMethod
public void refreshToken() {
Log.d(LOGTAG, "Native method invocation: refreshToken()");
startGcmIntentService(GcmInstanceIdRefreshHandlerService.EXTRA_MANUAL_REFRESH);
startTokenService(FcmTokenService.ACTION_REFRESH_TOKEN);
}

@ReactMethod
public void invalidateToken() {
Log.d(LOGTAG, "Native method invocation: invalidateToken()");
startTokenService(FcmTokenService.ACTION_INVALIDATE_TOKEN);
}

@ReactMethod
Expand All @@ -64,7 +70,7 @@ public void getInitialNotification(final Promise promise) {
Object result = null;

try {
final PushNotificationProps notification = InitialNotificationHolder.getInstance().get();
final NotificationProps notification = InitialNotificationHolder.getInstance().get();
if (notification == null) {
return;
}
Expand All @@ -76,22 +82,35 @@ public void getInitialNotification(final Promise promise) {
}

@ReactMethod
public void postLocalNotification(ReadableMap notificationPropsMap, int notificationId) {
public void postLocalNotification(ReadableMap propsMap, int notificationId) {
Log.d(LOGTAG, "Native method invocation: postLocalNotification");
final Bundle notificationProps = Arguments.toBundle(notificationPropsMap);
final IPushNotification pushNotification = PushNotification.get(getReactApplicationContext().getApplicationContext(), notificationProps);
pushNotification.onPostRequest(notificationId);
final Context context = getReactApplicationContext().getApplicationContext();
final NotificationProps localNotificationProps = NotificationProps.fromBundle(context, Arguments.toBundle(propsMap));
final ILocalNotification notification = LocalNotification.get(context, localNotificationProps);
notification.post(notificationId);
}

@ReactMethod
public void cancelLocalNotification(int notificationId, @Nullable String notificationTag) {
INotificationDrawer notificationsDrawer = NotificationDrawer.get(getReactApplicationContext().getApplicationContext());
notificationsDrawer.onCancelLocalNotification(notificationTag, notificationId);
}

@ReactMethod
public void cancelAllLocalNotifications() {
INotificationDrawer notificationDrawer = NotificationDrawer.get(getReactApplicationContext().getApplicationContext());
notificationDrawer.onCancelAllLocalNotifications();
}

@ReactMethod
public void cancelLocalNotification(int notificationId) {
IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
notificationsDrawer.onNotificationClearRequest(notificationId);
public void consumeBackgroundQueue() {
final JsIOHelper jsIOHelper = new JsIOHelper(getReactApplicationContext().getApplicationContext());
jsIOHelper.consumeBackgroundQueue();
}

@Override
public void onAppVisible() {
final IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
final INotificationDrawer notificationsDrawer = NotificationDrawer.get(getReactApplicationContext().getApplicationContext());
notificationsDrawer.onAppVisible();
}

Expand All @@ -101,7 +120,7 @@ public void onAppNotVisible() {

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
final IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
final INotificationDrawer notificationsDrawer = NotificationDrawer.get(getReactApplicationContext().getApplicationContext());
notificationsDrawer.onNewActivity(activity);
}

Expand Down Expand Up @@ -129,10 +148,10 @@ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
public void onActivityDestroyed(Activity activity) {
}

protected void startGcmIntentService(String extraFlag) {
protected void startTokenService(String action) {
final Context appContext = getReactApplicationContext().getApplicationContext();
final Intent tokenFetchIntent = new Intent(appContext, GcmInstanceIdRefreshHandlerService.class);
tokenFetchIntent.putExtra(extraFlag, true);
appContext.startService(tokenFetchIntent);
final Intent intent = new Intent(appContext, FcmTokenService.class);
intent.setAction(action);
appContext.startService(intent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactConte
return Arrays.<NativeModule>asList(new RNNotificationsModule(mApplication, reactContext));
}

@Override
// Deprecated RN 0.47
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

import com.wix.reactnativenotifications.core.notifications.IntentExtras;

public class AppLaunchHelper {
private static final String TAG = AppLaunchHelper.class.getSimpleName();

private static final String LAUNCH_FLAG_KEY_NAME = "launchedFromNotification";

public Intent getLaunchIntent(Context appContext) {
try {
// The desired behavior upon notification opening is as follows:
Expand All @@ -23,7 +24,7 @@ public Intent getLaunchIntent(Context appContext) {
final Intent helperIntent = appContext.getPackageManager().getLaunchIntentForPackage(appContext.getPackageName());
final Intent intent = new Intent(appContext, Class.forName(helperIntent.getComponent().getClassName()));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
intent.putExtra(LAUNCH_FLAG_KEY_NAME, true);
intent.putExtra(IntentExtras.FCM_PREFIX, true);
return intent;
} catch (ClassNotFoundException e) {
// Note: this is an imaginary scenario cause we're asking for a class of our very own package.
Expand All @@ -40,6 +41,25 @@ public boolean isLaunchIntentsActivity(Activity activity) {
}

public boolean isLaunchIntentOfNotification(Intent intent) {
return intent.getBooleanExtra(LAUNCH_FLAG_KEY_NAME, false);
return intent.getBooleanExtra(IntentExtras.LAUNCH_FLAG, false);
}

public boolean isLaunchIntentOfBackgroundPushNotification(Intent intent) {
final Bundle extras = intent.getExtras();

if (extras != null) {
// We don't look for FCM_FROM as "from" is far too generic and may appear in other launch intents.
if (extras.containsKey(IntentExtras.FCM_COLLAPSE_KEY)) {
return true;
}

for (final String key : extras.keySet()) {
if (key.startsWith(IntentExtras.FCM_PREFIX)) {
return true;
}
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.wix.reactnativenotifications.core;

import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.util.Log;

import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;

import static com.wix.reactnativenotifications.Defs.LOGTAG;

public class BitmapLoader {
public interface OnBitmapLoadedCallback {
// The lifetime of the Bitmap is only valid for the duration of the callback. If the Bitmap
// is required for a longer duration it should be copied.
void onBitmapLoaded(Bitmap bitmap);
}

private final Context mContext;

public BitmapLoader(Context context) {
mContext = context;
}

public void loadUri(final Uri uri, final OnBitmapLoadedCallback callback) {
final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
.setProgressiveRenderingEnabled(false)
.build();

final ImagePipeline imagePipeline = Fresco.getImagePipeline();
final DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(request, mContext);

final DataSubscriber<CloseableReference<CloseableImage>> dataSubscriber = new BaseDataSubscriber<CloseableReference<CloseableImage>>() {

@Override
protected void onNewResultImpl(final DataSource<CloseableReference<CloseableImage>> dataSource) {
if (dataSource.isFinished()) {
final CloseableReference<CloseableImage> imageReference = dataSource.getResult();

if (imageReference != null) {
final CloseableImage image = imageReference.get();

if (image instanceof CloseableBitmap) {
final Bitmap bitmap = ((CloseableBitmap) image).getUnderlyingBitmap();
callback.onBitmapLoaded(bitmap);
image.close();
} else {
Log.e(LOGTAG, "Image loaded from " + uri + " is not a bitmap image");
}

imageReference.close();
}

dataSource.close();
}
}

@Override
protected void onFailureImpl(final DataSource<CloseableReference<CloseableImage>> dataSource) {
Log.e(LOGTAG, "Failed to load image from " + uri, dataSource.getFailureCause());
dataSource.close();
}
};

dataSource.subscribe(dataSubscriber, CallerThreadExecutor.getInstance());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import android.support.annotation.Nullable;

import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
import com.wix.reactnativenotifications.core.notifications.NotificationProps;

public class InitialNotificationHolder {

private static InitialNotificationHolder sInstance;

private PushNotificationProps mNotification;
private NotificationProps mNotification;

public static void setInstance(InitialNotificationHolder instance) {
sInstance = instance;
Expand All @@ -24,16 +24,16 @@ public static InitialNotificationHolder getInstance() {
return sInstance;
}

public void set(PushNotificationProps pushNotificationProps) {
mNotification = pushNotificationProps;
public void set(NotificationProps notificationProps) {
mNotification = notificationProps;
}

public void clear() {
mNotification = null;
}

@Nullable
public PushNotificationProps get() {
public NotificationProps get() {
return mNotification;
}
}
Loading