14
14
15
15
package com .google .firebase .app .distribution ;
16
16
17
- import static com .google .firebase .app .distribution .FirebaseAppDistributionException .Status .AUTHENTICATION_FAILURE ;
18
- import static com .google .firebase .app .distribution .FirebaseAppDistributionException .Status .NETWORK_FAILURE ;
19
-
20
17
import android .content .Context ;
21
18
import android .content .pm .PackageManager ;
22
19
import androidx .annotation .NonNull ;
20
+ import androidx .annotation .Nullable ;
23
21
import com .google .android .gms .common .util .AndroidUtilsLight ;
24
22
import com .google .android .gms .common .util .Hex ;
23
+ import com .google .firebase .app .distribution .Constants .ErrorMessages ;
24
+ import com .google .firebase .app .distribution .FirebaseAppDistributionException .Status ;
25
25
import java .io .BufferedInputStream ;
26
26
import java .io .ByteArrayOutputStream ;
27
27
import java .io .IOException ;
28
28
import java .io .InputStream ;
29
29
import java .net .MalformedURLException ;
30
+ import java .net .ProtocolException ;
30
31
import java .net .URL ;
31
32
import javax .net .ssl .HttpsURLConnection ;
33
+ import org .json .JSONArray ;
32
34
import org .json .JSONException ;
33
35
import org .json .JSONObject ;
34
36
@@ -55,26 +57,42 @@ class FirebaseAppDistributionTesterApiClient {
55
57
56
58
public static final int DEFAULT_BUFFER_SIZE = 8192 ;
57
59
58
- public @ NonNull AppDistributionReleaseInternal fetchNewRelease (
60
+ /**
61
+ * Fetches and returns the lastest release for the app that the tester has access to, or null if
62
+ * the tester doesn't have access to any releases.
63
+ */
64
+ @ Nullable
65
+ public AppDistributionReleaseInternal fetchNewRelease (
59
66
@ NonNull String fid ,
60
67
@ NonNull String appId ,
61
68
@ NonNull String apiKey ,
62
69
@ NonNull String authToken ,
63
70
@ NonNull Context context )
64
71
throws FirebaseAppDistributionException {
72
+ HttpsURLConnection connection = openHttpsUrlConnection (appId , fid , apiKey , authToken , context );
73
+ String responseBody ;
74
+ try (BufferedInputStream inputStream = new BufferedInputStream (connection .getInputStream ())) {
75
+ responseBody = convertInputStreamToString (inputStream );
76
+ } catch (IOException e ) {
77
+ throw getExceptionForHttpResponse (connection , e );
78
+ } finally {
79
+ connection .disconnect ();
80
+ }
81
+ return parseNewRelease (responseBody );
82
+ }
65
83
66
- AppDistributionReleaseInternal newRelease ;
67
- HttpsURLConnection connection = openHttpsUrlConnection ( appId , fid );
84
+ AppDistributionReleaseInternal parseNewRelease ( String responseBody )
85
+ throws FirebaseAppDistributionException {
68
86
try {
69
- connection . setRequestMethod ( REQUEST_METHOD );
70
- connection . setRequestProperty ( API_KEY_HEADER , apiKey );
71
- connection . setRequestProperty ( INSTALLATION_AUTH_HEADER , authToken ) ;
72
- connection . addRequestProperty ( X_ANDROID_PACKAGE_HEADER_KEY , context . getPackageName ());
73
- connection . addRequestProperty (
74
- X_ANDROID_CERT_HEADER_KEY , getFingerprintHashForPackage ( context ));
75
-
76
- InputStream inputStream = connection . getInputStream ();
77
- JSONObject newReleaseJson = readFetchReleaseInputStream ( inputStream );
87
+ JSONObject responseJson = new JSONObject ( responseBody );
88
+ if (! responseJson . has ( "releases" )) {
89
+ return null ;
90
+ }
91
+ JSONArray releasesJson = responseJson . getJSONArray ( "releases" );
92
+ if ( releasesJson . length () == 0 ) {
93
+ return null ;
94
+ }
95
+ JSONObject newReleaseJson = releasesJson . getJSONObject ( 0 );
78
96
final String displayVersion = newReleaseJson .getString (DISPLAY_VERSION_JSON_KEY );
79
97
final String buildVersion = newReleaseJson .getString (BUILD_VERSION_JSON_KEY );
80
98
String releaseNotes = tryGetValue (newReleaseJson , RELEASE_NOTES_JSON_KEY );
@@ -88,7 +106,7 @@ class FirebaseAppDistributionTesterApiClient {
88
106
? BinaryType .APK
89
107
: BinaryType .AAB ;
90
108
91
- newRelease =
109
+ AppDistributionReleaseInternal newRelease =
92
110
AppDistributionReleaseInternal .builder ()
93
111
.setDisplayVersion (displayVersion )
94
112
.setBuildVersion (buildVersion )
@@ -99,48 +117,50 @@ class FirebaseAppDistributionTesterApiClient {
99
117
.setApkHash (apkHash )
100
118
.setDownloadUrl (downloadUrl )
101
119
.build ();
102
- inputStream .close ();
103
120
104
- } catch (IOException | JSONException e ) {
105
- if (e instanceof JSONException ) {
106
- LogWrapper .getInstance ().e (TAG + "Error parsing the new release." , e );
107
- throw new FirebaseAppDistributionException (
108
- Constants .ErrorMessages .JSON_PARSING_ERROR , NETWORK_FAILURE , e );
109
- }
110
- throw getExceptionForHttpResponse (connection );
111
- } finally {
112
- connection .disconnect ();
121
+ LogWrapper .getInstance ().v ("Zip hash for the new release " + newRelease .getApkHash ());
122
+ return newRelease ;
123
+ } catch (JSONException e ) {
124
+ LogWrapper .getInstance ().e (TAG + "Error parsing the new release." , e );
125
+ throw new FirebaseAppDistributionException (
126
+ ErrorMessages .JSON_PARSING_ERROR , Status .UNKNOWN , e );
113
127
}
114
- LogWrapper .getInstance ().v ("Zip hash for the new release " + newRelease .getApkHash ());
115
- return newRelease ;
116
128
}
117
129
118
130
private FirebaseAppDistributionException getExceptionForHttpResponse (
119
- HttpsURLConnection connection ) {
131
+ HttpsURLConnection connection , Exception cause ) {
132
+ // TODO(lkellogg): this try-catch should be unnecessary because it will only throw an
133
+ // IOException here if we couldn't connect to the server, in which case getInputStream() would
134
+ // have already failed with the same exception. We also weirdly have to choose one of the two
135
+ // thrown exceptions to set as the cause. We can avoid this by checking the response code
136
+ // first, and then catching any unexpected exceptions when reading the input stream, essentially
137
+ // combining the "default" case below with this try-catch.
138
+ int responseCode ;
120
139
try {
121
- LogWrapper .getInstance ().e (TAG + "Failed due to " + connection .getResponseCode ());
122
- switch (connection .getResponseCode ()) {
123
- case 401 :
124
- return new FirebaseAppDistributionException (
125
- Constants .ErrorMessages .AUTHENTICATION_ERROR , AUTHENTICATION_FAILURE );
126
- case 403 :
127
- case 400 :
128
- return new FirebaseAppDistributionException (
129
- Constants .ErrorMessages .AUTHORIZATION_ERROR , AUTHENTICATION_FAILURE );
130
- case 404 :
131
- return new FirebaseAppDistributionException (
132
- Constants .ErrorMessages .NOT_FOUND_ERROR , AUTHENTICATION_FAILURE );
133
- case 408 :
134
- case 504 :
135
- return new FirebaseAppDistributionException (
136
- Constants .ErrorMessages .TIMEOUT_ERROR , NETWORK_FAILURE );
137
- default :
138
- return new FirebaseAppDistributionException (
139
- Constants .ErrorMessages .NETWORK_ERROR , NETWORK_FAILURE );
140
- }
141
- } catch (IOException ex ) {
140
+ responseCode = connection .getResponseCode ();
141
+ } catch (IOException e ) {
142
142
return new FirebaseAppDistributionException (
143
- Constants .ErrorMessages .NETWORK_ERROR , NETWORK_FAILURE , ex );
143
+ ErrorMessages .NETWORK_ERROR , Status .NETWORK_FAILURE , e );
144
+ }
145
+ LogWrapper .getInstance ().e (TAG + "Failed due to " + responseCode );
146
+ switch (responseCode ) {
147
+ case 401 :
148
+ return new FirebaseAppDistributionException (
149
+ ErrorMessages .AUTHENTICATION_ERROR , Status .AUTHENTICATION_FAILURE , cause );
150
+ case 403 :
151
+ case 400 :
152
+ return new FirebaseAppDistributionException (
153
+ ErrorMessages .AUTHORIZATION_ERROR , Status .AUTHENTICATION_FAILURE , cause );
154
+ case 404 :
155
+ return new FirebaseAppDistributionException (
156
+ ErrorMessages .NOT_FOUND_ERROR , Status .AUTHENTICATION_FAILURE , cause );
157
+ case 408 :
158
+ case 504 :
159
+ return new FirebaseAppDistributionException (
160
+ ErrorMessages .TIMEOUT_ERROR , Status .NETWORK_FAILURE , cause );
161
+ default :
162
+ return new FirebaseAppDistributionException (
163
+ ErrorMessages .UNKNOWN_ERROR , Status .UNKNOWN , cause );
144
164
}
145
165
}
146
166
@@ -152,33 +172,27 @@ private String tryGetValue(JSONObject jsonObject, String key) {
152
172
}
153
173
}
154
174
155
- private JSONObject readFetchReleaseInputStream (InputStream in )
156
- throws FirebaseAppDistributionException , IOException {
157
- JSONObject newRelease ;
158
- InputStream jsonIn = new BufferedInputStream (in );
159
- String result = convertInputStreamToString (jsonIn );
160
- try {
161
- JSONObject json = new JSONObject (result );
162
- newRelease = json .getJSONArray ("releases" ).getJSONObject (0 );
163
- } catch (JSONException e ) {
164
- throw new FirebaseAppDistributionException (
165
- Constants .ErrorMessages .JSON_PARSING_ERROR ,
166
- FirebaseAppDistributionException .Status .UNKNOWN ,
167
- e );
168
- }
169
- return newRelease ;
170
- }
171
-
172
- HttpsURLConnection openHttpsUrlConnection (String appId , String fid )
175
+ HttpsURLConnection openHttpsUrlConnection (
176
+ String appId , String fid , String apiKey , String authToken , Context context )
173
177
throws FirebaseAppDistributionException {
174
178
HttpsURLConnection httpsURLConnection ;
175
179
URL url = getReleasesEndpointUrl (appId , fid );
176
180
try {
177
181
httpsURLConnection = (HttpsURLConnection ) url .openConnection ();
178
182
} catch (IOException e ) {
179
183
throw new FirebaseAppDistributionException (
180
- Constants . ErrorMessages .NETWORK_ERROR , NETWORK_FAILURE , e );
184
+ ErrorMessages .NETWORK_ERROR , Status . NETWORK_FAILURE , e );
181
185
}
186
+ try {
187
+ httpsURLConnection .setRequestMethod (REQUEST_METHOD );
188
+ } catch (ProtocolException e ) {
189
+ throw new FirebaseAppDistributionException (ErrorMessages .UNKNOWN_ERROR , Status .UNKNOWN , e );
190
+ }
191
+ httpsURLConnection .setRequestProperty (API_KEY_HEADER , apiKey );
192
+ httpsURLConnection .setRequestProperty (INSTALLATION_AUTH_HEADER , authToken );
193
+ httpsURLConnection .addRequestProperty (X_ANDROID_PACKAGE_HEADER_KEY , context .getPackageName ());
194
+ httpsURLConnection .addRequestProperty (
195
+ X_ANDROID_CERT_HEADER_KEY , getFingerprintHashForPackage (context ));
182
196
return httpsURLConnection ;
183
197
}
184
198
@@ -188,9 +202,7 @@ private URL getReleasesEndpointUrl(String appId, String fid)
188
202
return new URL (String .format (RELEASE_ENDPOINT_URL_FORMAT , appId , fid ));
189
203
} catch (MalformedURLException e ) {
190
204
throw new FirebaseAppDistributionException (
191
- Constants .ErrorMessages .UNKNOWN_ERROR ,
192
- FirebaseAppDistributionException .Status .UNKNOWN ,
193
- e );
205
+ ErrorMessages .UNKNOWN_ERROR , FirebaseAppDistributionException .Status .UNKNOWN , e );
194
206
}
195
207
}
196
208
@@ -204,11 +216,7 @@ private static String convertInputStreamToString(InputStream is) throws IOExcept
204
216
return result .toString ();
205
217
}
206
218
207
- /**
208
- * Gets the Android package's SHA-1 fingerprint.
209
- *
210
- * @param context
211
- */
219
+ /** Gets the Android package's SHA-1 fingerprint. */
212
220
private String getFingerprintHashForPackage (Context context ) {
213
221
byte [] hash ;
214
222
0 commit comments