-
Notifications
You must be signed in to change notification settings - Fork 626
Implement SignIn MVP #2783
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement SignIn MVP #2783
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,5 @@ | |
# limitations under the License. | ||
|
||
version=0.0.1 | ||
android.useAndroidX=true | ||
android.enableJetifier=true |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,24 +14,69 @@ | |
|
||
package com.google.firebase.appdistribution; | ||
|
||
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.AUTHENTICATION_CANCELED; | ||
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.AUTHENTICATION_FAILURE; | ||
|
||
import android.app.Activity; | ||
import android.app.AlertDialog; | ||
import android.app.Application; | ||
import android.content.Context; | ||
import android.content.DialogInterface; | ||
import android.content.Intent; | ||
import android.content.pm.ResolveInfo; | ||
import android.net.Uri; | ||
import android.os.Bundle; | ||
import android.util.Log; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.VisibleForTesting; | ||
import androidx.browser.customtabs.CustomTabsIntent; | ||
import com.google.android.gms.common.internal.Preconditions; | ||
import com.google.android.gms.tasks.CancellationTokenSource; | ||
import com.google.android.gms.tasks.OnFailureListener; | ||
import com.google.android.gms.tasks.OnSuccessListener; | ||
import com.google.android.gms.tasks.Task; | ||
import com.google.android.gms.tasks.TaskCompletionSource; | ||
import com.google.android.gms.tasks.Tasks; | ||
import com.google.firebase.FirebaseApp; | ||
import com.google.firebase.installations.FirebaseInstallationsApi; | ||
import java.util.List; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
public class FirebaseAppDistribution { | ||
public class FirebaseAppDistribution implements Application.ActivityLifecycleCallbacks { | ||
|
||
private final FirebaseApp firebaseApp; | ||
private final FirebaseInstallationsApi firebaseInstallationsApi; | ||
private static final String TAG = "FirebaseAppDistribution"; | ||
private Activity currentActivity; | ||
@VisibleForTesting private boolean currentlySigningIn = false; | ||
andrewbibiloni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private TaskCompletionSource<Void> signInTaskCompletionSource = null; | ||
private CancellationTokenSource signInCancellationSource; | ||
|
||
/** Constructor for FirebaseAppDistribution */ | ||
FirebaseAppDistribution(@NonNull FirebaseApp firebaseApp) { | ||
public FirebaseAppDistribution( | ||
@NonNull FirebaseApp firebaseApp, | ||
@NonNull FirebaseInstallationsApi firebaseInstallationsApi) { | ||
this.firebaseApp = firebaseApp; | ||
this.firebaseInstallationsApi = firebaseInstallationsApi; | ||
} | ||
|
||
/** @return a FirebaseInstallationsApi instance */ | ||
/** @return a FirebaseAppDistribution instance */ | ||
@NonNull | ||
public static FirebaseAppDistribution getInstance() { | ||
return new FirebaseAppDistribution(FirebaseApp.getInstance()); | ||
return getInstance(FirebaseApp.getInstance()); | ||
} | ||
|
||
/** | ||
* Returns the {@link FirebaseAppDistribution} initialized with a custom {@link FirebaseApp}. | ||
* | ||
* @param app a custom {@link FirebaseApp} | ||
* @return a {@link FirebaseAppDistribution} instance | ||
*/ | ||
@NonNull | ||
public static FirebaseAppDistribution getInstance(@NonNull FirebaseApp app) { | ||
Preconditions.checkArgument(app != null, "Null is not a valid value of FirebaseApp."); | ||
return (FirebaseAppDistribution) app.get(FirebaseAppDistribution.class); | ||
} | ||
|
||
/** | ||
|
@@ -44,7 +89,27 @@ public static FirebaseAppDistribution getInstance() { | |
*/ | ||
@NonNull | ||
public Task<AppDistributionRelease> updateToLatestRelease() { | ||
return Tasks.forResult(null); | ||
|
||
TaskCompletionSource<AppDistributionRelease> taskCompletionSource = | ||
new TaskCompletionSource<>(); | ||
|
||
andrewbibiloni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
signInTester() | ||
.addOnSuccessListener( | ||
new OnSuccessListener<Void>() { | ||
@Override | ||
public void onSuccess(Void unused) { | ||
taskCompletionSource.setResult(null); | ||
} | ||
}) | ||
.addOnFailureListener( | ||
new OnFailureListener() { | ||
@Override | ||
public void onFailure(@NonNull @NotNull Exception e) { | ||
taskCompletionSource.setException(e); | ||
} | ||
}); | ||
|
||
return taskCompletionSource.getTask(); | ||
} | ||
|
||
/** | ||
|
@@ -62,18 +127,126 @@ public Task<AppDistributionRelease> checkForUpdate() { | |
* starts an installation If the latest release is an AAB, directs the tester to the Play app to | ||
* complete the download and installation. | ||
* | ||
* @throws an {@link Status.UPDATE_NOT_AVAILABLE_ERROR} exception if no new release is cached from | ||
* checkForUpdate | ||
* @throws FirebaseAppDistributionException with UPDATE_NOT_AVAIALBLE exception if no new release | ||
* is cached from checkForUpdate | ||
*/ | ||
@NonNull | ||
public UpdateTask updateApp() { | ||
return (UpdateTask) Tasks.forResult(new UpdateState(0, 0, UpdateStatus.PENDING)); | ||
} | ||
|
||
private boolean supportsCustomTabs(Context context) { | ||
Intent customTabIntent = new Intent("android.support.customtabs.action.CustomTabsService"); | ||
customTabIntent.setPackage("com.android.chrome"); | ||
List<ResolveInfo> resolveInfos = | ||
context.getPackageManager().queryIntentServices(customTabIntent, 0); | ||
return resolveInfos != null && !resolveInfos.isEmpty(); | ||
} | ||
|
||
andrewbibiloni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private static String getApplicationName(Context context) { | ||
try { | ||
return context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); | ||
} catch (Exception e) { | ||
Log.e(TAG, "Unable to retrieve App name"); | ||
return ""; | ||
} | ||
} | ||
|
||
andrewbibiloni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private void openSignInFlowInBrowser(Uri uri) { | ||
currentlySigningIn = true; | ||
if (supportsCustomTabs(firebaseApp.getApplicationContext())) { | ||
// If we can launch a chrome view, try that. | ||
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build(); | ||
Intent intent = customTabsIntent.intent; | ||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); | ||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||
customTabsIntent.launchUrl(currentActivity, uri); | ||
|
||
} else { | ||
// If we can't launch a chrome view try to launch anything that can handle a URL. | ||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri); | ||
ResolveInfo info = currentActivity.getPackageManager().resolveActivity(browserIntent, 0); | ||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); | ||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||
currentActivity.startActivity(browserIntent); | ||
} | ||
} | ||
|
||
private OnSuccessListener<String> getFidGenerationOnSuccessListener(Context context) { | ||
return new OnSuccessListener<String>() { | ||
@Override | ||
public void onSuccess(String fid) { | ||
Uri uri = | ||
Uri.parse( | ||
String.format( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should change to |
||
"https://appdistribution.firebase.google.com/pub/apps/%s/installations/%s/buildalerts?appName=%s&packageName=%s", | ||
firebaseApp.getOptions().getApplicationId(), | ||
fid, | ||
getApplicationName(context), | ||
context.getPackageName())); | ||
openSignInFlowInBrowser(uri); | ||
} | ||
}; | ||
} | ||
|
||
private AlertDialog getSignInAlertDialog(Context context) { | ||
AlertDialog alertDialog = new AlertDialog.Builder(currentActivity).create(); | ||
andrewbibiloni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
alertDialog.setTitle(context.getString(R.string.signin_dialog_title)); | ||
alertDialog.setMessage(context.getString(R.string.singin_dialog_message)); | ||
alertDialog.setButton( | ||
AlertDialog.BUTTON_POSITIVE, | ||
context.getString(R.string.singin_yes_button), | ||
new DialogInterface.OnClickListener() { | ||
@Override | ||
public void onClick(DialogInterface dialogInterface, int i) { | ||
firebaseInstallationsApi | ||
.getId() | ||
.addOnSuccessListener(getFidGenerationOnSuccessListener(context)) | ||
.addOnFailureListener( | ||
new OnFailureListener() { | ||
@Override | ||
public void onFailure(@NonNull @NotNull Exception e) { | ||
setSignInTaskCompletionError( | ||
new FirebaseAppDistributionException(AUTHENTICATION_FAILURE)); | ||
} | ||
}); | ||
} | ||
}); | ||
alertDialog.setButton( | ||
AlertDialog.BUTTON_NEGATIVE, | ||
context.getString(R.string.singin_no_button), | ||
new DialogInterface.OnClickListener() { | ||
@Override | ||
public void onClick(DialogInterface dialogInterface, int i) { | ||
setSignInTaskCompletionError( | ||
new FirebaseAppDistributionException(AUTHENTICATION_CANCELED)); | ||
dialogInterface.dismiss(); | ||
} | ||
}); | ||
return alertDialog; | ||
} | ||
|
||
/** Signs in the App Distribution tester. Presents the tester with a Google sign in UI */ | ||
@NonNull | ||
public Task<Void> signInTester() { | ||
return Tasks.forResult(null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the comment here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Forgot to add cancellation logic, will do that in the next push. |
||
if (signInTaskCompletionSource != null && !signInTaskCompletionSource.getTask().isComplete()) { | ||
signInCancellationSource.cancel(); | ||
} | ||
|
||
signInCancellationSource = new CancellationTokenSource(); | ||
signInTaskCompletionSource = new TaskCompletionSource<>(signInCancellationSource.getToken()); | ||
|
||
Context context = firebaseApp.getApplicationContext(); | ||
AlertDialog alertDialog = getSignInAlertDialog(context); | ||
alertDialog.show(); | ||
|
||
return signInTaskCompletionSource.getTask(); | ||
} | ||
|
||
private void setSignInTaskCompletionError(FirebaseAppDistributionException e) { | ||
andrewbibiloni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (signInTaskCompletionSource != null && !signInTaskCompletionSource.getTask().isComplete()) { | ||
signInTaskCompletionSource.setException(e); | ||
} | ||
} | ||
|
||
/** Returns true if the App Distribution tester is signed in */ | ||
|
@@ -84,4 +257,62 @@ public boolean isTesterSignedIn() { | |
|
||
/** Signs out the App Distribution tester */ | ||
public void signOutTester() {} | ||
|
||
@Override | ||
public void onActivityCreated( | ||
@NonNull @NotNull Activity activity, @androidx.annotation.Nullable @Nullable Bundle bundle) { | ||
Log.d(TAG, "Created activity: " + activity.getClass().getName()); | ||
// if signinactivity is created, sign-in was succesful | ||
if (currentlySigningIn && activity instanceof SignInResultActivity) { | ||
currentlySigningIn = false; | ||
signInTaskCompletionSource.setResult(null); | ||
} | ||
} | ||
|
||
@Override | ||
public void onActivityStarted(@NonNull @NotNull Activity activity) { | ||
Log.d(TAG, "Started activity: " + activity.getClass().getName()); | ||
} | ||
|
||
@Override | ||
public void onActivityResumed(@NonNull @NotNull Activity activity) { | ||
Log.d(TAG, "Resumed activity: " + activity.getClass().getName()); | ||
|
||
// signInActivity is only opened after successful redirection from signIn flow, | ||
// should not be treated as reentering the app | ||
if (activity instanceof SignInResultActivity) { | ||
andrewbibiloni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
} | ||
|
||
// throw error if app reentered during signin | ||
if (currentlySigningIn) { | ||
currentlySigningIn = false; | ||
setSignInTaskCompletionError(new FirebaseAppDistributionException(AUTHENTICATION_FAILURE)); | ||
} | ||
this.currentActivity = activity; | ||
} | ||
|
||
@Override | ||
public void onActivityPaused(@NonNull @NotNull Activity activity) { | ||
Log.d(TAG, "Paused activity: " + activity.getClass().getName()); | ||
} | ||
|
||
@Override | ||
public void onActivityStopped(@NonNull @NotNull Activity activity) { | ||
Log.d(TAG, "Stopped activity: " + activity.getClass().getName()); | ||
} | ||
|
||
@Override | ||
public void onActivitySaveInstanceState( | ||
@NonNull @NotNull Activity activity, @NonNull @NotNull Bundle bundle) { | ||
Log.d(TAG, "Saved activity: " + activity.getClass().getName()); | ||
} | ||
|
||
@Override | ||
public void onActivityDestroyed(@NonNull @NotNull Activity activity) { | ||
Log.d(TAG, "Destroyed activity: " + activity.getClass().getName()); | ||
andrewbibiloni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (this.currentActivity == activity) { | ||
this.currentActivity = null; | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.