/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.customtabs;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.support.v4.util.ArrayMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Abstract service class for implementing Custom Tabs related functionality. The service should
* be responding to the action ACTION_CUSTOM_TABS_CONNECTION. This class should be used by
* implementers that want to provide Custom Tabs functionality, not by clients that want to launch
* Custom Tabs.
*/
public abstract class CustomTabsService extends Service {
/**
* The Intent action that a CustomTabsService must respond to.
*/
public static final String ACTION_CUSTOM_TABS_CONNECTION =
"android.support.customtabs.action.CustomTabsService";
/**
* For {@link CustomTabsService#mayLaunchUrl} calls that wants to specify more than one url,
* this key can be used with {@link Bundle#putParcelable(String, android.os.Parcelable)}
* to insert a new url to each bundle inside list of bundles.
*/
public static final String KEY_URL =
"android.support.customtabs.otherurls.URL";
private final Map<IBinder, DeathRecipient> mDeathRecipientMap = new ArrayMap<>();
private ICustomTabsService.Stub mBinder = new ICustomTabsService.Stub() {
@Override
public boolean warmup(long flags) {
return CustomTabsService.this.warmup(flags);
}
@Override
public boolean newSession(ICustomTabsCallback callback) {
final CustomTabsSessionToken sessionToken = new CustomTabsSessionToken(callback);
try {
DeathRecipient deathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
cleanUpSession(sessionToken);
}
};
synchronized (mDeathRecipientMap) {
callback.asBinder().linkToDeath(deathRecipient, 0);
mDeathRecipientMap.put(callback.asBinder(), deathRecipient);
}
return CustomTabsService.this.newSession(sessionToken);
} catch (RemoteException e) {
return false;
}
}
@Override
public boolean mayLaunchUrl(ICustomTabsCallback callback, Uri url,
Bundle extras, List<Bundle> otherLikelyBundles) {
return CustomTabsService.this.mayLaunchUrl(
new CustomTabsSessionToken(callback), url, extras, otherLikelyBundles);
}
@Override
public Bundle extraCommand(String commandName, Bundle args) {
return CustomTabsService.this.extraCommand(commandName, args);
}
@Override
public boolean updateVisuals(ICustomTabsCallback callback, Bundle bundle) {
return CustomTabsService.this.updateVisuals(
new CustomTabsSessionToken(callback), bundle);
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* Called when the client side {@link IBinder} for this {@link CustomTabsSessionToken} is dead.
* Can also be used to clean up {@link DeathRecipient} instances allocated for the given token.
* @param sessionToken The session token for which the {@link DeathRecipient} call has been
* received.
* @return Whether the clean up was successful. Multiple calls with two tokens holdings the
* same binder will return false.
*/
protected boolean cleanUpSession(CustomTabsSessionToken sessionToken) {
try {
synchronized (mDeathRecipientMap) {
IBinder binder = sessionToken.getCallbackBinder();
DeathRecipient deathRecipient =
mDeathRecipientMap.get(binder);
binder.unlinkToDeath(deathRecipient, 0);
mDeathRecipientMap.remove(binder);
}
} catch (NoSuchElementException e) {
return false;
}
return true;
}
/**
* Warms up the browser process asynchronously.
*
* @param flags Reserved for future use.
* @return Whether warmup was/had been completed successfully. Multiple successful
* calls will return true.
*/
protected abstract boolean warmup(long flags);
/**
* Creates a new session through an ICustomTabsService with the optional callback. This session
* can be used to associate any related communication through the service with an intent and
* then later with a Custom Tab. The client can then send later service calls or intents to
* through same session-intent-Custom Tab association.
* @param sessionToken Session token to be used as a unique identifier. This also has access
* to the {@link CustomTabsCallback} passed from the client side through
* {@link CustomTabsSessionToken#getCallback()}.
* @return Whether a new session was successfully created.
*/
protected abstract boolean newSession(CustomTabsSessionToken sessionToken);
/**
* Tells the browser of a likely future navigation to a URL.
*
* The method {@link CustomTabsService#warmup(long)} has to be called beforehand.
* The most likely URL has to be specified explicitly. Optionally, a list of
* other likely URLs can be provided. They are treated as less likely than
* the first one, and have to be sorted in decreasing priority order. These
* additional URLs may be ignored.
* All previous calls to this method will be deprioritized.
*
* @param sessionToken The unique identifier for the session. Can not be null.
* @param url Most likely URL.
* @param extras Reserved for future use.
* @param otherLikelyBundles Other likely destinations, sorted in decreasing
* likelihood order. Each Bundle has to provide a url.
* @return Whether the call was successful.
*/
protected abstract boolean mayLaunchUrl(CustomTabsSessionToken sessionToken, Uri url,
Bundle extras, List<Bundle> otherLikelyBundles);
/**
* Unsupported commands that may be provided by the implementation.
*
* <p>
* <strong>Note:</strong>Clients should <strong>never</strong> rely on this method to have a
* defined behavior, as it is entirely implementation-defined and not supported.
*
* <p> This call can be used by implementations to add extra commands, for testing or
* experimental purposes.
*
* @param commandName Name of the extra command to execute.
* @param args Arguments for the command
* @return The result {@link Bundle}, or null.
*/
protected abstract Bundle extraCommand(String commandName, Bundle args);
/**
* Updates the visuals of custom tabs for the given session. Will only succeed if the given
* session matches the currently active one.
* @param sessionToken The currently active session that the custom tab belongs to.
* @param bundle The action button configuration bundle. This bundle should be constructed
* with the same structure in {@link CustomTabsIntent.Builder}.
* @return Whether the operation was successful.
*/
protected abstract boolean updateVisuals(CustomTabsSessionToken sessionToken,
Bundle bundle);
}