1/* 2 * Copyright 2018 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 androidx.browser.customtabs; 18 19import android.app.PendingIntent; 20import android.content.ComponentName; 21import android.graphics.Bitmap; 22import android.net.Uri; 23import android.os.Bundle; 24import android.os.IBinder; 25import android.os.RemoteException; 26import android.support.customtabs.ICustomTabsCallback; 27import android.support.customtabs.ICustomTabsService; 28import android.view.View; 29import android.widget.RemoteViews; 30 31import androidx.annotation.NonNull; 32import androidx.annotation.Nullable; 33import androidx.annotation.VisibleForTesting; 34 35import java.util.List; 36 37/** 38 * A class to be used for Custom Tabs related communication. Clients that want to launch Custom Tabs 39 * can use this class exclusively to handle all related communication. 40 */ 41public final class CustomTabsSession { 42 private static final String TAG = "CustomTabsSession"; 43 private final Object mLock = new Object(); 44 private final ICustomTabsService mService; 45 private final ICustomTabsCallback mCallback; 46 private final ComponentName mComponentName; 47 48 /** 49 * Provides browsers a way to generate a mock {@link CustomTabsSession} for testing 50 * purposes. 51 * 52 * @param componentName The component the session should be created for. 53 * @return A mock session with no functionality. 54 */ 55 @VisibleForTesting 56 @NonNull 57 public static CustomTabsSession createMockSessionForTesting( 58 @NonNull ComponentName componentName) { 59 return new CustomTabsSession( 60 null, new CustomTabsSessionToken.MockCallback(), componentName); 61 } 62 63 /* package */ CustomTabsSession( 64 ICustomTabsService service, ICustomTabsCallback callback, ComponentName componentName) { 65 mService = service; 66 mCallback = callback; 67 mComponentName = componentName; 68 } 69 70 /** 71 * Tells the browser of a likely future navigation to a URL. 72 * The most likely URL has to be specified first. Optionally, a list of 73 * other likely URLs can be provided. They are treated as less likely than 74 * the first one, and have to be sorted in decreasing priority order. These 75 * additional URLs may be ignored. 76 * All previous calls to this method will be deprioritized. 77 * 78 * @param url Most likely URL. 79 * @param extras Reserved for future use. 80 * @param otherLikelyBundles Other likely destinations, sorted in decreasing 81 * likelihood order. Inside each Bundle, the client should provide a 82 * {@link Uri} using {@link CustomTabsService#KEY_URL} with 83 * {@link Bundle#putParcelable(String, android.os.Parcelable)}. 84 * @return true for success. 85 */ 86 public boolean mayLaunchUrl(Uri url, Bundle extras, List<Bundle> otherLikelyBundles) { 87 try { 88 return mService.mayLaunchUrl(mCallback, url, extras, otherLikelyBundles); 89 } catch (RemoteException e) { 90 return false; 91 } 92 } 93 94 /** 95 * This sets the action button on the toolbar with ID 96 * {@link CustomTabsIntent#TOOLBAR_ACTION_BUTTON_ID}. 97 * 98 * @param icon The new icon of the action button. 99 * @param description Content description of the action button. 100 * 101 * @see CustomTabsSession#setToolbarItem(int, Bitmap, String) 102 */ 103 public boolean setActionButton(@NonNull Bitmap icon, @NonNull String description) { 104 Bundle bundle = new Bundle(); 105 bundle.putParcelable(CustomTabsIntent.KEY_ICON, icon); 106 bundle.putString(CustomTabsIntent.KEY_DESCRIPTION, description); 107 108 Bundle metaBundle = new Bundle(); 109 metaBundle.putBundle(CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE, bundle); 110 try { 111 return mService.updateVisuals(mCallback, metaBundle); 112 } catch (RemoteException e) { 113 return false; 114 } 115 } 116 117 /** 118 * Updates the {@link RemoteViews} of the secondary toolbar in an existing custom tab session. 119 * @param remoteViews The updated {@link RemoteViews} that will be shown in secondary toolbar. 120 * If null, the current secondary toolbar will be dismissed. 121 * @param clickableIDs The ids of clickable views. The onClick event of these views will be 122 * handled by custom tabs. 123 * @param pendingIntent The {@link PendingIntent} that will be sent when the user clicks on one 124 * of the {@link View}s in clickableIDs. 125 */ 126 public boolean setSecondaryToolbarViews(@Nullable RemoteViews remoteViews, 127 @Nullable int[] clickableIDs, @Nullable PendingIntent pendingIntent) { 128 Bundle bundle = new Bundle(); 129 bundle.putParcelable(CustomTabsIntent.EXTRA_REMOTEVIEWS, remoteViews); 130 bundle.putIntArray(CustomTabsIntent.EXTRA_REMOTEVIEWS_VIEW_IDS, clickableIDs); 131 bundle.putParcelable(CustomTabsIntent.EXTRA_REMOTEVIEWS_PENDINGINTENT, pendingIntent); 132 try { 133 return mService.updateVisuals(mCallback, bundle); 134 } catch (RemoteException e) { 135 return false; 136 } 137 } 138 139 /** 140 * Updates the visuals for toolbar items. Will only succeed if a custom tab created using this 141 * session is in the foreground in browser and the given id is valid. 142 * @param id The id for the item to update. 143 * @param icon The new icon of the toolbar item. 144 * @param description Content description of the toolbar item. 145 * @return Whether the update succeeded. 146 * @deprecated Use 147 * CustomTabsSession#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent) 148 */ 149 @Deprecated 150 public boolean setToolbarItem(int id, @NonNull Bitmap icon, @NonNull String description) { 151 Bundle bundle = new Bundle(); 152 bundle.putInt(CustomTabsIntent.KEY_ID, id); 153 bundle.putParcelable(CustomTabsIntent.KEY_ICON, icon); 154 bundle.putString(CustomTabsIntent.KEY_DESCRIPTION, description); 155 156 Bundle metaBundle = new Bundle(); 157 metaBundle.putBundle(CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE, bundle); 158 try { 159 return mService.updateVisuals(mCallback, metaBundle); 160 } catch (RemoteException e) { 161 return false; 162 } 163 } 164 165 /** 166 * Sends a request to create a two way postMessage channel between the client and the browser. 167 * 168 * @param postMessageOrigin A origin that the client is requesting to be identified as 169 * during the postMessage communication. 170 * @return Whether the implementation accepted the request. Note that returning true 171 * here doesn't mean an origin has already been assigned as the validation is 172 * asynchronous. 173 */ 174 public boolean requestPostMessageChannel(Uri postMessageOrigin) { 175 try { 176 return mService.requestPostMessageChannel( 177 mCallback, postMessageOrigin); 178 } catch (RemoteException e) { 179 return false; 180 } 181 } 182 183 /** 184 * Sends a postMessage request using the origin communicated via 185 * {@link CustomTabsService#requestPostMessageChannel( 186 * CustomTabsSessionToken, Uri)}. Fails when called before 187 * {@link PostMessageServiceConnection#notifyMessageChannelReady(Bundle)} is received on 188 * the client side. 189 * 190 * @param message The message that is being sent. 191 * @param extras Reserved for future use. 192 * @return An integer constant about the postMessage request result. Will return 193 * {@link CustomTabsService#RESULT_SUCCESS} if successful. 194 */ 195 @CustomTabsService.Result 196 public int postMessage(String message, Bundle extras) { 197 synchronized (mLock) { 198 try { 199 return mService.postMessage(mCallback, message, extras); 200 } catch (RemoteException e) { 201 return CustomTabsService.RESULT_FAILURE_REMOTE_ERROR; 202 } 203 } 204 } 205 206 /** 207 * Requests to validate a relationship between the application and an origin. 208 * 209 * <p> 210 * See <a href="https://developers.google.com/digital-asset-links/v1/getting-started">here</a> 211 * for documentation about Digital Asset Links. This methods requests the browser to verify 212 * a relation with the calling application, to grant the associated rights. 213 * 214 * <p> 215 * If this method returns {@code true}, the validation result will be provided through 216 * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}. 217 * Otherwise the request didn't succeed. The client must call 218 * {@link CustomTabsClient#warmup(long)} before this. 219 * 220 * @param relation Relation to check, must be one of the {@code CustomTabsService#RELATION_* } 221 * constants. 222 * @param origin Origin. 223 * @param extras Reserved for future use. 224 * @return {@code true} if the request has been submitted successfully. 225 */ 226 public boolean validateRelationship(@CustomTabsService.Relation int relation, @NonNull Uri origin, 227 @Nullable Bundle extras) { 228 if (relation < CustomTabsService.RELATION_USE_AS_ORIGIN 229 || relation > CustomTabsService.RELATION_HANDLE_ALL_URLS) { 230 return false; 231 } 232 try { 233 return mService.validateRelationship(mCallback, relation, origin, extras); 234 } catch (RemoteException e) { 235 return false; 236 } 237 } 238 239 /* package */ IBinder getBinder() { 240 return mCallback.asBinder(); 241 } 242 243 /* package */ ComponentName getComponentName() { 244 return mComponentName; 245 } 246} 247