[Signin][Android] Add SigninActivityMonitor

SigninActivityMonitor tracks sign-in activities started from SigninUtils
and exposes this as an observable value. This CL also wires up
ConsistencyCookieManager so it tracks this value and exposes it for the
native side.

Bug: 949562
Change-Id: If2e5f9b596bd3d03a8d73d3606785e1aff020300
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1578548
Commit-Queue: David Roger <droger@chromium.org>
Reviewed-by: David Roger <droger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#653509}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java
index 2732c24..3e5a5ce 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java
@@ -12,7 +12,6 @@
 import android.provider.Settings;
 import android.support.annotation.Nullable;
 
-import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.browser.ChromeFeatureList;
@@ -20,6 +19,7 @@
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.GAIAServiceType;
+import org.chromium.components.signin.SigninActivityMonitor;
 import org.chromium.ui.base.WindowAndroid;
 
 /**
@@ -49,6 +49,7 @@
         return IntentUtils.safeStartActivity(context, intent);
     }
 
+    // TODO(https://crbug.com/955501): Migrate all clients to WindowAndroid and remove this.
     /**
      * Opens a Settings page with all accounts on the device.
      * @param context Context to use when starting the Activity.
@@ -60,6 +61,16 @@
         return IntentUtils.safeStartActivity(context, intent);
     }
 
+    /**
+     * Opens a Settings page with all accounts on the device.
+     * @param windowAndroid WindowAndroid to use when starting the Activity.
+     * @return Whether or not Android accepted the Intent.
+     */
+    public static boolean openSettingsForAllAccounts(WindowAndroid windowAndroid) {
+        Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
+        return startActivity(windowAndroid, intent);
+    }
+
     @CalledByNative
     private static void openAccountManagementScreen(WindowAndroid windowAndroid,
             @GAIAServiceType int gaiaServiceType, @Nullable String email) {
@@ -86,7 +97,7 @@
                     break;
                 default:
                     // Open generic accounts settings.
-                    SigninUtils.openSettingsForAllAccounts(ContextUtils.getApplicationContext());
+                    openSettingsForAllAccounts(windowAndroid);
                     break;
             }
             return;
@@ -105,16 +116,35 @@
         logEvent(ProfileAccountManagementMetrics.DIRECT_ADD_ACCOUNT, gaiaServiceTypeSignup);
 
         AccountManagerFacade.get().createAddAccountIntent((@Nullable Intent intent) -> {
-            Activity activity = windowAndroid.getActivity().get();
-            if (intent == null || activity == null
-                    || !IntentUtils.safeStartActivity(activity, intent)) {
-                // Failed to create or show an intent, open settings for all accounts so
-                // the user has a chance to create an account manually.
-                SigninUtils.openSettingsForAllAccounts(ContextUtils.getApplicationContext());
+            if (intent != null && startActivity(windowAndroid, intent)) {
+                return;
             }
+            // Failed to create or show an intent, open settings for all accounts so
+            // the user has a chance to create an account manually.
+            SigninUtils.openSettingsForAllAccounts(windowAndroid);
         });
     }
 
+    // TODO(https://crbug.com/953765): Move this to SigninActivityMonitor.
+    /**
+     * Starts an activity using the provided intent. The started activity will be tracked by
+     * {@link SigninActivityMonitor#hasOngoingActivity()}.
+     *
+     * @param windowAndroid The window to use when launching the intent.
+     * @param intent The intent to launch.
+     * @return Whether {@link WindowAndroid#showIntent} succeeded.
+     */
+    private static boolean startActivity(WindowAndroid windowAndroid, Intent intent) {
+        SigninActivityMonitor signinActivityMonitor = SigninActivityMonitor.get();
+        WindowAndroid.IntentCallback intentCallback =
+                (window, resultCode, data) -> signinActivityMonitor.activityFinished();
+        if (windowAndroid.showIntent(intent, intentCallback, null)) {
+            signinActivityMonitor.activityStarted();
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Log a UMA event for a given metric and a signin type.
      * @param metric One of ProfileAccountManagementMetrics constants.
diff --git a/components/signin/core/browser/android/BUILD.gn b/components/signin/core/browser/android/BUILD.gn
index 497cd55..52d54b2 100644
--- a/components/signin/core/browser/android/BUILD.gn
+++ b/components/signin/core/browser/android/BUILD.gn
@@ -24,6 +24,7 @@
     "//net/android:net_java",
     "//third_party/android_deps:android_support_v4_java",
     "//third_party/android_deps:com_android_support_support_annotations_java",
+    "//ui/android:ui_java",
   ]
 
   java_files = [
@@ -46,6 +47,7 @@
     "java/src/org/chromium/components/signin/OAuth2TokenService.java",
     "java/src/org/chromium/components/signin/ObservableValue.java",
     "java/src/org/chromium/components/signin/ProfileDataSource.java",
+    "java/src/org/chromium/components/signin/SigninActivityMonitor.java",
     "java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java",
   ]
 
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ConsistencyCookieManager.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ConsistencyCookieManager.java
index 3fdb3acc..56185c9 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ConsistencyCookieManager.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ConsistencyCookieManager.java
@@ -18,15 +18,17 @@
 public class ConsistencyCookieManager implements ObservableValue.Observer {
     private final long mNativeConsistencyCookieManager;
     private final AccountManagerFacade mAccountManagerFacade;
+    private final SigninActivityMonitor mSigninActivityMonitor;
     private boolean mIsUpdatePending;
 
     private ConsistencyCookieManager(long nativeConsistencyCookieManager) {
         ThreadUtils.assertOnUiThread();
         mNativeConsistencyCookieManager = nativeConsistencyCookieManager;
         mAccountManagerFacade = AccountManagerFacade.get();
+        mSigninActivityMonitor = SigninActivityMonitor.get();
 
         mAccountManagerFacade.isUpdatePending().addObserver(this);
-        // TODO(https://crbug.com/949562): Observe ongoing sign-in activities.
+        mSigninActivityMonitor.hasOngoingActivity().addObserver(this);
 
         mIsUpdatePending = calculateIsUpdatePending();
     }
@@ -41,8 +43,8 @@
     }
 
     private boolean calculateIsUpdatePending() {
-        // TODO(https://crbug.com/949562): Check for ongoing sign-in activities.
-        return mAccountManagerFacade.isUpdatePending().get();
+        return mAccountManagerFacade.isUpdatePending().get()
+                || mSigninActivityMonitor.hasOngoingActivity().get();
     }
 
     @CalledByNative
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/SigninActivityMonitor.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/SigninActivityMonitor.java
new file mode 100644
index 0000000..761b3f2
--- /dev/null
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/SigninActivityMonitor.java
@@ -0,0 +1,67 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.signin;
+
+import android.annotation.SuppressLint;
+import android.support.annotation.MainThread;
+
+import org.chromium.base.ThreadUtils;
+
+/**
+ * Monitors external activities started from sign-in flows (for example, activity to add an account
+ * to the device). Activities have to be launched using {@link #startSigninActivity}.
+ */
+public class SigninActivityMonitor {
+    @SuppressLint("StaticFieldLeak")
+    private static SigninActivityMonitor sInstance;
+
+    private int mActivityCounter;
+    private MutableObservableValue<Boolean> mHasOngoingActivity =
+            new MutableObservableValue<>(false);
+
+    private SigninActivityMonitor() {}
+
+    /**
+     * Returns a singleton instance of the SigninActivityMonitor.
+     */
+    @MainThread
+    public static SigninActivityMonitor get() {
+        ThreadUtils.assertOnUiThread();
+        if (sInstance == null) {
+            sInstance = new SigninActivityMonitor();
+        }
+        return sInstance;
+    }
+
+    /**
+     * Returns whether there are any ongoing sign-in activities.
+     */
+    public ObservableValue<Boolean> hasOngoingActivity() {
+        return mHasOngoingActivity;
+    }
+
+    // TODO(https://crbug.com/953765): Make this private.
+    /**
+     * Should be invoked when a signin activity is started.
+     */
+    public void activityStarted() {
+        assert mActivityCounter >= 0;
+
+        ++mActivityCounter;
+        if (mActivityCounter == 1) mHasOngoingActivity.set(true);
+    }
+
+    // TODO(https://crbug.com/953765): Make this private.
+    /**
+     * Should be invoked when a signin activity is finished. There should be a strict parity between
+     * {@link #activityStarted()} and {@link #activityFinished()} calls.
+     */
+    public void activityFinished() {
+        assert mActivityCounter > 0;
+
+        --mActivityCounter;
+        if (mActivityCounter == 0) mHasOngoingActivity.set(false);
+    }
+}