Page MenuHomePhabricator

D9069.id30795.diff
No OneTemporary

D9069.id30795.diff

diff --git a/native/android/app/build.gradle b/native/android/app/build.gradle
--- a/native/android/app/build.gradle
+++ b/native/android/app/build.gradle
@@ -707,6 +707,15 @@
} else {
implementation jscFlavor
}
+ def work_version = "2.8.1"
+ // (Java only)
+ implementation "androidx.work:work-runtime:$work_version"
+ // Kotlin + coroutines
+ implementation "androidx.work:work-runtime-ktx:$work_version"
+ // Guava for listenable future to solve the bug:
+ // https://stackoverflow.com/questions/64290141/android-studio-class-file-for-com-google-common-util-concurrent-listenablefuture
+ // https://github.com/google/ExoPlayer/issues/7993
+ implementation "com.google.guava:guava:31.0.1-android"
}
if (isNewArchitectureEnabled()) {
diff --git a/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsBlobServiceClient.java b/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsBlobServiceClient.java
--- a/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsBlobServiceClient.java
+++ b/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsBlobServiceClient.java
@@ -1,6 +1,13 @@
package app.comm.android.notifications;
+import android.content.Context;
import android.util.Log;
+import androidx.work.Constraints;
+import androidx.work.Data;
+import androidx.work.NetworkType;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
+import androidx.work.WorkRequest;
import com.google.firebase.messaging.RemoteMessage;
import java.io.IOException;
import java.lang.OutOfMemoryError;
@@ -17,8 +24,6 @@
import org.json.JSONObject;
public class CommAndroidNotificationsBlobServiceClient {
- private static final String BLOB_SERVICE_URL =
- "https://blob.commtechnologies.org";
// The FirebaseMessagingService docs state that message
// processing should complete within at most 20 seconds
// window. Therefore we limit http time call to 15 seconds
@@ -26,11 +31,17 @@
private static final int NOTIFICATION_PROCESSING_TIME_LIMIT = 15;
// OkHttp docs advise to share OkHttpClient instances
// https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/#okhttpclients-should-be-shared
- private static final OkHttpClient httpClient =
+
+ public static final OkHttpClient httpClient =
new OkHttpClient.Builder()
.callTimeout(NOTIFICATION_PROCESSING_TIME_LIMIT, TimeUnit.SECONDS)
.build();
+ public static final String BLOB_SERVICE_URL =
+ "https://blob.commtechnologies.org";
+ public static final String BLOB_HASH_KEY = "blob_hash";
+ public static final String BLOB_HOLDER_KEY = "holder";
+
@FunctionalInterface
public interface BlobServiceMessageConsumer {
void accept(RemoteMessage message, byte[] blob);
@@ -71,7 +82,28 @@
});
}
- private String getAuthToken() {
+ public void scheduleDeferredBlobDeletion(
+ String blobHash,
+ String blobHolder,
+ Context context) {
+ Constraints constraints = new Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build();
+ WorkRequest deleteBlobWorkRequest =
+ new OneTimeWorkRequest.Builder(DeleteBlobWork.class)
+ .setConstraints(constraints)
+ .setInitialDelay(
+ NOTIFICATION_PROCESSING_TIME_LIMIT, TimeUnit.SECONDS)
+ .setInputData(new Data.Builder()
+ .putString(BLOB_HASH_KEY, blobHash)
+ .putString(BLOB_HOLDER_KEY, blobHolder)
+ .build())
+ .build();
+
+ WorkManager.getInstance(context).enqueue(deleteBlobWorkRequest);
+ }
+
+ public static String getAuthToken() {
// Authentication data are retrieved on every request
// since they might change while CommNotificationsHandler
// thread is running so we should not rely on caching
diff --git a/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java b/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
--- a/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
+++ b/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
@@ -44,6 +44,7 @@
private static final String ENCRYPTED_PAYLOAD_KEY = "encryptedPayload";
private static final String ENCRYPTION_FAILED_KEY = "encryptionFailed";
private static final String BLOB_HASH_KEY = "blobHash";
+ private static final String BLOB_HOLDER_KEY = "holder";
private static final String AES_ENCRYPTION_KEY_KEY = "encryptionKey";
private static final String CHANNEL_ID = "default";
@@ -108,7 +109,11 @@
}
String blobHash = message.getData().get(BLOB_HASH_KEY);
- if (blobHash != null) {
+ String blobHolder = message.getData().get(BLOB_HOLDER_KEY);
+
+ if (blobHash != null && blobHolder != null) {
+ blobServiceClient.scheduleDeferredBlobDeletion(
+ blobHash, blobHolder, this.getApplicationContext());
blobServiceClient.getAndConsumeAsync(
blobHash,
message,
diff --git a/native/android/app/src/main/java/app/comm/android/notifications/DeleteBlobWork.java b/native/android/app/src/main/java/app/comm/android/notifications/DeleteBlobWork.java
new file mode 100644
--- /dev/null
+++ b/native/android/app/src/main/java/app/comm/android/notifications/DeleteBlobWork.java
@@ -0,0 +1,95 @@
+package app.comm.android.notifications;
+
+import android.content.Context;
+import android.util.Log;
+import androidx.work.Data;
+import androidx.work.ListenableWorker.Result;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+import java.io.IOException;
+import java.util.function.Consumer;
+import okhttp3.MediaType;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class DeleteBlobWork extends Worker {
+
+ private static final int MAX_RETRY_ATTEMPTS = 10;
+
+ public DeleteBlobWork(Context context, WorkerParameters params) {
+ super(context, params);
+ }
+
+ @Override
+ public Result doWork() {
+ String blobHash = getInputData().getString(
+ CommAndroidNotificationsBlobServiceClient.BLOB_HASH_KEY);
+ String blobHolder = getInputData().getString(
+ CommAndroidNotificationsBlobServiceClient.BLOB_HOLDER_KEY);
+
+ String jsonBody;
+ try {
+ jsonBody =
+ new JSONObject()
+ .put(
+ CommAndroidNotificationsBlobServiceClient.BLOB_HASH_KEY,
+ blobHash)
+ .put(
+ CommAndroidNotificationsBlobServiceClient.BLOB_HOLDER_KEY,
+ blobHolder)
+ .toString();
+ } catch (JSONException e) {
+ // This should never happen since the code
+ // throwing is just simple JSON creation.
+ // If it happens there is no way to retry
+ // so we fail immediately.
+ Log.w(
+ "COMM",
+ "Failed to create JSON from blob hash and holder provided.",
+ e);
+ return Result.failure();
+ }
+
+ String authToken = CommAndroidNotificationsBlobServiceClient.getAuthToken();
+ RequestBody requestBody =
+ RequestBody.create(MediaType.parse("application/json"), jsonBody);
+ Request request =
+ new Request.Builder()
+ .delete(requestBody)
+ .url(
+ CommAndroidNotificationsBlobServiceClient.BLOB_SERVICE_URL +
+ "/blob")
+ .header("Authorization", authToken)
+ .build();
+
+ try {
+ Response response =
+ CommAndroidNotificationsBlobServiceClient.httpClient.newCall(request)
+ .execute();
+ if (response.isSuccessful()) {
+ return Result.success();
+ }
+ Log.w(
+ "COMM",
+ "Failed to execute blob deletion request. HTTP code:" +
+ response.code() + " status: " + response.message());
+ return retryOrFail();
+ } catch (IOException e) {
+ Log.w(
+ "COMM",
+ "IO exception occurred while issuing blob deletion request.",
+ e);
+ return retryOrFail();
+ }
+ }
+
+ private Result retryOrFail() {
+ if (getRunAttemptCount() > MAX_RETRY_ATTEMPTS) {
+ return Result.failure();
+ }
+ return Result.retry();
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Wed, Nov 27, 3:28 PM (19 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2590821
Default Alt Text
D9069.id30795.diff (8 KB)

Event Timeline