diff --git a/BUILD.gn b/BUILD.gn index 7b5bd9b28b5910..7fe7c7296bcc16 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -384,10 +384,10 @@ group("gn_all") { "//chrome/android:chrome_public_apk", "//chrome/android:chrome_public_test_apk", "//chrome/android/features/media_router:media_router_junit_tests", + "//chrome/browser/android/examples/custom_tabs_client:custom_tabs_client_example_apk", "//chrome/browser/android/examples/partner_browser_customizations_provider:partner_browser_customizations_example_apk", "//chrome/test/chromedriver/test/webview_shell:chromedriver_webview_shell_apk", "//content/shell/android:content_shell_test_apk", - "//third_party/custom_tabs_client:custom_tabs_client_example_apk", ] } diff --git a/DEPS b/DEPS index 9d1ad3ff9fbd31..a62b2ece848aaf 100644 --- a/DEPS +++ b/DEPS @@ -871,11 +871,6 @@ deps = { 'condition': 'checkout_linux', }, - 'src/third_party/custom_tabs_client/src': { - 'url': Var('chromium_git') + '/custom-tabs-client.git' + '@' + 'a633542d9854151eb4f0bfd1d93da88f5934a11a', - 'condition': 'checkout_android', - }, - 'src/third_party/depot_tools': Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '19d4809e112652f918494840bab819603b0a2816', diff --git a/chrome/browser/android/examples/custom_tabs_client/BUILD.gn b/chrome/browser/android/examples/custom_tabs_client/BUILD.gn new file mode 100644 index 00000000000000..48d7238251b2d7 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/BUILD.gn @@ -0,0 +1,76 @@ +# Copyright 2015 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. + +import("//build/config/android/rules.gni") + +android_resources("chrome_tabs_client_example_apk_resources") { + sources = [ + "src/res/anim/slide_in_left.xml", + "src/res/anim/slide_in_right.xml", + "src/res/anim/slide_out_left.xml", + "src/res/anim/slide_out_right.xml", + "src/res/drawable-hdpi/ic_arrow_back.png", + "src/res/drawable-hdpi/ic_launcher.png", + "src/res/drawable-hdpi/ic_notification_icon.png", + "src/res/drawable-hdpi/ic_play.png", + "src/res/drawable-hdpi/ic_share.png", + "src/res/drawable-hdpi/ic_stop.png", + "src/res/drawable-mdpi/ic_arrow_back.png", + "src/res/drawable-mdpi/ic_launcher.png", + "src/res/drawable-mdpi/ic_notification_icon.png", + "src/res/drawable-mdpi/ic_play.png", + "src/res/drawable-mdpi/ic_share.png", + "src/res/drawable-mdpi/ic_stop.png", + "src/res/drawable-xhdpi/ic_arrow_back.png", + "src/res/drawable-xhdpi/ic_launcher.png", + "src/res/drawable-xhdpi/ic_notification_icon.png", + "src/res/drawable-xhdpi/ic_play.png", + "src/res/drawable-xhdpi/ic_share.png", + "src/res/drawable-xhdpi/ic_stop.png", + "src/res/drawable-xxhdpi/cover.jpg", + "src/res/drawable-xxhdpi/ic_arrow_back.png", + "src/res/drawable-xxhdpi/ic_launcher.png", + "src/res/drawable-xxhdpi/ic_notification_icon.png", + "src/res/drawable-xxhdpi/ic_play.png", + "src/res/drawable-xxhdpi/ic_share.png", + "src/res/drawable-xxhdpi/ic_stop.png", + "src/res/drawable-xxxhdpi/ic_arrow_back.png", + "src/res/drawable-xxxhdpi/ic_launcher.png", + "src/res/drawable-xxxhdpi/ic_share.png", + "src/res/layout/main.xml", + "src/res/layout/remote_view.xml", + "src/res/raw/amazing_grace.mp3", + "src/res/values/strings.xml", + ] + android_manifest = "src/AndroidManifest.xml" + custom_package = "org.chromium.customtabsclient" + deps = [ "//third_party/android_deps:android_support_v7_appcompat_java" ] +} + +android_apk("custom_tabs_client_example_apk") { + sources = [ + "src/java/org/chromium/customtabsclient/BottomBarManager.java", + "src/java/org/chromium/customtabsclient/BrowserActionsReceiver.java", + "src/java/org/chromium/customtabsclient/MainActivity.java", + "src/java/org/chromium/customtabsclient/SessionHelper.java", + "src/java/org/chromium/customtabsclient/shared/CustomTabsHelper.java", + "src/java/org/chromium/customtabsclient/shared/KeepAliveService.java", + "src/java/org/chromium/customtabsclient/shared/ServiceConnection.java", + "src/java/org/chromium/customtabsclient/shared/ServiceConnectionCallback.java", + ] + + android_manifest = "src/AndroidManifest.xml" + min_sdk_version = 19 + target_sdk_version = 21 + apk_name = "CustomTabsClientExample" + + deps = [ + ":chrome_tabs_client_example_apk_resources", + "//third_party/android_deps:android_support_v7_appcompat_java", + "//third_party/android_deps:androidx_annotation_annotation_java", + "//third_party/android_deps:androidx_appcompat_appcompat_java", + "//third_party/android_deps:androidx_lifecycle_lifecycle_common_java", + "//third_party/android_sdk/androidx_browser:androidx_browser_java", + ] +} diff --git a/chrome/browser/android/examples/custom_tabs_client/OWNERS b/chrome/browser/android/examples/custom_tabs_client/OWNERS new file mode 100644 index 00000000000000..0922ed2dacf90f --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/OWNERS @@ -0,0 +1,4 @@ +file://chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS + +# COMPONENT: UI>Browser>Mobile>CustomTabs +# OS: Android \ No newline at end of file diff --git a/chrome/browser/android/examples/custom_tabs_client/src/AndroidManifest.xml b/chrome/browser/android/examples/custom_tabs_client/src/AndroidManifest.xml new file mode 100644 index 00000000000000..43883a02313f33 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/AndroidManifest.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/BottomBarManager.java b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/BottomBarManager.java new file mode 100644 index 00000000000000..7ad35749a81f7d --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/BottomBarManager.java @@ -0,0 +1,86 @@ +// Copyright 2015 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.customtabsclient; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.MediaPlayer; +import android.widget.RemoteViews; +import android.widget.Toast; + +import androidx.browser.customtabs.CustomTabsIntent; +import androidx.browser.customtabs.CustomTabsSession; + +import java.lang.ref.WeakReference; + +/** + * A {@link BroadcastReceiver} that manages the interaction with the active Custom Tab. + */ +public class BottomBarManager extends BroadcastReceiver { + private static WeakReference sMediaPlayerWeakRef; + + @Override + public void onReceive(Context context, Intent intent) { + int clickedId = intent.getIntExtra(CustomTabsIntent.EXTRA_REMOTEVIEWS_CLICKED_ID, -1); + Toast.makeText(context, "Current URL " + intent.getDataString() + "\nClicked id " + + clickedId, Toast.LENGTH_SHORT).show(); + + CustomTabsSession session = SessionHelper.getCurrentSession(); + if (session == null) return; + + if (clickedId == R.id.play_pause) { + MediaPlayer player = sMediaPlayerWeakRef.get(); + if (player != null) { + boolean isPlaying = player.isPlaying(); + if (isPlaying) player.pause(); + else player.start(); + // Update the play/stop icon to respect the current state. + session.setSecondaryToolbarViews(createRemoteViews(context, isPlaying), + getClickableIDs(), getOnClickPendingIntent(context)); + } + } else if (clickedId == R.id.cover) { + // Clicking on the cover image will dismiss the bottom bar. + session.setSecondaryToolbarViews(null, null, null); + } + } + + /** + * Creates a RemoteViews that will be shown as the bottom bar of the custom tab. + * @param showPlayIcon If true, a play icon will be shown, otherwise show a pause icon. + * @return The created RemoteViews instance. + */ + public static RemoteViews createRemoteViews(Context context, boolean showPlayIcon) { + RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.remote_view); + + int iconRes = showPlayIcon ? R.drawable.ic_play : R.drawable.ic_stop; + remoteViews.setImageViewResource(R.id.play_pause, iconRes); + return remoteViews; + } + + /** + * @return A list of View ids, the onClick event of which is handled by Custom Tab. + */ + public static int[] getClickableIDs() { + return new int[]{R.id.play_pause, R.id.cover}; + } + + /** + * @return The PendingIntent that will be triggered when the user clicks on the Views listed by + * {@link BottomBarManager#getClickableIDs()}. + */ + public static PendingIntent getOnClickPendingIntent(Context context) { + Intent broadcastIntent = new Intent(context, BottomBarManager.class); + return PendingIntent.getBroadcast(context, 0, broadcastIntent, 0); + } + + /** + * Sets the {@link MediaPlayer} to be used when the user clicks on the RemoteViews. + */ + public static void setMediaPlayer(MediaPlayer player) { + sMediaPlayerWeakRef = new WeakReference<>(player); + } +} diff --git a/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/BrowserActionsReceiver.java b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/BrowserActionsReceiver.java new file mode 100644 index 00000000000000..93f67529a47dae --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/BrowserActionsReceiver.java @@ -0,0 +1,22 @@ +// Copyright 2015 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.customtabsclient; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.widget.Toast; + +/** + * A {@link BroadcastReceiver} that handles the callback if default menu items are chosen from + * Browser Actions. + */ +public class BrowserActionsReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String toastMsg = "Chosen item Id: " + intent.getDataString(); + Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show(); + } +} \ No newline at end of file diff --git a/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/MainActivity.java b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/MainActivity.java new file mode 100644 index 00000000000000..d67d86b03f3710 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/MainActivity.java @@ -0,0 +1,298 @@ +// Copyright 2015 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.customtabsclient; + +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +import androidx.browser.customtabs.CustomTabsCallback; +import androidx.browser.customtabs.CustomTabsClient; +import androidx.browser.customtabs.CustomTabsIntent; +import androidx.browser.customtabs.CustomTabsServiceConnection; +import androidx.browser.customtabs.CustomTabsSession; + +import org.chromium.customtabsclient.shared.CustomTabsHelper; +import org.chromium.customtabsclient.shared.ServiceConnection; +import org.chromium.customtabsclient.shared.ServiceConnectionCallback; + +import java.util.ArrayList; +import java.util.List; + +/** + * Example client activity for using Chrome Custom Tabs. + */ +public class MainActivity + extends AppCompatActivity implements OnClickListener, ServiceConnectionCallback { + private static final String TAG = "CustomTabsClientExample"; + private static final String TOOLBAR_COLOR = "#ef6c00"; + + private EditText mEditText; + private CustomTabsSession mCustomTabsSession; + private CustomTabsClient mClient; + private CustomTabsServiceConnection mConnection; + private String mPackageNameToBind; + private Button mConnectButton; + private Button mWarmupButton; + private Button mMayLaunchButton; + private Button mLaunchButton; + private MediaPlayer mMediaPlayer; + + /** + * Once per second, asks the framework for the process importance, and logs any change. + */ + private Runnable mLogImportance = new Runnable() { + private int mPreviousImportance = -1; + private boolean mPreviousServiceInUse = false; + private Handler mHandler = new Handler(Looper.getMainLooper()); + + @Override + public void run() { + ActivityManager.RunningAppProcessInfo state = + new ActivityManager.RunningAppProcessInfo(); + ActivityManager.getMyMemoryState(state); + int importance = state.importance; + boolean serviceInUse = state.importanceReasonCode + == ActivityManager.RunningAppProcessInfo.REASON_SERVICE_IN_USE; + if (importance != mPreviousImportance || serviceInUse != mPreviousServiceInUse) { + mPreviousImportance = importance; + mPreviousServiceInUse = serviceInUse; + String message = "New importance = " + importance; + if (serviceInUse) message += " (Reason: Service in use)"; + Log.w(TAG, message); + } + mHandler.postDelayed(this, 1000); + } + }; + + private static class NavigationCallback extends CustomTabsCallback { + @Override + public void onNavigationEvent(int navigationEvent, Bundle extras) { + Log.w(TAG, "onNavigationEvent: Code = " + navigationEvent); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + mEditText = (EditText) findViewById(R.id.edit); + mConnectButton = (Button) findViewById(R.id.connect_button); + mWarmupButton = (Button) findViewById(R.id.warmup_button); + mMayLaunchButton = (Button) findViewById(R.id.may_launch_button); + mLaunchButton = (Button) findViewById(R.id.launch_button); + Spinner spinner = (Spinner) findViewById(R.id.spinner); + mEditText.requestFocus(); + mConnectButton.setOnClickListener(this); + mWarmupButton.setOnClickListener(this); + mMayLaunchButton.setOnClickListener(this); + mLaunchButton.setOnClickListener(this); + mMediaPlayer = MediaPlayer.create(this, R.raw.amazing_grace); + findViewById(R.id.register_twa_service).setOnClickListener(this); + + Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")); + PackageManager pm = getPackageManager(); + List resolvedActivityList = pm.queryIntentActivities( + activityIntent, PackageManager.MATCH_ALL); + List> packagesSupportingCustomTabs = new ArrayList<>(); + for (ResolveInfo info : resolvedActivityList) { + Intent serviceIntent = new Intent(); + serviceIntent.setAction("android.support.customtabs.action.CustomTabsService"); + serviceIntent.setPackage(info.activityInfo.packageName); + if (pm.resolveService(serviceIntent, 0) != null) { + packagesSupportingCustomTabs.add( + Pair.create(info.loadLabel(pm).toString(), info.activityInfo.packageName)); + } + } + + final ArrayAdapter> adapter = new ArrayAdapter>( + this, 0, packagesSupportingCustomTabs) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + view = LayoutInflater.from(MainActivity.this).inflate( + android.R.layout.simple_list_item_2, parent, false); + } + Pair data = getItem(position); + ((TextView) view.findViewById(android.R.id.text1)).setText(data.first); + ((TextView) view.findViewById(android.R.id.text2)).setText(data.second); + return view; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + return getView(position, convertView, parent); + } + }; + spinner.setAdapter(adapter); + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + Pair item = adapter.getItem(position); + if (TextUtils.isEmpty(item.second)) { + onNothingSelected(parent); + return; + } + mPackageNameToBind = item.second; + } + + @Override + public void onNothingSelected(AdapterView parent) { + mPackageNameToBind = null; + } + }); + + mLogImportance.run(); + } + + @Override + protected void onDestroy() { + unbindCustomTabsService(); + super.onDestroy(); + } + + private CustomTabsSession getSession() { + if (mClient == null) { + mCustomTabsSession = null; + } else if (mCustomTabsSession == null) { + mCustomTabsSession = mClient.newSession(new NavigationCallback()); + SessionHelper.setCurrentSession(mCustomTabsSession); + } + return mCustomTabsSession; + } + + private void bindCustomTabsService() { + if (mClient != null) return; + if (TextUtils.isEmpty(mPackageNameToBind)) { + mPackageNameToBind = CustomTabsHelper.getPackageNameToUse(this); + if (mPackageNameToBind == null) return; + } + mConnection = new ServiceConnection(this); + boolean ok = CustomTabsClient.bindCustomTabsService(this, mPackageNameToBind, mConnection); + if (ok) { + mConnectButton.setEnabled(false); + } else { + mConnection = null; + } + } + + private void unbindCustomTabsService() { + if (mConnection == null) return; + unbindService(mConnection); + mClient = null; + mCustomTabsSession = null; + } + + @Override + public void onClick(View v) { + String url = mEditText.getText().toString(); + int viewId = v.getId(); + + if (viewId == R.id.connect_button) { + bindCustomTabsService(); + } else if (viewId == R.id.warmup_button) { + boolean success = false; + if (mClient != null) success = mClient.warmup(0); + if (!success) mWarmupButton.setEnabled(false); + } else if (viewId == R.id.may_launch_button) { + CustomTabsSession session = getSession(); + boolean success = false; + if (mClient != null) success = session.mayLaunchUrl(Uri.parse(url), null, null); + if (!success) mMayLaunchButton.setEnabled(false); + } else if (viewId == R.id.launch_button) { + CustomTabsSession session = getSession(); + CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(session); + builder.setToolbarColor(Color.parseColor(TOOLBAR_COLOR)).setShowTitle(true); + prepareMenuItems(builder); + prepareActionButton(builder); + if (session != null) prepareBottombar(builder); + builder.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left); + builder.setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right); + builder.setCloseButtonIcon( + BitmapFactory.decodeResource(getResources(), R.drawable.ic_arrow_back)); + CustomTabsIntent customTabsIntent = builder.build(); + if (session != null) { + CustomTabsHelper.addKeepAliveExtra(this, customTabsIntent.intent); + } else { + if (!TextUtils.isEmpty(mPackageNameToBind)) { + customTabsIntent.intent.setPackage(mPackageNameToBind); + } + } + customTabsIntent.launchUrl(this, Uri.parse(url)); + } + } + + private void prepareMenuItems(CustomTabsIntent.Builder builder) { + Intent menuIntent = new Intent(); + menuIntent.setClass(getApplicationContext(), this.getClass()); + // Optional animation configuration when the user clicks menu items. + Bundle menuBundle = ActivityOptions.makeCustomAnimation(this, android.R.anim.slide_in_left, + android.R.anim.slide_out_right).toBundle(); + PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0, menuIntent, 0, + menuBundle); + builder.addMenuItem("Menu entry 1", pi); + } + + private void prepareActionButton(CustomTabsIntent.Builder builder) { + // An example intent that sends an email. + Intent actionIntent = new Intent(Intent.ACTION_SEND); + actionIntent.setType("*/*"); + actionIntent.putExtra(Intent.EXTRA_EMAIL, "example@example.com"); + actionIntent.putExtra(Intent.EXTRA_SUBJECT, "example"); + PendingIntent pi = PendingIntent.getActivity(this, 0, actionIntent, 0); + Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_share); + builder.setActionButton(icon, "send email", pi, true); + } + + private void prepareBottombar(CustomTabsIntent.Builder builder) { + BottomBarManager.setMediaPlayer(mMediaPlayer); + builder.setSecondaryToolbarViews(BottomBarManager.createRemoteViews(this, true), + BottomBarManager.getClickableIDs(), BottomBarManager.getOnClickPendingIntent(this)); + } + + @Override + public void onServiceConnected(CustomTabsClient client) { + mClient = client; + mConnectButton.setEnabled(false); + mWarmupButton.setEnabled(true); + mMayLaunchButton.setEnabled(true); + mLaunchButton.setEnabled(true); + } + + @Override + public void onServiceDisconnected() { + mConnectButton.setEnabled(true); + mWarmupButton.setEnabled(false); + mMayLaunchButton.setEnabled(false); + mLaunchButton.setEnabled(false); + mClient = null; + } +} diff --git a/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/SessionHelper.java b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/SessionHelper.java new file mode 100644 index 00000000000000..98a46d145eb1cd --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/SessionHelper.java @@ -0,0 +1,34 @@ +// Copyright 2015 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.customtabsclient; + +import android.support.annotation.Nullable; + +import androidx.browser.customtabs.CustomTabsSession; + +import java.lang.ref.WeakReference; + +/** + * A class that keeps tracks of the current {@link CustomTabsSession} and helps other components of + * the app to get access to the current session. + */ +public class SessionHelper { + private static WeakReference sCurrentSession; + + /** + * @return The current {@link CustomTabsSession} object. + */ + public static @Nullable CustomTabsSession getCurrentSession() { + return sCurrentSession == null ? null : sCurrentSession.get(); + } + + /** + * Sets the current session to the given one. + * @param session The current session. + */ + public static void setCurrentSession(CustomTabsSession session) { + sCurrentSession = new WeakReference(session); + } +} diff --git a/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/CustomTabsHelper.java b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/CustomTabsHelper.java new file mode 100644 index 00000000000000..132769ce040ce3 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/CustomTabsHelper.java @@ -0,0 +1,132 @@ +// Copyright 2015 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.customtabsclient.shared; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for Custom Tabs. + */ +public class CustomTabsHelper { + private static final String TAG = "CustomTabsHelper"; + static final String STABLE_PACKAGE = "com.android.chrome"; + static final String BETA_PACKAGE = "com.chrome.beta"; + static final String DEV_PACKAGE = "com.chrome.dev"; + static final String LOCAL_PACKAGE = "com.google.android.apps.chrome"; + private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE = + "android.support.customtabs.extra.KEEP_ALIVE"; + private static final String ACTION_CUSTOM_TABS_CONNECTION = + "android.support.customtabs.action.CustomTabsService"; + + private static String sPackageNameToUse; + + private CustomTabsHelper() {} + + public static void addKeepAliveExtra(Context context, Intent intent) { + Intent keepAliveIntent = new Intent().setClassName( + context.getPackageName(), KeepAliveService.class.getCanonicalName()); + intent.putExtra(EXTRA_CUSTOM_TABS_KEEP_ALIVE, keepAliveIntent); + } + + /** + * Goes through all apps that handle VIEW intents and have a warmup service. Picks + * the one chosen by the user if there is one, otherwise makes a best effort to return a + * valid package name. + * + * This is not threadsafe. + * + * @param context {@link Context} to use for accessing {@link PackageManager}. + * @return The package name recommended to use for connecting to custom tabs related components. + */ + public static String getPackageNameToUse(Context context) { + if (sPackageNameToUse != null) return sPackageNameToUse; + + PackageManager pm = context.getPackageManager(); + // Get default VIEW intent handler. + Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")); + ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0); + String defaultViewHandlerPackageName = null; + if (defaultViewHandlerInfo != null) { + defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName; + } + + // Get all apps that can handle VIEW intents. + List resolvedActivityList = pm.queryIntentActivities(activityIntent, 0); + List packagesSupportingCustomTabs = new ArrayList<>(); + for (ResolveInfo info : resolvedActivityList) { + Intent serviceIntent = new Intent(); + serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION); + serviceIntent.setPackage(info.activityInfo.packageName); + if (pm.resolveService(serviceIntent, 0) != null) { + packagesSupportingCustomTabs.add(info.activityInfo.packageName); + } + } + + // Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents + // and service calls. + if (packagesSupportingCustomTabs.isEmpty()) { + sPackageNameToUse = null; + } else if (packagesSupportingCustomTabs.size() == 1) { + sPackageNameToUse = packagesSupportingCustomTabs.get(0); + } else if (!TextUtils.isEmpty(defaultViewHandlerPackageName) + && !hasSpecializedHandlerIntents(context, activityIntent) + && packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) { + sPackageNameToUse = defaultViewHandlerPackageName; + } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) { + sPackageNameToUse = STABLE_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) { + sPackageNameToUse = BETA_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) { + sPackageNameToUse = DEV_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) { + sPackageNameToUse = LOCAL_PACKAGE; + } + return sPackageNameToUse; + } + + /** + * Used to check whether there is a specialized handler for a given intent. + * @param intent The intent to check with. + * @return Whether there is a specialized handler for the given intent. + */ + private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) { + try { + PackageManager pm = context.getPackageManager(); + List handlers = pm.queryIntentActivities( + intent, + PackageManager.GET_RESOLVED_FILTER); + if (handlers == null || handlers.size() == 0) { + return false; + } + for (ResolveInfo resolveInfo : handlers) { + IntentFilter filter = resolveInfo.filter; + if (filter == null) continue; + if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue; + if (resolveInfo.activityInfo == null) continue; + return true; + } + } catch (RuntimeException e) { + Log.e(TAG, "Runtime exception while getting specialized handlers"); + } + return false; + } + + /** + * @return All possible chrome package names that provide custom tabs feature. + */ + public static String[] getPackages() { + return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE}; + } +} diff --git a/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/KeepAliveService.java b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/KeepAliveService.java new file mode 100644 index 00000000000000..3bde921bc627d2 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/KeepAliveService.java @@ -0,0 +1,22 @@ +// Copyright 2015 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.customtabsclient.shared; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; + +/** + * Empty service used by the custom tab to bind to, raising the application's importance. + */ +public class KeepAliveService extends Service { + private static final Binder sBinder = new Binder(); + + @Override + public IBinder onBind(Intent intent) { + return sBinder; + } +} diff --git a/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/ServiceConnection.java b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/ServiceConnection.java new file mode 100644 index 00000000000000..3de4ff2fd487d3 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/ServiceConnection.java @@ -0,0 +1,37 @@ +// Copyright 2015 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.customtabsclient.shared; + +import android.content.ComponentName; + +import androidx.browser.customtabs.CustomTabsClient; +import androidx.browser.customtabs.CustomTabsServiceConnection; + +import java.lang.ref.WeakReference; + +/** + * Implementation for the CustomTabsServiceConnection that avoids leaking the + * ServiceConnectionCallback + */ +public class ServiceConnection extends CustomTabsServiceConnection { + // A weak reference to the ServiceConnectionCallback to avoid leaking it. + private WeakReference mConnectionCallback; + + public ServiceConnection(ServiceConnectionCallback connectionCallback) { + mConnectionCallback = new WeakReference<>(connectionCallback); + } + + @Override + public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) { + ServiceConnectionCallback connectionCallback = mConnectionCallback.get(); + if (connectionCallback != null) connectionCallback.onServiceConnected(client); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + ServiceConnectionCallback connectionCallback = mConnectionCallback.get(); + if (connectionCallback != null) connectionCallback.onServiceDisconnected(); + } +} diff --git a/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/ServiceConnectionCallback.java b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/ServiceConnectionCallback.java new file mode 100644 index 00000000000000..dcb57a992bfbb9 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/java/org/chromium/customtabsclient/shared/ServiceConnectionCallback.java @@ -0,0 +1,23 @@ +// Copyright 2015 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.customtabsclient.shared; + +import androidx.browser.customtabs.CustomTabsClient; + +/** + * Callback for events when connecting and disconnecting from Custom Tabs Service. + */ +public interface ServiceConnectionCallback { + /** + * Called when the service is connected. + * @param client a CustomTabsClient + */ + void onServiceConnected(CustomTabsClient client); + + /** + * Called when the service is disconnected. + */ + void onServiceDisconnected(); +} diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_in_left.xml b/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_in_left.xml new file mode 100644 index 00000000000000..373537bb30c092 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_in_left.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_in_right.xml b/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_in_right.xml new file mode 100644 index 00000000000000..75d067596c018c --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_in_right.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_out_left.xml b/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_out_left.xml new file mode 100644 index 00000000000000..6ce16766c00b86 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_out_left.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_out_right.xml b/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_out_right.xml new file mode 100644 index 00000000000000..def11b24ae65e3 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/res/anim/slide_out_right.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_arrow_back.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_arrow_back.png new file mode 100644 index 00000000000000..fe424468ef985f Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_arrow_back.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_launcher.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 00000000000000..2a258242898b56 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_launcher.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_notification_icon.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_notification_icon.png new file mode 100644 index 00000000000000..c2c80d47a4aa24 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_notification_icon.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_play.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_play.png new file mode 100644 index 00000000000000..8baf85aaabc640 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_play.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_share.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_share.png new file mode 100644 index 00000000000000..b09a6926de5aa4 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_share.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_stop.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_stop.png new file mode 100644 index 00000000000000..33fe92417c2e1b Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-hdpi/ic_stop.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_arrow_back.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_arrow_back.png new file mode 100644 index 00000000000000..09040008e8d884 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_arrow_back.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_launcher.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000000000..c38d44c5c7d740 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_launcher.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_notification_icon.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_notification_icon.png new file mode 100644 index 00000000000000..9960b1bf6c62fe Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_notification_icon.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_play.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_play.png new file mode 100644 index 00000000000000..000238c0c261a4 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_play.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_share.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_share.png new file mode 100644 index 00000000000000..e944fd70c42861 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_share.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_stop.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_stop.png new file mode 100644 index 00000000000000..4315863d4afce3 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-mdpi/ic_stop.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_arrow_back.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_arrow_back.png new file mode 100644 index 00000000000000..ebc826c225cfe4 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_arrow_back.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_launcher.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000000000..b12097ce144420 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_launcher.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_notification_icon.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_notification_icon.png new file mode 100644 index 00000000000000..00f5b4fbe6b585 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_notification_icon.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_play.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_play.png new file mode 100644 index 00000000000000..e4d298d6dd3520 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_play.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_share.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_share.png new file mode 100644 index 00000000000000..22a8783e70f014 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_share.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_stop.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_stop.png new file mode 100644 index 00000000000000..7f24bf2dcdda55 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xhdpi/ic_stop.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/cover.jpg b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/cover.jpg new file mode 100644 index 00000000000000..2f5cc5801361d1 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/cover.jpg differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_arrow_back.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_arrow_back.png new file mode 100644 index 00000000000000..099f9edccdb817 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_arrow_back.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_launcher.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000000..71b68abe0f3da1 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_notification_icon.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_notification_icon.png new file mode 100644 index 00000000000000..c2c7d734408a9d Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_notification_icon.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_play.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_play.png new file mode 100644 index 00000000000000..9f5d2b7bf39032 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_play.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_share.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_share.png new file mode 100644 index 00000000000000..a35b3cd14af898 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_share.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_stop.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_stop.png new file mode 100644 index 00000000000000..82a4cccbcaa230 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxhdpi/ic_stop.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxxhdpi/ic_arrow_back.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxxhdpi/ic_arrow_back.png new file mode 100644 index 00000000000000..fb06e1d485ce12 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxxhdpi/ic_arrow_back.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxxhdpi/ic_launcher.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000000..32443beb569b66 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxxhdpi/ic_launcher.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxxhdpi/ic_share.png b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxxhdpi/ic_share.png new file mode 100644 index 00000000000000..e351c7beb089e9 Binary files /dev/null and b/chrome/browser/android/examples/custom_tabs_client/src/res/drawable-xxxhdpi/ic_share.png differ diff --git a/chrome/browser/android/examples/custom_tabs_client/src/res/layout/main.xml b/chrome/browser/android/examples/custom_tabs_client/src/res/layout/main.xml new file mode 100644 index 00000000000000..141866ac98edf4 --- /dev/null +++ b/chrome/browser/android/examples/custom_tabs_client/src/res/layout/main.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + +