[GCM] Support registration before unregistration finishes

This is to solve the problem that the app could be uninstalled and
then reinstalled immediately. We do not want the app to encounter
ASYNC_OPERATION_PENDING error. We now wait till the unregistration
completes and then triggers the pending registration.

BUG=371880
TEST=new test added

Review URL: https://codereview.chromium.org/683913005

Cr-Commit-Position: refs/heads/master@{#301939}
diff --git a/components/gcm_driver/gcm_driver.cc b/components/gcm_driver/gcm_driver.cc
index 2e2d485..db036240 100644
--- a/components/gcm_driver/gcm_driver.cc
+++ b/components/gcm_driver/gcm_driver.cc
@@ -24,7 +24,7 @@
   return group_name == kGCMFieldTrialEnabledGroupName;
 }
 
-GCMDriver::GCMDriver() {
+GCMDriver::GCMDriver() : weak_ptr_factory_(this) {
 }
 
 GCMDriver::~GCMDriver() {
@@ -43,8 +43,8 @@
     return;
   }
 
-  // If previous un/register operation is still in progress, bail out.
-  if (IsAsyncOperationPending(app_id)) {
+  // If previous register operation is still in progress, bail out.
+  if (register_callbacks_.find(app_id) != register_callbacks_.end()) {
     callback.Run(std::string(), GCMClient::ASYNC_OPERATION_PENDING);
     return;
   }
@@ -55,6 +55,28 @@
 
   register_callbacks_[app_id] = callback;
 
+  // If previous unregister operation is still in progress, wait until it
+  // finishes. We don't want to throw ASYNC_OPERATION_PENDING when the user
+  // uninstalls an app (ungistering) and then reinstalls the app again
+  // (registering).
+  std::map<std::string, UnregisterCallback>::iterator unregister_iter =
+      unregister_callbacks_.find(app_id);
+  if (unregister_iter != unregister_callbacks_.end()) {
+    // Replace the original unregister callback with an intermediate callback
+    // that will invoke the original unregister callback and trigger the pending
+    // registration after the unregistration finishes.
+    // Note that some parameters to RegisterAfterUnregister are specified here
+    // when the callback is created (base::Bind supports the partial binding
+    // of parameters).
+    unregister_iter->second = base::Bind(
+        &GCMDriver::RegisterAfterUnregister,
+        weak_ptr_factory_.GetWeakPtr(),
+        app_id,
+        normalized_sender_ids,
+        unregister_iter->second);
+    return;
+  }
+
   RegisterImpl(app_id, normalized_sender_ids);
 }
 
@@ -70,7 +92,8 @@
   }
 
   // If previous un/register operation is still in progress, bail out.
-  if (IsAsyncOperationPending(app_id)) {
+  if (register_callbacks_.find(app_id) != register_callbacks_.end() ||
+      unregister_callbacks_.find(app_id) != unregister_callbacks_.end()) {
     callback.Run(GCMClient::ASYNC_OPERATION_PENDING);
     return;
   }
@@ -198,9 +221,17 @@
   send_callbacks_.clear();
 }
 
-bool GCMDriver::IsAsyncOperationPending(const std::string& app_id) const {
-  return register_callbacks_.find(app_id) != register_callbacks_.end() ||
-         unregister_callbacks_.find(app_id) != unregister_callbacks_.end();
+void GCMDriver::RegisterAfterUnregister(
+    const std::string& app_id,
+    const std::vector<std::string>& normalized_sender_ids,
+    const UnregisterCallback& unregister_callback,
+    GCMClient::Result result) {
+  // Invoke the original unregister callback.
+  unregister_callback.Run(result);
+
+  // Trigger the pending registration.
+  DCHECK(register_callbacks_.find(app_id) != register_callbacks_.end());
+  RegisterImpl(app_id, normalized_sender_ids);
 }
 
 }  // namespace gcm