Skip to content

Commit 4270d62

Browse files
FAD API error handling (firebase#2835)
* Adding API error handling and better error messages * adding tests * adding copyright check * Moving additional error messages to the the constants class * Removing unnecessary tag * changing the visbility of the internal constructor
1 parent 52e1cf8 commit 4270d62

File tree

5 files changed

+208
-56
lines changed

5 files changed

+208
-56
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.appdistribution;
16+
17+
class Constants {
18+
static class ErrorMessages {
19+
public static final String NETWORK_ERROR =
20+
"Failed to fetch releases due to unknown network error";
21+
22+
public static final String JSON_PARSING_ERROR = "Error parsing service response";
23+
24+
public static final String AUTHENTICATION_ERROR = "Failed to authenticate the tester";
25+
26+
public static final String AUTHORIZATION_ERROR = "Failed to authorize the tester";
27+
28+
public static final String AUTHENTICATION_CANCELLED =
29+
"Tester cancelled the authentication flow";
30+
31+
public static final String NOT_FOUND_ERROR = "Tester or release not found";
32+
33+
public static final String TIMEOUT_ERROR = "Failed to fetch releases due to timeout";
34+
35+
public static final String UNKNOWN_ERROR = "Unknown Error";
36+
}
37+
}

firebase-app-distribution/src/main/java/com/google/firebase/appdistribution/FirebaseAppDistribution.java

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import android.os.Looper;
3232
import android.util.Log;
3333
import androidx.annotation.NonNull;
34+
import androidx.annotation.VisibleForTesting;
3435
import androidx.browser.customtabs.CustomTabsIntent;
3536
import androidx.core.content.pm.PackageInfoCompat;
3637
import androidx.core.os.HandlerCompat;
@@ -46,7 +47,6 @@
4647
import com.google.firebase.appdistribution.internal.ReleaseIdentificationUtils;
4748
import com.google.firebase.installations.FirebaseInstallationsApi;
4849
import com.google.firebase.installations.InstallationTokenResult;
49-
import java.net.ProtocolException;
5050
import java.util.List;
5151
import java.util.concurrent.Executor;
5252
import java.util.concurrent.Executors;
@@ -70,8 +70,9 @@ public class FirebaseAppDistribution implements Application.ActivityLifecycleCal
7070
private CancellationTokenSource checkForUpdateCancellationSource;
7171
private final Executor checkForUpdateExecutor;
7272

73-
/** Constructor for FirebaseAppDistribution */
74-
public FirebaseAppDistribution(
73+
/** Internal Constructor for FirebaseAppDistribution */
74+
@VisibleForTesting
75+
FirebaseAppDistribution(
7576
@NonNull FirebaseApp firebaseApp,
7677
@NonNull FirebaseInstallationsApi firebaseInstallationsApi,
7778
@NonNull FirebaseAppDistributionTesterApiClient firebaseAppDistributionTesterApiClient) {
@@ -207,7 +208,8 @@ public Task<AppDistributionRelease> checkForUpdate() {
207208
.addOnFailureListener(
208209
e ->
209210
setCheckForUpdateTaskCompletionError(
210-
new FirebaseAppDistributionException(e.getMessage(), AUTHENTICATION_FAILURE)));
211+
new FirebaseAppDistributionException(
212+
Constants.ErrorMessages.AUTHENTICATION_ERROR, AUTHENTICATION_FAILURE, e)));
211213

212214
return checkForUpdateTaskCompletionSource.getTask();
213215
}
@@ -217,16 +219,15 @@ public Task<AppDistributionRelease> checkForUpdate() {
217219
* starts an installation If the latest release is an AAB, directs the tester to the Play app to
218220
* complete the download and installation.
219221
*
220-
* @throws FirebaseAppDistributionException with UPDATE_NOT_AVAIALBLE exception if no new release
221-
* is cached from checkForUpdate
222+
* <p>cancels task with FirebaseAppDistributionException with UPDATE_NOT_AVAIALBLE exception if no
223+
* new release is cached from checkForUpdate
222224
*/
223225
@NonNull
224226
public UpdateTask updateApp() {
225227
return (UpdateTask) Tasks.forResult(new UpdateState(0, 0, UpdateStatus.PENDING));
226228
}
227229

228230
/** Returns true if the App Distribution tester is signed in */
229-
@NonNull
230231
public boolean isTesterSignedIn() {
231232
// todo: implement when signIn persistence is done
232233
throw new UnsupportedOperationException("Not yet implemented.");
@@ -266,7 +267,9 @@ public void onActivityResumed(@NonNull Activity activity) {
266267
// throw error if app reentered during signin
267268
if (currentlySigningIn) {
268269
currentlySigningIn = false;
269-
setSignInTaskCompletionError(new FirebaseAppDistributionException(AUTHENTICATION_CANCELED));
270+
setSignInTaskCompletionError(
271+
new FirebaseAppDistributionException(
272+
Constants.ErrorMessages.AUTHENTICATION_CANCELLED, AUTHENTICATION_CANCELED));
270273
}
271274
this.currentActivity = activity;
272275
}
@@ -350,7 +353,9 @@ public void onClick(DialogInterface dialogInterface, int i) {
350353
public void onFailure(@NonNull Exception e) {
351354
setSignInTaskCompletionError(
352355
new FirebaseAppDistributionException(
353-
e.getMessage(), AUTHENTICATION_FAILURE));
356+
Constants.ErrorMessages.AUTHENTICATION_ERROR,
357+
AUTHENTICATION_FAILURE,
358+
e));
354359
}
355360
});
356361
}
@@ -362,7 +367,8 @@ public void onFailure(@NonNull Exception e) {
362367
@Override
363368
public void onClick(DialogInterface dialogInterface, int i) {
364369
setSignInTaskCompletionError(
365-
new FirebaseAppDistributionException(AUTHENTICATION_CANCELED));
370+
new FirebaseAppDistributionException(
371+
"Tester cancelled authentication flow", AUTHENTICATION_CANCELED));
366372
dialogInterface.dismiss();
367373
}
368374
});
@@ -386,6 +392,7 @@ public void onSuccess(String fid) {
386392
};
387393
}
388394

395+
@VisibleForTesting
389396
AppDistributionRelease getLatestReleaseFromClient(
390397
String fid, String appId, String apiKey, String authToken)
391398
throws FirebaseAppDistributionException {
@@ -403,13 +410,11 @@ AppDistributionRelease getLatestReleaseFromClient(
403410
// Return null if retrieved latest release is older or currently installed
404411
return null;
405412
}
406-
} catch (FirebaseAppDistributionException | ProtocolException | NumberFormatException e) {
407-
if (e instanceof FirebaseAppDistributionException) {
408-
throw (FirebaseAppDistributionException) e;
409-
} else {
410-
throw new FirebaseAppDistributionException(
411-
e.getMessage(), FirebaseAppDistributionException.Status.NETWORK_FAILURE);
412-
}
413+
} catch (NumberFormatException e) {
414+
throw new FirebaseAppDistributionException(
415+
Constants.ErrorMessages.NETWORK_ERROR,
416+
FirebaseAppDistributionException.Status.NETWORK_FAILURE,
417+
e);
413418
}
414419
}
415420

@@ -434,7 +439,9 @@ private long getInstalledAppVersionCode(Context context) {
434439
} catch (PackageManager.NameNotFoundException e) {
435440
checkForUpdateTaskCompletionSource.setException(
436441
new FirebaseAppDistributionException(
437-
e.getMessage(), FirebaseAppDistributionException.Status.UNKNOWN));
442+
Constants.ErrorMessages.UNKNOWN_ERROR,
443+
FirebaseAppDistributionException.Status.UNKNOWN,
444+
e));
438445
}
439446
return PackageInfoCompat.getLongVersionCode(pInfo);
440447
}

firebase-app-distribution/src/main/java/com/google/firebase/appdistribution/FirebaseAppDistributionException.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,6 @@ public enum Status {
5858
@NonNull private final Status status;
5959
@Nullable private final AppDistributionRelease release;
6060

61-
public FirebaseAppDistributionException(
62-
@NonNull Status status, @Nullable AppDistributionRelease release) {
63-
this.status = status;
64-
this.release = release;
65-
}
66-
67-
public FirebaseAppDistributionException(@NonNull Status status) {
68-
this.status = status;
69-
this.release = null;
70-
}
71-
7261
public FirebaseAppDistributionException(@NonNull String message, @NonNull Status status) {
7362
super(message);
7463
this.status = status;
@@ -82,6 +71,13 @@ public FirebaseAppDistributionException(
8271
this.release = release;
8372
}
8473

74+
public FirebaseAppDistributionException(
75+
@NonNull String message, @NonNull Status status, @NonNull Throwable cause) {
76+
super(message, cause);
77+
this.status = status;
78+
this.release = null;
79+
}
80+
8581
public FirebaseAppDistributionException(
8682
@NonNull String message,
8783
@NonNull Status status,

firebase-app-distribution/src/main/java/com/google/firebase/appdistribution/FirebaseAppDistributionTesterApiClient.java

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,22 @@
1414

1515
package com.google.firebase.appdistribution;
1616

17+
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.AUTHENTICATION_FAILURE;
18+
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.NETWORK_FAILURE;
19+
1720
import androidx.annotation.NonNull;
1821
import com.google.firebase.appdistribution.internal.AppDistributionReleaseInternal;
1922
import java.io.BufferedInputStream;
2023
import java.io.ByteArrayOutputStream;
2124
import java.io.IOException;
2225
import java.io.InputStream;
2326
import java.net.MalformedURLException;
24-
import java.net.ProtocolException;
2527
import java.net.URL;
2628
import javax.net.ssl.HttpsURLConnection;
2729
import org.json.JSONException;
2830
import org.json.JSONObject;
2931

30-
public class FirebaseAppDistributionTesterApiClient {
32+
class FirebaseAppDistributionTesterApiClient {
3133

3234
private static final String RELEASE_ENDPOINT_URL_FORMAT =
3335
"https://firebaseapptesters.googleapis.com/v1alpha/devices/-/testerApps/%s/installations/%s/releases";
@@ -45,16 +47,16 @@ public class FirebaseAppDistributionTesterApiClient {
4547

4648
public @NonNull AppDistributionReleaseInternal fetchLatestRelease(
4749
@NonNull String fid, @NonNull String appId, @NonNull String apiKey, @NonNull String authToken)
48-
throws FirebaseAppDistributionException, ProtocolException {
50+
throws FirebaseAppDistributionException {
4951

5052
AppDistributionReleaseInternal latestRelease;
51-
HttpsURLConnection conn = openHttpsUrlConnection(appId, fid);
52-
conn.setRequestMethod(REQUEST_METHOD);
53-
conn.setRequestProperty(API_KEY_HEADER, apiKey);
54-
conn.setRequestProperty(INSTALLATION_AUTH_HEADER, authToken);
55-
53+
HttpsURLConnection connection = openHttpsUrlConnection(appId, fid);
5654
try {
57-
JSONObject latestReleaseJson = readFetchReleaseInputStream(conn.getInputStream());
55+
connection.setRequestMethod(REQUEST_METHOD);
56+
connection.setRequestProperty(API_KEY_HEADER, apiKey);
57+
connection.setRequestProperty(INSTALLATION_AUTH_HEADER, authToken);
58+
59+
JSONObject latestReleaseJson = readFetchReleaseInputStream(connection.getInputStream());
5860
final String displayVersion = latestReleaseJson.getString(DISPLAY_VERSION_JSON_KEY);
5961
final String buildVersion = latestReleaseJson.getString(BUILD_VERSION_JSON_KEY);
6062
String releaseNotes = tryGetValue(latestReleaseJson, RELEASE_NOTES_JSON_KEY);
@@ -79,16 +81,47 @@ public class FirebaseAppDistributionTesterApiClient {
7981
.build();
8082

8183
} catch (IOException | JSONException e) {
82-
// todo: change error status based on response code
83-
throw new FirebaseAppDistributionException(
84-
FirebaseAppDistributionException.Status.NETWORK_FAILURE);
84+
if (e instanceof JSONException) {
85+
throw new FirebaseAppDistributionException(
86+
Constants.ErrorMessages.JSON_PARSING_ERROR, NETWORK_FAILURE, e);
87+
}
88+
89+
throw getExceptionForHttpResponse(connection);
8590
} finally {
86-
conn.disconnect();
91+
connection.disconnect();
8792
}
8893

8994
return latestRelease;
9095
}
9196

97+
private FirebaseAppDistributionException getExceptionForHttpResponse(
98+
HttpsURLConnection connection) {
99+
try {
100+
switch (connection.getResponseCode()) {
101+
case 401:
102+
return new FirebaseAppDistributionException(
103+
Constants.ErrorMessages.AUTHENTICATION_ERROR, AUTHENTICATION_FAILURE);
104+
case 403:
105+
case 400:
106+
return new FirebaseAppDistributionException(
107+
Constants.ErrorMessages.AUTHORIZATION_ERROR, AUTHENTICATION_FAILURE);
108+
case 404:
109+
return new FirebaseAppDistributionException(
110+
Constants.ErrorMessages.NOT_FOUND_ERROR, AUTHENTICATION_FAILURE);
111+
case 408:
112+
case 504:
113+
return new FirebaseAppDistributionException(
114+
Constants.ErrorMessages.TIMEOUT_ERROR, NETWORK_FAILURE);
115+
default:
116+
return new FirebaseAppDistributionException(
117+
Constants.ErrorMessages.NETWORK_ERROR, NETWORK_FAILURE);
118+
}
119+
} catch (IOException ex) {
120+
return new FirebaseAppDistributionException(
121+
Constants.ErrorMessages.NETWORK_ERROR, NETWORK_FAILURE, ex);
122+
}
123+
}
124+
92125
private String tryGetValue(JSONObject jsonObject, String key) {
93126
try {
94127
return jsonObject.getString(key);
@@ -107,7 +140,9 @@ private JSONObject readFetchReleaseInputStream(InputStream in)
107140
latestRelease = json.getJSONArray("releases").getJSONObject(0);
108141
} catch (JSONException e) {
109142
throw new FirebaseAppDistributionException(
110-
e.getMessage(), FirebaseAppDistributionException.Status.UNKNOWN);
143+
Constants.ErrorMessages.JSON_PARSING_ERROR,
144+
FirebaseAppDistributionException.Status.UNKNOWN,
145+
e);
111146
}
112147
return latestRelease;
113148
}
@@ -120,7 +155,7 @@ HttpsURLConnection openHttpsUrlConnection(String appId, String fid)
120155
httpsURLConnection = (HttpsURLConnection) url.openConnection();
121156
} catch (IOException e) {
122157
throw new FirebaseAppDistributionException(
123-
e.getMessage(), FirebaseAppDistributionException.Status.NETWORK_FAILURE);
158+
Constants.ErrorMessages.NETWORK_ERROR, NETWORK_FAILURE, e);
124159
}
125160
return httpsURLConnection;
126161
}
@@ -131,7 +166,9 @@ private URL getReleasesEndpointUrl(String appId, String fid)
131166
return new URL(String.format(RELEASE_ENDPOINT_URL_FORMAT, appId, fid));
132167
} catch (MalformedURLException e) {
133168
throw new FirebaseAppDistributionException(
134-
e.getMessage(), FirebaseAppDistributionException.Status.UNKNOWN);
169+
Constants.ErrorMessages.UNKNOWN_ERROR,
170+
FirebaseAppDistributionException.Status.UNKNOWN,
171+
e);
135172
}
136173
}
137174

0 commit comments

Comments
 (0)