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
1
package com .google .firebase .appdistribution ;
16
2
3
+ import static com .google .firebase .appdistribution .FirebaseAppDistributionException .Status .AUTHENTICATION_CANCELED ;
4
+ import static com .google .firebase .appdistribution .FirebaseAppDistributionException .Status .AUTHENTICATION_FAILURE ;
5
+
6
+ import android .app .Activity ;
7
+ import android .app .AlertDialog ;
8
+ import android .app .Application ;
9
+ import android .content .Context ;
10
+ import android .content .DialogInterface ;
11
+ import android .content .Intent ;
12
+ import android .content .pm .ResolveInfo ;
13
+ import android .net .Uri ;
14
+ import android .os .Bundle ;
15
+ import android .util .Log ;
17
16
import androidx .annotation .NonNull ;
17
+ import androidx .annotation .VisibleForTesting ;
18
+ import androidx .browser .customtabs .CustomTabsIntent ;
19
+ import com .google .android .gms .common .internal .Preconditions ;
20
+ import com .google .android .gms .tasks .OnFailureListener ;
21
+ import com .google .android .gms .tasks .OnSuccessListener ;
18
22
import com .google .android .gms .tasks .Task ;
23
+ import com .google .android .gms .tasks .TaskCompletionSource ;
19
24
import com .google .android .gms .tasks .Tasks ;
20
25
import com .google .firebase .FirebaseApp ;
26
+ import com .google .firebase .installations .FirebaseInstallationsApi ;
27
+ import java .util .List ;
28
+ import org .jetbrains .annotations .NotNull ;
29
+ import org .jetbrains .annotations .Nullable ;
21
30
22
- public class FirebaseAppDistribution {
31
+ public class FirebaseAppDistribution implements Application . ActivityLifecycleCallbacks {
23
32
24
33
private final FirebaseApp firebaseApp ;
34
+ private final FirebaseInstallationsApi firebaseInstallationsApi ;
35
+ private static final String TAG = "FirebaseAppDistribution" ;
36
+ private Activity currentActivity ;
37
+ @ VisibleForTesting private boolean currentlySigningIn = false ;
38
+ private TaskCompletionSource <Void > signInTaskCompletionSource ;
25
39
26
40
/** Constructor for FirebaseAppDistribution */
27
- FirebaseAppDistribution (@ NonNull FirebaseApp firebaseApp ) {
41
+ public FirebaseAppDistribution (
42
+ @ NonNull FirebaseApp firebaseApp ,
43
+ @ NonNull FirebaseInstallationsApi firebaseInstallationsApi ) {
28
44
this .firebaseApp = firebaseApp ;
45
+ this .firebaseInstallationsApi = firebaseInstallationsApi ;
29
46
}
30
47
31
- /** @return a FirebaseInstallationsApi instance */
48
+ /** @return a FirebaseAppDistribution instance */
32
49
@ NonNull
33
50
public static FirebaseAppDistribution getInstance () {
34
- return new FirebaseAppDistribution (FirebaseApp .getInstance ());
51
+ return getInstance (FirebaseApp .getInstance ());
52
+ }
53
+
54
+ /**
55
+ * Returns the {@link FirebaseAppDistribution} initialized with a custom {@link FirebaseApp}.
56
+ *
57
+ * @param app a custom {@link FirebaseApp}
58
+ * @return a {@link FirebaseAppDistribution} instance
59
+ */
60
+ @ NonNull
61
+ public static FirebaseAppDistribution getInstance (@ NonNull FirebaseApp app ) {
62
+ Preconditions .checkArgument (app != null , "Null is not a valid value of FirebaseApp." );
63
+ return (FirebaseAppDistribution ) app .get (FirebaseAppDistribution .class );
35
64
}
36
65
37
66
/**
@@ -44,7 +73,31 @@ public static FirebaseAppDistribution getInstance() {
44
73
*/
45
74
@ NonNull
46
75
public Task <AppDistributionRelease > updateToLatestRelease () {
47
- return Tasks .forResult (null );
76
+ Log .v ("updateToLatestRelease" , "called" );
77
+ Log .v ("updateToLatestRelease" , currentActivity .getClass ().getName ());
78
+
79
+ TaskCompletionSource <AppDistributionRelease > taskCompletionSource =
80
+ new TaskCompletionSource <>();
81
+
82
+ Task <Void > signInTask = signInTester ();
83
+
84
+ signInTask
85
+ .addOnSuccessListener (
86
+ new OnSuccessListener <Void >() {
87
+ @ Override
88
+ public void onSuccess (Void unused ) {
89
+ taskCompletionSource .setResult (null );
90
+ }
91
+ })
92
+ .addOnFailureListener (
93
+ new OnFailureListener () {
94
+ @ Override
95
+ public void onFailure (@ NonNull @ NotNull Exception e ) {
96
+ taskCompletionSource .setException (e );
97
+ }
98
+ });
99
+
100
+ return taskCompletionSource .getTask ();
48
101
}
49
102
50
103
/**
@@ -62,18 +115,129 @@ public Task<AppDistributionRelease> checkForUpdate() {
62
115
* starts an installation If the latest release is an AAB, directs the tester to the Play app to
63
116
* complete the download and installation.
64
117
*
65
- * @throws an {@link Status.UPDATE_NOT_AVAILABLE_ERROR} exception if no new release is cached from
66
- * checkForUpdate
118
+ * @throws FirebaseAppDistributionException with UPDATE_NOT_AVAIALBLE exception if no new release
119
+ * is cached from checkForUpdate
67
120
*/
68
121
@ NonNull
69
122
public UpdateTask updateApp () {
70
123
return (UpdateTask ) Tasks .forResult (new UpdateState (0 , 0 , UpdateStatus .PENDING ));
71
124
}
72
125
126
+ private boolean supportsCustomTabs (Context context ) {
127
+ Intent customTabIntent = new Intent ("android.support.customtabs.action.CustomTabsService" );
128
+ customTabIntent .setPackage ("com.android.chrome" );
129
+ List <ResolveInfo > resolveInfos =
130
+ context .getPackageManager ().queryIntentServices (customTabIntent , 0 );
131
+ return resolveInfos != null && !resolveInfos .isEmpty ();
132
+ }
133
+
134
+ public static @ NonNull String getApplicationName (@ NonNull Context context ) {
135
+ try {
136
+ return context .getApplicationInfo ().loadLabel (context .getPackageManager ()).toString ();
137
+ } catch (Exception e ) {
138
+ Log .e (TAG , "Unable to retrieve App name" );
139
+ return "" ;
140
+ }
141
+ }
142
+
143
+ private void openSignIn (Uri uri ) {
144
+ currentlySigningIn = true ;
145
+ if (supportsCustomTabs (firebaseApp .getApplicationContext ())) {
146
+ // If we can launch a chrome view, try that.
147
+ CustomTabsIntent .Builder builder = new CustomTabsIntent .Builder ();
148
+
149
+ builder .setStartAnimations (
150
+ currentActivity .getApplicationContext (), R .anim .slide_in_right , R .anim .slide_out_left );
151
+
152
+ builder .setExitAnimations (
153
+ currentActivity .getApplicationContext (), R .anim .slide_in_left , R .anim .slide_out_right );
154
+
155
+ CustomTabsIntent customTabsIntent = builder .build ();
156
+ Intent intent = customTabsIntent .intent ;
157
+ intent .addFlags (Intent .FLAG_ACTIVITY_NO_HISTORY );
158
+ intent .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
159
+
160
+ customTabsIntent .launchUrl (currentActivity , uri );
161
+
162
+ } else {
163
+ // If we can't launch a chrome view try to launch anything that can handle a URL.
164
+ Intent browserIntent = new Intent (Intent .ACTION_VIEW , uri );
165
+ ResolveInfo info = currentActivity .getPackageManager ().resolveActivity (browserIntent , 0 );
166
+ browserIntent .addFlags (Intent .FLAG_ACTIVITY_NO_HISTORY );
167
+ browserIntent .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
168
+ currentActivity .startActivity (browserIntent );
169
+ }
170
+ }
171
+
73
172
/** Signs in the App Distribution tester. Presents the tester with a Google sign in UI */
74
173
@ NonNull
75
174
public Task <Void > signInTester () {
76
- return Tasks .forResult (null );
175
+ // TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();
176
+ this .signInTaskCompletionSource = new TaskCompletionSource <>();
177
+
178
+ Context context = firebaseApp .getApplicationContext ();
179
+
180
+ AlertDialog alertDialog = new AlertDialog .Builder (currentActivity ).create ();
181
+ alertDialog .setTitle (context .getString (R .string .signin_dialog_title ));
182
+ alertDialog .setMessage (context .getString (R .string .singin_dialog_message ));
183
+ alertDialog .setButton (
184
+ AlertDialog .BUTTON_POSITIVE ,
185
+ context .getString (R .string .singin_yes_button ),
186
+ new DialogInterface .OnClickListener () {
187
+ @ Override
188
+ public void onClick (DialogInterface dialogInterface , int i ) {
189
+ firebaseInstallationsApi
190
+ .getId ()
191
+ .addOnSuccessListener (
192
+ new OnSuccessListener <String >() {
193
+ @ Override
194
+ public void onSuccess (String fid ) {
195
+
196
+ Uri uri =
197
+ Uri .parse (
198
+ String .format (
199
+ "https://appdistribution.firebase.dev/nba/pub/apps/"
200
+ + "%s/installations/%s/buildalerts?appName=%s" ,
201
+ firebaseApp .getOptions ().getApplicationId (),
202
+ fid ,
203
+ getApplicationName (context )));
204
+
205
+ Log .v ("FAD Url" , uri .toString ());
206
+ Log .v ("FirebaseApp name" , firebaseApp .getName ());
207
+ openSignIn (uri );
208
+ }
209
+ })
210
+ .addOnFailureListener (
211
+ new OnFailureListener () {
212
+ @ Override
213
+ public void onFailure (@ NonNull @ NotNull Exception e ) {
214
+ setSignInTaskCompletionError (
215
+ new FirebaseAppDistributionException (AUTHENTICATION_FAILURE ));
216
+ }
217
+ });
218
+ }
219
+ });
220
+ alertDialog .setButton (
221
+ AlertDialog .BUTTON_NEGATIVE ,
222
+ context .getString (R .string .singin_no_button ),
223
+ new DialogInterface .OnClickListener () {
224
+ @ Override
225
+ public void onClick (DialogInterface dialogInterface , int i ) {
226
+ setSignInTaskCompletionError (
227
+ new FirebaseAppDistributionException (AUTHENTICATION_CANCELED ));
228
+ dialogInterface .dismiss ();
229
+ }
230
+ });
231
+
232
+ alertDialog .show ();
233
+
234
+ return signInTaskCompletionSource .getTask ();
235
+ }
236
+
237
+ private void setSignInTaskCompletionError (FirebaseAppDistributionException e ) {
238
+ if (!signInTaskCompletionSource .getTask ().isComplete ()) {
239
+ this .signInTaskCompletionSource .setException (e );
240
+ }
77
241
}
78
242
79
243
/** Returns true if the App Distribution tester is signed in */
@@ -84,4 +248,60 @@ public boolean isTesterSignedIn() {
84
248
85
249
/** Signs out the App Distribution tester */
86
250
public void signOutTester () {}
251
+
252
+ @ Override
253
+ public void onActivityCreated (
254
+ @ NonNull @ NotNull Activity activity , @ androidx .annotation .Nullable @ Nullable Bundle bundle ) {
255
+ Log .d (TAG , "Created activity: " + activity .getClass ().getName ());
256
+ // if signinactivity is created, sign-in was succesful
257
+ if (currentlySigningIn && activity instanceof SignInResultActivity ) {
258
+ currentlySigningIn = false ;
259
+ signInTaskCompletionSource .setResult (null );
260
+ }
261
+ }
262
+
263
+ @ Override
264
+ public void onActivityStarted (@ NonNull @ NotNull Activity activity ) {
265
+ Log .d (TAG , "Started activity: " + activity .getClass ().getName ());
266
+ }
267
+
268
+ @ Override
269
+ public void onActivityResumed (@ NonNull @ NotNull Activity activity ) {
270
+ Log .d (TAG , "Resumed activity: " + activity .getClass ().getName ());
271
+
272
+ if (activity instanceof SignInResultActivity ) {
273
+ return ;
274
+ }
275
+ // throw error if app reentered during signin
276
+ if (currentlySigningIn ) {
277
+ currentlySigningIn = false ;
278
+ setSignInTaskCompletionError (new FirebaseAppDistributionException (AUTHENTICATION_FAILURE ));
279
+ }
280
+ this .currentActivity = activity ;
281
+ }
282
+
283
+ @ Override
284
+ public void onActivityPaused (@ NonNull @ NotNull Activity activity ) {
285
+ Log .d (TAG , "Paused activity: " + activity .getClass ().getName ());
286
+ }
287
+
288
+ @ Override
289
+ public void onActivityStopped (@ NonNull @ NotNull Activity activity ) {
290
+ Log .d (TAG , "Stopped activity: " + activity .getClass ().getName ());
291
+ }
292
+
293
+ @ Override
294
+ public void onActivitySaveInstanceState (
295
+ @ NonNull @ NotNull Activity activity , @ NonNull @ NotNull Bundle bundle ) {
296
+ Log .d (TAG , "Saved activity: " + activity .getClass ().getName ());
297
+ }
298
+
299
+ @ Override
300
+ public void onActivityDestroyed (@ NonNull @ NotNull Activity activity ) {
301
+ Log .d (TAG , "Destroyed activity: " + activity .getClass ().getName ());
302
+ // destroyed comes after resumed
303
+ if (this .currentActivity == activity ) {
304
+ this .currentActivity = null ;
305
+ }
306
+ }
87
307
}
0 commit comments