Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect and send custom attributes for capture feature #89

Merged
merged 34 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
80d1bc1
chore(sdk): add constant values for collect custom attributes
satsukies Jul 22, 2024
6be4756
feat(sdk): add interface for set/remove custom attributes from user
satsukies Jul 22, 2024
58aa865
feat(sdk): manage custom attributes with HashMap and implement valida…
satsukies Jul 22, 2024
fd54751
chore(sdk): bumpup version to 4.8.0
satsukies Jul 22, 2024
e4a3d40
fix(sdk): add import
satsukies Jul 22, 2024
ce47071
fix(sdk): fix test failed
satsukies Jul 22, 2024
b94250d
test(sdk): add test for CustomAttributes
satsukies Jul 22, 2024
d8bcc73
fix(sdk): add CustomAttributes for capture feature
satsukies Jul 22, 2024
c5c8f3c
test(sdk): add CustomAttributesTest#toJsonString()
satsukies Jul 22, 2024
9be4d92
feat(sdk): write custom attributes when collect device state event re…
satsukies Jul 22, 2024
480de52
chore(sample): update sample code set custom attributes
satsukies Jul 22, 2024
6b56334
fix(sdk): fix build failed due to missing some methods in sdkMock
satsukies Jul 22, 2024
11d51ab
fix(sdk): don't send values when it is null
satsukies Jul 22, 2024
2b8ecee
fix(sdk): don't send package name from SDK
satsukies Jul 22, 2024
95dad51
fix(sdk): provide getter only
satsukies Jul 22, 2024
4889e37
fix(sdk): fix too general naming of some constant values
satsukies Jul 22, 2024
823463e
fix(sdk): class became final and hide constructor from outside
satsukies Jul 22, 2024
71e158f
fix(sdk): simplify redundant condition
satsukies Jul 22, 2024
9bcb4f1
fix(sdk): improve put operation for thread safety
satsukies Jul 22, 2024
9c16ebf
fix(sdk): use Logger instead of Log
satsukies Jul 22, 2024
f1a9aff
test(sdk): add CustomAttributesInterfaceTest
satsukies Jul 22, 2024
854288a
chore(sample): improvement sample codes
satsukies Jul 22, 2024
c829658
test(sdk): fix symbolic link
satsukies Jul 23, 2024
1c5407d
test(sdk): fix testcase method naming
satsukies Jul 23, 2024
45ac031
fix(sdk): all public method always return non-null value
satsukies Jul 23, 2024
45084e1
test(sdk): add testcase checking behavior of isEmpty() and size()
satsukies Jul 23, 2024
4519aa2
fix(sdk): use try-catch insert value via content resolver
satsukies Jul 23, 2024
024b6cc
fix(sdk): append prefix to keys when call toJsonString()
satsukies Jul 23, 2024
1627bff
fix(sdk): use synchronized when access CustomAttributes
satsukies Jul 23, 2024
6f0ebd8
test(sdk): use different values, check return type
satsukies Jul 24, 2024
7a510d6
fix(sdk): fix CustomAttributes class become thread-safe
satsukies Jul 24, 2024
7391f7f
fix(sdk): send attributes collect by SDK
satsukies Jul 24, 2024
110a00e
chore(sdk): documentation
satsukies Jul 24, 2024
063fc76
test(sdk): add testcase of toJSONString()
satsukies Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ abstract class BaseSdkPlugin : Plugin<Project> {
/**
* sdk/java/com/deploygate/sdk/HostAppTest.java needs to be changed for a new release
*/
const val ARTIFACT_VERSION = "4.7.1"
const val ARTIFACT_VERSION = "4.8.0"

val JAVA_VERSION = JavaVersion.VERSION_1_7
}
Expand Down
7 changes: 7 additions & 0 deletions sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<!-- only for sample app -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<!-- collecting stats data from app&device -->
<uses-permission android:name="com.deploygate.permission.ACCESS_SDK" />

<!--
If you have multiple processes in your application, or you want to customize SDK initializer,
you need to add your own Application class here. In this example, the class is
Expand Down Expand Up @@ -67,4 +70,8 @@
<!-- SDK will be initialized through ContentProvider by default -->
<!-- Please refer to stablereal/AndroidManifest.xml -->
</application>

<queries>
<provider android:authorities="com.deploygate.external.sdk" />
</queries>
</manifest>
24 changes: 24 additions & 0 deletions sample/src/main/java/com/deploygate/sample/App.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.deploygate.sample;

import android.app.Application;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;

import com.deploygate.sdk.CustomAttributes;
import com.deploygate.sdk.DeployGate;
import com.deploygate.sdk.DeployGateCallback;

Expand Down Expand Up @@ -34,6 +36,14 @@ public void onInitialized(boolean isServiceAvailable) {
if (isServiceAvailable) {
Log.i(TAG, "SDK is available");
DeployGate.logInfo("SDK is available");

CustomAttributes attrs = new CustomAttributes();
attrs.putString("build_type", BuildConfig.BUILD_TYPE);
attrs.putString("flavor", BuildConfig.FLAVOR);
attrs.putString("version_name", BuildConfig.VERSION_NAME);
attrs.putInt("version_code", BuildConfig.VERSION_CODE);
attrs.putString("application_id", BuildConfig.APPLICATION_ID);
DeployGate.setRuntimeExtra(attrs);
} else {
Log.i(TAG, "SDK is unavailable");
DeployGate.logInfo("SDK is unavailable"); // this fails silently
Expand Down Expand Up @@ -84,4 +94,18 @@ public void onUpdateAvailable(
//
// You can use DeployGate.isAuthorized() later to check the installation is valid or not.
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);

CustomAttributes attrs = DeployGate.getRuntimeExtra();
if (attrs == null) {
attrs = new CustomAttributes();
}
attrs.putString("locale", newConfig.locale.toString());
attrs.putInt("orientation", newConfig.orientation);
attrs.putFloat("font_scale", newConfig.fontScale);
DeployGate.setRuntimeExtra(attrs);
}
}
14 changes: 14 additions & 0 deletions sample/src/main/java/com/deploygate/sample/SampleActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import android.widget.TextView;
import android.widget.Toast;

import com.deploygate.sdk.CustomAttributes;
import com.deploygate.sdk.DeployGate;
import com.deploygate.sdk.DeployGateCallback;

Expand Down Expand Up @@ -53,6 +54,19 @@ public void onCreate(Bundle savedInstanceState) {
mUpdateButton = (Button) findViewById(R.id.updateButton);
mLogMessage = (EditText) findViewById(R.id.message);
mDistributionComments = (LinearLayout) findViewById(R.id.distributionComments);


CustomAttributes attrs = DeployGate.getRuntimeExtra();
if (attrs == null) {
attrs = new CustomAttributes();
}
attrs.putString("string", "value");
attrs.putInt("int", 123);
attrs.putBoolean("boolean", true);
attrs.putFloat("float", 1.23f);
attrs.putDouble("double", 1.23);
attrs.putLong("long", 123L);
DeployGate.setRuntimeExtra(attrs);
}

@Override
Expand Down
115 changes: 115 additions & 0 deletions sdk/src/main/java/com/deploygate/sdk/CustomAttributes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.deploygate.sdk;

import android.util.Log;

import org.json.JSONObject;

import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

public class CustomAttributes {

private static final String TAG = "CustomAttributes";

private static final int MAX_ATTRIBUTES_SIZE = 8;
private static final Pattern VALID_KEY_PATTERN = Pattern.compile("^[a-z][_a-z0-9]{2,31}$");
private static final int MIN_KEY_LENGTH = 3;
private static final int MAX_KEY_LENGTH = 32;
private static final int MAX_VALUE_LENGTH = 64;

private final ConcurrentHashMap<String, Object> attributes;

public CustomAttributes() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't make it non-final/public unless required.

Suggested change
public class CustomAttributes {
private static final String TAG = "CustomAttributes";
private static final int MAX_ATTRIBUTES_SIZE = 8;
private static final Pattern VALID_KEY_PATTERN = Pattern.compile("^[a-z][_a-z0-9]{2,31}$");
private static final int MIN_KEY_LENGTH = 3;
private static final int MAX_KEY_LENGTH = 32;
private static final int MAX_VALUE_LENGTH = 64;
private final ConcurrentHashMap<String, Object> attributes;
public CustomAttributes() {
public final class CustomAttributes {
private static final String TAG = "CustomAttributes";
private static final int MAX_ATTRIBUTES_SIZE = 8;
private static final Pattern VALID_KEY_PATTERN = Pattern.compile("^[a-z][_a-z0-9]{2,31}$");
private static final int MIN_KEY_LENGTH = 3;
private static final int MAX_KEY_LENGTH = 32;
private static final int MAX_VALUE_LENGTH = 64;
private final ConcurrentHashMap<String, Object> attributes;
CustomAttributes() {

attributes = new ConcurrentHashMap<>();
}

public boolean putString(String key, String value) {
return putInternal(key, value);
}

public boolean putInt(String key, int value) {
return putInternal(key, value);
}

public boolean putLong(String key, long value) {
return putInternal(key, value);
}

public boolean putFloat(String key, float value) {
return putInternal(key, value);
}

public boolean putDouble(String key, double value) {
return putInternal(key, value);
}

public boolean putBoolean(String key, boolean value) {
return putInternal(key, value);
}

public void remove(String key) {
attributes.remove(key);
}

public void removeAll() {
attributes.clear();
}

public int size() {
return attributes.size();
}

public String toJsonString() {
return new JSONObject(attributes).toString();
}

private boolean putInternal(String key, Object value) {
if (!isValidKey(key)) {
return false;
}

if (!isValidValue(value)) {
return false;
}

attributes.put(key, value);
return true;
}

private boolean isValidKey(String key) {
if (size() >= MAX_ATTRIBUTES_SIZE && !attributes.containsKey(key)) {
Log.w(TAG, "Attributes already reached max size. Ignored: " + key);
return false;
}

if (key == null || key.equals("true") || key.equals("false") || key.equals("null")) {
Log.w(TAG, "Not allowed key: " + key);
return false;
}

if (key.length() < MIN_KEY_LENGTH || key.length() > MAX_KEY_LENGTH || !VALID_KEY_PATTERN.matcher(key).matches()) {
Log.w(TAG, "Invalid key: " + key);
return false;
}

return true;
}

private boolean isValidValue(Object value) {
if (value == null) {
Log.w(TAG, "Value is null");
return false;
}

if (value instanceof String && ((String) value).length() > MAX_VALUE_LENGTH) {
Log.w(TAG, "Value too long: " + value);
return false;
} else if (value instanceof String || value instanceof Number || value instanceof Boolean) {
return true;
} else {
// dead code
Log.w(TAG, "Invalid value: " + value);
return false;
}
}
}
83 changes: 83 additions & 0 deletions sdk/src/main/java/com/deploygate/sdk/DeployGate.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
Expand Down Expand Up @@ -59,6 +62,8 @@ public class DeployGate {
private final CustomLogInstructionSerializer mCustomLogInstructionSerializer;
private final HashSet<DeployGateCallback> mCallbacks;
private final HashMap<String, Bundle> mPendingEvents;
private CustomAttributes mBuildEnvironment;
private CustomAttributes mRuntimeExtra;
private final String mExpectedAuthor;
private String mAuthor;

Expand Down Expand Up @@ -127,6 +132,18 @@ public void onEvent(
} else {
Logger.w("streamed logcat is not supported");
}
} else if (DeployGateEvent.ACTION_COLLECT_DEVICE_STATES.equals(action)) {
Uri targetUri = Uri.parse(extras.getString(DeployGateEvent.EXTRA_TARGET_URI_FOR_REPORT_DEVICE_STATES));
Logger.d("collect-device-status event received: %s", targetUri);

ContentValues cv = new ContentValues();
cv.put(DeployGateEvent.KEY_BUILD_ENVIRONMENT, mBuildEnvironment.toJsonString());
cv.put(DeployGateEvent.KEY_RUNTIME_EXTRAS, mRuntimeExtra.toJsonString());
cv.put(DeployGateEvent.KEY_PACKAGE_NAME, mApplicationContext.getPackageName());
cv.put(DeployGateEvent.KEY_EVENT_AT, System.currentTimeMillis());

ContentResolver cr = mApplicationContext.getContentResolver();
cr.insert(targetUri, cv);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please introduce try-catch and suppress the error for safer operations.

} else {
Logger.w("%s is not supported by this sdk version", action);
}
Expand Down Expand Up @@ -279,6 +296,8 @@ private DeployGate(
mCustomLogInstructionSerializer = new CustomLogInstructionSerializer(mHostApp.packageName, sdkConfiguration.customLogConfiguration);
mCallbacks = new HashSet<>();
mPendingEvents = new HashMap<>();
mBuildEnvironment = null;
mRuntimeExtra = null;
mExpectedAuthor = sdkConfiguration.appOwnerName;

prepareBroadcastReceiver();
Expand Down Expand Up @@ -1390,4 +1409,68 @@ public static String getDistributionUserName() {

return sInstance.mDistributionUserName;
}

public static CustomAttributes getBuildEnvironment() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (sInstance != null) {
return sInstance.getBuildEnvironmentInternal();
}
return null;
}

public static void setBuildEnvironment(CustomAttributes attrs) {
if (sInstance != null) {
sInstance.setBuildEnvironmentInternal(attrs);
}
}

public static void clearBuildEnvironment() {
if (sInstance != null) {
sInstance.clearBuildEnvironmentInternal();
}
}

public static CustomAttributes getRuntimeExtra() {
if (sInstance != null) {
return sInstance.getRuntimeExtraInternal();
}
return null;
}

public static void setRuntimeExtra(CustomAttributes attrs) {
if (sInstance != null) {
sInstance.setRuntimeExtraInternal(attrs);
}
}

public static void clearRuntimeExtra() {
if (sInstance != null) {
sInstance.clearRuntimeExtraInternal();
}
}

private CustomAttributes getBuildEnvironmentInternal() {
return mBuildEnvironment;
}

private void setBuildEnvironmentInternal(CustomAttributes attrs) {
mBuildEnvironment = attrs;
}


private void clearBuildEnvironmentInternal() {
mBuildEnvironment = null;
}

private CustomAttributes getRuntimeExtraInternal() {
return mRuntimeExtra;
}


private void setRuntimeExtraInternal(CustomAttributes attrs) {
mRuntimeExtra = attrs;
}

private void clearRuntimeExtraInternal() {
mRuntimeExtra = null;
}
}
31 changes: 31 additions & 0 deletions sdk/src/main/java/com/deploygate/service/DeployGateEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public interface DeployGateEvent {
// namespace:
// ACTION => a
// EXTRA => e
// KEY => k
//
// content:
// should be hyphen-separated string and be lower cases
Expand All @@ -27,6 +28,11 @@ public interface DeployGateEvent {
public static final String ACTION_COMPOSE_COMMENT = "composeComment";
public static final String ACTION_VISIBILITY_EVENT = "a.visibility-event";

/**
* @since 4.8.0
*/
public static final String ACTION_COLLECT_DEVICE_STATES = "a.collect-device-states";

public static final String EXTRA_AUTHOR = "author";
public static final String EXTRA_EXPECTED_AUTHOR = "expectedAuthor";

Expand Down Expand Up @@ -121,6 +127,31 @@ public interface DeployGateEvent {
*/
public static final String EXTRA_VISIBILITY_EVENT_ELAPSED_REAL_TIME_IN_NANOS = "e.visibility-event-elapsed-real-time";

/**
* @since 4.8.0
*/
public static final String EXTRA_TARGET_URI_FOR_REPORT_DEVICE_STATES = "e.target-uri-for-report-device-states";

/**
* @since 4.8.0
*/
public static final String KEY_BUILD_ENVIRONMENT = "k.build-environment";

/**
* @since 4.8.0
*/
public static final String KEY_RUNTIME_EXTRAS = "k.runtime-extras";

/**
* @since 4.8.0
*/
public static final String KEY_PACKAGE_NAME = "k.package-name";

/**
* @since 4.8.0
*/
public static final String KEY_EVENT_AT = "k.event-at";

interface VisibilityType {
int BACKGROUND = 0;
int FOREGROUND = 1;
Expand Down
Loading
Loading