Skip to content

Commit

Permalink
Android O support (#495)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio Alonso Fernández committed Sep 21, 2017
1 parent 1007e4c commit 4216808
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 36 deletions.
25 changes: 14 additions & 11 deletions .travis.yml
@@ -1,36 +1,39 @@
language: android
jdk: oraclejdk8
sudo: false

env:
global:
- ANDROID_API_LEVEL=26
- EMULATOR_API_LEVEL=24
- ANDROID_BUILD_TOOLS_VERSION=26.0.1
- ANDROID_ABI=armeabi-v7a # x86 requires HAXM and it's not supported by travis (https://github.com/travis-ci/travis-ci/issues/1419)
- ADB_INSTALL_TIMEOUT=8 # in minutes (2 minutes by default)

android:
components:
- tools
- platform-tools
- build-tools-24.0.3
- android-24
- build-tools-$ANDROID_BUILD_TOOLS_VERSION
- android-$ANDROID_API_LEVEL
- android-$EMULATOR_API_LEVEL
- extra-google-google_play_services
- extra-android-m2repository
- extra-google-m2repository
- sys-img-armeabi-v7a-android-24

env:
global:
# install timeout in minutes (2 minutes by default)
- ADB_INSTALL_TIMEOUT=8
- sys-img-armeabi-v7a-android-$EMULATOR_API_LEVEL

before_install:
- adb logcat > logcat.log &

# Emulator Management: Create, Start and Wait
before_script:
- android list targets
- echo no | android create avd --force -n mp --target android-24 --abi armeabi-v7a
- echo no | android create avd --force -n mp --target "android-"$EMULATOR_API_LEVEL --abi $ANDROID_ABI
- emulator -avd mp -noskin -no-boot-anim -no-window &
- android-wait-for-emulator
- adb shell input keyevent 82 &
- adb shell input keyevent 1 &

script:
- android list target
- ./gradlew lint
- ./gradlew --info connectedAndroidTest
- ./gradlew --info androidJavadocs
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Expand Up @@ -15,8 +15,8 @@ group = GROUP
version = VERSION_NAME

android {
compileSdkVersion 24
buildToolsVersion '24.0.3'
compileSdkVersion 26
buildToolsVersion '26.0.1'

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
Expand Down
Expand Up @@ -124,7 +124,7 @@ protected Handler restartWorkerThread() {
final Handler ret = new AnalyticsMessageHandler(thread.getLooper()) {
@Override
protected DecideChecker createDecideChecker() {
return new DecideChecker(mContext, mConfig, new SystemInformation(mContext)) {
return new DecideChecker(mContext, mConfig) {
@Override
public void runDecideCheck(String token, RemoteService poster) throws RemoteService.ServiceUnavailableException {
if (mCanRunDecide) {
Expand Down Expand Up @@ -283,7 +283,7 @@ protected Handler restartWorkerThread() {
final Handler ret = new AnalyticsMessageHandler(thread.getLooper()) {
@Override
protected DecideChecker createDecideChecker() {
return new DecideChecker(mContext, mConfig, new SystemInformation(mContext)) {
return new DecideChecker(mContext, mConfig) {
@Override
public void runDecideCheck(String token, RemoteService poster) throws RemoteService.ServiceUnavailableException {
if (mCanRunSecondDecideInstance) {
Expand Down
Expand Up @@ -24,7 +24,7 @@ public class DecideCheckerTest extends AndroidTestCase {
@Override
public void setUp() {
mConfig = new MPConfig(new Bundle(), null);
mDecideChecker = new DecideChecker(getContext(), mConfig, new SystemInformation(getContext()));
mDecideChecker = new DecideChecker(getContext(), mConfig);
mPoster = new MockPoster();
mEventBinder = new MockUpdatesFromMixpanel();
mEventBinder.startUpdates();
Expand Down
Expand Up @@ -101,7 +101,7 @@ protected Handler restartWorkerThread() {
final Handler ret = new AnalyticsMessageHandler(thread.getLooper()) {
@Override
protected DecideChecker createDecideChecker() {
return new DecideChecker(mContext, mConfig, new SystemInformation(mContext)) {
return new DecideChecker(mContext, mConfig) {
@Override
protected ImageStore createImageStore(final Context context) {
return new ImageStore(context, "MixpanelAPI.Images.DecideChecker", mMockPoster);
Expand All @@ -116,7 +116,7 @@ protected ImageStore createImageStore(final Context context) {
};

try {
SystemInformation systemInformation = new SystemInformation(mContext);
SystemInformation systemInformation = SystemInformation.getInstance(mContext);

final StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("&properties=");
Expand Down
Expand Up @@ -43,7 +43,7 @@ protected void setUp() throws Exception {
Thread.sleep(2000);

try {
SystemInformation systemInformation = new SystemInformation(mContext);
SystemInformation systemInformation = SystemInformation.getInstance(mContext);

final StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("&properties=");
Expand Down
Expand Up @@ -260,13 +260,13 @@ class AnalyticsMessageHandler extends Handler {
public AnalyticsMessageHandler(Looper looper) {
super(looper);
mDbAdapter = null;
mSystemInformation = new SystemInformation(mContext);
mSystemInformation = SystemInformation.getInstance(mContext);
mDecideChecker = createDecideChecker();
mFlushInterval = mConfig.getFlushInterval();
}

protected DecideChecker createDecideChecker() {
return new DecideChecker(mContext, mConfig, mSystemInformation);
return new DecideChecker(mContext, mConfig);
}

@Override
Expand Down
Expand Up @@ -61,12 +61,12 @@ public Result() {
public boolean automaticEvents;
}

public DecideChecker(final Context context, final MPConfig config, final SystemInformation systemInformation) {
public DecideChecker(final Context context, final MPConfig config) {
mContext = context;
mConfig = config;
mChecks = new HashMap<String, DecideMessages>();
mImageStore = createImageStore(context);
mSystemInformation = systemInformation;
mSystemInformation = SystemInformation.getInstance(context);
}

protected ImageStore createImageStore(final Context context) {
Expand Down
54 changes: 53 additions & 1 deletion src/main/java/com/mixpanel/android/mpmetrics/GCMReceiver.java
Expand Up @@ -2,6 +2,7 @@

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.NotificationChannel;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.support.v4.app.NotificationCompat;
Expand Down Expand Up @@ -251,7 +252,9 @@ private Notification buildNotification(Context context, Intent inboundIntent, Re
);

final Notification notification;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification = makeNotificationSDK26OrHigher(context, contentIntent, notificationData);
} else 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);
Expand Down Expand Up @@ -399,6 +402,55 @@ protected Notification makeNotificationSDK21OrHigher(Context context, PendingInt
return n;
}

@SuppressLint("NewApi")
@TargetApi(26)
protected Notification makeNotificationSDK26OrHigher(Context context, PendingIntent intent, NotificationData notificationData) {
NotificationManager mNotificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

String channelId = MPConfig.getInstance(context).getNotificationChannelId();
String channelName = MPConfig.getInstance(context).getNotificationChannelName();
int importance = MPConfig.getInstance(context).getNotificationChannelImportance();

NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
int notificationDefaults = MPConfig.getInstance(context).getNotificationDefaults();
if (notificationDefaults == Notification.DEFAULT_VIBRATE || notificationDefaults == Notification.DEFAULT_ALL) {
channel.enableVibration(true);
}
if (notificationDefaults == Notification.DEFAULT_LIGHTS || notificationDefaults == Notification.DEFAULT_ALL) {
channel.enableLights(true);
channel.setLightColor(Color.WHITE);
}
mNotificationManager.createNotificationChannel(channel);

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)).
setChannelId(channelId);

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, final String extraLogData) {
if (campaignId != null && messageId != null) {
MixpanelAPI.allInstances(new InstanceProcessor() {
Expand Down
45 changes: 42 additions & 3 deletions src/main/java/com/mixpanel/android/mpmetrics/MPConfig.java
Expand Up @@ -77,6 +77,15 @@
*
* <dt>com.mixpanel.android.MPConfig.UseIpAddressForGeolocation</dt>
* <dd>A boolean value. If true, Mixpanel will automatically determine city, region and country data using the IP address of the client.Defaults to true.</dd>
*
* <dt>com.mixpanel.android.MPConfig.NotificationChannelId</dt>
* <dd>An string value. If present, the library will use this id when creating a notification channel. Applicable only for Android 26 and above.</dd>
*
* <dt>com.mixpanel.android.MPConfig.NotificationChannelName</dt>
* <dd>An string value. If present, the library will use this user-visible name for our notification channel. Applicable only for Android 26 and above. Defaults to the application name.</dd>
*
* <dt>com.mixpanel.android.MPConfig.NotificationChannelImportance</dt>
* <dd>An integer number. If present, the library will use this user-visible name for our notification channel. Applicable only for Android 26 and above.</dd>
* </dl>
*
*/
Expand Down Expand Up @@ -204,8 +213,20 @@ public synchronized void setOfflineMode(OfflineMode offlineMode) {
mMinSessionDuration = metaData.getInt("com.mixpanel.android.MPConfig.MinimumSessionDuration", 10 * 1000); // 10 seconds
mSessionTimeoutDuration = metaData.getInt("com.mixpanel.android.MPConfig.SessionTimeoutDuration", Integer.MAX_VALUE); // no timeout by default
mUseIpAddressForGeolocation = metaData.getBoolean("com.mixpanel.android.MPConfig.UseIpAddressForGeolocation", true);

mTestMode = metaData.getBoolean("com.mixpanel.android.MPConfig.TestMode", false);
mNotificationChannelImportance = metaData.getInt("com.mixpanel.android.MPConfig.NotificationChannelImportance", 3); // NotificationManger.IMPORTANCE_DEFAULT

String notificationChannelId = metaData.getString("com.mixpanel.android.MPConfig.NotificationChannelId");
if (notificationChannelId == null) {
notificationChannelId = "mp";
}
mNotificationChannelId = notificationChannelId;

String notificationChannelName = metaData.getString("com.mixpanel.android.MPConfig.NotificationChannelName");
if (notificationChannelName == null) {
notificationChannelName = SystemInformation.getInstance(context).getAppName();
}
mNotificationChannelName = notificationChannelName;

String eventsEndpoint = metaData.getString("com.mixpanel.android.MPConfig.EventsEndpoint");
if (null == eventsEndpoint) {
Expand Down Expand Up @@ -259,8 +280,11 @@ public synchronized void setOfflineMode(OfflineMode offlineMode) {
" IgnoreInvisibleViewsEditor " + getIgnoreInvisibleViewsEditor() + "\n" +
" NotificationDefaults " + getNotificationDefaults() + "\n" +
" MinimumSessionDuration: " + getMinimumSessionDuration() + "\n" +
" SessionTimeoutDuration: " + getSessionTimeoutDuration()
);
" SessionTimeoutDuration: " + getSessionTimeoutDuration() + "\n" +
" NotificationChannelId: " + getNotificationChannelId() + "\n" +
" NotificationChannelName: " + getNotificationChannelName() + "\n" +
" NotificationChannelImportance: " + getNotificationChannelImportance()
);
}

// Max size of queue before we require a flush. Must be below the limit the service will accept.
Expand Down Expand Up @@ -347,6 +371,18 @@ public int getSessionTimeoutDuration() {
return mSessionTimeoutDuration;
}

public String getNotificationChannelId() {
return mNotificationChannelId;
}

public String getNotificationChannelName() {
return mNotificationChannelName;
}

public int getNotificationChannelImportance() {
return mNotificationChannelImportance;
}

// Pre-configured package name for resources, if they differ from the application package name
//
// mContext.getPackageName() actually returns the "application id", which
Expand Down Expand Up @@ -418,6 +454,9 @@ public int getImageCacheMaxMemoryFactor() {
private final int mMinSessionDuration;
private final int mSessionTimeoutDuration;
private final boolean mUseIpAddressForGeolocation;
private final int mNotificationChannelImportance;
private final String mNotificationChannelId;
private final String mNotificationChannelName;

// Mutable, with synchronized accessor and mutator
private SSLSocketFactory mSSLSocketFactory;
Expand Down
Expand Up @@ -41,10 +41,6 @@ public void onActivityStarted(Activity activity) {
trackCampaignOpenedIfNeeded(activity.getIntent());

if (android.os.Build.VERSION.SDK_INT >= MPConfig.UI_FEATURES_MIN_API && mConfig.getAutoShowMixpanelUpdates()) {
if (!activity.isTaskRoot()) {
return; // No checks, no nothing.
}

mMpInstance.getPeople().showNotificationIfAvailable(activity);
}
new GestureTracker(mMpInstance, activity);
Expand Down
Expand Up @@ -7,6 +7,7 @@
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
Expand All @@ -25,7 +26,18 @@
*/
/* package */ class SystemInformation {

public SystemInformation(Context context) {
/* package */ static SystemInformation getInstance(Context context) {
synchronized (sInstanceLock) {
if (null == sInstance) {
final Context appContext = context.getApplicationContext();
sInstance = new SystemInformation(appContext);
}
}

return sInstance;
}

private SystemInformation(Context context) {
mContext = context;

PackageManager packageManager = mContext.getPackageManager();
Expand All @@ -40,8 +52,12 @@ public SystemInformation(Context context) {
MPLog.w(LOGTAG, "System information constructed with a context that apparently doesn't exist.");
}

ApplicationInfo applicationInfo = context.getApplicationInfo();
int appNameStringId = applicationInfo.labelRes;

mAppVersionName = foundAppVersionName;
mAppVersionCode = foundAppVersionCode;
mAppName = appNameStringId == 0 ? applicationInfo.nonLocalizedLabel == null ? "Misc" : applicationInfo.nonLocalizedLabel.toString() : context.getString(appNameStringId);

// We can't count on these features being available, since we need to
// run on old devices. Thus, the reflection fandango below...
Expand Down Expand Up @@ -79,6 +95,8 @@ public SystemInformation(Context context) {

public Integer getAppVersionCode() { return mAppVersionCode; }

public String getAppName() { return mAppName; }

public boolean hasNFC() { return mHasNFC; }

public boolean hasTelephony() { return mHasTelephony; }
Expand Down Expand Up @@ -175,6 +193,10 @@ public String getBluetoothVersion() {
private final DisplayMetrics mDisplayMetrics;
private final String mAppVersionName;
private final Integer mAppVersionCode;
private final String mAppName;

private static SystemInformation sInstance;
private static final Object sInstanceLock = new Object();

private static final String LOGTAG = "MixpanelAPI.SysInfo";
}
Expand Up @@ -240,19 +240,19 @@ private void uninstallConnectionSensor(final Activity activity) {
}

private boolean isInEmulator() {
if (!Build.HARDWARE.equals("goldfish") && !Build.HARDWARE.equals("ranchu")) {
if (!Build.HARDWARE.toLowerCase().equals("goldfish") && !Build.HARDWARE.toLowerCase().equals("ranchu")) {
return false;
}

if (!Build.BRAND.startsWith("generic") && !Build.BRAND.equals("Android")) {
if (!Build.BRAND.toLowerCase().startsWith("generic") && !Build.BRAND.toLowerCase().equals("android") && !Build.BRAND.toLowerCase().equals("google")) {
return false;
}

if (!Build.DEVICE.startsWith("generic")) {
if (!Build.DEVICE.toLowerCase().startsWith("generic")) {
return false;
}

if (!Build.PRODUCT.contains("sdk")) {
if (!Build.PRODUCT.toLowerCase().contains("sdk")) {
return false;
}

Expand Down

0 comments on commit 4216808

Please sign in to comment.