CustomTabsIntent.java revision 9440f0b000fc2740382eb4ae5f1afec58c245c2c
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.customtabs;
18
19import android.app.Activity;
20import android.app.ActivityOptions;
21import android.app.PendingIntent;
22import android.content.Context;
23import android.content.Intent;
24import android.graphics.Bitmap;
25import android.graphics.Color;
26import android.net.Uri;
27import android.os.Build;
28import android.os.Bundle;
29import android.os.IBinder;
30import android.support.annotation.AnimRes;
31import android.support.annotation.NonNull;
32import android.support.annotation.ColorInt;
33import android.support.annotation.Nullable;
34
35import java.lang.reflect.InvocationTargetException;
36import java.lang.reflect.Method;
37import java.util.ArrayList;
38
39/**
40 * Class holding the {@link Intent} and start bundle for a Custom Tabs Activity.
41 *
42 * <p>
43 * <strong>Note:</strong> The constants below are public for the browser implementation's benefit.
44 * You are strongly encouraged to use {@link CustomTabsIntent.Builder}.</p>
45 */
46public final class CustomTabsIntent {
47
48    /**
49     * Extra used to match the session. This has to be included in the intent to open in
50     * a custom tab. This is the same IBinder that gets passed to ICustomTabsService#newSession.
51     * Null if there is no need to match any service side sessions with the intent.
52     */
53    public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
54
55    /**
56     * Extra that changes the background color for the toolbar. colorRes is an int that specifies a
57     * {@link Color}, not a resource id.
58     */
59    public static final String EXTRA_TOOLBAR_COLOR =
60            "android.support.customtabs.extra.TOOLBAR_COLOR";
61
62    /**
63     * Boolean extra that enables the url bar to hide as the user scrolls down the page
64     */
65    public static final String EXTRA_ENABLE_URLBAR_HIDING =
66            "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
67
68    /**
69     * Extra bitmap that specifies the icon of the back button on the toolbar. If the client chooses
70     * not to customize it, a default close button will be used.
71     */
72    public static final String EXTRA_CLOSE_BUTTON_ICON =
73            "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
74
75    /**
76     * Extra (int) that specifies state for showing the page title. Default is {@link #NO_TITLE}.
77     */
78    public static final String EXTRA_TITLE_VISIBILITY_STATE =
79            "android.support.customtabs.extra.TITLE_VISIBILITY";
80
81    /**
82     * Don't show any title. Shows only the domain.
83     */
84    public static final int NO_TITLE = 0;
85
86    /**
87     * Shows the page title and the domain.
88     */
89    public static final int SHOW_PAGE_TITLE = 1;
90
91    /**
92     * Bundle used for adding a custom action button to the custom tab toolbar. The client should
93     * provide a description, an icon {@link Bitmap} and a {@link PendingIntent} for the button.
94     * All three keys must be present.
95     */
96    public static final String EXTRA_ACTION_BUTTON_BUNDLE =
97            "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
98
99    /**
100     * Key that specifies the {@link Bitmap} to be used as the image source for the action button.
101     *  The icon should't be more than 24dp in height (No padding needed. The button itself will be
102     *  48dp in height) and have a width/height ratio of less than 2.
103     */
104    public static final String KEY_ICON = "android.support.customtabs.customaction.ICON";
105
106    /**
107     * Key that specifies the content description for the custom action button.
108     */
109    public static final String KEY_DESCRIPTION =
110            "android.support.customtabs.customaction.DESCRIPTION";
111
112    /**
113     * Key that specifies the PendingIntent to launch when the action button or menu item was
114     * clicked. The custom tab will be calling {@link PendingIntent#send()} on clicks after adding
115     * the url as data. The client app can call {@link Intent#getDataString()} to get the url.
116     */
117    public static final String KEY_PENDING_INTENT =
118            "android.support.customtabs.customaction.PENDING_INTENT";
119
120    /**
121     * Extra boolean that specifies whether the custom action button should be tinted. Default is
122     * false and the action button will not be tinted.
123     */
124    public static final String EXTRA_TINT_ACTION_BUTTON =
125            "android.support.customtabs.extra.TINT_ACTION_BUTTON";
126
127    /**
128     * Use an {@code ArrayList<Bundle>} for specifying menu related params. There should be a
129     * separate {@link Bundle} for each custom menu item.
130     */
131    public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
132
133    /**
134     * Key for specifying the title of a menu item.
135     */
136    public static final String KEY_MENU_ITEM_TITLE =
137            "android.support.customtabs.customaction.MENU_ITEM_TITLE";
138
139    /**
140     * Bundle constructed out of {@link ActivityOptions} that will be running when the
141     * {@link Activity} that holds the custom tab gets finished. A similar ActivityOptions
142     * for creation should be constructed and given to the startActivity() call that
143     * launches the custom tab.
144     */
145    public static final String EXTRA_EXIT_ANIMATION_BUNDLE =
146            "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
147
148    /**
149     * An {@link Intent} used to start the Custom Tabs Activity.
150     */
151    @NonNull public final Intent intent;
152
153    /**
154     * A {@link Bundle} containing the start animation for the Custom Tabs Activity.
155     */
156    @Nullable public final Bundle startAnimationBundle;
157
158    /**
159     * Convenience method to launch a Custom Tabs Activity.
160     * @param context The source Activity.
161     * @param url The URL to load in the Custom Tab.
162     */
163    public void launchUrl(Activity context, Uri url) {
164        intent.setData(url);
165        if (startAnimationBundle != null){
166            context.startActivity(intent, startAnimationBundle);
167        } else {
168            context.startActivity(intent);
169        }
170    }
171
172    private CustomTabsIntent(Intent intent, Bundle startAnimationBundle) {
173        this.intent = intent;
174        this.startAnimationBundle = startAnimationBundle;
175    }
176
177    /**
178     * Builder class for {@link CustomTabsIntent} objects.
179     */
180    public static final class Builder {
181        private final Intent mIntent = new Intent(Intent.ACTION_VIEW);
182        private ArrayList<Bundle> mMenuItems = null;
183        private Bundle mStartAnimationBundle = null;
184
185        /**
186         * Creates a {@link CustomTabsIntent.Builder} object associated with no
187         * {@link CustomTabsSession}.
188         */
189        public Builder() {
190            this(null);
191        }
192
193        /**
194         * Creates a {@link CustomTabsIntent.Builder} object associated with a given
195         * {@link CustomTabsSession}.
196         *
197         * Guarantees that the {@link Intent} will be sent to the same component as the one the
198         * session is associated with.
199         *
200         * @param session The session to associate this Builder with.
201         */
202        public Builder(@Nullable CustomTabsSession session) {
203            if (session != null) mIntent.setPackage(session.getComponentName().getPackageName());
204            Bundle bundle = new Bundle();
205            safePutBinder(bundle, EXTRA_SESSION, session == null ? null : session.getBinder());
206            mIntent.putExtras(bundle);
207        }
208
209        /**
210         * Sets the toolbar color.
211         *
212         * @param color {@link Color}
213         */
214        public Builder setToolbarColor(@ColorInt int color) {
215            mIntent.putExtra(EXTRA_TOOLBAR_COLOR, color);
216            return this;
217        }
218
219        /**
220         * Enables the url bar to hide as the user scrolls down on the page.
221         */
222        public Builder enableUrlBarHiding() {
223            mIntent.putExtra(EXTRA_ENABLE_URLBAR_HIDING, true);
224            return this;
225        }
226
227        /**
228         * Sets the Close button icon for the custom tab.
229         *
230         * @param icon The icon {@link Bitmap}
231         */
232        public Builder setCloseButtonIcon(@NonNull Bitmap icon) {
233            mIntent.putExtra(EXTRA_CLOSE_BUTTON_ICON, icon);
234            return this;
235        }
236
237        /**
238         * Sets whether the title should be shown in the custom tab.
239         *
240         * @param showTitle Whether the title should be shown.
241         */
242        public Builder setShowTitle(boolean showTitle) {
243            mIntent.putExtra(EXTRA_TITLE_VISIBILITY_STATE,
244                    showTitle ? SHOW_PAGE_TITLE : NO_TITLE);
245            return this;
246        }
247
248        /**
249         * Adds a menu item.
250         *
251         * @param label Menu label.
252         * @param pendingIntent Pending intent delivered when the menu item is clicked.
253         */
254        public Builder addMenuItem(@NonNull String label, @NonNull PendingIntent pendingIntent) {
255            if (mMenuItems == null) mMenuItems = new ArrayList<>();
256            Bundle bundle = new Bundle();
257            bundle.putString(KEY_MENU_ITEM_TITLE, label);
258            bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
259            mMenuItems.add(bundle);
260            return this;
261        }
262
263        /**
264         * Set the action button.
265         *
266         * @param icon The icon.
267         * @param description The description for the button. To be used for accessibility.
268         * @param pendingIntent pending intent delivered when the button is clicked.
269         * @param shouldTint Whether the action button should be tinted.
270         */
271        public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description,
272                @NonNull PendingIntent pendingIntent, boolean shouldTint) {
273            Bundle bundle = new Bundle();
274            bundle.putParcelable(KEY_ICON, icon);
275            bundle.putString(KEY_DESCRIPTION, description);
276            bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
277            mIntent.putExtra(EXTRA_ACTION_BUTTON_BUNDLE, bundle);
278            mIntent.putExtra(EXTRA_TINT_ACTION_BUTTON, shouldTint);
279            return this;
280        }
281
282        /**
283         * See {@link CustomTabsIntent.Builder#setActionButton(
284         * Bitmap, String, PendingIntent, boolean)}
285         */
286        public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description,
287                @NonNull PendingIntent pendingIntent) {
288            return setActionButton(icon, description, pendingIntent, false);
289        }
290
291        /**
292         * Sets the start animations,
293         *
294         * @param context Application context.
295         * @param enterResId Resource ID of the "enter" animation for the browser.
296         * @param exitResId Resource ID of the "exit" animation for the application.
297         */
298        public Builder setStartAnimations(
299                @NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) {
300            mStartAnimationBundle =
301                    ActivityOptions.makeCustomAnimation(context, enterResId, exitResId).toBundle();
302            return this;
303        }
304
305        /**
306         * Sets the exit animations,
307         *
308         * @param context Application context.
309         * @param enterResId Resource ID of the "enter" animation for the application.
310         * @param exitResId Resource ID of the "exit" animation for the browser.
311         */
312        public Builder setExitAnimations(
313                @NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) {
314            Bundle bundle =
315                    ActivityOptions.makeCustomAnimation(context, enterResId, exitResId).toBundle();
316            mIntent.putExtra(EXTRA_EXIT_ANIMATION_BUNDLE, bundle);
317            return this;
318        }
319
320        /**
321         * Combines all the options that have been set and returns a new {@link CustomTabsIntent}
322         * object.
323         */
324        public CustomTabsIntent build() {
325            if (mMenuItems != null) {
326                mIntent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems);
327            }
328            return new CustomTabsIntent(mIntent, mStartAnimationBundle);
329        }
330
331        /**
332         * A convenience method to handle putting an {@link IBinder} inside a {@link Bundle} for all
333         * Android version.
334         * @param bundle The bundle to insert the {@link IBinder}.
335         * @param key    The key to use while putting the {@link IBinder}.
336         * @param binder The {@link IBinder} to put.
337         * @return       Whether the operation was successful.
338         */
339        private boolean safePutBinder(Bundle bundle, String key, IBinder binder) {
340            try {
341                // {@link Bundle#putBinder} exists since JB MR2, but we have
342                // {@link Bundle#putIBinder} which is the same method since the dawn of time. Use
343                // reflection when necessary to call it.
344                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
345                    bundle.putBinder(key, binder);
346                } else {
347                    Method putBinderMethod =
348                            Bundle.class.getMethod("putIBinder", String.class, IBinder.class);
349                    putBinderMethod.invoke(bundle, key, binder);
350                }
351            } catch (InvocationTargetException | IllegalAccessException
352                    | IllegalArgumentException | NoSuchMethodException e) {
353                return false;
354            }
355            return true;
356        }
357    }
358}
359