CustomTabsIntent.java revision e063d4d36883c9f7ca100036b9344b665ab6fd1e
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 * Extra bitmap that specifies the icon of the back button on the toolbar. If the client chooses 64 * not to customize it, a default close button will be used. 65 */ 66 public static final String EXTRA_CLOSE_BUTTON_ICON = 67 "android.support.customtabs.extra.CLOSE_BUTTON_ICON"; 68 69 /** 70 * Extra (int) that specifies state for showing the page title. Default is {@link #NO_TITLE}. 71 */ 72 public static final String EXTRA_TITLE_VISIBILITY_STATE = 73 "android.support.customtabs.extra.TITLE_VISIBILITY"; 74 75 /** 76 * Don't show any title. Shows only the domain. 77 */ 78 public static final int NO_TITLE = 0; 79 80 /** 81 * Shows the page title and the domain. 82 */ 83 public static final int SHOW_PAGE_TITLE = 1; 84 85 /** 86 * Bundle used for adding a custom action button to the custom tab toolbar. The client should 87 * provide a description, an icon {@link Bitmap} and a {@link PendingIntent} for the button. 88 * All three keys must be present. 89 */ 90 public static final String EXTRA_ACTION_BUTTON_BUNDLE = 91 "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE"; 92 93 /** 94 * Key that specifies the {@link Bitmap} to be used as the image source for the action button. 95 * The icon should't be more than 24dp in height (No padding needed. The button itself will be 96 * 48dp in height) and have a width/height ratio of less than 2. 97 */ 98 public static final String KEY_ICON = "android.support.customtabs.customaction.ICON"; 99 100 /** 101 * Key that specifies the content description for the custom action button. 102 */ 103 public static final String KEY_DESCRIPTION = 104 "android.support.customtabs.customaction.DESCRIPTION"; 105 106 /** 107 * Key that specifies the PendingIntent to launch when the action button or menu item was 108 * clicked. The custom tab will be calling {@link PendingIntent#send()} on clicks after adding 109 * the url as data. The client app can call {@link Intent#getDataString()} to get the url. 110 */ 111 public static final String KEY_PENDING_INTENT = 112 "android.support.customtabs.customaction.PENDING_INTENT"; 113 114 /** 115 * Use an {@code ArrayList<Bundle>} for specifying menu related params. There should be a 116 * separate {@link Bundle} for each custom menu item. 117 */ 118 public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS"; 119 120 /** 121 * Key for specifying the title of a menu item. 122 */ 123 public static final String KEY_MENU_ITEM_TITLE = 124 "android.support.customtabs.customaction.MENU_ITEM_TITLE"; 125 126 /** 127 * Bundle constructed out of {@link ActivityOptions} that will be running when the 128 * {@link Activity} that holds the custom tab gets finished. A similar ActivityOptions 129 * for creation should be constructed and given to the startActivity() call that 130 * launches the custom tab. 131 */ 132 public static final String EXTRA_EXIT_ANIMATION_BUNDLE = 133 "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE"; 134 135 /** 136 * An {@link Intent} used to start the Custom Tabs Activity. 137 */ 138 @NonNull public final Intent intent; 139 140 /** 141 * A {@link Bundle} containing the start animation for the Custom Tabs Activity. 142 */ 143 @Nullable public final Bundle startAnimationBundle; 144 145 /** 146 * Convenience method to launch a Custom Tabs Activity. 147 * @param context The source Activity. 148 * @param url The URL to load in the Custom Tab. 149 */ 150 public void launchUrl(Activity context, Uri url) { 151 intent.setData(url); 152 if (startAnimationBundle != null){ 153 context.startActivity(intent, startAnimationBundle); 154 } else { 155 context.startActivity(intent); 156 } 157 } 158 159 private CustomTabsIntent(Intent intent, Bundle startAnimationBundle) { 160 this.intent = intent; 161 this.startAnimationBundle = startAnimationBundle; 162 } 163 164 /** 165 * Builder class for {@link CustomTabsIntent} objects. 166 */ 167 public static final class Builder { 168 private final Intent mIntent = new Intent(Intent.ACTION_VIEW); 169 private ArrayList<Bundle> mMenuItems = null; 170 private Bundle mStartAnimationBundle = null; 171 172 /** 173 * Creates a {@link CustomTabsIntent.Builder} object associated with no 174 * {@link CustomTabsSession}. 175 */ 176 public Builder() { 177 this(null); 178 } 179 180 /** 181 * Creates a {@link CustomTabsIntent.Builder} object associated with a given 182 * {@link CustomTabsSession}. 183 * 184 * Guarantees that the {@link Intent} will be sent to the same component as the one the 185 * session is associated with. 186 * 187 * @param session The session to associate this Builder with. 188 */ 189 public Builder(@Nullable CustomTabsSession session) { 190 if (session != null) mIntent.setPackage(session.getComponentName().getPackageName()); 191 Bundle bundle = new Bundle(); 192 safePutBinder(bundle, EXTRA_SESSION, session == null ? null : session.getBinder()); 193 mIntent.putExtras(bundle); 194 } 195 196 /** 197 * Sets the toolbar color. 198 * 199 * @param color {@link Color} 200 */ 201 public Builder setToolbarColor(@ColorInt int color) { 202 mIntent.putExtra(EXTRA_TOOLBAR_COLOR, color); 203 return this; 204 } 205 206 /** 207 * Sets the Close button icon for the custom tab. 208 * 209 * @param icon The icon {@link Bitmap} 210 */ 211 public Builder setCloseButtonIcon(@NonNull Bitmap icon) { 212 mIntent.putExtra(EXTRA_CLOSE_BUTTON_ICON, icon); 213 return this; 214 } 215 216 /** 217 * Sets whether the title should be shown in the custom tab. 218 * 219 * @param showTitle Whether the title should be shown. 220 */ 221 public Builder setShowTitle(boolean showTitle) { 222 mIntent.putExtra(EXTRA_TITLE_VISIBILITY_STATE, 223 showTitle ? SHOW_PAGE_TITLE : NO_TITLE); 224 return this; 225 } 226 227 /** 228 * Adds a menu item. 229 * 230 * @param label Menu label. 231 * @param pendingIntent Pending intent delivered when the menu item is clicked. 232 */ 233 public Builder addMenuItem(@NonNull String label, @NonNull PendingIntent pendingIntent) { 234 if (mMenuItems == null) mMenuItems = new ArrayList<>(); 235 Bundle bundle = new Bundle(); 236 bundle.putString(KEY_MENU_ITEM_TITLE, label); 237 bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent); 238 mMenuItems.add(bundle); 239 return this; 240 } 241 242 /** 243 * Set the action button. 244 * 245 * @param icon The icon. 246 * @param description The description for the button. To be used for accessibility. 247 * @param pendingIntent pending intent delivered when the button is clicked. 248 */ 249 public Builder setActionButton(@NonNull Bitmap icon, 250 @NonNull String description, @NonNull PendingIntent pendingIntent) { 251 Bundle bundle = new Bundle(); 252 bundle.putParcelable(KEY_ICON, icon); 253 bundle.putString(KEY_DESCRIPTION, description); 254 bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent); 255 mIntent.putExtra(EXTRA_ACTION_BUTTON_BUNDLE, bundle); 256 return this; 257 } 258 259 /** 260 * Sets the start animations, 261 * 262 * @param context Application context. 263 * @param enterResId Resource ID of the "enter" animation for the browser. 264 * @param exitResId Resource ID of the "exit" animation for the application. 265 */ 266 public Builder setStartAnimations( 267 @NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) { 268 mStartAnimationBundle = 269 ActivityOptions.makeCustomAnimation(context, enterResId, exitResId).toBundle(); 270 return this; 271 } 272 273 /** 274 * Sets the exit animations, 275 * 276 * @param context Application context. 277 * @param enterResId Resource ID of the "enter" animation for the application. 278 * @param exitResId Resource ID of the "exit" animation for the browser. 279 */ 280 public Builder setExitAnimations( 281 @NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) { 282 Bundle bundle = 283 ActivityOptions.makeCustomAnimation(context, enterResId, exitResId).toBundle(); 284 mIntent.putExtra(EXTRA_EXIT_ANIMATION_BUNDLE, bundle); 285 return this; 286 } 287 288 /** 289 * Combines all the options that have been set and returns a new {@link CustomTabsIntent} 290 * object. 291 */ 292 public CustomTabsIntent build() { 293 if (mMenuItems != null) { 294 mIntent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems); 295 } 296 return new CustomTabsIntent(mIntent, mStartAnimationBundle); 297 } 298 299 /** 300 * A convenience method to handle putting an {@link IBinder} inside a {@link Bundle} for all 301 * Android version. 302 * @param bundle The bundle to insert the {@link IBinder}. 303 * @param key The key to use while putting the {@link IBinder}. 304 * @param binder The {@link IBinder} to put. 305 * @return Whether the operation was successful. 306 */ 307 private boolean safePutBinder(Bundle bundle, String key, IBinder binder) { 308 try { 309 // {@link Bundle#putBinder} exists since JB MR2, but we have 310 // {@link Bundle#putIBinder} which is the same method since the dawn of time. Use 311 // reflection when necessary to call it. 312 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 313 bundle.putBinder(key, binder); 314 } else { 315 Method putBinderMethod = 316 Bundle.class.getMethod("putIBinder", String.class, IBinder.class); 317 putBinderMethod.invoke(bundle, key, binder); 318 } 319 } catch (InvocationTargetException | IllegalAccessException 320 | IllegalArgumentException | NoSuchMethodException e) { 321 return false; 322 } 323 return true; 324 } 325 } 326} 327