/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.customtabs; import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.support.annotation.AnimRes; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.app.BundleCompat; import android.support.v4.content.ContextCompat; import android.view.View; import android.widget.RemoteViews; import java.util.ArrayList; /** * Class holding the {@link Intent} and start bundle for a Custom Tabs Activity. * *
* Note: The constants below are public for the browser implementation's benefit. * You are strongly encouraged to use {@link CustomTabsIntent.Builder}.
*/ public final class CustomTabsIntent { /** * Indicates that the user explicitly opted out of Custom Tabs in the calling application. ** If an application provides a mechanism for users to opt out of Custom Tabs, this extra should * be provided with {@link Intent#FLAG_ACTIVITY_NEW_TASK} to ensure the browser does not attempt * to trigger any Custom Tab-like experiences as a result of the VIEW intent. *
* If this extra is present with {@link Intent#FLAG_ACTIVITY_NEW_TASK}, all Custom Tabs
* customizations will be ignored.
*/
private static final String EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS =
"android.support.customtabs.extra.user_opt_out";
/**
* Extra used to match the session. This has to be included in the intent to open in
* a custom tab. This is the same IBinder that gets passed to ICustomTabsService#newSession.
* Null if there is no need to match any service side sessions with the intent.
*/
public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
/**
* Extra that changes the background color for the toolbar. colorRes is an int that specifies a
* {@link Color}, not a resource id.
*/
public static final String EXTRA_TOOLBAR_COLOR =
"android.support.customtabs.extra.TOOLBAR_COLOR";
/**
* Boolean extra that enables the url bar to hide as the user scrolls down the page
*/
public static final String EXTRA_ENABLE_URLBAR_HIDING =
"android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
/**
* Extra bitmap that specifies the icon of the back button on the toolbar. If the client chooses
* not to customize it, a default close button will be used.
*/
public static final String EXTRA_CLOSE_BUTTON_ICON =
"android.support.customtabs.extra.CLOSE_BUTTON_ICON";
/**
* Extra (int) that specifies state for showing the page title. Default is {@link #NO_TITLE}.
*/
public static final String EXTRA_TITLE_VISIBILITY_STATE =
"android.support.customtabs.extra.TITLE_VISIBILITY";
/**
* Don't show any title. Shows only the domain.
*/
public static final int NO_TITLE = 0;
/**
* Shows the page title and the domain.
*/
public static final int SHOW_PAGE_TITLE = 1;
/**
* Bundle used for adding a custom action button to the custom tab toolbar. The client should
* provide a description, an icon {@link Bitmap} and a {@link PendingIntent} for the button.
* All three keys must be present.
*/
public static final String EXTRA_ACTION_BUTTON_BUNDLE =
"android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
/**
* List
* Note that Custom Tabs will override the default onClick behavior of the listed {@link View}s.
* If you do not care about the current url, you can safely ignore this extra and use
* {@link RemoteViews#setOnClickPendingIntent(int, PendingIntent)} instead.
* @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent).
*/
public static final String EXTRA_REMOTEVIEWS_VIEW_IDS =
"android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS";
/**
* Extra that specifies the {@link PendingIntent} to be sent when the user clicks on the
* {@link View}s that is listed by {@link #EXTRA_REMOTEVIEWS_VIEW_IDS}.
*
* Note when this {@link PendingIntent} is triggered, it will have the current url as data
* field, also the id of the clicked {@link View}, specified by
* {@link #EXTRA_REMOTEVIEWS_CLICKED_ID}.
* @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent).
*/
public static final String EXTRA_REMOTEVIEWS_PENDINGINTENT =
"android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT";
/**
* Extra that specifies which {@link View} has been clicked. This extra will be put to the
* {@link PendingIntent} sent from Custom Tabs when a view in the {@link RemoteViews} is clicked
* @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent).
*/
public static final String EXTRA_REMOTEVIEWS_CLICKED_ID =
"android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
/**
* Extra that specifies whether Instant Apps is enabled.
*/
public static final String EXTRA_ENABLE_INSTANT_APPS =
"android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
/**
* Key that specifies the unique ID for an action button. To make a button to show on the
* toolbar, use {@link #TOOLBAR_ACTION_BUTTON_ID} as its ID.
*/
public static final String KEY_ID = "android.support.customtabs.customaction.ID";
/**
* The ID allocated to the custom action button that is shown on the toolbar.
*/
public static final int TOOLBAR_ACTION_BUTTON_ID = 0;
/**
* The maximum allowed number of toolbar items.
*/
private static final int MAX_TOOLBAR_ITEMS = 5;
/**
* An {@link Intent} used to start the Custom Tabs Activity.
*/
@NonNull public final Intent intent;
/**
* A {@link Bundle} containing the start animation for the Custom Tabs Activity.
*/
@Nullable public final Bundle startAnimationBundle;
/**
* Convenience method to launch a Custom Tabs Activity.
* @param context The source Context.
* @param url The URL to load in the Custom Tab.
*/
public void launchUrl(Context context, Uri url) {
intent.setData(url);
ContextCompat.startActivity(context, intent, startAnimationBundle);
}
private CustomTabsIntent(Intent intent, Bundle startAnimationBundle) {
this.intent = intent;
this.startAnimationBundle = startAnimationBundle;
}
/**
* Builder class for {@link CustomTabsIntent} objects.
*/
public static final class Builder {
private final Intent mIntent = new Intent(Intent.ACTION_VIEW);
private ArrayList
* This is equivalent to calling
* {@link CustomTabsIntent.Builder#addToolbarItem(int, Bitmap, String, PendingIntent)}
* with {@link #TOOLBAR_ACTION_BUTTON_ID} as id.
*
* @param icon The icon.
* @param description The description for the button. To be used for accessibility.
* @param pendingIntent pending intent delivered when the button is clicked.
* @param shouldTint Whether the action button should be tinted.
*
* @see CustomTabsIntent.Builder#addToolbarItem(int, Bitmap, String, PendingIntent)
*/
public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description,
@NonNull PendingIntent pendingIntent, boolean shouldTint) {
Bundle bundle = new Bundle();
bundle.putInt(KEY_ID, TOOLBAR_ACTION_BUTTON_ID);
bundle.putParcelable(KEY_ICON, icon);
bundle.putString(KEY_DESCRIPTION, description);
bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
mIntent.putExtra(EXTRA_ACTION_BUTTON_BUNDLE, bundle);
mIntent.putExtra(EXTRA_TINT_ACTION_BUTTON, shouldTint);
return this;
}
/**
* Sets the action button that is displayed in the Toolbar with default tinting behavior.
*
* @see {@link CustomTabsIntent.Builder#setActionButton(
* Bitmap, String, PendingIntent, boolean)}
*/
public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description,
@NonNull PendingIntent pendingIntent) {
return setActionButton(icon, description, pendingIntent, false);
}
/**
* Adds an action button to the custom tab. Multiple buttons can be added via this method.
* If the given id equals {@link #TOOLBAR_ACTION_BUTTON_ID}, the button will be placed on
* the toolbar; if the bitmap is too wide, it will be put to the bottom bar instead. If
* the id is not {@link #TOOLBAR_ACTION_BUTTON_ID}, it will be directly put on secondary
* toolbar. The maximum number of allowed toolbar items in a single intent is
* {@link CustomTabsIntent#getMaxToolbarItems()}. Throws an
* {@link IllegalStateException} when that number is exceeded per intent.
*
* @param id The unique id of the action button. This should be non-negative.
* @param icon The icon.
* @param description The description for the button. To be used for accessibility.
* @param pendingIntent The pending intent delivered when the button is clicked.
*
* @see CustomTabsIntent#getMaxToolbarItems()
* @deprecated Use
* CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent).
*/
@Deprecated
public Builder addToolbarItem(int id, @NonNull Bitmap icon, @NonNull String description,
PendingIntent pendingIntent) throws IllegalStateException {
if (mActionButtons == null) {
mActionButtons = new ArrayList<>();
}
if (mActionButtons.size() >= MAX_TOOLBAR_ITEMS) {
throw new IllegalStateException(
"Exceeded maximum toolbar item count of " + MAX_TOOLBAR_ITEMS);
}
Bundle bundle = new Bundle();
bundle.putInt(KEY_ID, id);
bundle.putParcelable(KEY_ICON, icon);
bundle.putString(KEY_DESCRIPTION, description);
bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
mActionButtons.add(bundle);
return this;
}
/**
* Sets the color of the secondary toolbar.
* @param color The color for the secondary toolbar.
*/
public Builder setSecondaryToolbarColor(@ColorInt int color) {
mIntent.putExtra(EXTRA_SECONDARY_TOOLBAR_COLOR, color);
return this;
}
/**
* Sets the remote views displayed in the secondary toolbar in a custom tab.
*
* @param remoteViews The {@link RemoteViews} that will be shown on the secondary toolbar.
* @param clickableIDs The IDs of clickable views. The onClick event of these views will be
* handled by custom tabs.
* @param pendingIntent The {@link PendingIntent} that will be sent when the user clicks on
* one of the {@link View}s in clickableIDs. When the
* {@link PendingIntent} is sent, it will have the current URL as its
* intent data.
* @see CustomTabsIntent#EXTRA_REMOTEVIEWS
* @see CustomTabsIntent#EXTRA_REMOTEVIEWS_VIEW_IDS
* @see CustomTabsIntent#EXTRA_REMOTEVIEWS_PENDINGINTENT
* @see CustomTabsIntent#EXTRA_REMOTEVIEWS_CLICKED_ID
*/
public Builder setSecondaryToolbarViews(@NonNull RemoteViews remoteViews,
@Nullable int[] clickableIDs, @Nullable PendingIntent pendingIntent) {
mIntent.putExtra(EXTRA_REMOTEVIEWS, remoteViews);
mIntent.putExtra(EXTRA_REMOTEVIEWS_VIEW_IDS, clickableIDs);
mIntent.putExtra(EXTRA_REMOTEVIEWS_PENDINGINTENT, pendingIntent);
return this;
}
/**
* Sets whether Instant Apps is enabled for this Custom Tab.
* @param enabled Whether Instant Apps should be enabled.
*/
public Builder setInstantAppsEnabled(boolean enabled) {
mInstantAppsEnabled = enabled;
return this;
}
/**
* Sets the start animations.
*
* @param context Application context.
* @param enterResId Resource ID of the "enter" animation for the browser.
* @param exitResId Resource ID of the "exit" animation for the application.
*/
public Builder setStartAnimations(
@NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) {
mStartAnimationBundle = ActivityOptionsCompat.makeCustomAnimation(
context, enterResId, exitResId).toBundle();
return this;
}
/**
* Sets the exit animations.
*
* @param context Application context.
* @param enterResId Resource ID of the "enter" animation for the application.
* @param exitResId Resource ID of the "exit" animation for the browser.
*/
public Builder setExitAnimations(
@NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) {
Bundle bundle = ActivityOptionsCompat.makeCustomAnimation(
context, enterResId, exitResId).toBundle();
mIntent.putExtra(EXTRA_EXIT_ANIMATION_BUNDLE, bundle);
return this;
}
/**
* Combines all the options that have been set and returns a new {@link CustomTabsIntent}
* object.
*/
public CustomTabsIntent build() {
if (mMenuItems != null) {
mIntent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems);
}
if (mActionButtons != null) {
mIntent.putParcelableArrayListExtra(EXTRA_TOOLBAR_ITEMS, mActionButtons);
}
mIntent.putExtra(EXTRA_ENABLE_INSTANT_APPS, mInstantAppsEnabled);
return new CustomTabsIntent(mIntent, mStartAnimationBundle);
}
}
/**
* @return The maximum number of allowed toolbar items for
* {@link CustomTabsIntent.Builder#addToolbarItem(int, Bitmap, String, PendingIntent)} and
* {@link CustomTabsIntent#EXTRA_TOOLBAR_ITEMS}.
*/
public static int getMaxToolbarItems() {
return MAX_TOOLBAR_ITEMS;
}
/**
* Adds the necessary flags and extras to signal any browser supporting custom tabs to use the
* browser UI at all times and avoid showing custom tab like UI. Calling this with an intent
* will override any custom tabs related customizations.
* @param intent The intent to modify for always showing browser UI.
* @return The same intent with the necessary flags and extras added.
*/
public static Intent setAlwaysUseBrowserUI(Intent intent) {
if (intent == null) intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS, true);
return intent;
}
/**
* Whether a browser receiving the given intent should always use browser UI and avoid using any
* custom tabs UI.
*
* @param intent The intent to check for the required flags and extras.
* @return Whether the browser UI should be used exclusively.
*/
public static boolean shouldAlwaysUseBrowserUI(Intent intent) {
return intent.getBooleanExtra(EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS, false)
&& (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0;
}
}