Skip to content

Commit 02522c8

Browse files
andrewbibiloniRachel Prince
authored andcommitted
Implement SignIn MVP
1 parent 460fd3f commit 02522c8

File tree

13 files changed

+580
-35
lines changed

13 files changed

+580
-35
lines changed

firebase-app-distribution/firebase-app-distribution.gradle

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ android {
2020
compileSdkVersion project.targetSdkVersion
2121

2222
defaultConfig {
23-
minSdkVersion project.minSdkVersion
23+
minSdkVersion 16
2424
targetSdkVersion project.targetSdkVersion
2525
multiDexEnabled true
2626
versionName version
@@ -40,6 +40,21 @@ android {
4040
dependencies {
4141
implementation 'org.jetbrains:annotations:15.0'
4242
implementation project(path: ':firebase-components')
43-
implementation 'com.google.android.gms:play-services-tasks:17.0.0'
43+
implementation project(path: ':firebase-installations-interop')
4444
implementation project(path: ':firebase-common')
45-
}
45+
implementation 'com.android.support:appcompat-v7:28.0.0'
46+
implementation project(path: ':firebase-installations')
47+
48+
testImplementation 'junit:junit:4.12'
49+
testImplementation 'junit:junit:4.12'
50+
testImplementation "org.robolectric:robolectric:$robolectricVersion"
51+
testImplementation "com.google.truth:truth:$googleTruthVersion"
52+
testImplementation 'org.mockito:mockito-core:2.25.0'
53+
androidTestImplementation "org.mockito:mockito-android:2.25.0"
54+
testImplementation 'androidx.test:core:1.2.0'
55+
56+
implementation 'com.google.android.gms:play-services-tasks:17.0.0'
57+
58+
implementation 'com.android.support:customtabs:28.0.0'
59+
implementation "androidx.browser:browser:1.3.0"
60+
}

firebase-app-distribution/gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313
# limitations under the License.
1414

1515
version=0.0.1
16+
android.useAndroidX=true
17+
android.enableJetifier=true

firebase-app-distribution/src/main/AndroidManifest.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,16 @@
2424
android:name="com.google.firebase.components:com.google.firebase.appdistribution.FirebaseAppDistributionRegistrar"
2525
android:value="com.google.firebase.components.ComponentRegistrar" />
2626
</service>
27+
28+
<activity android:name=".SignInResultActivity" android:exported="true">
29+
<intent-filter>
30+
<action android:name="android.intent.action.VIEW"/>
31+
<category android:name="android.intent.category.DEFAULT"/>
32+
<category android:name="android.intent.category.BROWSABLE"/>
33+
34+
<data android:scheme="${applicationId}" android:host="authredirect" />
35+
36+
</intent-filter>
37+
</activity>
2738
</application>
28-
</manifest>
39+
</manifest>
Lines changed: 242 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,66 @@
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-
151
package com.google.firebase.appdistribution;
162

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;
1716
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;
1822
import com.google.android.gms.tasks.Task;
23+
import com.google.android.gms.tasks.TaskCompletionSource;
1924
import com.google.android.gms.tasks.Tasks;
2025
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;
2130

22-
public class FirebaseAppDistribution {
31+
public class FirebaseAppDistribution implements Application.ActivityLifecycleCallbacks {
2332

2433
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;
2539

2640
/** Constructor for FirebaseAppDistribution */
27-
FirebaseAppDistribution(@NonNull FirebaseApp firebaseApp) {
41+
public FirebaseAppDistribution(
42+
@NonNull FirebaseApp firebaseApp,
43+
@NonNull FirebaseInstallationsApi firebaseInstallationsApi) {
2844
this.firebaseApp = firebaseApp;
45+
this.firebaseInstallationsApi = firebaseInstallationsApi;
2946
}
3047

31-
/** @return a FirebaseInstallationsApi instance */
48+
/** @return a FirebaseAppDistribution instance */
3249
@NonNull
3350
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);
3564
}
3665

3766
/**
@@ -44,7 +73,31 @@ public static FirebaseAppDistribution getInstance() {
4473
*/
4574
@NonNull
4675
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();
48101
}
49102

50103
/**
@@ -62,18 +115,129 @@ public Task<AppDistributionRelease> checkForUpdate() {
62115
* starts an installation If the latest release is an AAB, directs the tester to the Play app to
63116
* complete the download and installation.
64117
*
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
67120
*/
68121
@NonNull
69122
public UpdateTask updateApp() {
70123
return (UpdateTask) Tasks.forResult(new UpdateState(0, 0, UpdateStatus.PENDING));
71124
}
72125

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+
73172
/** Signs in the App Distribution tester. Presents the tester with a Google sign in UI */
74173
@NonNull
75174
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+
}
77241
}
78242

79243
/** Returns true if the App Distribution tester is signed in */
@@ -84,4 +248,60 @@ public boolean isTesterSignedIn() {
84248

85249
/** Signs out the App Distribution tester */
86250
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+
}
87307
}

0 commit comments

Comments
 (0)