Feb 20 - Conversion feature for Android SDK added in 2.2.0

Tracking User Actions with the Sensorberg Conversion Feature.

The “Conversion” feature enables you to measure user interactions with beacons and campaigns. With this feature you can track the following informations:

  • How many users were around the beacon region
  • How many campaigns were delivered to the app
  • How many campaign actions were performed by users

Feb 9 - Sensorberg iOS SDK v2.5.2

This week we launched version 2.5.2 of our iOS SDK - be sure to update!

Improvements

  • Used CDN server as default

Fixed Bugs

  • Fixed beacon ranging problem when the device is already in the monitored beacon region.

Greetings from Berlin!

Jan 7 - User targeting with the Sensorberg SDK

With the release of the iOS SDK 2.4 and Android SDK 2.2 we have also released the capability to target a specific subgroup of your app users.

The system is very flexible and can be used for multiple use cases. Here is a list of examples:

  • Send a notification to all logged in users of my app
  • Send a notification to all users of my app who have a score above 1000
  • Send a notification to all users who have specified their hometown to be Berlin

From a data privacy perspective, we designed the feature in a way so that data is only stored on the client. We are providing a new API in the SDKs the enable customers to set any String key values which filters the campaigns for the current app user.

Dec 21 - Delayed notification issue on Huawei devices

Over the last couple months we’ve noticed some issue with delayed notifications on some Huawei devices.

In this article we will explain the issue, the cause and some possible fixes.

The issue

This is a known issue with Huawei devices and is being caused by the battery manager and the way it’s trying to save battery.

Possible fixes

There are couple settings that can help.

Dec 2 - Sensorberg iOS SDK v2.4

This week we launched version 2.4 of our iOS SDK - be sure to update!

Improvements

  • Added support for iOS 10
  • Added support for individual beacons monitoring
  • Added support target attributes

Fixed Bugs

  • Fixed geohash missing from some analytics
  • Fixed warnings related to deprecated methods usage

The biggest change in this version though is related to actual beacon monitoring, which changed with iOS 10. Click through to read more about this change.

Nov 15 - Advanced Settings of Application for iOS SDK and Showcase app

Advanced Settings

Our SDKs have a little-known feature - the Advanced Settings - that allow more fine customizations of the SDKs functionality.
In this post we go over some of keys you can add/edit to customize the Sensorberg app.

  1. Settings for iOS SDK : The settings are used to configure the behaviours of iOS SDK
  2. Settings for iOS Showcase app : The settings to change all colour and font styles of iOS ‘Showcase’ app.

Oct 17 - Sensorberg iOS SDK v2.2

We released version 2.2 of the Sensorberg iOS SDK

Improvements

  • Added support for silent campaigns
  • Updated the dmeo app to Swift 3
  • Better history reporting
  • Better network reachability detection

Fixed Bugs

  • Fixed UUID parsing
  • Fixed crash related to invalid date formats

Related Posts

Greetings from Berlin!

Jul 27 - Beacon Configuration app for iOS

Hello AppStore, again

Today we released a new BeaCfg app on the AppStore

Version 1.4 brings a few minor improvements to detection and editing beacon values, and a new sorting functionality which allows ordering the detected beacons either by proximity (Received Signal Strength) or by the order in which your iOS device discovered them.
To configure a beacon:

  • remove the battery (in the case of USB beacons simply disconnect them)
  • open the app
  • re-insert the battery (connect USB beacon)
  • tap on the beacon and start the configuration process

To get a better idea, here’s a short video of how it works:

Download the BeaCfg App on the AppStore

Greetings from Berlin!

Jul 25 - How do I... implement the Advertiser Identifier in Android?

For the Advertiser Identifier there are multiple ways of implementing the Google identifier depending on your application. We will show an example of what can be done. Of course the most important thing is to set and unset the ID, how you do that is where the implementation can differ.

  • Set the identifier. Please note, you will most likely want input from the user, like a switch or button to toggle the setting of the value. In the example below we used a switch, below is the switch listener.
    @OnCheckedChanged(R.id.use_google_advertiser_id_switch)
    void googleAdvertiserIdSwitchChanged(boolean checked) {
        if (!this.resumed) {
            return;
        }
        if (checked) {
            Logger.log.logSettingsUpdateState(ShowcaseTracking.ADVERTISER_IDENTIFIER_ENABLED);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        AdvertisingIdClient.Info info = AdvertisingIdClient.getAdvertisingIdInfo(getActivity().getApplicationContext());
                        ShowcaseApplication.getInstance().boot.setAdvertisingIdentifier(info.getId());
                    } catch (IOException e) {
                        Logger.log.logError("foreground could not fetch the advertising identifier because of an IO Exception", e);
                    } catch (GooglePlayServicesNotAvailableException e) {
                        Logger.log.logError("foreground play services not available", e);
                    } catch (GooglePlayServicesRepairableException e) {
                        Logger.log.logError("foreground  services need repairing", e);
                    } catch (Exception e) {
                        Logger.log.logError("foreground could not fetch the advertising identifier because of an unknown error", e);
                    }
                }
            }).start();
            Toast.makeText(getActivity(), ShowcaseTracking.ADVERTISER_IDENTIFIER_ENABLED, Toast.LENGTH_LONG).show();
        } else {
            Logger.log.logSettingsUpdateState(ShowcaseTracking.ADVERTISER_IDENTIFIER_DISABLED);
            ShowcaseApplication.getInstance().boot.setAdvertisingIdentifier(null); //set to null ie. turn off.
            Toast.makeText(getActivity(), ShowcaseTracking.ADVERTISER_IDENTIFIER_DISABLED, Toast.LENGTH_LONG).show();
                    }
    }
  • If you need to unset, you can set the identifier to null.
ShowcaseApplication.getInstance().boot.setAdvertisingIdentifier(null);

The user can reset the Google identifier through their device settings.

-The Sensorberg Android Team

Jul 20 - Proguard support for Android SDK

We have added support for Proguard to our Android SDK, from release 1.2.1.

It has been added on a library level, so the only thing you need to do in your project is to add/set minifyEnabled true in your build.gradle file. We have tested it extensively in our internal builds and our Showcase app, but if you find any problems, don’t hesitate to reach out or open an issue on Github.

-The Sensorberg Android Team

Jul 13 - The Android Version 2 SDK is here!!

We have released version 2 of the Sensorberg Android SDK

Improvements

  • Android 6 permissions now apply. You can view the specifics on the blogpost for Android 6.
  • Proguard Configuration was added. Find more information here.
  • How you will implement the SDK has changed. For more information please see our guide under the “New 2.X SDK implementation” section.

-Sensorberg Android Team

Jul 13 - What's NEW in iOS SDK 2.1.2 !!

We released version 2.1.2 of the Sensorberg iOS SDK

Improvements

  • Removed AFNetworking dependency.
  • Improved CLBeaconRegion detection.
  • Conversion measurement[Beta WIP].
  • Remote configurations for Campaign.

Fixed Bugs

  • Fixed CBUUID generation.

Related Posts

Greetings from Berlin!

Jul 6 - Sensorberg Android SDK is ready for Android 6 Permissions, are you?

How Android will handle permissions in Android 6 will affect the Sensorberg SDK.

Google is changing how Android will handle permissions. Unfortunately, these changes will affect the Sensorberg SDK. In this article we will first explain the new Android permission framework, we will then discuss how this affects the Sensorberg SDK, finally we will explain how you can update your app to work with Android 6 permissions.

The major difference between permissions in previous versions of Android and Android 6 is quite simple. In Android 6 there is the addition of Run-time permissions. What does this exactly mean? Well, if you remember in previous Android versions when the user downloaded your applications they would be asked if they accept the permissions you have defined in your Android Manifest. This remains. The additional requirement now is that at Run-Time, not just at install, the user will be asked if they want or still want to grant the permission you have defined. Though all permissions are not created equally.

Google as decided to categories permissions into “dangerous” and “normal” permissions. Dangerous permissions are simply those permissions which (could) put the user’s security/data at risk. And this is why our SDK is affected. This doesn’t appear to be intuitive at first, we will get to that shortly.

Ok. Here is the secret. Your app will continue to scan for beacons in Android 6, but here is the kicker, if you want it to scan in the background, which for a beacon is precisely what we want, then you need to update your permissions in Android 6. Bluetooth LE though is not actually a dangerous permission. So in theory you should not have to ask for a permission. Bluetooth LE, in theory can give away your location, so Google has decided to make it mandatory for when using BLE, to require one of the two location permissions, (fine or coarse grain) so users are can made aware of the issues surrounding Bluetooth LE.

So, we now don’t actually have to worry about Bluetooth, the previous permissions still stand. Now we have to ask for one of two location permissions for beacon scanning to work properly. Before we discuss how to do that, we should first explain how we now support location services in our SDK. We basically receive an intent which flags the scanner to stop or start based on whether location permissions are set. If your app does targets Android 6, the SDK will send the developer a log message that they need to ask the user for their permission to use Location Services. The sdk will not crash the app, simply no beacons will be found. Yes, it is true that in the foreground even without the location permission beacons may be found, though we decided to just disable scanning to avoid problems and confusion. Wow, that was a lot, but we have one more topic to discuss, implementation from your side.

As an user of our SDK you need to do one thing; you need to at some point before scanning is to occur to ask your users for the Fine Location Services. So how to do this.. see the example below from the developer test app.

        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.ACCESS_FINE_LOCATION)) {
                final AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("Functionality limited");
                builder.setMessage("Since location access has not been granted, " +
                        "this app will not be able to discover beacons when in the background.");
                builder.setPositiveButton(android.R.string.ok, null);
                builder.setOnDismissListener(new DialogInterface.OnDismissListener() {

                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        ActivityCompat.requestPermissions(DemoActivity.this,
                                new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                                MY_PERMISSION_REQUEST_LOCATION_SERVICES);
                    }

                });
                builder.show();
            } else {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        MY_PERMISSION_REQUEST_LOCATION_SERVICES);
            }
        }

Finally we need to receive the callback from the requestPermissions() call.

     @Override
     public void onRequestPermissionsResult(int requestCode,
                                            String permissions[], int[] grantResults) {
         switch (requestCode) {
             case MY_PERMISSION_REQUEST_LOCATION_SERVICES: {
                 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                     Log.d("Scanner Message", "location permission granted");
                     ((DemoApplication) getApplication()).setLocationPermissionGranted(SensorbergServiceMessage.MSG_LOCATION_SET);
                 } else {
                     ((MyApplicati) getApplication()).setLocationPermissionGranted(SensorbergServiceMessage.MSG_LOCATION_NOT_SET_WHEN_NEEDED);
                 }
                 return;
             }
         }
     }

You will notice two things. There is a method we call from the application class and we have two constants. These two constants are only flags to tell the method in the application class whether the location permission requested was granted. The method can be called whatever you want, but what is key is you need to use our method sendLocationFlagToReceiver in the SensorbergSdk class in your application class.

 public void setLocationPermissionGranted(int type) {
        boot.sendLocationFlagToReceiver(type);
    }

Type parameter is whether the permission is set or not. The call will be received by the PermissionBroadcastReceiver class and processed.

And that is it. Not so difficult. Please be nice to your users and only ask for the permissions when you will be scanning. Don’t over ask, also try to explain to your users why you need the permission, some people will not understand why they need the location permission. Be friendly, descriptive and helpful.

If you have any questions, don’t hesitate to reach out.

-The Sensorberg Android Team

Jul 6 - A New Android SDK version is Here!

The Sensorberg Android Team is Excited to Announce a New SDK Version.

It has been a long time in the works… The Android team has be toiling away. The 1.2.0 and forthcoming 2.0.0 release changes have/will mainly focus on refactoring changes, rather than new functionality. The SDK needed a big spring cleaning, so for the past few months we have been trimming, cleaning and reworking to make the SDK even better, lighter and easier to use.

Some Highlights of 1.2.0:

  • We have added an advertiser id with added control of the ID (which will be discussed in a separate blog).
  • We added much need coverage to our unit tests..

The SDK can be found on bintray at: Android 1.2.0 SDK

To use it, you can just change the sdk version in your build.gradle.

For Example:

compile 'com.sensorberg.sdk:android-sdk:1.2.0'   

We have also updated the Showcase Application. You shouldn’t notice any funtionality changes, as usual if you find any issues please let us know! The showcase can be found here: Android Showcase App on Google Play.

-The Sensorberg Android Team

Jun 10 - Setting Custom API Keys, Resolver URLs and Proximity UUIDs for Campaign Simulation

Setting Custom API Keys, Resolver URLs and Proximity UUIDs for Campaign Simulation on the iOS Showcase App

The iOS Showcase app has the following 3 features.

  1. Simulating a beacon with a custom proximity UUID
  2. Changing the Resolver URL for the App
  3. Setting the Application API Key manually on the App instead of scanning QR-Code

Jun 7 - New conversion feature in iOS SDK

Tracking User Actions with the Sensorberg Conversion Feature.

The “Conversion” feature enables you to measure user interactions with beacons and campaigns. With this feature you can track the following informations:

  • how many users were around the beacon region
  • how many campaigns were delivered to the app
  • how many campaign actions were performed by users

Jun 6 - iOS Actionable Notification with Payload

Customise Your notification with Payload

Since 2015 April, we have had a payload feature[see here] in the Sensorberg Management Platform. In this post we will highlight the usage of the payload object for actionable notifications on iOS devices and show different usages of the payload object.

Use Case:

We would like to show an actionable notification with custom content to our users based on the payload which we have entered user into the management platform. Using this article you can see how to set an actionable notification with a custom payload and how to handle it.

May 13 - Beacon Showcase iOS App Ver.2.1

Hello AppStore

Today we released a new Showcase app on the AppStore

This is a complete re-write of the app, and, going forward, we will continue to update it with the latest features from our Sensorberg iOS SDK.

Here’s a short video of how it works:

What’s new!

  • Easily change the API key, just by scanning a QR code
  • New (and beautiful) Sensorberg theme colors
  • Campaign notifications or in-app alerts for your beacon related actions

New Sensorberg Theme

- Notifications / Search Beacons / App Status

Fired Notifications Screen Scanning for beacons Status

How (easy it is) to change API Key?

1. Login on the Sensorberg Management Portal
2. Open the QR Code for your app
3. In the Showcase app, on the status page, select the Scan QR Code option

Download the Showcase App

Greetings from Berlin!

May 6 - Beacon Uploader

We have been working hard to help you manage beacons more effectively. Today we are happy to announce that you can now create multiple beacons at once without filling countless browser forms and endlessly hitting the save button. This process can now be automated using our CSV Beacon Upload Tool.

Check it out

CSV Beacon Uploader: Beacon Management Platform

https://manage.sensorberg.com/#/beacon/upload

How it works

The Beacon Upload Tool takes a CSV file, goes through each line, and if all of the information in a given line is valid, a new beacon is added to your Sensorberg account based on that information. The concept is pretty simple: each line of your csv describes a single beacon. The fields that can be filled in using the Beacon Upload Tool are as follows:

  • name
  • description
  • proximityId
  • major
  • minor
  • latitude
  • longitude

The Beacon Upload Tool is smart enough to generate valid entries for a beacon’s proximityId, major and minor. The only field we actually require you to fill in is name, the rest is optional.

Acceptable Formats

We recommend using the CSV template below as a base for creating your CSV data file. Note that the order of the columns in your file matters. The columns must be defined in the first line of your CSV, and they must match the example below.

Please use decimal values for latitude and longitude: e.g. 52.51948. Do not use military format coordinates, because our system does not understand them. If you need to convert your military formatted coordinates to decimal format, you may be able to do so using this GPS Conversion Tool.

Along the same lines please pay attention to your delimiters. Meaning that if there are commas in your descriptions, or in your numbers please use semicolons as the delimiter for your file.

CSV template example

name,description,proximityId,major,minor,latitude,longitude
Entrance Beacon,South Entrance parking,73676723-7400-0000-ffff-0000ffff0001,62169,12870,52.51946,13.39851
Registration Beacon,Check-in desk,73676723-7400-0000-ffff-0000ffff0001,62169,12873,52.51948,13.39855

So the next time you have a box full of new beacons to register, give the Beacon Upload Tool a try and let us know about your experience.

Happy Uploading.

Sensorberg Tech

Sep 10 - iOS SDK callback cleanUp 1.0.5

We have cleaned up the callbacks in the iOS SDK in the 1.0.5 release. We are now exposing the SBSDKBeaconAction directly which contains all the necessary field you will need for your integration.

The change will also imply, that the integration needs to take care of the application state. If your app is in the background, show an UILocalNotification, when you app is open, you can choose to show custom UI. This sample shows an easy UIAlertView:

The short version:

  1. Your delegate receives the action in beaconManager:didResolveAction
  2. when the app is in the foreground, we show our in-app UI immediately.
  3. when the app is in the background, we can only show a UILocalNotification
  4. when the action has a delay, schedule a notification
  5. application:didReceiveLocalNotification receives the local notification and the attached data when the app is opened. Get the metadata off the notification and show the same custom UI.

Please check the latest sample implementation in the SBSDKAppDelegate.m on github, here are the relevant methods:

# pragma mark - Local Notifications & actions

- (void)beaconManager:(SBSDKManager *)manager didResolveAction:(SBSDKBeaconAction *)action {
    if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive || action.delaySeconds.integerValue > 0){
        [self displayLocalNotificationForAction:action];
    } else {
        [self showActionAsAlertView:action];
    }
}

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    NSLog(@"%s %@", __PRETTY_FUNCTION__, notification.alertBody);
    if (notification.userInfo[SBSDKAppDelegateLocalNotificationActionKey]) {
        SBSDKBeaconAction * action = [NSKeyedUnarchiver unarchiveObjectWithData:notification.userInfo[SBSDKAppDelegateLocalNotificationActionKey]];
        [self showActionAsAlertView:action];
    }
}

- (void)displayLocalNotificationForAction:(SBSDKBeaconAction *)action {
    // Construct local notification.
    UILocalNotification *localNotification = [[UILocalNotification alloc] init];

    localNotification.alertBody = [NSString stringWithFormat:@"%@\n%@", action.subject, action.body];
    localNotification.alertAction = @"Open";
    localNotification.soundName = UILocalNotificationDefaultSoundName;
    localNotification.userInfo = @{ SBSDKAppDelegateLocalNotificationActionKey   : [NSKeyedArchiver archivedDataWithRootObject:action]};
    if (action.delaySeconds.integerValue > 0) {
        localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:action.delaySeconds.doubleValue];
        [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
    } else {
        [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
    }

    self.localNotifications[action.actionId] = localNotification;
}

- (void)showActionAsAlertView:(SBSDKBeaconAction *)action {
    //show a boring notification:
    NSDictionary * payload = action.payload;
    //do something usefull with the payload, we´e boring and will just show an UIAlertView

    NSString * body;
    if (payload){
            body = [NSString stringWithFormat:@"%@\nPayload:\n%@", action.body, [action.payload description]];
        } else {
            body = action.body;
        }

    UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:action.subject
                                                             message:body
                                                            delegate:self
                                                   cancelButtonTitle:@"Ignore"
                                                   otherButtonTitles:@"Open URL", nil];
    objc_setAssociatedObject(alertView, SBSDKAppDelegateInAppMessageUrlKey, action.url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_setAssociatedObject(alertView, SBSDKAppDelegateInAppPayloadKey, payload, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    [alertView show];
}

# pragma mark UIAlertViewDelegate

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    // Check for associated URL to be presented to the user.
    NSURL *url = objc_getAssociatedObject(alertView, SBSDKAppDelegateInAppMessageUrlKey);
    NSDictionary * payload = objc_getAssociatedObject(alertView, SBSDKAppDelegateInAppPayloadKey);
    //do something usfull with the payload. We´e boring and we´l just open the URL

    if ((alertView.firstOtherButtonIndex == buttonIndex) && (url != nil)) {
        [[UIApplication sharedApplication] openURL:url];
    }
}

Jul 1 - New feature on the platform: Maps

Get a quick overview of the location of your Beacons

You can now enter the location of any of your Beacons on our Beacon Management Platform. While adding or editing a Beacon, you can do that by:

  • Directly entering the latitude and longitude coordinates of your Beacon (up to 14 decimals after the comma, way more than what is needed for a millimeter precision…)

  • Searching a location using the textfield; you can also move and zoom the map to manually find the location. You’ll be able to add it by a click on the map

Jun 26 - Tip: Remove Secret Codes Broadcastreceiver from your manifest

If you don´t want to ship the secret codes feature of the SDK, add this to your application part of your manifest.

<receiver
   android:name="com.sensorberg.sdk.SensorbergCodeReceiver"
   tools:node="remove"
   tools:selector="com.sensorberg.sdk" />

If you leave the receiver active you can also trigger it using adb:

#enable logging
adb shell am broadcast -W -a android.provider.Telephony.SECRET_CODE -d android_secret_code://73676723741
#disable logging
adb shell am broadcast -W -a android.provider.Telephony.SECRET_CODE -d android_secret_code://73676723740

Jun 26 - READ_SYNC_SETTINGS permission optional

If, for some reason your don´t like the READ_SYNC_SETTINGS permission to be added to your application since our Android SDK is using it, just add this line to your manifest:

<uses-permission
            android:name="android.permission.READ_SYNC_SETTINGS"
            tools:node="remove"
            tools:selector="com.sensorberg.sdk" />

This change comes to affect in the final Android 1.0.1 release.

Read all about the tools:remove feature in the Manifest Merger documentation

Don´t remove before 1.0.1

Don´t follow this tip unless you´re using 1.0.1. Previous versions of the SDK do not know about this optional permission and will crash.

Apr 28 - Detect your process

Detect the process your Application objects are running in.

When integrating the Sensorberg SDK you probably did notice, that we run in a separate process. This also means that our SDK process has its own Application instance.

The “Sensorberg Application object” actually should not do of your logic. No need to instantiate singletons…

IF you want to detect the process you´re running in here is some code:

Apr 22 - Payload feature for developers

Payload feature is your developers best friend

The payload feature in the beacon management frontend enables you to implement virtually unlimited use-case posibilities with beacons. We will highlight one use case in this post:

Use case description:

We would like to show a notification when the application is in the background with a message. Upon tapping the notification, the app should be opened and a new Activity (screen) should be shown with a QR code and some text. This can be used to deliver a coupon code when entering a shop.

Apr 17 - Software quality at Sensorberg

Software quality is important for us

We´re trying to keep you in the loop about the quality of our software. We have public continuous integration jobs for the majority of our open projects. Here is an overview of all the integration jobs and what they are doing:

Resolver Resolver Microservice Build Status

  • compile the project
  • run all tests

Apr 14 - Open sourcing the Resolver Microservice

We have decided to Open Source the Resolver microservice. You can use this microservice to set up your own location based service. For now, the service syncs with our cloud platform at manage.sensorberg.com and enables your apps to resolve beacons even when they are offline.

This is a prerelease of the Resolver as some features are still missing for a full rollout. We will keep you up to date about any changes in this blog.

Apr 14 - Open sourcing the Android SDK

We have decided to Open Source the Sensorberg Android SDK. You will find the source at github.com/sensorberg-dev/android-sdk. Feel free to investigate its structure and don’t forget pull-requests on issues that you encounter.

The SDK is a Release Candidate for the upcoming 1.0 release. It should be fully functional except some backend integrations for statistic features. We will keep you informed about the formal release of the 1.0 Version of the SDK.

Mar 12 - Android SDK Dependency Releases

Over the last weeks, we have open sourced the dependencies of the upcoming Android SDK.

There is currently 3 projects that are available via jcenter: