Skip to content

Commit a2502fe

Browse files
authored
Add uploadScreenshot to FirebaseAppDistributionTesterApiClient (#3814)
* Add upload request to TesterApiHttpClient * Address Kai's feedback * Add uploadScreenshot to FirebaseAppDistributionTesterApiClient * Formatting * Make request body writing more efficient
1 parent da89e74 commit a2502fe

File tree

5 files changed

+174
-106
lines changed

5 files changed

+174
-106
lines changed

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionTesterApiClient.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414

1515
package com.google.firebase.appdistribution.impl;
1616

17+
import static android.graphics.Bitmap.CompressFormat.PNG;
1718
import static com.google.firebase.appdistribution.impl.TaskUtils.runAsyncInTask;
1819

20+
import android.graphics.Bitmap;
1921
import androidx.annotation.NonNull;
20-
import androidx.annotation.VisibleForTesting;
2122
import com.google.android.gms.tasks.Task;
2223
import com.google.android.gms.tasks.Tasks;
2324
import com.google.firebase.FirebaseApp;
@@ -58,30 +59,17 @@ private interface FidDependentJob<TResult> {
5859
private static final String FIND_RELEASE_TAG = "Finding installed release";
5960
private static final String CREATE_FEEDBACK_TAG = "Creating feedback";
6061
private static final String COMMIT_FEEDBACK_TAG = "Committing feedback";
62+
private static final String UPLOAD_SCREENSHOT_TAG = "Uploading screenshot";
6163

6264
private final FirebaseApp firebaseApp;
6365
private final Provider<FirebaseInstallationsApi> firebaseInstallationsApiProvider;
6466
private final TesterApiHttpClient testerApiHttpClient;
65-
private final Executor taskExecutor;
67+
private final Executor taskExecutor = Executors.newSingleThreadExecutor();
6668

6769
FirebaseAppDistributionTesterApiClient(
6870
@NonNull FirebaseApp firebaseApp,
6971
@NonNull Provider<FirebaseInstallationsApi> firebaseInstallationsApiProvider,
7072
@NonNull TesterApiHttpClient testerApiHttpClient) {
71-
this(
72-
Executors.newSingleThreadExecutor(),
73-
firebaseApp,
74-
firebaseInstallationsApiProvider,
75-
testerApiHttpClient);
76-
}
77-
78-
@VisibleForTesting
79-
FirebaseAppDistributionTesterApiClient(
80-
@NonNull Executor taskExecutor,
81-
@NonNull FirebaseApp firebaseApp,
82-
@NonNull Provider<FirebaseInstallationsApi> firebaseInstallationsApiProvider,
83-
@NonNull TesterApiHttpClient testerApiHttpClient) {
84-
this.taskExecutor = taskExecutor;
8573
this.firebaseApp = firebaseApp;
8674
this.firebaseInstallationsApiProvider = firebaseInstallationsApiProvider;
8775
this.testerApiHttpClient = testerApiHttpClient;
@@ -157,6 +145,27 @@ Task<String> createFeedback(String testerReleaseName, String feedbackText) {
157145
});
158146
}
159147

148+
/**
149+
* Attaches a screenshot to feedback.
150+
*
151+
* @return a {@link Task} containing the feedback name, for convenience when chaining subsequent
152+
* requests off of this task
153+
*/
154+
Task<String> attachScreenshot(String feedbackName, Bitmap screenshot) {
155+
return runWithFidAndToken(
156+
(unused, token) -> {
157+
LogWrapper.getInstance().i("Uploading screenshot for feedback: " + feedbackName);
158+
String path =
159+
String.format("upload/v1alpha/%s:uploadArtifact?type=SCREENSHOT", feedbackName);
160+
testerApiHttpClient.makeUploadRequest(
161+
UPLOAD_SCREENSHOT_TAG,
162+
path,
163+
token,
164+
stream -> screenshot.compress(PNG, /* quality= */ 100, stream));
165+
return feedbackName;
166+
});
167+
}
168+
160169
/** Commits the feedback with the given name. */
161170
@NonNull
162171
Task<Void> commitFeedback(String feedbackName) {
@@ -258,8 +267,9 @@ private <TResult> Task<TResult> runWithFidAndToken(FidDependentJob<TResult> job)
258267
firebaseInstallationsApiProvider.get().getToken(/* forceRefresh= */ false);
259268

260269
return Tasks.whenAllSuccess(installationIdTask, installationAuthTokenTask)
261-
.continueWithTask(TaskUtils::handleTaskFailure)
270+
.continueWithTask(taskExecutor, TaskUtils::handleTaskFailure)
262271
.onSuccessTask(
272+
taskExecutor,
263273
resultsList -> {
264274
// Tasks.whenAllSuccess combines task results into a list
265275
if (resultsList.size() != 2) {

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/TesterApiHttpClient.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,27 @@
2727
import java.io.ByteArrayOutputStream;
2828
import java.io.IOException;
2929
import java.io.InputStream;
30+
import java.io.OutputStream;
3031
import java.io.UnsupportedEncodingException;
3132
import java.util.HashMap;
3233
import java.util.Map;
33-
import java.util.zip.GZIPOutputStream;
3434
import javax.net.ssl.HttpsURLConnection;
3535
import org.json.JSONException;
3636
import org.json.JSONObject;
3737

3838
/** Client that makes FIS-authenticated GET and POST requests to the App Distribution Tester API. */
3939
class TesterApiHttpClient {
4040

41+
/** Functional interface for a function that writes a request body to an output stream */
42+
interface RequestBodyWriter {
43+
void write(OutputStream os) throws IOException;
44+
}
45+
4146
private static final String APP_TESTERS_HOST = "firebaseapptesters.googleapis.com";
4247
private static final String REQUEST_METHOD_GET = "GET";
4348
private static final String REQUEST_METHOD_POST = "POST";
4449
private static final String CONTENT_TYPE_HEADER_KEY = "Content-Type";
4550
private static final String JSON_CONTENT_TYPE = "application/json";
46-
private static final String CONTENT_ENCODING_HEADER_KEY = "Content-Encoding";
47-
private static final String GZIP_CONTENT_ENCODING = "gzip";
4851
private static final String API_KEY_HEADER = "x-goog-api-key";
4952
private static final String INSTALLATION_AUTH_HEADER = "X-Goog-Firebase-Installations-Auth";
5053
private static final String X_ANDROID_PACKAGE_HEADER_KEY = "X-Android-Package";
@@ -110,7 +113,8 @@ JSONObject makePostRequest(String tag, String path, String token, String request
110113
throw new FirebaseAppDistributionException(
111114
"Unsupported encoding: " + UTF_8, Status.UNKNOWN, e);
112115
}
113-
return makePostRequest(tag, path, token, bytes, new HashMap<>());
116+
return makePostRequest(
117+
tag, path, token, new HashMap<>(), outputStream -> outputStream.write(bytes));
114118
}
115119

116120
/**
@@ -120,34 +124,38 @@ JSONObject makePostRequest(String tag, String path, String token, String request
120124
*
121125
* @return the response body
122126
*/
123-
JSONObject makeUploadRequest(String tag, String path, String token, byte[] requestBody)
127+
JSONObject makeUploadRequest(
128+
String tag, String path, String token, RequestBodyWriter requestBodyWriter)
124129
throws FirebaseAppDistributionException {
125130
Map<String, String> extraHeaders = new HashMap<>();
126131
extraHeaders.put(X_GOOG_UPLOAD_PROTOCOL_HEADER, X_GOOG_UPLOAD_PROTOCOL_RAW);
127132
extraHeaders.put(X_GOOG_UPLOAD_FILE_NAME_HEADER, X_GOOG_UPLOAD_FILE_NAME);
128-
return makePostRequest(tag, path, token, requestBody, extraHeaders);
133+
return makePostRequest(tag, path, token, extraHeaders, requestBodyWriter);
129134
}
130135

131136
private JSONObject makePostRequest(
132-
String tag, String path, String token, byte[] requestBody, Map<String, String> extraHeaders)
137+
String tag,
138+
String path,
139+
String token,
140+
Map<String, String> extraHeaders,
141+
RequestBodyWriter requestBodyWriter)
133142
throws FirebaseAppDistributionException {
134143
HttpsURLConnection connection = null;
135144
try {
136145
connection = openHttpsUrlConnection(getTesterApiUrl(path), token);
137146
connection.setDoOutput(true);
138147
connection.setRequestMethod(REQUEST_METHOD_POST);
139148
connection.addRequestProperty(CONTENT_TYPE_HEADER_KEY, JSON_CONTENT_TYPE);
140-
connection.addRequestProperty(CONTENT_ENCODING_HEADER_KEY, GZIP_CONTENT_ENCODING);
141149
for (Map.Entry<String, String> e : extraHeaders.entrySet()) {
142150
connection.addRequestProperty(e.getKey(), e.getValue());
143151
}
144-
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(connection.getOutputStream());
152+
OutputStream outputStream = connection.getOutputStream();
145153
try {
146-
gzipOutputStream.write(requestBody);
154+
requestBodyWriter.write(outputStream);
147155
} catch (IOException e) {
148-
throw getException(tag, "Error compressing network request body", Status.UNKNOWN, e);
156+
throw getException(tag, "Error writing network request body", Status.UNKNOWN, e);
149157
} finally {
150-
gzipOutputStream.close();
158+
outputStream.close();
151159
}
152160
return readResponse(tag, connection);
153161
} catch (IOException e) {

0 commit comments

Comments
 (0)