From: uazo Date: Sun, 3 Oct 2021 16:18:24 +0000 Subject: Enable share intent This patch allows to activate the management of android.intent.action.SEND with new flag "shared-intent-ui" default active. See also: https://github.com/bromite/bromite/issues/1062 Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- chrome/android/chrome_java_resources.gni | 1 + chrome/android/chrome_java_sources.gni | 1 + chrome/android/java/AndroidManifest.xml | 28 ++- .../res/layout/sharing_intent_content.xml | 88 ++++++++++ .../chrome/browser/IntentHandler.java | 16 +- .../browser/LaunchIntentDispatcher.java | 2 +- .../document/ChromeLauncherActivity.java | 13 +- .../init/ProcessInitializationHandler.java | 2 + .../SharedIntentShareActivity.java | 159 ++++++++++++++++++ .../flags/android/chrome_feature_list.cc | 1 + .../browser/flags/ChromeFeatureList.java | 1 + .../strings/android_chrome_strings.grd | 13 ++ .../about_flags_cc/Enable-share-intent.inc | 12 ++ .../Enable-share-intent.inc | 3 + .../Enable-share-intent.inc | 1 + 15 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 chrome/android/java/res/layout/sharing_intent_content.xml create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/sharing/shared_intent/SharedIntentShareActivity.java create mode 100644 cromite_flags/chrome/browser/about_flags_cc/Enable-share-intent.inc create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Enable-share-intent.inc create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/Enable-share-intent.inc diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni @@ -606,6 +606,7 @@ chrome_java_resources = [ "java/res/layout/shopping_accessory_view_layout.xml", "java/res/layout/status_indicator_container.xml", "java/res/layout/suggestions_tile_view_condensed.xml", + "java/res/layout/sharing_intent_content.xml", "java/res/layout/suspended_tab.xml", "java/res/layout/sync_custom_passphrase.xml", "java/res/layout/sync_enter_passphrase.xml", diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni @@ -1087,6 +1087,7 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/signin/SigninBridge.java", "java/src/org/chromium/chrome/browser/signin/SigninCheckerProvider.java", "java/src/org/chromium/chrome/browser/signin/SigninFirstRunFragment.java", + "java/src/org/chromium/chrome/browser/sharing/shared_intent/SharedIntentShareActivity.java", "java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java", "java/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivity.java", "java/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorCoordinator.java", diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml --- a/chrome/android/java/AndroidManifest.xml +++ b/chrome/android/java/AndroidManifest.xml @@ -271,12 +271,6 @@ by a child template that "extends" this file. - - - - - - @@ -355,6 +349,10 @@ by a child template that "extends" this file. + + + + @@ -391,6 +389,24 @@ by a child template that "extends" this file. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java --- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java @@ -215,6 +215,9 @@ public class IntentHandler { private static final String EXTRA_TAB_LAUNCH_TYPE = "org.chromium.chrome.browser.tab_launch_type"; + private static final String EXTRA_TAB_FORCE_INCOGNITO_TYPE = + "org.chromium.chrome.browser.force_incognito"; + /** A hash code for the URL to verify intent data hasn't been modified. */ public static final String EXTRA_DATA_HASH_CODE = "org.chromium.chrome.browser.data_hash"; @@ -1636,6 +1639,17 @@ public class IntentHandler { return (tabContext instanceof Activity) ? (Activity) tabContext : null; } + public static void setForceIncognitoMode(Intent intent) { + intent.putExtra(EXTRA_TAB_FORCE_INCOGNITO_TYPE, 1); + intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true); + setTabLaunchType(intent, TabLaunchType.FROM_EXTERNAL_APP); + } + + public static boolean isForceIncognitoMode(Intent intent) { + Integer value = IntentUtils.safeGetSerializableExtra(intent, EXTRA_TAB_FORCE_INCOGNITO_TYPE); + return value == null ? false : value == 1; + } + /** * Creates an Intent that will launch a ChromeTabbedActivity on the new tab page. The Intent * will be trusted and therefore able to launch Incognito tabs. @@ -1857,7 +1871,7 @@ public class IntentHandler { String headers = getExtraHeadersFromIntent(intent); headers = maybeAddAdditionalContentHeaders(intent, url, headers); - if (IntentHandler.wasIntentSenderChrome(intent)) { + if (IntentHandler.wasIntentSenderChrome(intent) && !isForceIncognitoMode(intent)) { // Handle post data case. String postDataType = IntentUtils.safeGetStringExtra(intent, IntentHandler.EXTRA_POST_DATA_TYPE); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java --- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java @@ -361,8 +361,8 @@ public class LaunchIntentDispatcher { /*incognito*/true); newIntent.setData(mIntent.getData()); newIntent.setPackage(applicationContext.getPackageName()); - IntentHandler.setTabLaunchType(newIntent, TabLaunchType.FROM_EXTERNAL_APP); newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + IntentHandler.setForceIncognitoMode(newIntent); } if (!chromeTabbedTaskExists(mActivity)) { newIntent.putExtra(IntentHandler.EXTRA_STARTED_TABBED_CHROME_TASK, true); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java @@ -35,6 +35,7 @@ import org.chromium.chrome.browser.multiwindow.MultiWindowUtils; import org.chromium.chrome.browser.notifications.NotificationPlatformBridge; import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations; import org.chromium.chrome.browser.searchwidget.SearchActivity; +import org.chromium.chrome.browser.sharing.shared_intent.SharedIntentShareActivity; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tabwindow.TabWindowInfo; import org.chromium.chrome.browser.webapps.WebappLauncherActivity; @@ -158,12 +159,22 @@ public class ChromeLauncherActivity extends Activity { String query = null; final String action = intent.getAction(); + boolean isWebSearch = Intent.ACTION_WEB_SEARCH.equals(action); if (Intent.ACTION_SEARCH.equals(action) + || isWebSearch || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)) { query = IntentUtils.safeGetStringExtra(intent, SearchManager.QUERY); } if (TextUtils.isEmpty(query)) return false; - + if (isWebSearch) { + Intent i = new Intent(Intent.ACTION_SEND); + i.putExtra(Intent.EXTRA_TEXT, query); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + i.setClass(ContextUtils.getApplicationContext(), + SharedIntentShareActivity.class); + ContextUtils.getApplicationContext().startActivity(i); + return true; + } Intent searchIntent = new Intent(Intent.ACTION_WEB_SEARCH); searchIntent.putExtra(SearchManager.QUERY, query); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java --- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java @@ -109,6 +109,7 @@ import org.chromium.chrome.browser.tabmodel.TabPersistentStoreImpl; import org.chromium.chrome.browser.ui.cars.DrivingRestrictionsManager; import org.chromium.chrome.browser.ui.hats.SurveyClientFactory; import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityPreferencesManager; +import org.chromium.chrome.browser.sharing.shared_intent.SharedIntentShareActivity; import org.chromium.chrome.browser.usb.UsbNotificationManager; import org.chromium.chrome.browser.util.AfterStartupTaskUtils; import org.chromium.chrome.browser.webapps.ChromeWebApkHost; @@ -702,6 +703,7 @@ public class ProcessInitializationHandler { tasks.add(WebApkUninstallTracker::runDeferredTasks); + tasks.add(SharedIntentShareActivity::updateComponentEnabledState); tasks.add(OfflineContentAvailabilityStatusProvider::getInstance); tasks.add(() -> EnterpriseInfo.getInstance().logDeviceEnterpriseInfo()); tasks.add(TosDialogBehaviorSharedPrefInvalidator::refreshSharedPreferenceIfTosSkipped); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sharing/shared_intent/SharedIntentShareActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/sharing/shared_intent/SharedIntentShareActivity.java new file mode 100644 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/sharing/shared_intent/SharedIntentShareActivity.java @@ -0,0 +1,159 @@ +// 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.sharing.shared_intent; + +import org.chromium.base.Log; + +import android.content.res.Resources; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.view.View; +import android.widget.TextView; +import android.net.Uri; + +import org.chromium.base.ContextUtils; +import org.chromium.base.IntentUtils; +import org.chromium.base.ThreadUtils; +import org.chromium.base.task.PostTask; +import org.chromium.base.task.TaskTraits; +import org.chromium.base.supplier.OneshotSupplier; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor; +import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.chrome.browser.init.ActivityProfileProvider; +import org.chromium.chrome.browser.init.AsyncInitializationActivity; +import org.chromium.chrome.browser.LaunchIntentDispatcher; +import org.chromium.chrome.browser.IntentHandler; +import org.chromium.chrome.browser.profiles.ProfileProvider; +import org.chromium.ui.widget.ButtonCompat; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.profiles.ProfileManager; +import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; +import org.chromium.chrome.browser.privacy.settings.PrivacySettings; + +/** + * Activity to display device targets to share text. + */ +public class SharedIntentShareActivity + extends AsyncInitializationActivity { + + String linkUrl; + + @Override + protected OneshotSupplier createProfileProvider() { + return new ActivityProfileProvider(getLifecycleDispatcher()); + } + + /** + * Checks whether sending shared clipboard message is enabled for the user and enables/disables + * the SharedIntentShareActivity appropriately. This call requires native to be loaded. + */ + public static void updateComponentEnabledState() { + boolean enabled = ChromeFeatureList.isEnabled(ChromeFeatureList.SHARED_INTENT_UI); + PostTask.postTask(TaskTraits.USER_VISIBLE, () -> setComponentEnabled(enabled)); + } + + /** + * Sets whether or not the SharedIntentShareActivity should be enabled. This may trigger a + * StrictMode violation so shouldn't be called on the UI thread. + */ + private static void setComponentEnabled(boolean enabled) { + ThreadUtils.assertOnBackgroundThread(); + Context context = ContextUtils.getApplicationContext(); + PackageManager packageManager = context.getPackageManager(); + ComponentName componentName = + new ComponentName(context, SharedIntentShareActivity.class); + + int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + + // This indicates that we don't want to kill Chrome when changing component enabled state. + int flags = PackageManager.DONT_KILL_APP; + + if (packageManager.getComponentEnabledSetting(componentName) != newState) { + packageManager.setComponentEnabledSetting(componentName, newState, flags); + } + } + + @Override + protected void triggerLayoutInflation() { + setContentView(R.layout.sharing_intent_content); + linkUrl = IntentUtils.safeGetStringExtra(getIntent(), Intent.EXTRA_TEXT); + updateTextLink(); + + findViewById(R.id.share_content).setVisibility(View.GONE); + + View mask = findViewById(R.id.mask); + mask.setOnClickListener(v -> finish()); + + ButtonCompat open_url_button = findViewById(R.id.open_url_button); + open_url_button.setVisibility(View.VISIBLE); + open_url_button.setOnClickListener(view -> { + Context applicationContext = ContextUtils.getApplicationContext(); + Intent chromeIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(linkUrl)); + chromeIntent.setPackage(applicationContext.getPackageName()); + chromeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + LaunchIntentDispatcher.dispatchToTabbedActivity(this, chromeIntent); + finish(); + }); + + ButtonCompat open_url_incognito_button = findViewById(R.id.open_url_incognito_button); + open_url_incognito_button.setVisibility(View.VISIBLE); + open_url_incognito_button.setOnClickListener(view -> { + OpenLinkInIncognito(); + }); + + if (AlwaysIncognitoLinkInterceptor.isAlwaysIncognito()) + open_url_incognito_button.setVisibility(View.GONE); + + onInitialLayoutInflationComplete(); + } + + @Override + public void finishNativeInitialization() { + String scheme = Uri.parse(linkUrl).getScheme(); + if (scheme == null) { + linkUrl = + TemplateUrlServiceFactory.getForProfile(ProfileManager.getLastUsedRegularProfile()) + .getUrlForSearchQuery(linkUrl); + } + updateTextLink(); + if (ContextUtils.getAppSharedPreferences() + .getBoolean(PrivacySettings.PREF_OPEN_EXTERNAL_LINKS_INCOGNITO, false)) { + OpenLinkInIncognito(); + } else { + findViewById(R.id.share_content).setVisibility(View.VISIBLE); + } + } + + private void updateTextLink() { + Resources resources = ContextUtils.getApplicationContext().getResources(); + TextView share_message_text = findViewById(R.id.share_message_text); + share_message_text.setText( + resources.getString(R.string.shared_intent_share_activity_text, linkUrl)); + } + + private void OpenLinkInIncognito() { + Context applicationContext = ContextUtils.getApplicationContext(); + Intent chromeIntent = IntentHandler.createTrustedOpenNewTabIntent(applicationContext, + /*incognito*/true); + + chromeIntent.setData(Uri.parse(linkUrl)); + chromeIntent.setPackage(applicationContext.getPackageName()); + chromeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + IntentHandler.setForceIncognitoMode(chromeIntent); + + LaunchIntentDispatcher.dispatchToTabbedActivity(this, chromeIntent); + finish(); + } + + @Override + public boolean shouldStartGpuProcess() { + return false; + } +} diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc @@ -435,6 +435,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { &kRelatedSearchesAllLanguage, &kRelatedSearchesSwitch, &kReloadTabUiResourcesIfChanged, + &kSharedIntentUI, &kRemoveTabFocusOnShowingAndSelect, &kRightEdgeGoesForwardGestureNav, &kRobustWindowManagementExperimental, diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java @@ -602,6 +602,7 @@ public abstract class ChromeFeatureList { public static final String READALOUD_PLAYBACK = "ReadAloudPlayback"; public static final String RECORD_INCOGNITO_NTP_TIME_TO_FIRST_NAVIGATION_METRIC = "RecordIncognitoNtpTimeToFirstNavigationMetric"; + public static final String SHARED_INTENT_UI = "SharedIntentUI"; public static final String RECORD_SUPPRESSION_METRICS = "RecordSuppressionMetrics"; public static final String REENGAGEMENT_NOTIFICATION = "ReengagementNotification"; public static final String RELATED_SEARCHES_ALL_LANGUAGE = "RelatedSearchesAllLanguage"; diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd @@ -6595,6 +6595,19 @@ To change this setting, BEGIN_LINKdelete the Chrome d Make sure a phone app is enabled on this device + + Open URL with Bromite + + + Will open %1$s with Bromite + + + Open with Bromite + + + Open with Bromite Incognito + + Submit %1$s123 on %2$sZoe's Macbook? diff --git a/cromite_flags/chrome/browser/about_flags_cc/Enable-share-intent.inc b/cromite_flags/chrome/browser/about_flags_cc/Enable-share-intent.inc new file mode 100644 --- /dev/null +++ b/cromite_flags/chrome/browser/about_flags_cc/Enable-share-intent.inc @@ -0,0 +1,12 @@ +#if BUILDFLAG(IS_ANDROID) + +#ifdef FLAG_SECTION + + {"share-intent-ui", + "Enable share feature", + "Enables share feature", kOsAndroid, + FEATURE_VALUE_TYPE(chrome::android::kSharedIntentUI)}, + +#endif + +#endif diff --git a/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Enable-share-intent.inc b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Enable-share-intent.inc new file mode 100644 --- /dev/null +++ b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Enable-share-intent.inc @@ -0,0 +1,3 @@ +CROMITE_FEATURE(kSharedIntentUI, + "SharedIntentUI", + base::FEATURE_ENABLED_BY_DEFAULT); diff --git a/cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/Enable-share-intent.inc b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/Enable-share-intent.inc new file mode 100644 --- /dev/null +++ b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/Enable-share-intent.inc @@ -0,0 +1 @@ +BASE_DECLARE_FEATURE(kSharedIntentUI); --