Skip to content

Commit 0bf65d8

Browse files
authored
Merge pull request #89 from DeployGate/feat/collect-device-stats
Collect and send custom attributes for capture feature
2 parents 250ed74 + 063fc76 commit 0bf65d8

File tree

14 files changed

+669
-2
lines changed

14 files changed

+669
-2
lines changed

plugins/library/src/main/java/com/deploygate/plugins/BaseSdkPlugin.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ abstract class BaseSdkPlugin : Plugin<Project> {
1616
/**
1717
* sdk/java/com/deploygate/sdk/HostAppTest.java needs to be changed for a new release
1818
*/
19-
const val ARTIFACT_VERSION = "4.7.1"
19+
const val ARTIFACT_VERSION = "4.8.0"
2020

2121
val JAVA_VERSION = JavaVersion.VERSION_1_7
2222
}

sample/src/main/AndroidManifest.xml

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
<!-- only for sample app -->
1616
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
1717

18+
<!-- collecting stats data from app&device -->
19+
<uses-permission android:name="com.deploygate.permission.ACCESS_SDK" />
20+
1821
<!--
1922
If you have multiple processes in your application, or you want to customize SDK initializer,
2023
you need to add your own Application class here. In this example, the class is
@@ -67,4 +70,8 @@
6770
<!-- SDK will be initialized through ContentProvider by default -->
6871
<!-- Please refer to stablereal/AndroidManifest.xml -->
6972
</application>
73+
74+
<queries>
75+
<provider android:authorities="com.deploygate.external.sdk" />
76+
</queries>
7077
</manifest>

sample/src/main/java/com/deploygate/sample/App.java

+16
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.deploygate.sample;
22

33
import android.app.Application;
4+
import android.content.res.Configuration;
45
import android.os.Bundle;
56
import android.util.Log;
67

8+
import com.deploygate.sdk.CustomAttributes;
79
import com.deploygate.sdk.DeployGate;
810
import com.deploygate.sdk.DeployGateCallback;
911

@@ -34,6 +36,10 @@ public void onInitialized(boolean isServiceAvailable) {
3436
if (isServiceAvailable) {
3537
Log.i(TAG, "SDK is available");
3638
DeployGate.logInfo("SDK is available");
39+
40+
CustomAttributes attrs = DeployGate.getBuildEnvironment();
41+
attrs.putString("build_type", BuildConfig.BUILD_TYPE);
42+
attrs.putString("flavor", BuildConfig.FLAVOR);
3743
} else {
3844
Log.i(TAG, "SDK is unavailable");
3945
DeployGate.logInfo("SDK is unavailable"); // this fails silently
@@ -84,4 +90,14 @@ public void onUpdateAvailable(
8490
//
8591
// You can use DeployGate.isAuthorized() later to check the installation is valid or not.
8692
}
93+
94+
@Override
95+
public void onConfigurationChanged(Configuration newConfig) {
96+
super.onConfigurationChanged(newConfig);
97+
98+
CustomAttributes attrs = DeployGate.getRuntimeExtra();
99+
attrs.putString("locale", newConfig.locale.toString());
100+
attrs.putInt("orientation", newConfig.orientation);
101+
attrs.putFloat("font_scale", newConfig.fontScale);
102+
}
87103
}

sample/src/main/java/com/deploygate/sample/SampleActivity.java

+10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import android.widget.TextView;
1515
import android.widget.Toast;
1616

17+
import com.deploygate.sdk.CustomAttributes;
1718
import com.deploygate.sdk.DeployGate;
1819
import com.deploygate.sdk.DeployGateCallback;
1920

@@ -53,6 +54,15 @@ public void onCreate(Bundle savedInstanceState) {
5354
mUpdateButton = (Button) findViewById(R.id.updateButton);
5455
mLogMessage = (EditText) findViewById(R.id.message);
5556
mDistributionComments = (LinearLayout) findViewById(R.id.distributionComments);
57+
58+
59+
CustomAttributes attrs = DeployGate.getRuntimeExtra();
60+
attrs.putString("string", "value");
61+
attrs.putInt("int", 123);
62+
attrs.putBoolean("boolean", true);
63+
attrs.putFloat("float", 1.23f);
64+
attrs.putDouble("double", 1.23);
65+
attrs.putLong("long", 123L);
5666
}
5767

5868
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package com.deploygate.sdk;
2+
3+
import com.deploygate.sdk.internal.Logger;
4+
5+
import org.json.JSONException;
6+
import org.json.JSONObject;
7+
8+
import java.util.regex.Pattern;
9+
10+
/**
11+
* This class provides store key-value pairs.
12+
* These methods are thread-safe.
13+
*/
14+
public final class CustomAttributes {
15+
16+
private static final String TAG = "CustomAttributes";
17+
18+
private static final int MAX_ATTRIBUTES_SIZE = 8;
19+
private static final Pattern VALID_KEY_PATTERN = Pattern.compile("^[a-z][_a-z0-9]{2,31}$");
20+
private static final int MAX_VALUE_LENGTH = 64;
21+
22+
private final Object mLock;
23+
private JSONObject attributes;
24+
25+
CustomAttributes() {
26+
mLock = new Object();
27+
attributes = new JSONObject();
28+
}
29+
30+
/**
31+
* Put a string value with the key.
32+
* If the key already exists, the value will be overwritten.
33+
* @param key key must be non-null and match the valid pattern.
34+
* @param value value must be non-null and its length must be less than 64.
35+
* @return true if the value is put successfully, otherwise false.
36+
* @see CustomAttributes#VALID_KEY_PATTERN
37+
*/
38+
public boolean putString(String key, String value) {
39+
return putInternal(key, value);
40+
}
41+
42+
/**
43+
* Put an int value with the key.
44+
* If the key already exists, the value will be overwritten.
45+
* @param key key must be non-null and match the valid pattern.
46+
* @param value int value
47+
* @return true if the value is put successfully, otherwise false.
48+
* @see CustomAttributes#VALID_KEY_PATTERN
49+
*/
50+
public boolean putInt(String key, int value) {
51+
return putInternal(key, value);
52+
}
53+
54+
/**
55+
* Put a long value with the key.
56+
* If the key already exists, the value will be overwritten.
57+
* @param key key must be non-null and match the valid pattern.
58+
* @param value long value
59+
* @return true if the value is put successfully, otherwise false.
60+
* @see CustomAttributes#VALID_KEY_PATTERN
61+
*/
62+
public boolean putLong(String key, long value) {
63+
return putInternal(key, value);
64+
}
65+
66+
/**
67+
* Put a float value with the key.
68+
* If the key already exists, the value will be overwritten.
69+
* @param key key must be non-null and match the valid pattern.
70+
* @param value float value
71+
* @return true if the value is put successfully, otherwise false.
72+
* @see CustomAttributes#VALID_KEY_PATTERN
73+
*/
74+
public boolean putFloat(String key, float value) {
75+
return putInternal(key, value);
76+
}
77+
78+
/**
79+
* Put a double value with the key.
80+
* If the key already exists, the value will be overwritten.
81+
* @param key key must be non-null and match the valid pattern.
82+
* @param value double value
83+
* @return true if the value is put successfully, otherwise false.
84+
* @see CustomAttributes#VALID_KEY_PATTERN
85+
*/
86+
public boolean putDouble(String key, double value) {
87+
return putInternal(key, value);
88+
}
89+
90+
/**
91+
* Put a boolean value with the key.
92+
* If the key already exists, the value will be overwritten.
93+
* @param key key must be non-null and match the valid pattern.
94+
* @param value boolean value
95+
* @return true if the value is put successfully, otherwise false.
96+
* @see CustomAttributes#VALID_KEY_PATTERN
97+
*/
98+
public boolean putBoolean(String key, boolean value) {
99+
return putInternal(key, value);
100+
}
101+
102+
/**
103+
* Remove the value with the key.
104+
* @param key name of the key to be removed.
105+
*/
106+
public void remove(String key) {
107+
synchronized (mLock) {
108+
attributes.remove(key);
109+
}
110+
}
111+
112+
/**
113+
* Remove all key-value pairs.
114+
*/
115+
public void removeAll() {
116+
synchronized (mLock) {
117+
// recreate new object instead of removing all keys
118+
attributes = new JSONObject();
119+
}
120+
}
121+
122+
int size() {
123+
synchronized (mLock) {
124+
return attributes.length();
125+
}
126+
}
127+
128+
String getJSONString() {
129+
synchronized (mLock) {
130+
return attributes.toString();
131+
}
132+
}
133+
134+
private boolean putInternal(String key, Object value) {
135+
if (!isValidKey(key)) {
136+
return false;
137+
}
138+
139+
if (!isValidValue(value)) {
140+
return false;
141+
}
142+
143+
synchronized (mLock) {
144+
try {
145+
attributes.put(key, value);
146+
147+
if (attributes.length() > MAX_ATTRIBUTES_SIZE) {
148+
// rollback put operation
149+
attributes.remove(key);
150+
Logger.w(TAG, "Attributes already reached max size. Ignored: " + key);
151+
return false;
152+
}
153+
} catch (JSONException e) {
154+
Logger.w(TAG, "Failed to put attribute: " + key, e);
155+
return false;
156+
}
157+
}
158+
159+
return true;
160+
}
161+
162+
private boolean isValidKey(String key) {
163+
if (key == null || key.equals("true") || key.equals("false") || key.equals("null")) {
164+
Logger.w(TAG, "Not allowed key: " + key);
165+
return false;
166+
}
167+
168+
if (!VALID_KEY_PATTERN.matcher(key).matches()) {
169+
Logger.w(TAG, "Invalid key: " + key);
170+
return false;
171+
}
172+
173+
return true;
174+
}
175+
176+
private boolean isValidValue(Object value) {
177+
if (value == null) {
178+
Logger.w(TAG, "Value is null");
179+
return false;
180+
}
181+
182+
if (value instanceof String && ((String) value).length() > MAX_VALUE_LENGTH) {
183+
Logger.w(TAG, "Value too long: " + value);
184+
return false;
185+
} else if (value instanceof String || value instanceof Number || value instanceof Boolean) {
186+
return true;
187+
} else {
188+
// dead code
189+
Logger.w(TAG, "Invalid value: " + value);
190+
return false;
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)