From 47dc1965a9abe410e1b6256ea4301f00d203781b Mon Sep 17 00:00:00 2001 From: Mikhail Suendukov Date: Thu, 29 Jun 2023 17:52:31 +0200 Subject: [PATCH 1/2] Bump versions --- CHANGELOG.md | 4 +++- apps/sasquatch/build.gradle | 6 +++--- versions.gradle | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acb422ae1..cdcb4b655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # App Center SDK for Android Change Log -## Version 5.0.2 (In development) +## Version 5.0.3 (In development) + +## Version 5.0.2 ### AppCenter diff --git a/apps/sasquatch/build.gradle b/apps/sasquatch/build.gradle index 8164cfcb9..81554af26 100644 --- a/apps/sasquatch/build.gradle +++ b/apps/sasquatch/build.gradle @@ -14,8 +14,8 @@ android { flavorDimensions "dependency", "distribute" defaultConfig { - versionCode 300 - versionName "5.0.1" + versionCode 302 + versionName "5.0.3" externalNativeBuild { ndkBuild { arguments "NDK_APPLICATION_MK=Application.mk", "V=1" @@ -100,7 +100,7 @@ dependencies { projectDependencyImplementation project(':sdk:appcenter-analytics') projectDependencyImplementation project(':sdk:appcenter-crashes') - def appCenterSdkVersion = "5.0.1" + def appCenterSdkVersion = "5.0.2" mavenCentralDependencyImplementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}" mavenCentralDependencyImplementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}" diff --git a/versions.gradle b/versions.gradle index b1d7f3baa..f8f00f897 100644 --- a/versions.gradle +++ b/versions.gradle @@ -6,8 +6,8 @@ // Version constants ext { - versionCode = 71 - versionName = '5.0.2' + versionCode = 72 + versionName = '5.0.3' minSdkVersion = 21 compileSdkVersion = 33 targetSdkVersion = 33 From 46e6bf21ee208e30c568b094d057b1820086f9c5 Mon Sep 17 00:00:00 2001 From: MikhailSuendukov <110986399+MikhailSuendukov@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:44:52 +0200 Subject: [PATCH 2/2] Add dataResidencyRegion option for sending to portal (#1704) Co-authored-by: Dima --- .../sasquatch/activities/MainActivity.java | 7 ++ .../activities/SettingsActivity.java | 28 ++++++ .../src/main/res/values/settings.xml | 4 + apps/sasquatch/src/main/res/xml/settings.xml | 5 ++ .../utils/ErrorLogHelperAndroidTest.java | 27 ++++-- .../microsoft/appcenter/crashes/Crashes.java | 11 +++ .../crashes/utils/ErrorLogHelper.java | 44 +++++++++ .../crashes/utils/ErrorLogHelperTest.java | 12 ++- .../com/microsoft/appcenter/AppCenter.java | 25 ++++++ .../appcenter/channel/DefaultChannel.java | 6 ++ .../ingestion/models/AbstractLog.java | 34 ++++++- .../appcenter/ingestion/models/Log.java | 17 ++++ .../microsoft/appcenter/AppCenterTest.java | 10 +++ .../channel/ChannelLogDecorateTest.java | 1 + .../ingestion/models/AbstractLogTest.java | 89 +++++++++++++++++++ 15 files changed, 310 insertions(+), 10 deletions(-) diff --git a/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/MainActivity.java b/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/MainActivity.java index c6e5cc340..06772975d 100644 --- a/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/MainActivity.java +++ b/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/MainActivity.java @@ -132,6 +132,13 @@ static void startAppCenter(Application application, String startTypeString) { AppCenter.setCountryCode(countryCode); } +// TODO: uncomment this code after release sdk to maven +// /* Set data residency region. */ +// String dataResidencyRegion = MainActivity.sSharedPreferences.getString(application.getString(R.string.data_residency_region_key), null); +// if (dataResidencyRegion != null) { +// AppCenter.setDataResidencyRegion(dataResidencyRegion); +// } + /* Set the track explicitly only if we set it in settings, to test the initial public by default at first launch. */ int savedTrack = sSharedPreferences.getInt(application.getString(R.string.appcenter_distribute_track_state_key), 0); if (savedTrack != 0) { diff --git a/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/SettingsActivity.java b/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/SettingsActivity.java index af50f4378..6c2f1dfae 100644 --- a/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/SettingsActivity.java +++ b/apps/sasquatch/src/main/java/com/microsoft/appcenter/sasquatch/activities/SettingsActivity.java @@ -136,6 +136,34 @@ public void onClick(DialogInterface dialog, int which) { return true; } }); + initClickableSetting(R.string.data_residency_region_key, MainActivity.sSharedPreferences.getString(getActivity().getString(R.string.data_residency_region_key), null), new Preference.OnPreferenceClickListener() { + + @Override + public boolean onPreferenceClick(final Preference preference) { + final EditText input = new EditText(getActivity()); + input.setInputType(InputType.TYPE_CLASS_TEXT); + input.setHint(R.string.data_residency_region_title); + input.setText(MainActivity.sSharedPreferences.getString(getActivity().getString(R.string.data_residency_region_key), null)); + input.setSelection(input.getText().length()); + new AlertDialog.Builder(getActivity()).setTitle(R.string.data_residency_region_title).setView(input) + .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { + + @SuppressLint("CommitPrefEdits") + @Override + public void onClick(DialogInterface dialog, int which) { + MainActivity.sSharedPreferences + .edit() + .putString(getActivity().getString(R.string.data_residency_region_key), input.getText().toString()) + .apply(); + preference.setSummary(input.getText()); + Toast.makeText(getActivity(), getActivity().getString(R.string.data_residency_region_save_message), Toast.LENGTH_SHORT).show(); + } + }) + .setNegativeButton(R.string.cancel, null) + .create().show(); + return true; + } + }); initClickableSetting(R.string.storage_size_key, Formatter.formatFileSize(getActivity(), MainActivity.sSharedPreferences.getLong(MAX_STORAGE_SIZE_KEY, DEFAULT_MAX_STORAGE_SIZE)), new Preference.OnPreferenceClickListener() { @Override diff --git a/apps/sasquatch/src/main/res/values/settings.xml b/apps/sasquatch/src/main/res/values/settings.xml index 1ee12aa7f..a78dd2103 100644 --- a/apps/sasquatch/src/main/res/values/settings.xml +++ b/apps/sasquatch/src/main/res/values/settings.xml @@ -16,6 +16,10 @@ Country Code Country code value will be applied after the application restart. + data_residency_region + Data residency region + Data residency region value will be applied after the application restart. + storage_file_size_key Storage File Size diff --git a/apps/sasquatch/src/main/res/xml/settings.xml b/apps/sasquatch/src/main/res/xml/settings.xml index 6ff261e1d..1c8e8a0ae 100644 --- a/apps/sasquatch/src/main/res/xml/settings.xml +++ b/apps/sasquatch/src/main/res/xml/settings.xml @@ -11,6 +11,11 @@ + diff --git a/sdk/appcenter-crashes/src/androidTest/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelperAndroidTest.java b/sdk/appcenter-crashes/src/androidTest/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelperAndroidTest.java index e1ebe2903..18bcfff1f 100644 --- a/sdk/appcenter-crashes/src/androidTest/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelperAndroidTest.java +++ b/sdk/appcenter-crashes/src/androidTest/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelperAndroidTest.java @@ -5,6 +5,12 @@ package com.microsoft.appcenter.crashes.utils; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + import androidx.test.platform.app.InstrumentationRegistry; import com.microsoft.appcenter.Constants; @@ -19,12 +25,6 @@ import java.io.File; import java.util.UUID; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - @SuppressWarnings("unused") public class ErrorLogHelperAndroidTest { @@ -170,4 +170,19 @@ public void parseDevice() { assertNotNull(device4); assertNull(userId4); } + + @Test + public void parseDataResidencyRegion() { + String mockDataResidencyRegion = "mockRegion"; + String mockContextInformation = "{\"dataResidencyRegion\":\"" + mockDataResidencyRegion + "\"}"; + String result = ErrorLogHelper.parseDataResidencyRegion(mockContextInformation); + assertEquals(mockDataResidencyRegion, result); + } + + @Test() + public void parseDataResidencyRegionIncorrectJson() { + String invalidJson = "invalidJson"; + String result = ErrorLogHelper.parseDataResidencyRegion(invalidJson); + assertNull(result); + } } diff --git a/sdk/appcenter-crashes/src/main/java/com/microsoft/appcenter/crashes/Crashes.java b/sdk/appcenter-crashes/src/main/java/com/microsoft/appcenter/crashes/Crashes.java index 4ae6e3864..024b2e459 100644 --- a/sdk/appcenter-crashes/src/main/java/com/microsoft/appcenter/crashes/Crashes.java +++ b/sdk/appcenter-crashes/src/main/java/com/microsoft/appcenter/crashes/Crashes.java @@ -15,6 +15,7 @@ import androidx.annotation.WorkerThread; import com.microsoft.appcenter.AbstractAppCenterService; +import com.microsoft.appcenter.AppCenter; import com.microsoft.appcenter.Constants; import com.microsoft.appcenter.Flags; import com.microsoft.appcenter.channel.Channel; @@ -639,6 +640,8 @@ private synchronized UUID queueException(@NonNull final ExceptionModelBuilder ex final String userId = UserIdContext.getInstance().getUserId(); final UUID errorId = UUID.randomUUID(); final Map validatedProperties = ErrorLogHelper.validateProperties(properties, "HandledError"); + final String dataResidencyRegion = AppCenter.getDataResidencyRegion(); + post(new Runnable() { @Override @@ -648,11 +651,17 @@ public void run() { HandledErrorLog errorLog = new HandledErrorLog(); errorLog.setId(errorId); errorLog.setUserId(userId); + errorLog.setDataResidencyRegion(dataResidencyRegion); errorLog.setException(exceptionModelBuilder.buildExceptionModel()); errorLog.setProperties(validatedProperties); mChannel.enqueue(errorLog, ERROR_GROUP, Flags.DEFAULTS); /* Then attachments if any. */ + if (attachments != null) { + for (ErrorAttachmentLog attachment : attachments) { + attachment.setDataResidencyRegion(dataResidencyRegion); + } + } sendErrorAttachment(errorId, attachments); } }); @@ -780,6 +789,7 @@ private void processSingleMinidump(File minidumpFile, File minidumpFolder) { errorLog.setProcessName(""); try { String savedUserId = ErrorLogHelper.getStoredUserInfo(minidumpFolder); + String dataResidencyRegion = ErrorLogHelper.getStoredDataResidencyRegion(minidumpFolder); Device savedDeviceInfo = ErrorLogHelper.getStoredDeviceInfo(minidumpFolder); if (savedDeviceInfo == null) { @@ -792,6 +802,7 @@ private void processSingleMinidump(File minidumpFile, File minidumpFolder) { } errorLog.setDevice(savedDeviceInfo); errorLog.setUserId(savedUserId); + errorLog.setDataResidencyRegion(dataResidencyRegion); saveErrorLogFiles(new NativeException(), errorLog); if (!minidumpFile.renameTo(dest)) { throw new IOException("Failed to move file"); diff --git a/sdk/appcenter-crashes/src/main/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelper.java b/sdk/appcenter-crashes/src/main/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelper.java index a76d54fbd..068ed00b4 100644 --- a/sdk/appcenter-crashes/src/main/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelper.java +++ b/sdk/appcenter-crashes/src/main/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelper.java @@ -14,6 +14,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.microsoft.appcenter.AppCenter; import com.microsoft.appcenter.Constants; import com.microsoft.appcenter.crashes.Crashes; import com.microsoft.appcenter.crashes.ingestion.models.Exception; @@ -144,6 +145,11 @@ public class ErrorLogHelper { @VisibleForTesting static String DEVICE_INFO_KEY = "DEVICE_INFO"; + /** + * Key for data residency region. + */ + private static final String DATA_RESIDENCY_REGION_KEY = "dataResidencyRegion"; + /** * Key for saving userId to JSON. */ @@ -168,6 +174,9 @@ public static ManagedErrorLog createErrorLog(@NonNull Context context, @NonNull /* Set user identifier. */ errorLog.setUserId(UserIdContext.getInstance().getUserId()); + /* Set data residency region. */ + errorLog.setDataResidencyRegion(AppCenter.getDataResidencyRegion()); + /* Snapshot device properties. */ try { errorLog.setDevice(DeviceInfoHelper.getDeviceInfo(context)); @@ -287,6 +296,7 @@ public static synchronized File getNewMinidumpSubfolderWithContextData(Context c try { Device deviceInfo = DeviceInfoHelper.getDeviceInfo(context); String userIdContext = UserIdContext.getInstance().getUserId(); + String dataResidencyRegion = AppCenter.getDataResidencyRegion(); deviceInfo.setWrapperSdkName(WRAPPER_SDK_NAME_NDK); /* To JSON. */ @@ -296,6 +306,7 @@ public static synchronized File getNewMinidumpSubfolderWithContextData(Context c writer.endObject(); String deviceInfoString = writer.toString(); JSONObject jsonObject = new JSONObject(); + jsonObject.put(DATA_RESIDENCY_REGION_KEY, dataResidencyRegion); jsonObject.put(DEVICE_INFO_KEY, deviceInfoString); jsonObject.put(USER_ID_KEY, userIdContext); @@ -371,6 +382,21 @@ public static String getStoredUserInfo(File logFolder) { return parseUserId(userInformationString); } + /** + * Get data residency region. + * + * @param logFolder folder where to look for stored data residency region. + * @return a data residency region or null. + */ + @Nullable + public static String getStoredDataResidencyRegion(File logFolder) { + String context = getContextInformation(logFolder); + if (context == null) { + return null; + } + return parseDataResidencyRegion(context); + } + /** * Get data about userId and deviceInfo in JSON format. * @param logFolder - path to folder where placed file with data about userId and deviceId. @@ -439,6 +465,24 @@ static Device parseDevice(String contextInformation) { return null; } + /** + * Look for 'dataResidencyRegion' data in file inside the minidump folder and parse it. + * @param contextInformation - data with information about userId. + * @return dataResidencyRegion or null. + */ + @VisibleForTesting + static String parseDataResidencyRegion(String contextInformation) { + try { + JSONObject jsonObject = new JSONObject(contextInformation); + if (jsonObject.has(DATA_RESIDENCY_REGION_KEY)) { + return jsonObject.getString(DATA_RESIDENCY_REGION_KEY); + } + } catch (JSONException e) { + AppCenterLog.error(Crashes.LOG_TAG, "Failed to deserialize data residency region.", e); + } + return null; + } + /** * Remove the minidump sub-folders from previous sessions in the 'minidump/new' folder. * Minidumps from these folders should already be moved to the 'minidump/pending' folder, diff --git a/sdk/appcenter-crashes/src/test/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelperTest.java b/sdk/appcenter-crashes/src/test/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelperTest.java index e24fa5233..dac2d9749 100644 --- a/sdk/appcenter-crashes/src/test/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelperTest.java +++ b/sdk/appcenter-crashes/src/test/java/com/microsoft/appcenter/crashes/utils/ErrorLogHelperTest.java @@ -428,26 +428,30 @@ public void getStoredDeviceInfo() throws IOException { } @Test - public void getStoredDeviceInfoAndUserIdNull() { + public void getStoredMinidumpFileContentNull() { File minidumpFolder = mock(File.class); when(minidumpFolder.listFiles(any(FilenameFilter.class))).thenReturn(null); Device storedDeviceInfo = ErrorLogHelper.getStoredDeviceInfo(minidumpFolder); String storedUserId = ErrorLogHelper.getStoredUserInfo(minidumpFolder); + String dataResidencyRegion = ErrorLogHelper.getStoredDataResidencyRegion(minidumpFolder); assertNull(storedDeviceInfo); assertNull(storedUserId); + assertNull(dataResidencyRegion); } @Test - public void getStoredDeviceInfoAndUserIdEmpty() throws IOException { + public void getStoredMinidumpFileContentEmpty() throws IOException { File minidumpFolder = mTemporaryFolder.newFolder("minidump"); Device storedDeviceInfo = ErrorLogHelper.getStoredDeviceInfo(minidumpFolder); String storedUserId = ErrorLogHelper.getStoredUserInfo(minidumpFolder); + String dataResidencyRegion = ErrorLogHelper.getStoredDataResidencyRegion(minidumpFolder); assertNull(storedDeviceInfo); assertNull(storedUserId); + assertNull(dataResidencyRegion); } @Test - public void getStoredDeviceInfoAndUserInfoCannotRead() throws IOException { + public void getStoredMinidumpFileContentCannotRead() throws IOException { File minidumpFolder = mTemporaryFolder.newFolder("minidump"); File deviceInfoFile = new File(minidumpFolder, ErrorLogHelper.DEVICE_INFO_FILE); assertTrue(deviceInfoFile.createNewFile()); @@ -457,6 +461,8 @@ public void getStoredDeviceInfoAndUserInfoCannotRead() throws IOException { assertNull(storedDeviceInfo); String userInfo = ErrorLogHelper.getStoredUserInfo(minidumpFolder); assertNull(userInfo); + String dataResidencyRegion = ErrorLogHelper.getStoredDataResidencyRegion(minidumpFolder); + assertNull(dataResidencyRegion); } @Test diff --git a/sdk/appcenter/src/main/java/com/microsoft/appcenter/AppCenter.java b/sdk/appcenter/src/main/java/com/microsoft/appcenter/AppCenter.java index 87962c733..7d59dc468 100644 --- a/sdk/appcenter/src/main/java/com/microsoft/appcenter/AppCenter.java +++ b/sdk/appcenter/src/main/java/com/microsoft/appcenter/AppCenter.java @@ -13,6 +13,7 @@ import android.os.HandlerThread; import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; import android.util.Log; @@ -236,6 +237,11 @@ public class AppCenter { */ private Boolean mAllowedNetworkRequests; + /** + * Country code or any other string to identify residency region.. + */ + private @Nullable String mDataResidencyRegion; + /** * Get unique instance. * @@ -308,6 +314,25 @@ public static void setCountryCode(String countryCode) { DeviceInfoHelper.setCountryCode(countryCode); } + /** + * Set the country code or any other string to identify residency region. + * + * @param dataResidencyRegion residency region code. + */ + public static void setDataResidencyRegion(@Nullable String dataResidencyRegion) { + getInstance().mDataResidencyRegion = dataResidencyRegion; + } + + /** + * Set the country code or any other string to identify residency region. + * + * @return dataResidencyRegion residency region code if defined. + */ + @Nullable + public static String getDataResidencyRegion() { + return getInstance().mDataResidencyRegion; + } + /** * Get the current version of App Center SDK. * diff --git a/sdk/appcenter/src/main/java/com/microsoft/appcenter/channel/DefaultChannel.java b/sdk/appcenter/src/main/java/com/microsoft/appcenter/channel/DefaultChannel.java index 9400649cb..89235da2e 100644 --- a/sdk/appcenter/src/main/java/com/microsoft/appcenter/channel/DefaultChannel.java +++ b/sdk/appcenter/src/main/java/com/microsoft/appcenter/channel/DefaultChannel.java @@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; +import com.microsoft.appcenter.AppCenter; import com.microsoft.appcenter.CancellationException; import com.microsoft.appcenter.http.HttpClient; import com.microsoft.appcenter.http.HttpResponse; @@ -648,6 +649,11 @@ public void enqueue(@NonNull Log log, @NonNull final String groupName, int flags log.setDevice(mDevice); } + /* Attach data residency region property to every log if its not already attached by a service. */ + if (log.getDataResidencyRegion() == null) { + log.setDataResidencyRegion(AppCenter.getDataResidencyRegion()); + } + /* Set date to current if not explicitly set in the past by a module (such as a crash). */ if (log.getTimestamp() == null) { log.setTimestamp(new Date()); diff --git a/sdk/appcenter/src/main/java/com/microsoft/appcenter/ingestion/models/AbstractLog.java b/sdk/appcenter/src/main/java/com/microsoft/appcenter/ingestion/models/AbstractLog.java index 053461e80..7e3d879d8 100644 --- a/sdk/appcenter/src/main/java/com/microsoft/appcenter/ingestion/models/AbstractLog.java +++ b/sdk/appcenter/src/main/java/com/microsoft/appcenter/ingestion/models/AbstractLog.java @@ -6,6 +6,7 @@ package com.microsoft.appcenter.ingestion.models; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.microsoft.appcenter.ingestion.models.json.JSONDateUtils; @@ -31,7 +32,8 @@ public abstract class AbstractLog implements Log { /** * timestamp property. */ - private static final String TIMESTAMP = "timestamp"; + @VisibleForTesting + static final String TIMESTAMP = "timestamp"; /** * Session identifier property. @@ -54,6 +56,12 @@ public abstract class AbstractLog implements Log { @VisibleForTesting static final String DEVICE = "device"; + /** + * Data residency region property. + */ + @VisibleForTesting + static final String DATA_RESIDENCY_REGION = "dataResidencyRegion"; + /** * Collection of transmissionTargetTokens that this log should be sent to. */ @@ -84,6 +92,11 @@ public abstract class AbstractLog implements Log { */ private Device device; + /** + * Data residency region. + */ + private @Nullable String dataResidencyRegion; + /** * Transient object tag. */ @@ -139,6 +152,17 @@ public void setDevice(Device device) { this.device = device; } + @Nullable + @Override + public String getDataResidencyRegion() { + return this.dataResidencyRegion; + } + + @Override + public void setDataResidencyRegion(@Nullable String dataResidencyRegion) { + this.dataResidencyRegion = dataResidencyRegion; + } + @Override public Object getTag() { return tag; @@ -171,6 +195,9 @@ public void write(JSONStringer writer) throws JSONException { getDevice().write(writer); writer.endObject(); } + if (getDataResidencyRegion() != null) { + JSONUtils.write(writer, DATA_RESIDENCY_REGION, getDataResidencyRegion()); + } } @Override @@ -189,6 +216,9 @@ public void read(JSONObject object) throws JSONException { device.read(object.getJSONObject(DEVICE)); setDevice(device); } + if (object.has(DATA_RESIDENCY_REGION)) { + setDataResidencyRegion(object.optString(DATA_RESIDENCY_REGION, null)); + } } @SuppressWarnings("EqualsReplaceableByObjectsCall") @@ -207,6 +237,7 @@ public boolean equals(Object o) { return false; if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false; if (device != null ? !device.equals(that.device) : that.device != null) return false; + if (dataResidencyRegion != null ? !dataResidencyRegion.equals(that.dataResidencyRegion) : that.dataResidencyRegion != null) return false; return tag != null ? tag.equals(that.tag) : that.tag == null; } @@ -218,6 +249,7 @@ public int hashCode() { result = 31 * result + (distributionGroupId != null ? distributionGroupId.hashCode() : 0); result = 31 * result + (userId != null ? userId.hashCode() : 0); result = 31 * result + (device != null ? device.hashCode() : 0); + result = 31 * result + (dataResidencyRegion != null ? dataResidencyRegion.hashCode() : 0); result = 31 * result + (tag != null ? tag.hashCode() : 0); return result; } diff --git a/sdk/appcenter/src/main/java/com/microsoft/appcenter/ingestion/models/Log.java b/sdk/appcenter/src/main/java/com/microsoft/appcenter/ingestion/models/Log.java index 36d7dfa6e..2ab1eb2bd 100644 --- a/sdk/appcenter/src/main/java/com/microsoft/appcenter/ingestion/models/Log.java +++ b/sdk/appcenter/src/main/java/com/microsoft/appcenter/ingestion/models/Log.java @@ -5,6 +5,8 @@ package com.microsoft.appcenter.ingestion.models; +import androidx.annotation.Nullable; + import java.util.Date; import java.util.Set; import java.util.UUID; @@ -90,6 +92,21 @@ public interface Log extends Model { */ void setDevice(Device device); + /** + * Get the dataResidency value. + * + * @return the dataResidency value + */ + @Nullable + String getDataResidencyRegion(); + + /** + * Set the dataResidency value. + * + * @param dataResidencyRegion the dataResidency value to set + */ + void setDataResidencyRegion(@Nullable String dataResidencyRegion); + /** * Adds a transmissionTargetToken that this log should be sent to. * diff --git a/sdk/appcenter/src/test/java/com/microsoft/appcenter/AppCenterTest.java b/sdk/appcenter/src/test/java/com/microsoft/appcenter/AppCenterTest.java index 81915533f..07276c741 100644 --- a/sdk/appcenter/src/test/java/com/microsoft/appcenter/AppCenterTest.java +++ b/sdk/appcenter/src/test/java/com/microsoft/appcenter/AppCenterTest.java @@ -873,6 +873,16 @@ public void setCountryCode() { DeviceInfoHelper.setCountryCode(eq(expectedCountryCode)); } + @Test + public void setDataResidencyRegion() { + + /* Set country code. */ + String expectedDataResidencyRegion = "rg"; + AppCenter.setDataResidencyRegion(expectedDataResidencyRegion); + + assertEquals(AppCenter.getDataResidencyRegion(), expectedDataResidencyRegion); + } + @Test public void setDefaultLogLevelRelease() { mApplicationInfo.flags = 0; diff --git a/sdk/appcenter/src/test/java/com/microsoft/appcenter/channel/ChannelLogDecorateTest.java b/sdk/appcenter/src/test/java/com/microsoft/appcenter/channel/ChannelLogDecorateTest.java index f8af8ecf2..24864e7cd 100644 --- a/sdk/appcenter/src/test/java/com/microsoft/appcenter/channel/ChannelLogDecorateTest.java +++ b/sdk/appcenter/src/test/java/com/microsoft/appcenter/channel/ChannelLogDecorateTest.java @@ -64,6 +64,7 @@ public void checkLogAttributes() throws Exception { Log log2 = mock(Log.class); when(log2.getDevice()).thenReturn(device); when(log2.getTimestamp()).thenReturn(new Date(123L)); + when(log2.getDataResidencyRegion()).thenReturn("region"); channel.enqueue(log2, "", DEFAULTS); verify(log2, never()).setDevice(any(Device.class)); verify(log2, never()).setTimestamp(any(Date.class)); diff --git a/sdk/appcenter/src/test/java/com/microsoft/appcenter/ingestion/models/AbstractLogTest.java b/sdk/appcenter/src/test/java/com/microsoft/appcenter/ingestion/models/AbstractLogTest.java index 8e6a0cd77..454c42561 100644 --- a/sdk/appcenter/src/test/java/com/microsoft/appcenter/ingestion/models/AbstractLogTest.java +++ b/sdk/appcenter/src/test/java/com/microsoft/appcenter/ingestion/models/AbstractLogTest.java @@ -5,27 +5,43 @@ package com.microsoft.appcenter.ingestion.models; +import com.microsoft.appcenter.AppCenter; +import com.microsoft.appcenter.ingestion.models.json.JSONDateUtils; +import com.microsoft.appcenter.ingestion.models.json.JSONUtils; import com.microsoft.appcenter.test.TestUtils; +import com.microsoft.appcenter.utils.AppCenterLog; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONStringer; import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; import java.util.Date; import java.util.UUID; +import static com.microsoft.appcenter.ingestion.models.AbstractLog.DATA_RESIDENCY_REGION; +import static com.microsoft.appcenter.ingestion.models.AbstractLog.TIMESTAMP; import static com.microsoft.appcenter.test.TestUtils.checkEquals; import static com.microsoft.appcenter.test.TestUtils.checkNotEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; 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.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; @SuppressWarnings("unused") +@RunWith(PowerMockRunner.class) +@PrepareForTest({ JSONUtils.class }) public class AbstractLogTest { @SuppressWarnings("InstantiationOfUtilityClass") @@ -93,6 +109,16 @@ public void compare() { b.setUserId(userId1); checkEquals(a, b); + /* Data residency region. */ + String dataResidencyRegion1 = "rg1"; + String dataResidencyRegion2 = "rg2"; + a.setDataResidencyRegion(dataResidencyRegion1); + checkNotEquals(a, b); + b.setDataResidencyRegion(dataResidencyRegion2); + checkNotEquals(a, b); + b.setDataResidencyRegion(dataResidencyRegion1); + checkEquals(a, b); + /* Device. */ Device d1 = new Device(); d1.setLocale("a"); @@ -149,6 +175,69 @@ public void writeNullDeviceTest() throws JSONException { verify(mockJsonStringer, never()).key(AbstractLog.DEVICE); } + @Test + public void readNotNullDataResidencyRegionTest() throws JSONException { + JSONObject mockJsonObject = mock(JSONObject.class); + String dataResidencyRegion = "RG"; + + when(mockJsonObject.has(DATA_RESIDENCY_REGION)).thenReturn(true); + when(mockJsonObject.optString(DATA_RESIDENCY_REGION, null)).thenReturn(dataResidencyRegion); + when(mockJsonObject.getString(CommonProperties.TYPE)).thenReturn("mockType"); + when(mockJsonObject.getString(TIMESTAMP)).thenReturn(JSONDateUtils.toString(new Date())); + + AbstractLog mockLog = new MockLogWithType(); + mockLog.read(mockJsonObject); + + assertEquals(dataResidencyRegion, mockLog.getDataResidencyRegion()); + } + + @Test + public void readNullDataResidencyRegionTest() throws JSONException { + JSONObject mockJsonObject = mock(JSONObject.class); + + when(mockJsonObject.has(DATA_RESIDENCY_REGION)).thenReturn(true); + when(mockJsonObject.optString(DATA_RESIDENCY_REGION, null)).thenReturn(null); + when(mockJsonObject.getString(CommonProperties.TYPE)).thenReturn("mockType"); + when(mockJsonObject.getString(TIMESTAMP)).thenReturn(JSONDateUtils.toString(new Date())); + + AbstractLog mockLog = new MockLogWithType(); + mockLog.read(mockJsonObject); + + assertNull(mockLog.getDataResidencyRegion()); + } + + + @Test + public void writeNotNullDataResidencyRegionTest() throws JSONException { + JSONStringer mockJsonStringer = mock(JSONStringer.class); + mockStatic(JSONUtils.class); + when(mockJsonStringer.key(anyString())).thenReturn(mockJsonStringer); + when(mockJsonStringer.value(anyString())).thenReturn(mockJsonStringer); + + AbstractLog mockLog = new MockLog(); + mockLog.setTimestamp(new Date()); + mockLog.setDataResidencyRegion("RG"); + mockLog.write(mockJsonStringer); + + verifyStatic(JSONUtils.class); + JSONUtils.write(mockJsonStringer, DATA_RESIDENCY_REGION, "RG"); + } + + @Test + public void writeNullDataResidencyRegionTest() throws JSONException { + JSONStringer mockJsonStringer = mock(JSONStringer.class); + mockStatic(JSONUtils.class); + when(mockJsonStringer.key(anyString())).thenReturn(mockJsonStringer); + when(mockJsonStringer.value(anyString())).thenReturn(mockJsonStringer); + + AbstractLog mockLog = new MockLog(); + mockLog.setTimestamp(new Date()); + mockLog.write(mockJsonStringer); + + verifyStatic(JSONUtils.class, never()); + JSONUtils.write(eq(mockJsonStringer), eq(DATA_RESIDENCY_REGION), anyString()); + } + private static class MockLog extends AbstractLog { @Override