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.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.content.pm.PackageManager; 24import android.content.pm.ResolveInfo; 25import android.net.Uri; 26import android.os.Bundle; 27import android.os.RemoteException; 28import android.support.annotation.Nullable; 29import android.support.annotation.RestrictTo; 30import android.text.TextUtils; 31 32import java.util.ArrayList; 33import java.util.List; 34 35import static android.support.annotation.RestrictTo.Scope.GROUP_ID; 36 37/** 38 * Class to communicate with a {@link CustomTabsService} and create 39 * {@link CustomTabsSession} from it. 40 */ 41public class CustomTabsClient { 42 private final ICustomTabsService mService; 43 private final ComponentName mServiceComponentName; 44 45 /**@hide*/ 46 @RestrictTo(GROUP_ID) 47 CustomTabsClient(ICustomTabsService service, ComponentName componentName) { 48 mService = service; 49 mServiceComponentName = componentName; 50 } 51 52 /** 53 * Bind to a {@link CustomTabsService} using the given package name and 54 * {@link ServiceConnection}. 55 * @param context {@link Context} to use while calling 56 * {@link Context#bindService(Intent, ServiceConnection, int)} 57 * @param packageName Package name to set on the {@link Intent} for binding. 58 * @param connection {@link CustomTabsServiceConnection} to use when binding. This will 59 * return a {@link CustomTabsClient} on 60 * {@link CustomTabsServiceConnection 61 * #onCustomTabsServiceConnected(ComponentName, CustomTabsClient)} 62 * @return Whether the binding was successful. 63 */ 64 public static boolean bindCustomTabsService(Context context, 65 String packageName, CustomTabsServiceConnection connection) { 66 Intent intent = new Intent(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION); 67 if (!TextUtils.isEmpty(packageName)) intent.setPackage(packageName); 68 return context.bindService(intent, connection, 69 Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY); 70 } 71 72 /** 73 * Returns the preferred package to use for Custom Tabs, preferring the default VIEW handler. 74 * 75 * @see {@link #getPackageName(Context, List<String>, boolean)}. 76 */ 77 public static String getPackageName(Context context, @Nullable List<String> packages) { 78 return getPackageName(context, packages, false); 79 } 80 81 /** 82 * Returns the preferred package to use for Custom Tabs. 83 * 84 * The preferred package name is the default VIEW intent handler as long as it supports Custom 85 * Tabs. To modify this preferred behavior, set <code>ignoreDefault</code> to true and give a 86 * non empty list of package names in <code>packages</code>. 87 * 88 * @param context {@link Context} to use for querying the packages. 89 * @param packages Ordered list of packages to test for Custom Tabs support, in 90 * decreasing order of priority. 91 * @param ignoreDefault If set, the default VIEW handler won't get priority over other browsers. 92 * @return The preferred package name for handling Custom Tabs, or <code>null</code>. 93 */ 94 public static String getPackageName( 95 Context context, @Nullable List<String> packages, boolean ignoreDefault) { 96 PackageManager pm = context.getPackageManager(); 97 98 List<String> packageNames = packages == null ? new ArrayList<String>() : packages; 99 Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")); 100 101 if (!ignoreDefault) { 102 ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0); 103 if (defaultViewHandlerInfo != null) { 104 String packageName = defaultViewHandlerInfo.activityInfo.packageName; 105 packageNames = new ArrayList<String>(packageNames.size() + 1); 106 packageNames.add(packageName); 107 if (packages != null) packageNames.addAll(packages); 108 } 109 } 110 111 Intent serviceIntent = new Intent(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION); 112 for (String packageName : packageNames) { 113 serviceIntent.setPackage(packageName); 114 if (pm.resolveService(serviceIntent, 0) != null) return packageName; 115 } 116 return null; 117 } 118 119 /** 120 * Connects to the Custom Tabs warmup service, and initializes the browser. 121 * 122 * This convenience method connects to the service, and immediately warms up the Custom Tabs 123 * implementation. Since service connection is asynchronous, the return code is not the return 124 * code of warmup. 125 * This call is optional, and clients are encouraged to connect to the service, call 126 * <code>warmup()</code> and create a session. In this case, calling this method is not 127 * necessary. 128 * 129 * @param context {@link Context} to use to connect to the remote service. 130 * @param packageName Package name of the target implementation. 131 * @return Whether the binding was successful. 132 */ 133 public static boolean connectAndInitialize(Context context, String packageName) { 134 if (packageName == null) return false; 135 final Context applicationContext = context.getApplicationContext(); 136 CustomTabsServiceConnection connection = new CustomTabsServiceConnection() { 137 @Override 138 public final void onCustomTabsServiceConnected( 139 ComponentName name, CustomTabsClient client) { 140 client.warmup(0); 141 // Unbinding immediately makes the target process "Empty", provided that it is 142 // not used by anyone else, and doesn't contain any Activity. This makes it 143 // likely to get killed, but is preferable to keeping the connection around. 144 applicationContext.unbindService(this); 145 } 146 147 @Override 148 public final void onServiceDisconnected(ComponentName componentName) { } 149 }; 150 try { 151 return bindCustomTabsService(applicationContext, packageName, connection); 152 } catch (SecurityException e) { 153 return false; 154 } 155 } 156 157 /** 158 * Warm up the browser process. 159 * 160 * Allows the browser application to pre-initialize itself in the background. Significantly 161 * speeds up URL opening in the browser. This is asynchronous and can be called several times. 162 * 163 * @param flags Reserved for future use. 164 * @return Whether the warmup was successful. 165 */ 166 public boolean warmup(long flags) { 167 try { 168 return mService.warmup(flags); 169 } catch (RemoteException e) { 170 return false; 171 } 172 } 173 174 /** 175 * Creates a new session through an ICustomTabsService with the optional callback. This session 176 * can be used to associate any related communication through the service with an intent and 177 * then later with a Custom Tab. The client can then send later service calls or intents to 178 * through same session-intent-Custom Tab association. 179 * @param callback The callback through which the client will receive updates about the created 180 * session. Can be null. 181 * @return The session object that was created as a result of the transaction. The client can 182 * use this to relay {@link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)} calls. 183 * Null on error. 184 */ 185 public CustomTabsSession newSession(final CustomTabsCallback callback) { 186 ICustomTabsCallback.Stub wrapper = new ICustomTabsCallback.Stub() { 187 @Override 188 public void onNavigationEvent(int navigationEvent, Bundle extras) { 189 if (callback != null) callback.onNavigationEvent(navigationEvent, extras); 190 } 191 192 @Override 193 public void extraCallback(String callbackName, Bundle args) throws RemoteException { 194 if (callback != null) callback.extraCallback(callbackName, args); 195 } 196 }; 197 198 try { 199 if (!mService.newSession(wrapper)) return null; 200 } catch (RemoteException e) { 201 return null; 202 } 203 return new CustomTabsSession(mService, wrapper, mServiceComponentName); 204 } 205 206 public Bundle extraCommand(String commandName, Bundle args) { 207 try { 208 return mService.extraCommand(commandName, args); 209 } catch (RemoteException e) { 210 return null; 211 } 212 } 213} 214