package com.mixpanel.android.mpmetrics; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.graphics.BitmapFactory; import android.graphics.Color; import android.support.v4.app.NotificationCompat; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Build; import com.mixpanel.android.mpmetrics.MixpanelAPI.InstanceProcessor; import com.mixpanel.android.util.MPLog; import org.json.JSONException; import org.json.JSONObject; /** * BroadcastReceiver for handling Google Cloud Messaging intents. * * <p>You can use GCMReceiver to report Google Cloud Messaging registration identifiers * to Mixpanel, and to display incoming notifications from Mixpanel to * the device status bar. Together with {@link MixpanelAPI.People#initPushHandling(String) } * this is the simplest way to get up and running with notifications from Mixpanel. * * <p>To enable GCMReceiver in your application, add a clause like the following * to the <application> tag of your AndroidManifest.xml. (Be sure to replace "YOUR APPLICATION PACKAGE NAME" * in the snippet with the actual package name of your app.) * *<pre> *{@code * * <receiver android:name="com.mixpanel.android.mpmetrics.GCMReceiver" * 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="YOUR APPLICATION PACKAGE NAME" /> * </intent-filter> * </receiver> * *} *</pre> * * <p>In addition, GCMReceiver will also need the following permissions configured * in your AndroidManifest.xml file: * * <pre> * {@code * * <!-- Be sure to change YOUR_PACKAGE_NAME to the real name of your application package --> * <permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" android:protectionLevel="signature" /> * <uses-permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" /> * * <uses-permission android:name="android.permission.INTERNET" /> * <uses-permission android:name="android.permission.GET_ACCOUNTS" /> * <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> * <uses-permission android:name="android.permission.WAKE_LOCK" /> * * } * </pre> * * <p>Once the GCMReceiver is configured, the only thing you have to do to * get set up Mixpanel messages is call {@link MixpanelAPI.People#identify(String) } * with a distinct id for your user, and call {@link MixpanelAPI.People#initPushHandling(String) } * with the your Google API project identifier. * <pre> * {@code * * MixpanelAPI.People people = mMixpanelAPI.getPeople(); * people.identify("A USER DISTINCT ID"); * people.initPushHandling("123456789123"); * * } * </pre> * * <p>If you would prefer to handle either sending a registration id to Mixpanel yourself * but allow GCMReceiver to handle displaying Mixpanel messages, remove the * REGISTRATION intent from the GCMReceiver {@code <receiver> } tag, and call * {@link MixpanelAPI.People#setPushRegistrationId(String)} * in your own REGISTRATION handler. * * @see MixpanelAPI#getPeople() * @see MixpanelAPI.People#initPushHandling(String) * @see <a href="https://mixpanel.com/docs/people-analytics/android-push">Getting Started with Android Push Notifications</a> */ public class GCMReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if ("com.google.android.c2dm.intent.REGISTRATION".equals(action)) { handleRegistrationIntent(intent); } else if ("com.google.android.c2dm.intent.RECEIVE".equals(action)) { handleNotificationIntent(context, intent); } } protected static class NotificationData { private NotificationData(int anIcon, int aLargeIcon, int aWhiteIcon, CharSequence aTitle, String aMessage, Intent anIntent, int aColor) { icon = anIcon; largeIcon = aLargeIcon; whiteIcon = aWhiteIcon; title = aTitle; message = aMessage; intent = anIntent; color = aColor; } public final int icon; public final int largeIcon; public final int whiteIcon; public final CharSequence title; public final String message; public final Intent intent; public final int color; public static final int NOT_SET = -1; } /* package */ Intent getDefaultIntent(Context context) { final PackageManager manager = context.getPackageManager(); return manager.getLaunchIntentForPackage(context.getPackageName()); } /* package */ NotificationData readInboundIntent(Context context, Intent inboundIntent, ResourceIds iconIds) { final PackageManager manager = context.getPackageManager(); final String message = inboundIntent.getStringExtra("mp_message"); final String iconName = inboundIntent.getStringExtra("mp_icnm"); final String largeIconName = inboundIntent.getStringExtra("mp_icnm_l"); final String whiteIconName = inboundIntent.getStringExtra("mp_icnm_w"); final String uriString = inboundIntent.getStringExtra("mp_cta"); CharSequence notificationTitle = inboundIntent.getStringExtra("mp_title"); final String colorName = inboundIntent.getStringExtra("mp_color"); final String campaignId = inboundIntent.getStringExtra("mp_campaign_id"); final String messageId = inboundIntent.getStringExtra("mp_message_id"); int color = NotificationData.NOT_SET; trackCampaignReceived(campaignId, messageId); if (colorName != null) { try { color = Color.parseColor(colorName); } catch (IllegalArgumentException e) {} } if (message == null) { return null; } int notificationIcon = -1; if (null != iconName) { if (iconIds.knownIdName(iconName)) { notificationIcon = iconIds.idFromName(iconName); } } int largeNotificationIcon = NotificationData.NOT_SET; if (null != largeIconName) { if (iconIds.knownIdName(largeIconName)) { largeNotificationIcon = iconIds.idFromName(largeIconName); } } int whiteNotificationIcon = NotificationData.NOT_SET; if (null != whiteIconName) { if (iconIds.knownIdName(whiteIconName)) { whiteNotificationIcon = iconIds.idFromName(whiteIconName); } } ApplicationInfo appInfo; try { appInfo = manager.getApplicationInfo(context.getPackageName(), 0); } catch (final NameNotFoundException e) { appInfo = null; } if (notificationIcon == NotificationData.NOT_SET && null != appInfo) { notificationIcon = appInfo.icon; } if (notificationIcon == NotificationData.NOT_SET) { notificationIcon = android.R.drawable.sym_def_app_icon; } if (null == notificationTitle && null != appInfo) { notificationTitle = manager.getApplicationLabel(appInfo); } if (null == notificationTitle) { notificationTitle = "A message for you"; } final Intent notificationIntent = buildNotificationIntent(context, uriString, campaignId, messageId); return new NotificationData(notificationIcon, largeNotificationIcon, whiteNotificationIcon, notificationTitle, message, notificationIntent, color); } private Intent buildNotificationIntent(Context context, String uriString, String campaignId, String messageId) { Uri uri = null; if (null != uriString) { uri = Uri.parse(uriString); } final Intent ret; if (null == uri) { ret = getDefaultIntent(context); } else { ret = new Intent(Intent.ACTION_VIEW, uri); } if (campaignId != null) { ret.putExtra("mp_campaign_id", campaignId); } if (messageId != null) { ret.putExtra("mp_message_id", messageId); } return ret; } private Notification buildNotification(Context context, Intent inboundIntent, ResourceIds iconIds) { final NotificationData notificationData = readInboundIntent(context, inboundIntent, iconIds); if (null == notificationData) { return null; } MPLog.d(LOGTAG, "MP GCM notification received: " + notificationData.message); final PendingIntent contentIntent = PendingIntent.getActivity( context, 0, notificationData.intent, PendingIntent.FLAG_UPDATE_CURRENT ); final Notification notification; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { notification = makeNotificationSDK21OrHigher(context, contentIntent, notificationData); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { notification = makeNotificationSDK16OrHigher(context, contentIntent, notificationData); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { notification = makeNotificationSDK11OrHigher(context, contentIntent, notificationData); } else { notification = makeNotificationSDKLessThan11(context, contentIntent, notificationData); } return notification; } private void handleRegistrationIntent(Intent intent) { final String registration = intent.getStringExtra("registration_id"); if (intent.getStringExtra("error") != null) { MPLog.e(LOGTAG, "Error when registering for GCM: " + intent.getStringExtra("error")); } else if (registration != null) { MPLog.d(LOGTAG, "Registering GCM ID: " + registration); MixpanelAPI.allInstances(new InstanceProcessor() { @Override public void process(MixpanelAPI api) { api.getPeople().setPushRegistrationId(registration); } }); } else if (intent.getStringExtra("unregistered") != null) { MPLog.d(LOGTAG, "Unregistering from GCM"); MixpanelAPI.allInstances(new InstanceProcessor() { @Override public void process(MixpanelAPI api) { api.getPeople().clearPushRegistrationId(); } }); } } private void handleNotificationIntent(Context context, Intent intent) { final MPConfig config = MPConfig.getInstance(context); String resourcePackage = config.getResourcePackageName(); if (null == resourcePackage) { resourcePackage = context.getPackageName(); } final ResourceIds drawableIds = new ResourceReader.Drawables(resourcePackage, context); final Context applicationContext = context.getApplicationContext(); final Notification notification = buildNotification(applicationContext, intent, drawableIds); if (null != notification) { final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(0, notification); } } @SuppressWarnings("deprecation") @TargetApi(9) protected Notification makeNotificationSDKLessThan11(Context context, PendingIntent intent, NotificationData notificationData) { final NotificationCompat.Builder builder = new NotificationCompat.Builder(context). setSmallIcon(notificationData.icon). setTicker(notificationData.message). setWhen(System.currentTimeMillis()). setContentTitle(notificationData.title). setContentText(notificationData.message). setContentIntent(intent). setDefaults(MPConfig.getInstance(context).getNotificationDefaults()); if (notificationData.largeIcon != NotificationData.NOT_SET) { builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), notificationData.largeIcon)); } final Notification n = builder.getNotification(); n.flags |= Notification.FLAG_AUTO_CANCEL; return n; } @SuppressWarnings("deprecation") @TargetApi(11) protected Notification makeNotificationSDK11OrHigher(Context context, PendingIntent intent, NotificationData notificationData) { final Notification.Builder builder = new Notification.Builder(context). setSmallIcon(notificationData.icon). setTicker(notificationData.message). setWhen(System.currentTimeMillis()). setContentTitle(notificationData.title). setContentText(notificationData.message). setContentIntent(intent). setDefaults(MPConfig.getInstance(context).getNotificationDefaults()); if (notificationData.largeIcon != NotificationData.NOT_SET) { builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), notificationData.largeIcon)); } final Notification n = builder.getNotification(); n.flags |= Notification.FLAG_AUTO_CANCEL; return n; } @SuppressLint("NewApi") @TargetApi(16) protected Notification makeNotificationSDK16OrHigher(Context context, PendingIntent intent, NotificationData notificationData) { final Notification.Builder builder = new Notification.Builder(context). setSmallIcon(notificationData.icon). setTicker(notificationData.message). setWhen(System.currentTimeMillis()). setContentTitle(notificationData.title). setContentText(notificationData.message). setContentIntent(intent). setStyle(new Notification.BigTextStyle().bigText(notificationData.message)). setDefaults(MPConfig.getInstance(context).getNotificationDefaults()); if (notificationData.largeIcon != NotificationData.NOT_SET) { builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), notificationData.largeIcon)); } final Notification n = builder.build(); n.flags |= Notification.FLAG_AUTO_CANCEL; return n; } @SuppressLint("NewApi") @TargetApi(21) protected Notification makeNotificationSDK21OrHigher(Context context, PendingIntent intent, NotificationData notificationData) { final Notification.Builder builder = new Notification.Builder(context). setTicker(notificationData.message). setWhen(System.currentTimeMillis()). setContentTitle(notificationData.title). setContentText(notificationData.message). setContentIntent(intent). setStyle(new Notification.BigTextStyle().bigText(notificationData.message)). setDefaults(MPConfig.getInstance(context).getNotificationDefaults()); if (notificationData.whiteIcon != NotificationData.NOT_SET) { builder.setSmallIcon(notificationData.whiteIcon); } else { builder.setSmallIcon(notificationData.icon); } if (notificationData.largeIcon != NotificationData.NOT_SET) { builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), notificationData.largeIcon)); } if (notificationData.color != NotificationData.NOT_SET) { builder.setColor(notificationData.color); } final Notification n = builder.build(); n.flags |= Notification.FLAG_AUTO_CANCEL; return n; } private void trackCampaignReceived(final String campaignId, final String messageId) { if (campaignId != null && messageId != null) { MixpanelAPI.allInstances(new InstanceProcessor() { @Override public void process(MixpanelAPI api) { if(api.isAppInForeground()) { JSONObject pushProps = new JSONObject(); try { pushProps.put("campaign_id", campaignId); pushProps.put("message_id", messageId); pushProps.put("message_type", "push"); api.track("$campaign_received", pushProps); } catch (JSONException e) {} } } }); } } @SuppressWarnings("unused") private static final String LOGTAG = "MixpanelAPI.GCMReceiver"; }