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.Bundle;
28import android.support.annotation.AnimRes;
29import android.support.annotation.ColorInt;
30import android.support.annotation.NonNull;
31import android.support.annotation.Nullable;
32import android.support.v4.app.BundleCompat;
33
34import java.util.ArrayList;
35
36/**
37 * Class holding the {@link Intent} and start bundle for a Custom Tabs Activity.
38 *
39 * <p>
40 * <strong>Note:</strong> The constants below are public for the browser implementation's benefit.
41 * You are strongly encouraged to use {@link CustomTabsIntent.Builder}.</p>
42 */
43public final class CustomTabsIntent {
44
45    /**
46     * Extra used to match the session. This has to be included in the intent to open in
47     * a custom tab. This is the same IBinder that gets passed to ICustomTabsService#newSession.
48     * Null if there is no need to match any service side sessions with the intent.
49     */
50    public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
51
52    /**
53     * Extra that changes the background color for the toolbar. colorRes is an int that specifies a
54     * {@link Color}, not a resource id.
55     */
56    public static final String EXTRA_TOOLBAR_COLOR =
57            "android.support.customtabs.extra.TOOLBAR_COLOR";
58
59    /**
60     * Extra bitmap that specifies the icon of the back button on the toolbar. If the client chooses
61     * not to customize it, a default close button will be used.
62     */
63    public static final String EXTRA_CLOSE_BUTTON_ICON =
64            "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
65
66    /**
67     * Extra (int) that specifies state for showing the page title. Default is {@link #NO_TITLE}.
68     */
69    public static final String EXTRA_TITLE_VISIBILITY_STATE =
70            "android.support.customtabs.extra.TITLE_VISIBILITY";
71
72    /**
73     * Don't show any title. Shows only the domain.
74     */
75    public static final int NO_TITLE = 0;
76
77    /**
78     * Shows the page title and the domain.
79     */
80    public static final int SHOW_PAGE_TITLE = 1;
81
82    /**
83     * Bundle used for adding a custom action button to the custom tab toolbar. The client should
84     * provide a description, an icon {@link Bitmap} and a {@link PendingIntent} for the button.
85     * All three keys must be present.
86     */
87    public static final String EXTRA_ACTION_BUTTON_BUNDLE =
88            "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
89
90    /**
91     * Key that specifies the {@link Bitmap} to be used as the image source for the action button.
92     *  The icon should't be more than 24dp in height (No padding needed. The button itself will be
93     *  48dp in height) and have a width/height ratio of less than 2.
94     */
95    public static final String KEY_ICON = "android.support.customtabs.customaction.ICON";
96
97    /**
98     * Key that specifies the content description for the custom action button.
99     */
100    public static final String KEY_DESCRIPTION =
101            "android.support.customtabs.customaction.DESCRIPTION";
102
103    /**
104     * Key that specifies the PendingIntent to launch when the action button or menu item was
105     * clicked. The custom tab will be calling {@link PendingIntent#send()} on clicks after adding
106     * the url as data. The client app can call {@link Intent#getDataString()} to get the url.
107     */
108    public static final String KEY_PENDING_INTENT =
109            "android.support.customtabs.customaction.PENDING_INTENT";
110
111    /**
112     * Use an {@code ArrayList<Bundle>} for specifying menu related params. There should be a
113     * separate {@link Bundle} for each custom menu item.
114     */
115    public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
116
117    /**
118     * Key for specifying the title of a menu item.
119     */
120    public static final String KEY_MENU_ITEM_TITLE =
121            "android.support.customtabs.customaction.MENU_ITEM_TITLE";
122
123    /**
124     * Bundle constructed out of {@link ActivityOptions} that will be running when the
125     * {@link Activity} that holds the custom tab gets finished. A similar ActivityOptions
126     * for creation should be constructed and given to the startActivity() call that
127     * launches the custom tab.
128     */
129    public static final String EXTRA_EXIT_ANIMATION_BUNDLE =
130            "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
131
132    /**
133     * An {@link Intent} used to start the Custom Tabs Activity.
134     */
135    @NonNull public final Intent intent;
136
137    /**
138     * A {@link Bundle} containing the start animation for the Custom Tabs Activity.
139     */
140    @Nullable public final Bundle startAnimationBundle;
141
142    /**
143     * Convenience method to launch a Custom Tabs Activity.
144     * @param context The source Activity.
145     * @param url The URL to load in the Custom Tab.
146     */
147    public void launchUrl(Activity context, Uri url) {
148        intent.setData(url);
149        if (startAnimationBundle != null){
150            context.startActivity(intent, startAnimationBundle);
151        } else {
152            context.startActivity(intent);
153        }
154    }
155
156    private CustomTabsIntent(Intent intent, Bundle startAnimationBundle) {
157        this.intent = intent;
158        this.startAnimationBundle = startAnimationBundle;
159    }
160
161    /**
162     * Builder class for {@link CustomTabsIntent} objects.
163     */
164    public static final class Builder {
165        private final Intent mIntent = new Intent(Intent.ACTION_VIEW);
166        private ArrayList<Bundle> mMenuItems = null;
167        private Bundle mStartAnimationBundle = null;
168
169        /**
170         * Creates a {@link CustomTabsIntent.Builder} object associated with no
171         * {@link CustomTabsSession}.
172         */
173        public Builder() {
174            this(null);
175        }
176
177        /**
178         * Creates a {@link CustomTabsIntent.Builder} object associated with a given
179         * {@link CustomTabsSession}.
180         *
181         * Guarantees that the {@link Intent} will be sent to the same component as the one the
182         * session is associated with.
183         *
184         * @param session The session to associate this Builder with.
185         */
186        public Builder(@Nullable CustomTabsSession session) {
187            if (session != null) mIntent.setPackage(session.getComponentName().getPackageName());
188            Bundle bundle = new Bundle();
189            BundleCompat.putBinder(
190                    bundle, EXTRA_SESSION, session == null ? null : session.getBinder());
191            mIntent.putExtras(bundle);
192        }
193
194        /**
195         * Sets the toolbar color.
196         *
197         * @param color {@link Color}
198         */
199        public Builder setToolbarColor(@ColorInt int color) {
200            mIntent.putExtra(EXTRA_TOOLBAR_COLOR, color);
201            return this;
202        }
203
204        /**
205         * Sets the Close button icon for the custom tab.
206         *
207         * @param icon The icon {@link Bitmap}
208         */
209        public Builder setCloseButtonIcon(@NonNull Bitmap icon) {
210            mIntent.putExtra(EXTRA_CLOSE_BUTTON_ICON, icon);
211            return this;
212        }
213
214        /**
215         * Sets whether the title should be shown in the custom tab.
216         *
217         * @param showTitle Whether the title should be shown.
218         */
219        public Builder setShowTitle(boolean showTitle) {
220            mIntent.putExtra(EXTRA_TITLE_VISIBILITY_STATE,
221                    showTitle ? SHOW_PAGE_TITLE : NO_TITLE);
222            return this;
223        }
224
225        /**
226         * Adds a menu item.
227         *
228         * @param label Menu label.
229         * @param pendingIntent Pending intent delivered when the menu item is clicked.
230         */
231        public Builder addMenuItem(@NonNull String label, @NonNull PendingIntent pendingIntent) {
232            if (mMenuItems == null) mMenuItems = new ArrayList<>();
233            Bundle bundle = new Bundle();
234            bundle.putString(KEY_MENU_ITEM_TITLE, label);
235            bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
236            mMenuItems.add(bundle);
237            return this;
238        }
239
240        /**
241         * Set the action button.
242         *
243         * @param icon The icon.
244         * @param description The description for the button. To be used for accessibility.
245         * @param pendingIntent pending intent delivered when the button is clicked.
246         */
247        public Builder setActionButton(@NonNull Bitmap icon,
248                @NonNull String description, @NonNull PendingIntent pendingIntent) {
249            Bundle bundle = new Bundle();
250            bundle.putParcelable(KEY_ICON, icon);
251            bundle.putString(KEY_DESCRIPTION, description);
252            bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
253            mIntent.putExtra(EXTRA_ACTION_BUTTON_BUNDLE, bundle);
254            return this;
255        }
256
257        /**
258         * Sets the start animations,
259         *
260         * @param context Application context.
261         * @param enterResId Resource ID of the "enter" animation for the browser.
262         * @param exitResId Resource ID of the "exit" animation for the application.
263         */
264        public Builder setStartAnimations(
265                @NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) {
266            mStartAnimationBundle =
267                    ActivityOptions.makeCustomAnimation(context, enterResId, exitResId).toBundle();
268            return this;
269        }
270
271        /**
272         * Sets the exit animations,
273         *
274         * @param context Application context.
275         * @param enterResId Resource ID of the "enter" animation for the application.
276         * @param exitResId Resource ID of the "exit" animation for the browser.
277         */
278        public Builder setExitAnimations(
279                @NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) {
280            Bundle bundle =
281                    ActivityOptions.makeCustomAnimation(context, enterResId, exitResId).toBundle();
282            mIntent.putExtra(EXTRA_EXIT_ANIMATION_BUNDLE, bundle);
283            return this;
284        }
285
286        /**
287         * Combines all the options that have been set and returns a new {@link CustomTabsIntent}
288         * object.
289         */
290        public CustomTabsIntent build() {
291            if (mMenuItems != null) {
292                mIntent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems);
293            }
294            return new CustomTabsIntent(mIntent, mStartAnimationBundle);
295        }
296    }
297}
298