| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef COMPONENTS_COLLABORATION_INTERNAL_COLLABORATION_CONTROLLER_H_ |
| #define COMPONENTS_COLLABORATION_INTERNAL_COLLABORATION_CONTROLLER_H_ |
| |
| #include <array> |
| #include <memory> |
| |
| #include "base/functional/callback.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/threading/thread_checker.h" |
| #include "components/collaboration/public/collaboration_controller_delegate.h" |
| #include "components/collaboration/public/collaboration_flow_type.h" |
| #include "components/collaboration/public/collaboration_service.h" |
| #include "components/data_sharing/public/data_sharing_service.h" |
| #include "components/data_sharing/public/group_data.h" |
| #include "components/saved_tab_groups/public/tab_group_sync_service.h" |
| #include "components/saved_tab_groups/public/types.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| |
| namespace syncer { |
| class SyncService; |
| } // namespace syncer |
| |
| namespace collaboration { |
| |
| class ControllerState; |
| |
| // The class for managing a single collaboration group flow. |
| class CollaborationController |
| : public tab_groups::TabGroupSyncService::Observer, |
| public CollaborationService::Observer { |
| public: |
| // States of a collaboration group flow. All new flows starts PENDNG. |
| enum class StateId { |
| // Initial state. The request has been received, awaiting delegate to be |
| // initialized and authentication status to be verified. |
| kPending, |
| |
| // Waiting on more information about a potentially managed account. |
| kWaitingForPolicyUpdate, |
| |
| // UI is showing authentication screens (sign-in/sync/access token). Waiting |
| // for result. |
| kAuthenticating, |
| |
| // Waiting for tab group sync service and data sharing service to be ready |
| // to use. |
| kWaitingForServicesToInitialize, |
| |
| // Authentication is completed. Controller will check requirements for each |
| // specific flows. |
| kCheckingFlowRequirements, |
| |
| // Delegate is showing invitation screen to the user. |
| kAddingUserToGroup, |
| |
| // Waiting for tab group to be added in sync and people group to be added in |
| // DataSharing. Loading UI should be shown. |
| kWaitingForSyncAndDataSharingGroup, |
| |
| // Delegate is promoting the local tab group. |
| kOpeningLocalTabGroup, |
| |
| // Delegate is showing the share sheet. |
| kShowingShareScreen, |
| |
| // Delegate requested creating a shared tab group. |
| kMakingTabGroupShared, |
| |
| // Delegate is sharing the tab group's url. |
| kSharingTabGroupUrl, |
| |
| // Delegate is showing the manage people screen. |
| kShowingManageScreen, |
| |
| // Delegate is showing the leave group screen. |
| kLeavingGroup, |
| |
| // Delegate is showing the delete group screen. |
| kDeletingGroup, |
| |
| // A shared tab group has been deleted, cleaning up. |
| kCleaningUpSharedTabGroup, |
| |
| // The flow is cancelled. |
| kCancel, |
| |
| // An error occurred and need to be shown to the user. |
| kError, |
| }; |
| |
| class Flow { |
| public: |
| // Join flow constructor. |
| Flow(FlowType type, const data_sharing::GroupToken& token); |
| |
| // Share/manage/leave/delete flow constructor. |
| Flow(FlowType type, const tab_groups::EitherGroupID& either_id); |
| |
| ~Flow(); |
| |
| Flow(const Flow&); |
| |
| const FlowType type; |
| |
| const data_sharing::GroupToken& join_token() const { |
| DCHECK_EQ(type, FlowType::kJoin); |
| return join_token_; |
| } |
| |
| const tab_groups::EitherGroupID& either_id() const { |
| return either_id_; |
| } |
| |
| const data_sharing::GroupToken& share_token() const { |
| DCHECK_EQ(type, FlowType::kShareOrManage); |
| CHECK(share_token_.IsValid()); |
| return share_token_; |
| } |
| |
| void set_share_token(const data_sharing::GroupToken& token) { |
| share_token_ = token; |
| } |
| |
| private: |
| // ID for join flow. |
| const data_sharing::GroupToken join_token_; |
| |
| // ID for share/manage/leave/delete flow. |
| const tab_groups::EitherGroupID either_id_; |
| data_sharing::GroupToken share_token_; |
| }; |
| |
| using FinishCallback = base::OnceCallback<void(const void*)>; |
| |
| explicit CollaborationController( |
| const Flow& flow, |
| CollaborationService* collaboration_service, |
| data_sharing::DataSharingService* data_sharing_service, |
| tab_groups::TabGroupSyncService* tab_group_sync_service, |
| syncer::SyncService* sync_service, |
| signin::IdentityManager* identity_manager, |
| std::unique_ptr<CollaborationControllerDelegate> delegate, |
| FinishCallback finish_and_delete); |
| ~CollaborationController() override; |
| |
| // Disallow copy/assign. |
| CollaborationController(const CollaborationController&) = delete; |
| CollaborationController& operator=(const CollaborationController&) = delete; |
| |
| // Getters. |
| CollaborationControllerDelegate* delegate() { return delegate_.get(); } |
| data_sharing::DataSharingService* data_sharing_service() { |
| return data_sharing_service_.get(); |
| } |
| tab_groups::TabGroupSyncService* tab_group_sync_service() { |
| return tab_group_sync_service_.get(); |
| } |
| syncer::SyncService* sync_service() { return sync_service_.get(); } |
| signin::IdentityManager* identity_manager() { |
| return identity_manager_.get(); |
| } |
| CollaborationService* collaboration_service() { |
| return collaboration_service_.get(); |
| } |
| Flow& flow() { return flow_; } |
| base::Time flow_start_time() const { return flow_start_time_; } |
| |
| // Called to transition to another state. |
| void TransitionTo(StateId state, |
| const CollaborationControllerDelegate::ErrorInfo& error = |
| CollaborationControllerDelegate::ErrorInfo()); |
| |
| // Called to refocus the current flow. |
| void PromoteCurrentSession(); |
| |
| // Called when the flow is finished to exit and clean itself up in the |
| // service. |
| void Exit(); |
| |
| // Cancels and exits the current flow. |
| void Cancel(); |
| |
| // Helper functions used in tests. |
| void SetStateForTesting(StateId state); |
| StateId GetStateForTesting(); |
| |
| // TabGroupSyncService::Observer implementation. |
| void OnTabGroupRemoved(const tab_groups::LocalTabGroupID& local_id, |
| tab_groups::TriggerSource source) override; |
| void OnTabGroupRemoved(const base::Uuid& sync_id, |
| tab_groups::TriggerSource source) override; |
| void OnTabGroupMigrated(const tab_groups::SavedTabGroup& new_group, |
| const base::Uuid& old_sync_id, |
| tab_groups::TriggerSource source) override; |
| |
| // CollaborationService::Observer implementation. |
| void OnServiceStatusChanged( |
| const CollaborationService::Observer::ServiceStatusUpdate& update) |
| override; |
| |
| private: |
| base::Time flow_start_time_; |
| static constexpr std::array<std::pair<StateId, StateId>, 53> |
| kValidTransitions = {{ |
| // Note: All state transition to kCancel when exiting. |
| {StateId::kPending, StateId::kCancel}, |
| {StateId::kWaitingForPolicyUpdate, StateId::kCancel}, |
| {StateId::kAuthenticating, StateId::kCancel}, |
| {StateId::kWaitingForServicesToInitialize, StateId::kCancel}, |
| {StateId::kCheckingFlowRequirements, StateId::kCancel}, |
| {StateId::kAddingUserToGroup, StateId::kCancel}, |
| {StateId::kWaitingForSyncAndDataSharingGroup, StateId::kCancel}, |
| {StateId::kOpeningLocalTabGroup, StateId::kCancel}, |
| {StateId::kShowingShareScreen, StateId::kCancel}, |
| {StateId::kMakingTabGroupShared, StateId::kCancel}, |
| {StateId::kSharingTabGroupUrl, StateId::kCancel}, |
| {StateId::kShowingManageScreen, StateId::kCancel}, |
| {StateId::kLeavingGroup, StateId::kCancel}, |
| {StateId::kDeletingGroup, StateId::kCancel}, |
| {StateId::kCleaningUpSharedTabGroup, StateId::kCancel}, |
| {StateId::kError, StateId::kCancel}, |
| // |
| // kPending transitions to: |
| // |
| // kAuthenticating: After all initialization steps complete |
| // successfully and authentication status is not valid. |
| // kWaitingForPolicyUpdate: Current account info are not ready. |
| // kCheckingFlowRequirements: After all initialization steps |
| // complete successfully and authentication status is valid. |
| // kError: An error occurred during initialization. |
| {StateId::kPending, StateId::kAuthenticating}, |
| {StateId::kPending, StateId::kWaitingForPolicyUpdate}, |
| {StateId::kPending, StateId::kWaitingForServicesToInitialize}, |
| {StateId::kPending, StateId::kError}, |
| |
| // kWaitingForPolicyUpdate transitions to: |
| // |
| // kAuthenticating: Current account is not managed and sync consent |
| // is needed. |
| // kCheckingFlowRequirements: Current account is not managed. |
| // kError: Current account is managed. |
| {StateId::kWaitingForPolicyUpdate, StateId::kAuthenticating}, |
| {StateId::kWaitingForPolicyUpdate, |
| StateId::kCheckingFlowRequirements}, |
| {StateId::kWaitingForPolicyUpdate, StateId::kError}, |
| |
| // kAuthenticating transitions to: |
| // |
| // kWaitingForPolicyUpdate: Current account info are not ready. |
| // kCheckingFlowRequirements: After all authentication steps are |
| // completed and verified. |
| // kError: An error occurred during authentication. |
| {StateId::kAuthenticating, StateId::kWaitingForPolicyUpdate}, |
| {StateId::kAuthenticating, StateId::kWaitingForServicesToInitialize}, |
| {StateId::kAuthenticating, StateId::kError}, |
| |
| // kWaitingForServicesToInitialize transition to: |
| // |
| // kCheckingFlowRequirements: After all services finish |
| // initializing. |
| // kError: An error occurred while waiting for service |
| // initialization. |
| {StateId::kWaitingForServicesToInitialize, |
| StateId::kCheckingFlowRequirements}, |
| {StateId::kWaitingForServicesToInitialize, StateId::kError}, |
| |
| // kCheckingFlowRequirements transition to: |
| // |
| // kAddingUserToGroup: When user is not in current people group. |
| // kWaitingForSyncAndDataSharingGroup: When user is in current |
| // people group, |
| // but tab group not found in sync. |
| // kOpeningLocalTabGroup: When user is in current people group, and |
| // tab group found in sync. |
| // kShowingShareScreen: In share flow, when the tab group is not |
| // shared. |
| // kShowingManageScreen: In share flow, when the tab group is a |
| // shared tab group. |
| // kError: An error occurred while checking requirements. This could |
| // be due to version mismatch. |
| {StateId::kCheckingFlowRequirements, StateId::kAddingUserToGroup}, |
| {StateId::kCheckingFlowRequirements, |
| StateId::kWaitingForSyncAndDataSharingGroup}, |
| {StateId::kCheckingFlowRequirements, StateId::kOpeningLocalTabGroup}, |
| {StateId::kCheckingFlowRequirements, StateId::kShowingShareScreen}, |
| {StateId::kCheckingFlowRequirements, StateId::kShowingManageScreen}, |
| {StateId::kCheckingFlowRequirements, StateId::kLeavingGroup}, |
| {StateId::kCheckingFlowRequirements, StateId::kDeletingGroup}, |
| {StateId::kCheckingFlowRequirements, StateId::kError}, |
| |
| // kAddingUserToGroup transition to: |
| // |
| // kWaitingForSyncAndDataSharingGroup: After the user accept the |
| // join |
| // invitation and the tab group is not yet added in sync. |
| // kOpeningLocalTabGroup: After the user accept the join invitation |
| // and the tab group is in sync. |
| // kError: An error occurred during invitation screen. |
| {StateId::kAddingUserToGroup, |
| StateId::kWaitingForSyncAndDataSharingGroup}, |
| {StateId::kAddingUserToGroup, StateId::kOpeningLocalTabGroup}, |
| {StateId::kAddingUserToGroup, StateId::kError}, |
| |
| // kWaitingForSyncAndDataSharingGroup transition to: |
| // |
| // kOpeningLocalTabGroup: After tab group is added in sync. |
| // kError: An error occurred while waiting for sync tab group. |
| {StateId::kWaitingForSyncAndDataSharingGroup, |
| StateId::kOpeningLocalTabGroup}, |
| {StateId::kWaitingForSyncAndDataSharingGroup, StateId::kError}, |
| |
| // kOpeningLocalTabGroup transition to: |
| // |
| // kError: An error occurred while opening local tab group. |
| {StateId::kOpeningLocalTabGroup, StateId::kError}, |
| |
| // kShowingShareScreen transition to: |
| // |
| // kSharingTabGroupUrl: After share screen request creating a shared |
| // tab group. |
| // kError: An error occurred while showing the share screen. |
| {StateId::kShowingShareScreen, StateId::kMakingTabGroupShared}, |
| {StateId::kShowingShareScreen, StateId::kError}, |
| |
| // kMakingTabGroupShared transition to: |
| // |
| // kSharingTabGroupUrl: After shared tab group is successfully |
| // created. |
| // kError: An error occurred while creating the shared tab group. |
| {StateId::kMakingTabGroupShared, StateId::kSharingTabGroupUrl}, |
| {StateId::kMakingTabGroupShared, StateId::kError}, |
| |
| // kSharingTabGroupUrl transition to: |
| // |
| // kError: An error occurred while sharing the url. |
| {StateId::kSharingTabGroupUrl, StateId::kError}, |
| |
| // kShowingManageScreen transition to: |
| // |
| // kCleaningUpSharedTabGroup: When deletion happened on a manage |
| // screen. |
| // kError: An error occurred while showing the manage people screen. |
| {StateId::kShowingManageScreen, StateId::kCleaningUpSharedTabGroup}, |
| {StateId::kShowingManageScreen, StateId::kError}, |
| |
| // kShowingManageScreen transition to: |
| // |
| // kCleaningUpSharedTabGroup: After leaving group successfully. |
| // kError: An error occurred while leaving group. |
| {StateId::kLeavingGroup, StateId::kCleaningUpSharedTabGroup}, |
| {StateId::kLeavingGroup, StateId::kError}, |
| |
| // kDeletingGroup transition to: |
| // |
| // kCleaningUpSharedTabGroup: When deletion has been completed. |
| // kError: An error occurred while deleting group. |
| {StateId::kDeletingGroup, StateId::kCleaningUpSharedTabGroup}, |
| {StateId::kDeletingGroup, StateId::kError}, |
| }}; |
| |
| // Note: This would only cancel the flow if the flow was started with the same |
| // variant of the EitherGroupID. The caller must call this method using both |
| // sync ID and local ID to be sure that the flow for the tab group is |
| // cancelled. |
| void CancelShareOrManageFlow(const tab_groups::EitherGroupID& either_id); |
| bool IsValidStateTransition(StateId from, StateId to); |
| std::unique_ptr<ControllerState> CreateStateObject(StateId state); |
| void Start(); |
| |
| THREAD_CHECKER(thread_checker_); |
| |
| std::unique_ptr<ControllerState> current_state_; |
| |
| Flow flow_; |
| bool is_deleting_{false}; |
| const raw_ptr<CollaborationService> collaboration_service_; |
| const raw_ptr<data_sharing::DataSharingService> data_sharing_service_; |
| const raw_ptr<tab_groups::TabGroupSyncService> tab_group_sync_service_; |
| const raw_ptr<syncer::SyncService> sync_service_; |
| const raw_ptr<signin::IdentityManager> identity_manager_; |
| std::unique_ptr<CollaborationControllerDelegate> delegate_; |
| FinishCallback finish_and_delete_; |
| base::ScopedObservation<tab_groups::TabGroupSyncService, |
| tab_groups::TabGroupSyncService::Observer> |
| tab_group_sync_service_observer_{this}; |
| base::ScopedObservation<CollaborationService, CollaborationService::Observer> |
| collaboration_service_observer_{this}; |
| base::WeakPtrFactory<CollaborationController> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace collaboration |
| |
| #endif // COMPONENTS_COLLABORATION_INTERNAL_COLLABORATION_CONTROLLER_H_ |