CustomTabsService.java revision f2fe06734fa162d7d49f4891b7052bba8f19f50c
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.Service;
20import android.content.Intent;
21import android.net.Uri;
22import android.os.Bundle;
23import android.os.IBinder;
24import android.os.IBinder.DeathRecipient;
25import android.os.RemoteException;
26import android.support.v4.util.ArrayMap;
27
28import java.util.List;
29import java.util.Map;
30import java.util.NoSuchElementException;
31
32/**
33 * Abstract service class for implementing Custom Tabs related functionality. The service should
34 * be responding to the action ACTION_CUSTOM_TABS_CONNECTION. This class should be used by
35 * implementers that want to provide Custom Tabs functionality, not by clients that want to launch
36 * Custom Tabs.
37 */
38 public abstract class CustomTabsService extends Service {
39     /**
40      * The Intent action that a CustomTabsService must respond to.
41      */
42     public static final String ACTION_CUSTOM_TABS_CONNECTION =
43             "android.support.customtabs.action.CustomTabsService";
44
45     /**
46      * For {@link CustomTabsService#mayLaunchUrl} calls that wants to specify more than one url,
47      * this key can be used with {@link Bundle#putParcelable(String, android.os.Parcelable)}
48      * to insert a new url to each bundle inside list of bundles.
49      */
50     public static final String KEY_URL =
51             "android.support.customtabs.otherurls.URL";
52
53     private final Map<IBinder, DeathRecipient> mDeathRecipientMap = new ArrayMap<>();
54
55     private ICustomTabsService.Stub mBinder = new ICustomTabsService.Stub() {
56
57         @Override
58         public boolean warmup(long flags) {
59             return CustomTabsService.this.warmup(flags);
60         }
61
62         @Override
63         public boolean newSession(ICustomTabsCallback callback) {
64             final CustomTabsSessionToken sessionToken = new CustomTabsSessionToken(callback);
65             try {
66                 DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
67                     @Override
68                     public void binderDied() {
69                         cleanUpSession(sessionToken);
70                     }
71                 };
72                 synchronized (mDeathRecipientMap) {
73                     callback.asBinder().linkToDeath(deathRecipient, 0);
74                     mDeathRecipientMap.put(callback.asBinder(), deathRecipient);
75                 }
76                 return CustomTabsService.this.newSession(sessionToken);
77             } catch (RemoteException e) {
78                 return false;
79             }
80         }
81
82         @Override
83         public boolean mayLaunchUrl(ICustomTabsCallback callback, Uri url,
84                         Bundle extras, List<Bundle> otherLikelyBundles) {
85             return CustomTabsService.this.mayLaunchUrl(
86                     new CustomTabsSessionToken(callback), url, extras, otherLikelyBundles);
87         }
88
89         @Override
90         public Bundle extraCommand(String commandName, Bundle args) {
91             return CustomTabsService.this.extraCommand(commandName, args);
92         }
93
94         @Override
95         public boolean updateVisuals(ICustomTabsCallback callback, Bundle bundle) {
96             return CustomTabsService.this.updateVisuals(
97                     new CustomTabsSessionToken(callback), bundle);
98         }
99     };
100
101     @Override
102     public IBinder onBind(Intent intent) {
103         return mBinder;
104     }
105
106     /**
107      * Called when the client side {@link IBinder} for this {@link CustomTabsSessionToken} is dead.
108      * Can also be used to clean up {@link DeathRecipient} instances allocated for the given token.
109      * @param sessionToken The session token for which the {@link DeathRecipient} call has been
110      *                     received.
111      * @return Whether the clean up was successful. Multiple calls with two tokens holdings the
112      *         same binder will return false.
113      */
114     protected boolean cleanUpSession(CustomTabsSessionToken sessionToken) {
115         try {
116             synchronized (mDeathRecipientMap) {
117                IBinder binder = sessionToken.getCallbackBinder();
118                DeathRecipient deathRecipient =
119                        mDeathRecipientMap.get(binder);
120                binder.unlinkToDeath(deathRecipient, 0);
121                mDeathRecipientMap.remove(binder);
122            }
123         } catch (NoSuchElementException e) {
124             return false;
125         }
126         return true;
127     }
128
129     /**
130      * Warms up the browser process asynchronously.
131      *
132      * @param flags Reserved for future use.
133      * @return      Whether warmup was/had been completed successfully. Multiple successful
134      *              calls will return true.
135      */
136     protected abstract boolean warmup(long flags);
137
138     /**
139      * Creates a new session through an ICustomTabsService with the optional callback. This session
140      * can be used to associate any related communication through the service with an intent and
141      * then later with a Custom Tab. The client can then send later service calls or intents to
142      * through same session-intent-Custom Tab association.
143      * @param sessionToken Session token to be used as a unique identifier. This also has access
144      *                     to the {@link CustomTabsCallback} passed from the client side through
145      *                     {@link CustomTabsSessionToken#getCallback()}.
146      * @return             Whether a new session was successfully created.
147      */
148     protected abstract boolean newSession(CustomTabsSessionToken sessionToken);
149
150     /**
151      * Tells the browser of a likely future navigation to a URL.
152      *
153      * The method {@link CustomTabsService#warmup(long)} has to be called beforehand.
154      * The most likely URL has to be specified explicitly. Optionally, a list of
155      * other likely URLs can be provided. They are treated as less likely than
156      * the first one, and have to be sorted in decreasing priority order. These
157      * additional URLs may be ignored.
158      * All previous calls to this method will be deprioritized.
159      *
160      * @param sessionToken       The unique identifier for the session. Can not be null.
161      * @param url                Most likely URL.
162      * @param extras             Reserved for future use.
163      * @param otherLikelyBundles Other likely destinations, sorted in decreasing
164      *                           likelihood order. Each Bundle has to provide a url.
165      * @return                   Whether the call was successful.
166      */
167     protected abstract boolean mayLaunchUrl(CustomTabsSessionToken sessionToken, Uri url,
168             Bundle extras, List<Bundle> otherLikelyBundles);
169
170     /**
171      * Unsupported commands that may be provided by the implementation.
172      *
173      * <p>
174      * <strong>Note:</strong>Clients should <strong>never</strong> rely on this method to have a
175      * defined behavior, as it is entirely implementation-defined and not supported.
176      *
177      * <p> This call can be used by implementations to add extra commands, for testing or
178      * experimental purposes.
179      *
180      * @param commandName Name of the extra command to execute.
181      * @param args Arguments for the command
182      * @return The result {@link Bundle}, or null.
183      */
184     protected abstract Bundle extraCommand(String commandName, Bundle args);
185
186    /**
187     * Updates the visuals of custom tabs for the given session. Will only succeed if the given
188     * session matches the currently active one.
189     * @param sessionToken The currently active session that the custom tab belongs to.
190     * @param bundle       The action button configuration bundle. This bundle should be constructed
191     *                     with the same structure in {@link CustomTabsIntent.Builder}.
192     * @return Whether the operation was successful.
193     */
194     protected abstract boolean updateVisuals(CustomTabsSessionToken sessionToken,
195             Bundle bundle);
196 }
197