Skip to content

Commit 7bb60af

Browse files
authored
Merge pull request #496 from Microsoft/develop
Version 0.11.2
2 parents 0fe73cf + 0fc6cd1 commit 7bb60af

File tree

11 files changed

+254
-97
lines changed

11 files changed

+254
-97
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The Mobile Center SDK uses a modular architecture so you can use any or all of t
1717

1818
3. **Mobile Center Distribute**: Mobile Center Distribute will let your users install a new version of the app when you distribute it via the Mobile Center. With a new version of the app available, the SDK will present an update dialog to the users to either download or postpone the new version. Once they choose to update, the SDK will start to update your application. This feature will NOT work if your app is deployed to the app store.
1919

20-
4. **Mobile Center Push**: Mobile Center Push enables you to send push notifications to users of your app from the Mobile Center portal. To do that the Mobile Center SDK and portal integrate with [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/).
20+
4. **Mobile Center Push**: Mobile Center Push enables you to send push notifications to users of your app from the Mobile Center portal. To do that, the Mobile Center SDK and portal integrate with [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/). You can also segment your user base based on a set of properties and send them targeted notifications.
2121

2222
## 1. Get started
2323
It is super easy to use Mobile Center. Have a look at our [get started documentation](https://docs.microsoft.com/en-us/mobile-center/sdk/getting-started/android) and onboard your app within minutes. Our [detailed documentation](https://docs.microsoft.com/en-us/mobile-center/sdk/) is available as well.

apps/sasquatch/src/main/java/com/microsoft/azure/mobile/sasquatch/activities/MainActivity.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,8 @@ public boolean onOptionsItemSelected(MenuItem item) {
157157
@Override
158158
protected void onNewIntent(Intent intent) {
159159
super.onNewIntent(intent);
160-
try {
161-
Push.class.getMethod("checkLaunchedFromNotification", Activity.class, Intent.class).invoke(null, this, intent);
162-
} catch (Exception ignored) {
163-
}
160+
Log.d(LOG_TAG, "onNewIntent triggered");
161+
Push.checkLaunchedFromNotification(this, intent);
164162
}
165163

166164
private AnalyticsListener getAnalyticsListener() {

sdk/mobile-center-analytics/src/main/java/com/microsoft/azure/mobile/analytics/Analytics.java

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ public class Analytics extends AbstractMobileCenterService {
4949
*/
5050
private static final String ACTIVITY_SUFFIX = "Activity";
5151

52+
/**
53+
* Max number of properties.
54+
*/
55+
private static final int MAX_PROPERTY_COUNT = 5;
56+
57+
/**
58+
* Max length of event/page name.
59+
*/
60+
@VisibleForTesting
61+
static final int MAX_NAME_LENGTH = 256;
62+
63+
/**
64+
* Max length of properties.
65+
*/
66+
@VisibleForTesting
67+
static final int MAX_PROPERTY_ITEM_LENGTH = 64;
68+
5269
/**
5370
* Shared instance.
5471
*/
@@ -184,8 +201,9 @@ protected static void trackPage(String name) {
184201
* Track a custom page with name and optional properties.
185202
* The name parameter can not be null or empty. Maximum allowed length = 256.
186203
* The properties parameter maximum item count = 5.
187-
* The properties keys/names can not be null or empty, maximum allowed key length = 64.
204+
* The properties keys can not be null or empty, maximum allowed key length = 64.
188205
* The properties values can not be null, maximum allowed value length = 64.
206+
* Any length of name/keys/values that are longer than each limit will be truncated.
189207
* <p>
190208
* TODO the backend does not support that service yet, will be public method later.
191209
*
@@ -195,7 +213,8 @@ protected static void trackPage(String name) {
195213
@SuppressWarnings("WeakerAccess")
196214
protected static void trackPage(String name, Map<String, String> properties) {
197215
final String logType = "Page";
198-
if (validateName(name, logType)) {
216+
name = validateName(name, logType);
217+
if (name != null) {
199218
Map<String, String> validatedProperties = validateProperties(properties, name, logType);
200219
getInstance().trackPageAsync(name, validatedProperties);
201220
}
@@ -215,16 +234,18 @@ public static void trackEvent(String name) {
215234
* Track a custom event with name and optional properties.
216235
* The name parameter can not be null or empty. Maximum allowed length = 256.
217236
* The properties parameter maximum item count = 5.
218-
* The properties keys/names can not be null or empty, maximum allowed key length = 64.
237+
* The properties keys can not be null or empty, maximum allowed key length = 64.
219238
* The properties values can not be null, maximum allowed value length = 64.
239+
* Any length of name/keys/values that are longer than each limit will be truncated.
220240
*
221241
* @param name An event name.
222242
* @param properties Optional properties.
223243
*/
224244
@SuppressWarnings("WeakerAccess")
225245
public static void trackEvent(String name, Map<String, String> properties) {
226246
final String logType = "Event";
227-
if (validateName(name, logType)) {
247+
name = validateName(name, logType);
248+
if (name != null) {
228249
Map<String, String> validatedProperties = validateProperties(properties, name, logType);
229250
getInstance().trackEventAsync(name, validatedProperties);
230251
}
@@ -250,19 +271,18 @@ private static String generatePageName(Class<?> activityClass) {
250271
*
251272
* @param name Log name to validate.
252273
* @param logType Log type.
253-
* @return <code>true</code> if validation succeeds, otherwise <code>false</code>.
274+
* @return <code>null</code> if validation failed, otherwise a valid name within the length limit will be returned.
254275
*/
255-
private static boolean validateName(String name, String logType) {
256-
final int maxNameLength = 256;
276+
private static String validateName(String name, String logType) {
257277
if (name == null || name.isEmpty()) {
258278
MobileCenterLog.error(Analytics.LOG_TAG, logType + " name cannot be null or empty.");
259-
return false;
279+
return null;
260280
}
261-
if (name.length() > maxNameLength) {
262-
MobileCenterLog.error(Analytics.LOG_TAG, String.format("%s '%s' : name length cannot be longer than %s characters.", logType, name, maxNameLength));
263-
return false;
281+
if (name.length() > MAX_NAME_LENGTH) {
282+
MobileCenterLog.warn(Analytics.LOG_TAG, String.format("%s '%s' : name length cannot be longer than %s characters. Name will be truncated.", logType, name, MAX_NAME_LENGTH));
283+
name = name.substring(0, MAX_NAME_LENGTH);
264284
}
265-
return true;
285+
return name;
266286
}
267287

268288
/**
@@ -277,36 +297,36 @@ private static Map<String, String> validateProperties(Map<String, String> proper
277297
if (properties == null)
278298
return null;
279299
String message;
280-
final int maxPropertiesCount = 5;
281-
final int maxPropertyItemLength = 64;
282300
Map<String, String> result = new HashMap<>();
283301
for (Map.Entry<String, String> property : properties.entrySet()) {
284-
if (result.size() >= maxPropertiesCount) {
285-
message = String.format("%s '%s' : properties cannot contain more than %s items. Skipping other properties.", logType, logName, maxPropertiesCount);
302+
String key = property.getKey();
303+
String value = property.getValue();
304+
if (result.size() >= MAX_PROPERTY_COUNT) {
305+
message = String.format("%s '%s' : properties cannot contain more than %s items. Skipping other properties.", logType, logName, MAX_PROPERTY_COUNT);
286306
MobileCenterLog.warn(Analytics.LOG_TAG, message);
287307
break;
288308
}
289-
if (property.getKey() == null || property.getKey().isEmpty()) {
309+
if (key == null || key.isEmpty()) {
290310
message = String.format("%s '%s' : a property key cannot be null or empty. Property will be skipped.", logType, logName);
291311
MobileCenterLog.warn(Analytics.LOG_TAG, message);
292312
continue;
293313
}
294-
if (property.getKey().length() > maxPropertyItemLength) {
295-
message = String.format("%s '%s' : property '%s' : property key length cannot be longer than %s characters. Property '%s' will be skipped.", logType, logName, property.getKey(), maxPropertyItemLength, property.getKey());
314+
if (value == null) {
315+
message = String.format("%s '%s' : property '%s' : property value cannot be null. Property '%s' will be skipped.", logType, logName, key, key);
296316
MobileCenterLog.warn(Analytics.LOG_TAG, message);
297317
continue;
298318
}
299-
if (property.getValue() == null) {
300-
message = String.format("%s '%s' : property '%s' : property value cannot be null. Property '%s' will be skipped.", logType, logName, property.getKey(), property.getKey());
319+
if (key.length() > MAX_PROPERTY_ITEM_LENGTH) {
320+
message = String.format("%s '%s' : property '%s' : property key length cannot be longer than %s characters. Property key will be truncated.", logType, logName, key, MAX_PROPERTY_ITEM_LENGTH);
301321
MobileCenterLog.warn(Analytics.LOG_TAG, message);
302-
continue;
322+
key = key.substring(0, MAX_PROPERTY_ITEM_LENGTH);
303323
}
304-
if (property.getValue().length() > maxPropertyItemLength) {
305-
message = String.format("%s '%s' : property '%s' : property value cannot be longer than %s characters. Property '%s' will be skipped.", logType, logName, property.getKey(), maxPropertyItemLength, property.getKey());
324+
if (value.length() > MAX_PROPERTY_ITEM_LENGTH) {
325+
message = String.format("%s '%s' : property '%s' : property value cannot be longer than %s characters. Property value will be truncated.", logType, logName, key, MAX_PROPERTY_ITEM_LENGTH);
306326
MobileCenterLog.warn(Analytics.LOG_TAG, message);
307-
continue;
327+
value = value.substring(0, MAX_PROPERTY_ITEM_LENGTH);
308328
}
309-
result.put(property.getKey(), property.getValue());
329+
result.put(key, value);
310330
}
311331
return result;
312332
}

sdk/mobile-center-analytics/src/test/java/com/microsoft/azure/mobile/analytics/AnalyticsTest.java

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,18 +258,28 @@ public void testTrackEvent() {
258258
Analytics.trackEvent(" ", null);
259259
verify(channel, times(1)).enqueue(any(Log.class), anyString());
260260
reset(channel);
261-
Analytics.trackEvent(generateString(257, '*'), null);
262-
verify(channel, never()).enqueue(any(Log.class), anyString());
261+
final String maxName = generateString(Analytics.MAX_NAME_LENGTH, '*');
262+
Analytics.trackEvent(maxName + "*", null);
263+
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
264+
265+
@Override
266+
public boolean matches(Object item) {
267+
if (item instanceof EventLog) {
268+
EventLog eventLog = (EventLog) item;
269+
return eventLog.getName().equals(maxName) && eventLog.getProperties() == null;
270+
}
271+
return false;
272+
}
273+
}), anyString());
263274
reset(channel);
264-
Analytics.trackEvent(generateString(256, '*'), null);
275+
Analytics.trackEvent(maxName, null);
265276
verify(channel, times(1)).enqueue(any(Log.class), anyString());
266277
reset(channel);
267278
Analytics.trackEvent("eventName", new HashMap<String, String>() {{
268279
put(null, null);
269280
put("", null);
270-
put(generateString(65, '*'), null);
281+
put(generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH + 1, '*'), null);
271282
put("1", null);
272-
put("2", generateString(65, '*'));
273283
}});
274284
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
275285

@@ -300,6 +310,26 @@ public boolean matches(Object item) {
300310
return false;
301311
}
302312
}), anyString());
313+
reset(channel);
314+
final String longerMapItem = generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH + 1, '*');
315+
Analytics.trackEvent("eventName", new HashMap<String, String>() {{
316+
put(longerMapItem, longerMapItem);
317+
}});
318+
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
319+
320+
@Override
321+
public boolean matches(Object item) {
322+
if (item instanceof EventLog) {
323+
EventLog eventLog = (EventLog) item;
324+
if (eventLog.getProperties().size() == 1) {
325+
Map.Entry<String, String> entry = eventLog.getProperties().entrySet().iterator().next();
326+
String truncatedMapItem = generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH, '*');
327+
return entry.getKey().length() == Analytics.MAX_PROPERTY_ITEM_LENGTH && entry.getValue().length() == Analytics.MAX_PROPERTY_ITEM_LENGTH;
328+
}
329+
}
330+
return false;
331+
}
332+
}), anyString());
303333
}
304334

305335
@Test
@@ -317,18 +347,28 @@ public void testTrackPage() {
317347
Analytics.trackPage(" ", null);
318348
verify(channel, times(1)).enqueue(any(Log.class), anyString());
319349
reset(channel);
320-
Analytics.trackPage(generateString(257, '*'), null);
321-
verify(channel, never()).enqueue(any(Log.class), anyString());
350+
final String maxName = generateString(Analytics.MAX_NAME_LENGTH, '*');
351+
Analytics.trackPage(maxName + "*", null);
352+
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
353+
354+
@Override
355+
public boolean matches(Object item) {
356+
if (item instanceof PageLog) {
357+
PageLog pageLog = (PageLog) item;
358+
return pageLog.getName().equals(maxName) && pageLog.getProperties() == null;
359+
}
360+
return false;
361+
}
362+
}), anyString());
322363
reset(channel);
323-
Analytics.trackPage(generateString(256, '*'), null);
364+
Analytics.trackPage(maxName, null);
324365
verify(channel, times(1)).enqueue(any(Log.class), anyString());
325366
reset(channel);
326367
Analytics.trackPage("pageName", new HashMap<String, String>() {{
327368
put(null, null);
328369
put("", null);
329-
put(generateString(65, '*'), null);
370+
put(generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH + 1, '*'), null);
330371
put("1", null);
331-
put("2", generateString(65, '*'));
332372
}});
333373
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
334374

@@ -359,6 +399,26 @@ public boolean matches(Object item) {
359399
return false;
360400
}
361401
}), anyString());
402+
reset(channel);
403+
final String longerMapItem = generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH + 1, '*');
404+
Analytics.trackPage("pageName", new HashMap<String, String>() {{
405+
put(longerMapItem, longerMapItem);
406+
}});
407+
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
408+
409+
@Override
410+
public boolean matches(Object item) {
411+
if (item instanceof PageLog) {
412+
PageLog pageLog = (PageLog) item;
413+
if (pageLog.getProperties().size() == 1) {
414+
Map.Entry<String, String> entry = pageLog.getProperties().entrySet().iterator().next();
415+
String truncatedMapItem = generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH, '*');
416+
return entry.getKey().length() == Analytics.MAX_PROPERTY_ITEM_LENGTH && entry.getValue().length() == Analytics.MAX_PROPERTY_ITEM_LENGTH;
417+
}
418+
}
419+
return false;
420+
}
421+
}), anyString());
362422
}
363423

364424
@Test

sdk/mobile-center-crashes/src/androidTest/java/com/microsoft/azure/mobile/crashes/WrapperSdkExceptionManagerAndroidTest.java

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
package com.microsoft.azure.mobile.crashes;
22

3+
import android.annotation.SuppressLint;
34
import android.app.Application;
4-
import android.content.Context;
55
import android.support.test.InstrumentationRegistry;
66

7+
import com.microsoft.azure.mobile.Constants;
78
import com.microsoft.azure.mobile.MobileCenter;
9+
import com.microsoft.azure.mobile.MobileCenterPrivateHelper;
810
import com.microsoft.azure.mobile.channel.Channel;
911
import com.microsoft.azure.mobile.crashes.ingestion.models.Exception;
1012
import com.microsoft.azure.mobile.crashes.utils.ErrorLogHelper;
11-
import com.microsoft.azure.mobile.utils.MobileCenterLog;
1213
import com.microsoft.azure.mobile.utils.storage.StorageHelper;
1314

15+
import org.junit.Assert;
1416
import org.junit.Before;
1517
import org.junit.BeforeClass;
1618
import org.junit.Test;
1719

1820
import java.io.File;
21+
import java.lang.reflect.Method;
1922
import java.util.UUID;
2023

2124
import static com.microsoft.azure.mobile.test.TestUtils.TAG;
@@ -27,24 +30,50 @@
2730

2831
public class WrapperSdkExceptionManagerAndroidTest {
2932

33+
@SuppressLint("StaticFieldLeak")
34+
private static Application sApplication;
35+
3036
@BeforeClass
3137
public static void setUpClass() {
32-
MobileCenterLog.setLogLevel(android.util.Log.VERBOSE);
33-
Context context = InstrumentationRegistry.getContext();
34-
MobileCenter.configure((Application) context.getApplicationContext(), "dummy");
38+
sApplication = (Application) InstrumentationRegistry.getContext().getApplicationContext();
39+
StorageHelper.initialize(sApplication);
40+
Constants.loadFromContext(sApplication);
3541
}
3642

3743
@Before
38-
public void cleanup() {
44+
public void setUp() throws java.lang.Exception {
3945
android.util.Log.i(TAG, "Cleanup");
4046
StorageHelper.PreferencesStorage.clear();
4147
for (File logFile : ErrorLogHelper.getErrorStorageDirectory().listFiles()) {
4248
assertTrue(logFile.delete());
4349
}
4450
}
4551

52+
private void startFresh() throws java.lang.Exception {
53+
54+
/* Configure new instance. */
55+
MobileCenterPrivateHelper.unsetInstance();
56+
Crashes.unsetInstance();
57+
MobileCenter.setLogLevel(android.util.Log.VERBOSE);
58+
MobileCenter.configure(sApplication, "a");
59+
60+
/* Replace channel. */
61+
Method method = MobileCenter.class.getDeclaredMethod("getInstance");
62+
method.setAccessible(true);
63+
MobileCenter mobileCenter = (MobileCenter) method.invoke(null);
64+
method = MobileCenter.class.getDeclaredMethod("setChannel", Channel.class);
65+
method.setAccessible(true);
66+
method.invoke(mobileCenter, mock(Channel.class));
67+
68+
/* Start crashes. */
69+
MobileCenter.start(Crashes.class);
70+
71+
/* Wait for start. */
72+
Assert.assertTrue(Crashes.isEnabled().get());
73+
}
74+
4675
@Test
47-
public void saveWrapperException() {
76+
public void saveWrapperException() throws java.lang.Exception {
4877

4978
class ErrorData {
5079
private byte[] data;
@@ -65,8 +94,7 @@ class ErrorData {
6594
for (ErrorData error : errors) {
6695

6796
/* Reset crash state as only 1 crash is saved per process life time. */
68-
Crashes.unsetInstance();
69-
Crashes.getInstance().onStarted(InstrumentationRegistry.getContext(), "", mock(Channel.class));
97+
startFresh();
7098

7199
/* Save crash. */
72100
error.id = WrapperSdkExceptionManager.saveWrapperException(Thread.currentThread(), new Exception(), error.data);

0 commit comments

Comments
 (0)