Skip to content

Commit

Permalink
Add dataResidencyRegion option for sending to portal (#1704)
Browse files Browse the repository at this point in the history
Co-authored-by: Dima <[email protected]>
  • Loading branch information
MikhailSuendukov and DmitriyKirakosyan committed Aug 23, 2023
1 parent e58dad6 commit 46e6bf2
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions apps/sasquatch/src/main/res/values/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
<string name="country_code_title" tools:ignore="MissingTranslation">Country Code</string>
<string name="country_code_save_message" tools:ignore="MissingTranslation">Country code value will be applied after the application restart.</string>

<string name="data_residency_region_key" tools:ignore="MissingTranslation">data_residency_region</string>
<string name="data_residency_region_title" tools:ignore="MissingTranslation">Data residency region</string>
<string name="data_residency_region_save_message" tools:ignore="MissingTranslation">Data residency region value will be applied after the application restart.</string>

<string name="storage_file_size_key" tools:ignore="MissingTranslation">storage_file_size_key</string>
<string name="storage_file_size_title" tools:ignore="MissingTranslation">Storage File Size</string>

Expand Down
5 changes: 5 additions & 0 deletions apps/sasquatch/src/main/res/xml/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
<Preference
android:key="@string/country_code_key"
android:title="@string/country_code_title" />
<EditTextPreference
android:key="@string/data_residency_region_key"
android:selectAllOnFocus="false"
android:singleLine="false"
android:title="@string/data_residency_region_title" />
<Preference
android:key="@string/storage_size_key"
android:title="@string/storage_size_title" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, String> validatedProperties = ErrorLogHelper.validateProperties(properties, "HandledError");
final String dataResidencyRegion = AppCenter.getDataResidencyRegion();

post(new Runnable() {

@Override
Expand All @@ -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);
}
});
Expand Down Expand Up @@ -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) {

Expand All @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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));
Expand Down Expand Up @@ -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. */
Expand All @@ -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);

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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
Expand Down
25 changes: 25 additions & 0 deletions sdk/appcenter/src/main/java/com/microsoft/appcenter/AppCenter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
Loading

0 comments on commit 46e6bf2

Please sign in to comment.