From e4bba0cdb29b2a05062e49c0414263fd954f4f14 Mon Sep 17 00:00:00 2001 From: Dmitriy Kirakosyan Date: Tue, 28 Jun 2022 11:37:06 +0700 Subject: [PATCH 01/42] Start versino 4.4.6 --- CHANGELOG.md | 4 ++++ versions.gradle | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5aef28f3..fbaf95aff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # App Center SDK for Android Change Log +## Version 4.4.6 (Under development) + + ___ + ## Version 4.4.5 ### App Center diff --git a/versions.gradle b/versions.gradle index 97a69409e..2e4341cd4 100644 --- a/versions.gradle +++ b/versions.gradle @@ -7,7 +7,7 @@ ext { versionCode = 68 - versionName = '4.4.5' + versionName = '4.4.6' minSdkVersion = 21 compileSdkVersion = 31 targetSdkVersion = 31 From 9b6d19e33a3290793f823dc51f78bb727b955c27 Mon Sep 17 00:00:00 2001 From: JiglioNero Date: Thu, 25 Aug 2022 12:40:40 +0200 Subject: [PATCH 02/42] Fix deleting of large log files --- .../persistence/DatabasePersistence.java | 122 +++++++++++++++++- .../utils/storage/DatabaseManager.java | 64 ++++----- 2 files changed, 150 insertions(+), 36 deletions(-) diff --git a/sdk/appcenter/src/main/java/com/microsoft/appcenter/persistence/DatabasePersistence.java b/sdk/appcenter/src/main/java/com/microsoft/appcenter/persistence/DatabasePersistence.java index d35674610..62d8a76ea 100644 --- a/sdk/appcenter/src/main/java/com/microsoft/appcenter/persistence/DatabasePersistence.java +++ b/sdk/appcenter/src/main/java/com/microsoft/appcenter/persistence/DatabasePersistence.java @@ -9,6 +9,7 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteFullException; import android.database.sqlite.SQLiteQueryBuilder; import androidx.annotation.IntRange; import androidx.annotation.NonNull; @@ -29,8 +30,10 @@ import org.json.JSONException; import java.io.File; +import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -114,6 +117,12 @@ public class DatabasePersistence extends Persistence { @VisibleForTesting static final ContentValues SCHEMA = getContentValues("", "", "", "", "", 0); + /** + * Default maximum cache size for database and large payloads in separated files. + */ + @VisibleForTesting + private static final long DEFAULT_MAX_CACHE_SIZE_IN_BYTES = 10 * 1024 * 1024; + /** * Order by clause to select logs. */ @@ -186,13 +195,37 @@ public class DatabasePersistence extends Persistence { */ private final File mLargePayloadDirectory; + /** + * The size of the separated large files + */ + private long mLargePayloadsSize; + + /** + * The separated large files max size + */ + private long mLargePayloadsMaxSize; + + /** + * List of the large payloads files + */ + private ArrayList mLargePayloadFiles; + /** * Initializes variables with default values. * * @param context application context. */ public DatabasePersistence(Context context) { - this(context, VERSION, SCHEMA); + this(context, VERSION, SCHEMA, DEFAULT_MAX_CACHE_SIZE_IN_BYTES); + } + + /** + * Initializes variables with default cache size. + * + * @param context application context. + */ + public DatabasePersistence(Context context, int version, @SuppressWarnings("SameParameterValue") final ContentValues schema) { + this(context, version, schema, DEFAULT_MAX_CACHE_SIZE_IN_BYTES); } /** @@ -202,7 +235,7 @@ public DatabasePersistence(Context context) { * @param version The version of current schema. * @param schema schema. */ - DatabasePersistence(Context context, int version, @SuppressWarnings("SameParameterValue") final ContentValues schema) { + DatabasePersistence(Context context, int version, @SuppressWarnings("SameParameterValue") final ContentValues schema, long maxCacheSize) { mContext = context; mPendingDbIdentifiersGroups = new HashMap<>(); mPendingDbIdentifiers = new HashSet<>(); @@ -232,6 +265,16 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //noinspection ResultOfMethodCallIgnored we handle errors at read/write time for each file. mLargePayloadDirectory.mkdirs(); + + mLargePayloadsMaxSize = maxCacheSize; + mLargePayloadFiles = collectAllLargePayloadsFiles(); + mLargePayloadsSize = 0; + + for (File file: mLargePayloadFiles) { + mLargePayloadsSize += file.length(); + } + + deleteLogsThatNotFitMaxSize(); } /** @@ -260,6 +303,10 @@ public boolean setMaxStorageSize(long maxStorageSizeInBytes) { return mDatabaseManager.setMaxSize(maxStorageSizeInBytes); } + public void setMaxLargePayloadsSize(long maxLargePayloadsSizeInBytes) { + mLargePayloadsSize = maxLargePayloadsSizeInBytes; + } + @Override public long putLog(@NonNull Log log, @NonNull String group, @IntRange(from = Flags.NORMAL, to = Flags.CRITICAL) int flags) throws PersistenceException { @@ -293,8 +340,22 @@ public long putLog(@NonNull Log log, @NonNull String group, @IntRange(from = Fla throw new PersistenceException("Log is too large (" + payloadSize + " bytes) to store in database. " + "Current maximum database size is " + maxSize + " bytes."); } - contentValues = getContentValues(group, isLargePayload ? null : payload, targetToken, log.getType(), targetKey, Flags.getPersistenceFlag(flags, false)); - long databaseId = mDatabaseManager.put(contentValues, COLUMN_PRIORITY); + + int priority = Flags.getPersistenceFlag(flags, false); + contentValues = getContentValues(group, isLargePayload ? null : payload, targetToken, log.getType(), targetKey, priority); + + Long databaseId = null; + while (databaseId == null) { + try { + databaseId = mDatabaseManager.put(contentValues, COLUMN_PRIORITY); + } catch (SQLiteFullException e) { + AppCenterLog.debug(LOG_TAG, "Storage is full, trying to delete the oldest log that has the lowest priority which is lower or equal priority than the new log"); + if (deleteTheOldestLog(COLUMN_PRIORITY, priority) == -1) { + throw e; + } + } + } + if (databaseId == -1) { throw new PersistenceException("Failed to store a log to the Persistence database for log type " + log.getType() + "."); } @@ -308,6 +369,8 @@ public long putLog(@NonNull Log log, @NonNull String group, @IntRange(from = Fla File payloadFile = getLargePayloadFile(directory, databaseId); try { FileManager.write(payloadFile, payload); + mLargePayloadFiles.add(payloadFile); + mLargePayloadsSize += payloadFile.length(); } catch (IOException e) { /* Remove database entry if we cannot save payload as a file. */ @@ -316,6 +379,8 @@ public long putLog(@NonNull Log log, @NonNull String group, @IntRange(from = Fla } AppCenterLog.debug(LOG_TAG, "Payload written to " + payloadFile); } + deleteLogsThatNotFitMaxSize(); + return databaseId; } catch (JSONException e) { throw new PersistenceException("Cannot convert to JSON string.", e); @@ -579,6 +644,55 @@ public void close() { mDatabaseManager.close(); } + public void deleteLogsThatNotFitMaxSize() { + int normalPriority = Flags.getPersistenceFlag(Flags.NORMAL, false); + while (getCacheSize() >= mLargePayloadsMaxSize) { + if (deleteTheOldestLog(COLUMN_PRIORITY, normalPriority) == -1) { + break; + } + } + } + + private long getCacheSize() { + return mDatabaseManager.getCurrentSize() + mLargePayloadsSize; + } + + private long deleteTheOldestLog(@NonNull String priorityColumn, int priority) { + long deletedId = mDatabaseManager.deleteTheOldestRecord(priorityColumn, priority); + if (deletedId != -1) { + for (File file: mLargePayloadFiles) { + if (file.getName().equalsIgnoreCase(deletedId + PAYLOAD_FILE_EXTENSION)) { + long fileSize = file.length(); + if (file.delete()) { + mLargePayloadsSize -= fileSize; + mLargePayloadFiles.remove(file); + } else { + AppCenterLog.error(LOG_TAG, "Cannot delete large payload file with id " + deletedId); + } + break; + } + } + } + return deletedId; + } + + private ArrayList collectAllLargePayloadsFiles() { + FilenameFilter filter = (file, filename) -> filename.endsWith(PAYLOAD_FILE_EXTENSION); + + ArrayList largePayloadFiles = new ArrayList<>(); + File[] groupFiles = mLargePayloadDirectory.listFiles(); + if (groupFiles != null) { + for (File groupFile : groupFiles) { + File[] files = groupFile.listFiles(filter); + if (files != null) { + largePayloadFiles.addAll(Arrays.asList(files)); + } + } + } + + return largePayloadFiles; + } + private List getLogsIds(SQLiteQueryBuilder builder, String[] selectionArgs) { List result = new ArrayList<>(); try { diff --git a/sdk/appcenter/src/main/java/com/microsoft/appcenter/utils/storage/DatabaseManager.java b/sdk/appcenter/src/main/java/com/microsoft/appcenter/utils/storage/DatabaseManager.java index 283f6a79d..e2eebb981 100644 --- a/sdk/appcenter/src/main/java/com/microsoft/appcenter/utils/storage/DatabaseManager.java +++ b/sdk/appcenter/src/main/java/com/microsoft/appcenter/utils/storage/DatabaseManager.java @@ -18,9 +18,11 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.microsoft.appcenter.persistence.DatabasePersistence; import com.microsoft.appcenter.utils.AppCenterLog; import java.io.Closeable; +import java.io.File; import java.util.Arrays; import static com.microsoft.appcenter.utils.AppCenterLog.LOG_TAG; @@ -180,43 +182,16 @@ public ContentValues nextValues(Cursor cursor) { * @return If a log was inserted, the database identifier. Otherwise -1. */ public long put(@NonNull ContentValues values, @NonNull String priorityColumn) { - Long id = null; - Cursor cursor = null; + long id; try { - while (id == null) { - try { - - /* Insert data. */ - id = getDatabase().insertOrThrow(mDefaultTable, null, values); - } catch (SQLiteFullException e) { - - /* Delete the oldest log. */ - AppCenterLog.debug(LOG_TAG, "Storage is full, trying to delete the oldest log that has the lowest priority which is lower or equal priority than the new log"); - if (cursor == null) { - String priority = values.getAsString(priorityColumn); - SQLiteQueryBuilder queryBuilder = SQLiteUtils.newSQLiteQueryBuilder(); - queryBuilder.appendWhere(priorityColumn + " <= ?"); - cursor = getCursor(queryBuilder, SELECT_PRIMARY_KEY, new String[]{priority}, priorityColumn + " , " + PRIMARY_KEY); - } - if (cursor.moveToNext()) { - long deletedId = cursor.getLong(0); - delete(deletedId); - AppCenterLog.debug(LOG_TAG, "Deleted log id=" + deletedId); - } else { - throw e; - } - } - } + /* Insert data. */ + id = getDatabase().insertOrThrow(mDefaultTable, null, values); + } catch (SQLiteFullException e) { + throw e; } catch (RuntimeException e) { id = -1L; AppCenterLog.error(LOG_TAG, String.format("Failed to insert values (%s) to database %s.", values.toString(), mDatabase), e); } - if (cursor != null) { - try { - cursor.close(); - } catch (RuntimeException ignore) { - } - } return id; } @@ -229,6 +204,21 @@ public void delete(@IntRange(from = 0) long id) { delete(mDefaultTable, PRIMARY_KEY, id); } + public long deleteTheOldestRecord(@NonNull String priorityColumn, int priority) { + SQLiteQueryBuilder queryBuilder = SQLiteUtils.newSQLiteQueryBuilder(); + queryBuilder.appendWhere(priorityColumn + " <= ?"); + Cursor cursor = getCursor(queryBuilder, SELECT_PRIMARY_KEY, new String[]{String.valueOf(priority)}, priorityColumn + " , " + PRIMARY_KEY); + if (cursor.moveToNext()) { + long deletedId = cursor.getLong(0); + delete(deletedId); + AppCenterLog.debug(LOG_TAG, "Deleted log id=" + deletedId); + return deletedId; + } else { + AppCenterLog.error(LOG_TAG, String.format("Failed to delete the oldest log from database %s.", mDatabase)); + } + return -1; + } + /** * Deletes the entries that matches key == value. * @@ -418,6 +408,16 @@ public long getMaxSize() { } } + /** + * Gets the current size of the database file. + * + * @return The current size of database in bytes. + */ + public long getCurrentSize() { + File dbFile = mContext.getDatabasePath(mDatabase); + return dbFile.length(); + } + /** * Database listener. */ From 91db43a47798c18f5a2a2b6d498b4e49c457c692 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Mon, 29 Aug 2022 14:49:57 -0700 Subject: [PATCH 03/42] Add `$schema` to `cgmanifest.json` --- cgmanifest.json | 243 ++++++++++++++++++++++++------------------------ 1 file changed, 122 insertions(+), 121 deletions(-) diff --git a/cgmanifest.json b/cgmanifest.json index f6b9d59da..ecebf1b32 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -1,123 +1,124 @@ { - "Registrations": [ - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "gradle", - "groupId": "com.android.tools.build", - "version": "3.3.2" - } - } - }, - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "gradle-bintray-plugin", - "groupId": "com.jfrog.bintray.gradle", - "version": "1.8.0" - } - } - }, - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "android-maven-gradle-plugin", - "groupId": "com.github.dcendents", - "version": "2.0" - } - } - }, - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "coveralls-gradle-plugin", - "groupId": "org.kt3k.gradle.plugin", - "version": "2.8.2" - } - } - }, - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "espresso-core", - "groupId": "com.androidx.test.espresso", - "version": "3.0.2" - } - } - }, - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "espresso-idling-resource", - "groupId": "com.androidx.test.espresso", - "version": "3.0.2" - } - } - }, - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "rules", - "groupId": "com.androidx.test", - "version": "1.2.0" - } - } - }, - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "appcompat", - "groupId": "com.androidx.appcompat", - "version": "1.0.2" - } - } - }, - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "browser", - "groupId": "androidx.browser", - "version": "1.0.0" - } - } - }, - { - "component": { - "type": "Maven", - "maven": { - "artifactId": "recyclerview", - "groupId": "com.androidx.recyclerview", - "version": "1.1.0" - } - } - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://android.googlesource.com/platform/frameworks/support", - "commitHash": "cb7620b6f66a6e33fb9882bbca995a45c64f3c3e" - } - } - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/microsoft/appcenter-sdk-android-breakpad.git", - "commitHash": "0d8e4133e2b9caadb012d438c4c12cf6daff2a40" - } - } + "$schema": "https://json.schemastore.org/component-detection-manifest.json", + "Registrations": [ + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "gradle", + "groupId": "com.android.tools.build", + "version": "3.3.2" } - ], - "Version": 1 -} \ No newline at end of file + } + }, + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "gradle-bintray-plugin", + "groupId": "com.jfrog.bintray.gradle", + "version": "1.8.0" + } + } + }, + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "android-maven-gradle-plugin", + "groupId": "com.github.dcendents", + "version": "2.0" + } + } + }, + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "coveralls-gradle-plugin", + "groupId": "org.kt3k.gradle.plugin", + "version": "2.8.2" + } + } + }, + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "espresso-core", + "groupId": "com.androidx.test.espresso", + "version": "3.0.2" + } + } + }, + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "espresso-idling-resource", + "groupId": "com.androidx.test.espresso", + "version": "3.0.2" + } + } + }, + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "rules", + "groupId": "com.androidx.test", + "version": "1.2.0" + } + } + }, + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "appcompat", + "groupId": "com.androidx.appcompat", + "version": "1.0.2" + } + } + }, + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "browser", + "groupId": "androidx.browser", + "version": "1.0.0" + } + } + }, + { + "component": { + "type": "Maven", + "maven": { + "artifactId": "recyclerview", + "groupId": "com.androidx.recyclerview", + "version": "1.1.0" + } + } + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://android.googlesource.com/platform/frameworks/support", + "commitHash": "cb7620b6f66a6e33fb9882bbca995a45c64f3c3e" + } + } + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/microsoft/appcenter-sdk-android-breakpad.git", + "commitHash": "0d8e4133e2b9caadb012d438c4c12cf6daff2a40" + } + } + } + ], + "Version": 1 +} From c0969149000841c7c35e51eb267925e656e4f9b6 Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Wed, 31 Aug 2022 12:20:48 +0200 Subject: [PATCH 04/42] Add requesting post notification permission --- .../src/main/AndroidManifest.xml | 9 +- .../appcenter/distribute/Distribute.java | 41 +++++++-- .../PermissionRequestActivity.java | 89 +++++++++++++++++++ .../{ => permissions}/PermissionUtils.java | 16 +++- versions.gradle | 4 +- 5 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/permissions/PermissionRequestActivity.java rename sdk/appcenter-distribute/src/main/java/com/microsoft/appcenter/distribute/{ => permissions}/PermissionUtils.java (66%) diff --git a/sdk/appcenter-distribute/src/main/AndroidManifest.xml b/sdk/appcenter-distribute/src/main/AndroidManifest.xml index c621034b9..02f6125fd 100644 --- a/sdk/appcenter-distribute/src/main/AndroidManifest.xml +++ b/sdk/appcenter-distribute/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ +