| // 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.chrome.browser.share.share_sheet; |
| |
| import android.app.Activity; |
| import android.content.res.Configuration; |
| import android.view.View; |
| |
| import androidx.annotation.VisibleForTesting; |
| import androidx.appcompat.content.res.AppCompatResources; |
| |
| import org.chromium.base.ApiCompatibilityUtils; |
| import org.chromium.base.Callback; |
| import org.chromium.base.metrics.RecordHistogram; |
| import org.chromium.base.metrics.RecordUserAction; |
| import org.chromium.base.supplier.Supplier; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; |
| import org.chromium.chrome.browser.lifecycle.ConfigurationChangedObserver; |
| import org.chromium.chrome.browser.settings.SettingsLauncher; |
| import org.chromium.chrome.browser.share.ChromeShareExtras; |
| import org.chromium.chrome.browser.share.ShareHelper; |
| import org.chromium.chrome.browser.tab.Tab; |
| import org.chromium.chrome.modules.image_editor.ImageEditorModuleProvider; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver; |
| import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver; |
| import org.chromium.components.browser_ui.share.ShareParams; |
| import org.chromium.components.favicon.LargeIconBridge; |
| import org.chromium.ui.base.WindowAndroid; |
| import org.chromium.ui.base.WindowAndroid.ActivityStateObserver; |
| import org.chromium.ui.modelutil.PropertyModel; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Coordinator for displaying the share sheet. |
| */ |
| // TODO(crbug/1022172): Should be package-protected once modularization is complete. |
| public class ShareSheetCoordinator implements ActivityStateObserver, ChromeOptionShareCallback, |
| ConfigurationChangedObserver, |
| View.OnLayoutChangeListener { |
| private final BottomSheetController mBottomSheetController; |
| private final Supplier<Tab> mTabProvider; |
| private final ShareSheetPropertyModelBuilder mPropertyModelBuilder; |
| private final Callback<Tab> mPrintTabCallback; |
| private final SettingsLauncher mSettingsLauncher; |
| private final boolean mIsSyncEnabled; |
| private final ImageEditorModuleProvider mImageEditorModuleProvider; |
| private long mShareStartTime; |
| private boolean mExcludeFirstParty; |
| private boolean mIsMultiWindow; |
| private Set<Integer> mContentTypes; |
| private Activity mActivity; |
| private ActivityLifecycleDispatcher mLifecycleDispatcher; |
| private ChromeProvidedSharingOptionsProvider mChromeProvidedSharingOptionsProvider; |
| private ShareParams mShareParams; |
| private ShareSheetBottomSheetContent mBottomSheet; |
| private WindowAndroid mWindowAndroid; |
| private final BottomSheetObserver mBottomSheetObserver; |
| private final LargeIconBridge mIconBridge; |
| |
| /** |
| * Constructs a new ShareSheetCoordinator. |
| * |
| * @param controller The {@link BottomSheetController} for the current activity. |
| * @param lifecycleDispatcher Dispatcher for activity lifecycle events, e.g. configuration |
| * changes. |
| * @param tabProvider Supplier for the current activity tab. |
| * @param modelBuilder The {@link ShareSheetPropertyModelBuilder} for the share sheet. |
| * @param imageEditorModuleProvider Image Editor module entry point if present in the APK. |
| */ |
| // TODO(crbug/1022172): Should be package-protected once modularization is complete. |
| public ShareSheetCoordinator(BottomSheetController controller, |
| ActivityLifecycleDispatcher lifecycleDispatcher, Supplier<Tab> tabProvider, |
| ShareSheetPropertyModelBuilder modelBuilder, Callback<Tab> printTab, |
| LargeIconBridge iconBridge, SettingsLauncher settingsLauncher, boolean isSyncEnabled, |
| ImageEditorModuleProvider imageEditorModuleProvider) { |
| mBottomSheetController = controller; |
| mLifecycleDispatcher = lifecycleDispatcher; |
| mLifecycleDispatcher.register(this); |
| mTabProvider = tabProvider; |
| mPropertyModelBuilder = modelBuilder; |
| mPrintTabCallback = printTab; |
| mSettingsLauncher = settingsLauncher; |
| mIsSyncEnabled = isSyncEnabled; |
| mImageEditorModuleProvider = imageEditorModuleProvider; |
| mBottomSheetObserver = new EmptyBottomSheetObserver() { |
| @Override |
| public void onSheetContentChanged(BottomSheetContent bottomSheet) { |
| super.onSheetContentChanged(bottomSheet); |
| if (bottomSheet == mBottomSheet) { |
| mBottomSheet.getContentView().addOnLayoutChangeListener( |
| ShareSheetCoordinator.this::onLayoutChange); |
| } else { |
| mBottomSheet.getContentView().removeOnLayoutChangeListener( |
| ShareSheetCoordinator.this::onLayoutChange); |
| } |
| } |
| }; |
| mBottomSheetController.addObserver(mBottomSheetObserver); |
| mIconBridge = iconBridge; |
| } |
| |
| protected void destroy() { |
| if (mShareParams != null) { |
| ShareParams.TargetChosenCallback callback = mShareParams.getCallback(); |
| if (callback != null) { |
| callback.onCancel(); |
| } |
| } |
| if (mWindowAndroid != null) { |
| mWindowAndroid.removeActivityStateObserver(this); |
| mWindowAndroid = null; |
| } |
| if (mLifecycleDispatcher != null) { |
| mLifecycleDispatcher.unregister(this); |
| mLifecycleDispatcher = null; |
| } |
| } |
| |
| // TODO(crbug/1022172): Should be package-protected once modularization is complete. |
| public void showShareSheet( |
| ShareParams params, ChromeShareExtras chromeShareExtras, long shareStartTime) { |
| mShareParams = params; |
| mActivity = params.getWindow().getActivity().get(); |
| if (mActivity == null) return; |
| |
| if (mWindowAndroid == null) { |
| mWindowAndroid = params.getWindow(); |
| if (mWindowAndroid != null) { |
| mWindowAndroid.addActivityStateObserver(this); |
| } |
| } |
| |
| mBottomSheet = new ShareSheetBottomSheetContent(mActivity, mIconBridge, this, params); |
| |
| mShareStartTime = shareStartTime; |
| mContentTypes = ShareSheetPropertyModelBuilder.getContentTypes(params, chromeShareExtras); |
| List<PropertyModel> firstPartyApps = |
| createFirstPartyPropertyModels(mActivity, params, chromeShareExtras, mContentTypes); |
| List<PropertyModel> thirdPartyApps = createThirdPartyPropertyModels( |
| mActivity, params, mContentTypes, chromeShareExtras.saveLastUsed()); |
| |
| mBottomSheet.createRecyclerViews( |
| firstPartyApps, thirdPartyApps, mContentTypes, params.getFileContentType()); |
| |
| boolean shown = mBottomSheetController.requestShowContent(mBottomSheet, true); |
| if (shown) { |
| long delta = System.currentTimeMillis() - shareStartTime; |
| RecordHistogram.recordMediumTimesHistogram( |
| "Sharing.SharingHubAndroid.TimeToShowShareSheet", delta); |
| } |
| } |
| |
| // Used by first party features to share with only non-chrome apps. |
| @Override |
| public void showThirdPartyShareSheet( |
| ShareParams params, ChromeShareExtras chromeShareExtras, long shareStartTime) { |
| mExcludeFirstParty = true; |
| showShareSheet(params, chromeShareExtras, shareStartTime); |
| } |
| |
| List<PropertyModel> createFirstPartyPropertyModels(Activity activity, ShareParams shareParams, |
| ChromeShareExtras chromeShareExtras, Set<Integer> contentTypes) { |
| if (mExcludeFirstParty) { |
| return new ArrayList<>(); |
| } |
| mChromeProvidedSharingOptionsProvider = new ChromeProvidedSharingOptionsProvider(activity, |
| mTabProvider, mBottomSheetController, mBottomSheet, shareParams, chromeShareExtras, |
| mPrintTabCallback, mSettingsLauncher, mIsSyncEnabled, mShareStartTime, this, |
| mImageEditorModuleProvider); |
| mIsMultiWindow = ApiCompatibilityUtils.isInMultiWindowMode(activity); |
| |
| return mChromeProvidedSharingOptionsProvider.getPropertyModels( |
| contentTypes, mIsMultiWindow); |
| } |
| |
| @VisibleForTesting |
| List<PropertyModel> createThirdPartyPropertyModels(Activity activity, ShareParams params, |
| Set<Integer> contentTypes, boolean saveLastUsed) { |
| if (params == null) return null; |
| List<PropertyModel> models = mPropertyModelBuilder.selectThirdPartyApps(mBottomSheet, |
| contentTypes, params, saveLastUsed, params.getWindow(), mShareStartTime); |
| // More... |
| PropertyModel morePropertyModel = ShareSheetPropertyModelBuilder.createPropertyModel( |
| AppCompatResources.getDrawable(activity, R.drawable.sharing_more), |
| activity.getResources().getString(R.string.sharing_more_icon_label), |
| (shareParams) -> { |
| RecordUserAction.record("SharingHubAndroid.MoreSelected"); |
| mBottomSheetController.hideContent(mBottomSheet, true); |
| ShareHelper.showDefaultShareUi(params, saveLastUsed); |
| }); |
| models.add(morePropertyModel); |
| |
| return models; |
| } |
| |
| @VisibleForTesting |
| protected void disableFirstPartyFeaturesForTesting() { |
| mExcludeFirstParty = true; |
| } |
| |
| // ActivityStateObserver |
| @Override |
| public void onActivityResumed() {} |
| |
| @Override |
| public void onActivityPaused() { |
| if (mBottomSheet != null) { |
| mBottomSheetController.hideContent(mBottomSheet, true); |
| } |
| } |
| |
| // ConfigurationChangedObserver |
| @Override |
| public void onConfigurationChanged(Configuration newConfig) { |
| if (mActivity == null) { |
| return; |
| } |
| boolean isMultiWindow = ApiCompatibilityUtils.isInMultiWindowMode(mActivity); |
| // mContentTypes is null if Chrome features should not be shown. |
| if (mIsMultiWindow == isMultiWindow || mContentTypes == null) { |
| return; |
| } |
| |
| mIsMultiWindow = isMultiWindow; |
| mBottomSheet.createFirstPartyRecyclerViews( |
| mChromeProvidedSharingOptionsProvider.getPropertyModels( |
| mContentTypes, mIsMultiWindow)); |
| mBottomSheetController.requestShowContent(mBottomSheet, /*animate=*/false); |
| } |
| |
| // View.OnLayoutChangeListener |
| @Override |
| public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, |
| int oldTop, int oldRight, int oldBottom) { |
| if ((oldRight - oldLeft) == (right - left)) { |
| return; |
| } |
| mBottomSheet.getFirstPartyView().invalidate(); |
| mBottomSheet.getFirstPartyView().requestLayout(); |
| mBottomSheet.getThirdPartyView().invalidate(); |
| mBottomSheet.getThirdPartyView().requestLayout(); |
| } |
| |
| } |