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