Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
Merge branch 'release/v1.0-beta.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
Benny Reimold committed May 7, 2015
2 parents 8c52fd0 + 0cab3ca commit 3930d79
Show file tree
Hide file tree
Showing 41 changed files with 1,684 additions and 803 deletions.
32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[ ![Download](https://api.bintray.com/packages/appinsights-android/maven/ApplicationInsights-Android/images/download.svg) ](https://bintray.com/appinsights-android/maven/ApplicationInsights-Android/_latestVersion)

# Application Insights for Android (1.0-beta.3)
# Application Insights for Android (1.0-beta.4)

This project provides an Android SDK for Application Insights. [Application Insights](http://azure.microsoft.com/en-us/services/application-insights/) is a service that allows developers to keep their applications available, performing, and succeeding. This module allows you to send telemetry of various kinds (events, traces, exceptions, etc.) to the Application Insights service where your data can be visualized in the Azure Portal.

Expand All @@ -24,16 +24,22 @@ Automatic collection of lifecycle-events requires API level 15 and up (Ice Cream

## <a name="1"></a> 1. Release Notes

* Cleaned code
* Single configuration class ```ApplicationInsightsConfig```
* Generic tracking method for ```TelemetryClient```
* Separate methods for enabling/disabling auto collection features (auto page view tracking, auto session renewal)
* Fixed context fields in telemetry data payload
* Improvements regarding threat safety
* Improved unit tests (now using Mockito)
* Simplified threading model (still deferring work to background tasks)
* Bugfix for sending logic (number of running operations wasn't decremented when we don't have a connection)
* Fix for potential memory leaks
* Updated code in sample app
* Data is now persisted when the user sends the app into the background (requires API level 14)
* Data is now persisted when the device is low on memory

##<a name="2"></a> 2. Breaking Changes

Starting with the first 1.0 stable release, we will start deprecating API instead of breaking old ones.

* **[1.0-beta.4]** **No breaking API changes**.
* Two setup-methods for ```ApplicationInsights```have been deprecated and will be removed in the next beta

* **[1.0-beta.3]** Configuration of the Application Insights SDK is now done using ```ApplicationInsightsConfig```. The previous config-classes have been removed

* **[1.0-beta.2]** To enable automatic lifecycle-tracking, Application Insights has to be set up with an instance of Application (see [Life-cycle tracking] (#2)), otherwise, lifecycle-tracking is disabled.
Expand Down Expand Up @@ -98,7 +104,7 @@ import com.microsoft.applicationinsights.library.ApplicationInsights;
and add

```java
ApplicationInsights.setup(this, getApplication());
ApplicationInsights.setup(this.getApplicationContext(), this.getApplication());
ApplicationInsights.start();
```

Expand Down Expand Up @@ -134,7 +140,7 @@ android {
It is also possible to set the instrumentation key of your app in code. This will override the one you might have set in your gradle or manifest file. Setting the instrumentation key programmatically can be done while setting up Application Insights:

```java
ApplicationInsights.setup(this, getApplication(), "<YOUR-INSTRUMENTATION-KEY>");
ApplicationInsights.setup(this.getApplicationContext(), getApplication(), "<YOUR-INSTRUMENTATION-KEY>");
ApplicationInsights.start();
```

Expand All @@ -145,7 +151,7 @@ The **developer mode** is enabled automatically in case the debugger is attached
You can explicitly enable/disable the developer mode like this:

```java
//do this after ApplicationInsights.setup(this, getApplication())
//do this after ApplicationInsights.setup(this.getApplicationContext(), this.getApplication())
//and before ApplicationInsights.start()

ApplicationInsights.setDeveloperMode(false);
Expand Down Expand Up @@ -192,16 +198,16 @@ client.trackEvent("sample event", properties);

## <a name="7"></a>7. Automatic collection of life-cycle events (Sessions & Page Views)

This only works in Android SDK version 15 and up (Ice Cream Sandwich+) and is **enabled by default**. Don't forget to set the Application instance when setting up Application Insights (otherwise auto collection will be disabled):
This only works in Android SDK version 15 and up (Ice Cream Sandwich+) and is **enabled by default**. Don't forget to provide an Application instance when setting up Application Insights (otherwise auto collection will be disabled):

```java
ApplicationInsights.setup(this, getApplication());
ApplicationInsights.setup(this.getApplicationContext(), this.getApplication());
```

If you want to explicitly **Disable** automatic collection of life-cycle events (auto session tracking and auto page view tracking), call ```setAutoCollectionDisabled``` inbetween setup and start of Application Insights.

```java
ApplicationInsights.setup(this);
ApplicationInsights.setup(this.getApplicationContext());
ApplicationInsights.setAutoCollectionDisabled(true); //disable the auto-collection
ApplicationInsights.start();
```
Expand Down Expand Up @@ -232,7 +238,7 @@ This feature can be disabled as follows:
To configure Application Insights according to your needs, first, call

```java
ApplicationInsights.setup(this, getApplication());
ApplicationInsights.setup(this.getApplicationContext(), this.getApplication());
```

After that you can use `ApplicationInsightsConfig` to customize the behavior and values of the SDK.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.microsoft.applicationinsights.appsample;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
Expand Down Expand Up @@ -52,17 +53,17 @@ protected void onCreate(Bundle savedInstanceState) {
.findFragmentById(R.id.item_list))
.setActivateOnItemClick(true);
}
ApplicationInsights.setup(this, getApplication());
ApplicationInsights.setup(this.getApplicationContext(), getApplication());

//ApplicationInsightsConfig config = ApplicationInsights.getConfig();
ApplicationInsightsConfig config = ApplicationInsights.getConfig();
//config.setSessionIntervalMs(30000);
//config.setEndpointUrl("https://myserver.com/v2/track");
//config.setMaxBatchCount(45);
config.setMaxBatchCount(45);

ApplicationInsights.setUserId("New user ID");
ApplicationInsights.renewSession("New session ID");
//ApplicationInsights.setUserId("New user ID");
//ApplicationInsights.renewSession("New session ID");

//ApplicationInsights.setDeveloperMode(false);
ApplicationInsights.setDeveloperMode(true);

HashMap<String, String> properties = new HashMap<String,String>();
properties.put("Hometown", "Karlsruhe");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,172 +1,108 @@
package com.microsoft.applicationinsights.library;

import android.test.AndroidTestCase;
import android.test.InstrumentationTestCase;

import com.microsoft.applicationinsights.contracts.Envelope;
import com.microsoft.applicationinsights.contracts.shared.IJsonSerializable;
import com.microsoft.applicationinsights.library.config.ApplicationInsightsConfig;
import com.microsoft.applicationinsights.library.config.IQueueConfig;

import junit.framework.Assert;

import java.io.StringWriter;
import java.util.LinkedList;
import java.util.Timer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.times;

public class ChannelQueueTest extends AndroidTestCase {
public class ChannelQueueTest extends InstrumentationTestCase {

private TestQueue queue;
private IJsonSerializable item;

private final int batchMargin = 50;
private PublicChannelQueue sut;
private IQueueConfig mockConfig;
private PublicPersistence mockPersistence;

public void setUp() throws Exception {
super.setUp();
this.queue = new TestQueue();
int batchInterval = 100;
this.queue.getQueueConfig().setMaxBatchIntervalMs(batchInterval);
this.item = new Envelope();
Persistence.initialize(this.getContext());
System.setProperty("dexmaker.dexcache",getInstrumentation().getTargetContext().getCacheDir().getPath());
mockConfig = mock(IQueueConfig.class);
sut = new PublicChannelQueue(mockConfig);
mockPersistence = mock(PublicPersistence.class);
sut.setPersistence(mockPersistence);
}

public void testGetConfig() throws Exception {
Assert.assertNotNull("Sender constructor should initialize config", this.queue.getQueueConfig());
public void testInitialisationWorks() {
Assert.assertNotNull(sut.config);
Assert.assertNotNull(sut.timer);
Assert.assertNotNull(sut.list);
Assert.assertEquals(0, sut.list.size());
Assert.assertFalse(sut.isCrashing);
}

public void testQueue() throws Exception {
Envelope env = new Envelope();
this.queue.enqueue(env);
this.queue.getTimer().cancel();
IJsonSerializable env2 = this.queue.getQueue().peek();
Assert.assertTrue("item was successfully queued", env == env2);
}
public void testItemGetsEnqueued(){
// Setup
when(mockConfig.getMaxBatchIntervalMs()).thenReturn(10000);
when(mockConfig.getMaxBatchCount()).thenReturn(3);

public void testFlush() throws Exception {
// Test
sut.enqueue(new Envelope());
sut.enqueue(new Envelope());

// Verify
Assert.assertEquals(2, sut.list.size());
}

public void testBatchingLimit() {
this.queue.getQueueConfig().setMaxBatchCount(3);
this.queue.enqueue(this.item);

// enqueue one item and verify that it did not trigger a enqueue
try {
this.queue.sendSignal.await(batchMargin, TimeUnit.MILLISECONDS);
Assert.assertEquals("batch was not sent before MaxIntervalMs",
1, this.queue.sendSignal.getCount());
Assert.assertNotSame("queue is not empty prior to sending data",
this.queue.getQueue().size(), 0);
} catch (InterruptedException e) {
Assert.fail("Failed to validate API\n\n" + e.toString());
}

// enqueue two items (to reach maxBatchCount) and verify that data was flushed
this.queue.enqueue(this.item);
this.queue.enqueue(this.item);
try {
this.queue.sendSignal.await(batchMargin, TimeUnit.MILLISECONDS);
Assert.assertEquals("batch was sent before maxIntervalMs after reaching MaxBatchCount",
0, this.queue.sendSignal.getCount());
Assert.assertEquals("queue is empty after sending data",
this.queue.getQueue().size(), 0);
} catch (InterruptedException e) {
Assert.fail("Failed to validate API\n\n" + e.toString());
}
}
public void testQueueFlushedIfMaxBatchCountReached() {
// Setup
when(mockConfig.getMaxBatchIntervalMs()).thenReturn(10000);
when(mockConfig.getMaxBatchCount()).thenReturn(3);

public void testBatchingLimitExceed() {
this.queue.getQueueConfig().setMaxBatchCount(3);

// enqueue 4 items (exceeding maxBatchCount is supported) and verify that data was flushed
this.queue.enqueue(this.item);
this.queue.enqueue(this.item);
this.queue.enqueue(this.item);
this.queue.enqueue(this.item);

try {
this.queue.sendSignal.await(batchMargin, TimeUnit.MILLISECONDS);
Assert.assertEquals("second batch was sent before maxIntervalMs after reaching MaxBatchCount",
0, this.queue.sendSignal.getCount());
Assert.assertEquals("queue is empty after sending data",
this.queue.getQueue().size(), 0);
} catch (InterruptedException e) {
Assert.fail("Failed to validate API\n\n" + e.toString());
}
}
// Test
sut.enqueue(new Envelope());
sut.enqueue(new Envelope());

public void testBatchingTimer() {
this.queue.getQueueConfig().setMaxBatchCount(3);

// enqueue one item and wait for the queue to sendPendingData via the timer
this.queue.enqueue(this.item);
try {
this.queue.sendSignal.await(batchMargin + this.queue.getQueueConfig().getMaxBatchIntervalMs() + 1, TimeUnit.MILLISECONDS);
Assert.assertEquals("single item was sent after reaching MaxInterval",
0, this.queue.sendSignal.getCount());
Assert.assertEquals("queue is empty after sending data",
this.queue.getQueue().size(), 0);
} catch (InterruptedException e) {
Assert.fail("Failed to validate API\n\n" + e.toString());
}
}
// Verify
Assert.assertEquals(2, sut.list.size());
verify(mockPersistence,never()).persist(any(IJsonSerializable[].class), anyBoolean());

public void testBatchingFlush() {
this.queue.getQueueConfig().setMaxBatchCount(3);

// enqueue one item and sendPendingData it to bypass the timer
this.queue.enqueue(this.item);
try {

this.queue.sendSignal.await(batchMargin, TimeUnit.MILLISECONDS);
Assert.assertEquals("single item was not sent before reaching MaxInterval",
1, this.queue.sendSignal.getCount());
Assert.assertNotSame("queue is not empty prior to sending data",
this.queue.getQueue().size(), 0);

this.queue.flush();
this.queue.sendSignal.await(batchMargin, TimeUnit.MILLISECONDS);
Assert.assertEquals("single item was sent after calling sender.sendPendingData",
0, this.queue.sendSignal.getCount());
Assert.assertEquals("queue is empty after sending data",
this.queue.getQueue().size(), 0);
} catch (InterruptedException e) {
Assert.fail("Failed to validate API\n\n" + e.toString());
}
}
sut.enqueue(new Envelope());

public void testDisableTelemetry() {
ApplicationInsights.setTelemetryDisabled(true);
Assert.assertEquals(0, sut.list.size());
verify(mockPersistence,times(1)).persist(any(IJsonSerializable[].class), anyBoolean());
}

this.queue.enqueue(this.item);
long queueSize = this.queue.getQueue().size();
Assert.assertEquals("item is not queued when telemetry is disabled", 0, queueSize);
public void testQueueFlushedAfterBatchIntervalReached() {
// Setup
when(mockConfig.getMaxBatchIntervalMs()).thenReturn(200);
when(mockConfig.getMaxBatchCount()).thenReturn(3);

ApplicationInsights.setTelemetryDisabled(false);
// Test
sut.enqueue(new Envelope());

this.queue.enqueue(this.item);
queueSize = this.queue.getQueue().size();
Assert.assertEquals("item is queued when telemetry is enabled", 1, queueSize);
// Verify
Assert.assertEquals(1, sut.list.size());
verify(mockPersistence,never()).persist(any(IJsonSerializable[].class), anyBoolean());
verify(mockPersistence,after(250).times(1)).persist(any(IJsonSerializable[].class), anyBoolean());
Assert.assertEquals(0, sut.list.size());
}

private class TestQueue extends ChannelQueue {

public CountDownLatch sendSignal;
public CountDownLatch responseSignal;
public StringWriter writer;
public void testFlushingQueueWorks() {
//Setup
when(mockConfig.getMaxBatchIntervalMs()).thenReturn(200);
when(mockConfig.getMaxBatchCount()).thenReturn(3);

public TestQueue() {
super(new ApplicationInsightsConfig());
this.sendSignal = new CountDownLatch(1);
this.responseSignal = new CountDownLatch(1);
}
sut.enqueue(new Envelope());
Assert.assertEquals(1, sut.list.size());
verify(mockPersistence,never()).persist(any(IJsonSerializable[].class), anyBoolean());

public LinkedList<IJsonSerializable> getQueue() {
return (LinkedList) this.list;
}
// Test
sut.flush();

public Timer getTimer() {
return this.timer;
}
// Verify
Assert.assertEquals(0, sut.list.size());
verify(mockPersistence,times(1)).persist(any(IJsonSerializable[].class), anyBoolean());
}


}
Loading

0 comments on commit 3930d79

Please sign in to comment.