| /* |
| * Copyright 2011 Google Inc. |
| * |
| * 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 com.google.ipc.invalidation.ticl.android; |
| |
| import com.google.ipc.invalidation.external.client.SystemResources.Logger; |
| import com.google.ipc.invalidation.external.client.android.service.AndroidClientException; |
| import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; |
| import com.google.ipc.invalidation.external.client.android.service.Response.Status; |
| import com.google.ipc.invalidation.ticl.InvalidationClientCore; |
| import com.google.ipc.invalidation.util.TypedUtil; |
| import com.google.protos.ipc.invalidation.ClientProtocol.ClientConfigP; |
| |
| import android.accounts.Account; |
| import android.content.Context; |
| import android.content.Intent; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| |
| /** |
| * Manages active client instances for the Android invalidation service. The client manager contains |
| * the code to create, persist, load, and lookup client instances, as well as handling the |
| * propagation of any C2DM registration notifications to active clients. |
| * |
| */ |
| public class AndroidClientManager { |
| |
| /** Logger */ |
| private static final Logger logger = AndroidLogger.forTag("InvClientManager"); |
| |
| /** |
| * The client configuration used creating new invalidation client instances. This is normally |
| * a constant but may be varied for testing. |
| */ |
| private static ClientConfigP clientConfig = InvalidationClientCore.createConfig().build(); |
| |
| /** The invalidation service associated with this manager */ |
| private final AndroidInvalidationService service; |
| |
| /** |
| * When set, this registration ID is used rather than the ID returned by |
| * {@code GCMRegistrar.getRegistrationId()}. |
| */ |
| private static String registrationIdForTest; |
| |
| /** A map from client key to client proxy instances for in-memory client instances */ |
| private final Map<String, AndroidClientProxy> clientMap = |
| new HashMap<String, AndroidClientProxy>(); |
| |
| /** All client manager operations are synchronized on this lock */ |
| private final Object lock = new Object(); |
| |
| /** Creates a new client manager instance associated with the provided service */ |
| AndroidClientManager(AndroidInvalidationService service) { |
| this.service = service; |
| } |
| |
| /** |
| * Returns the number of managed clients. |
| */ |
| int getClientCount() { |
| synchronized (lock) { |
| return clientMap.size(); |
| } |
| } |
| |
| /** |
| * Creates a new Android client proxy with the provided attributes. Before creating, will check to |
| * see if there is an existing client with attributes that match and return it if found. If there |
| * is an existing client with the same key but attributes that do not match, an exception will be |
| * thrown. If no client with a matching key exists, a new client proxy will be created and |
| * returned. |
| * |
| * @param clientKey key that uniquely identifies the client on the device. |
| * @param clientType client type. |
| * @param account user account associated with the client. |
| * @param authType authentication type for the client. |
| * @param eventIntent intent that can be used to bind to an event listener for the client. |
| * @return an android invalidation client instance representing the client. |
| */ |
| AndroidClientProxy create(String clientKey, int clientType, Account account, String authType, |
| Intent eventIntent) { |
| synchronized (lock) { |
| |
| // First check to see if an existing client is found |
| AndroidClientProxy proxy = lookup(clientKey); |
| if (proxy != null) { |
| if (!proxy.getAccount().equals(account) || !proxy.getAuthType().equals(authType)) { |
| throw new AndroidClientException( |
| Status.INVALID_CLIENT, "Account does not match existing client"); |
| } |
| return proxy; |
| } |
| |
| // If not found, create a new client proxy instance to represent the client. |
| AndroidStorage store = createAndroidStorage(service, clientKey); |
| store.create(clientType, account, authType, eventIntent); |
| proxy = new AndroidClientProxy(service, store, clientConfig); |
| if (registrationIdForTest != null) { |
| proxy.getChannel().setRegistrationIdForTest(registrationIdForTest); |
| } |
| clientMap.put(clientKey, proxy); |
| logger.fine("Client %s created", clientKey); |
| return proxy; |
| } |
| } |
| |
| /** |
| * Retrieves an existing client that matches the provided key, loading it if necessary. If no |
| * matching client can be found, an exception is thrown. |
| * |
| * @param clientKey the client key for the client to retrieve. |
| * @return the matching client instance |
| */ |
| AndroidClientProxy get(String clientKey) { |
| synchronized (lock) { |
| return lookup(clientKey); |
| } |
| } |
| |
| /** |
| * Removes any client proxy instance associated with the provided key from memory but leaves the |
| * instance persisted. The client may subsequently be loaded again by calling {@code #get}. |
| * |
| * @param clientKey the client key of the instance to remove from memory. |
| */ |
| void remove(String clientKey) { |
| synchronized (lock) { |
| // Remove the proxy from the managed set and release any associated resources |
| AndroidClientProxy proxy = clientMap.remove(clientKey); |
| if (proxy != null) { |
| proxy.release(); |
| } |
| } |
| } |
| |
| /** |
| * Looks up the client proxy instance associated with the provided key and returns it (or {@code |
| * null} if not found). |
| * |
| * @param clientKey the client key to look up |
| * @return the client instance or {@code null}. |
| */ |
| |
| AndroidClientProxy lookup(String clientKey) { |
| synchronized (lock) { |
| // See if the client is already resident in memory |
| AndroidClientProxy client = clientMap.get(clientKey); |
| if (client == null) { |
| // Attempt to load the client from the store |
| AndroidStorage storage = createAndroidStorage(service, clientKey); |
| if (storage.load()) { |
| logger.fine("Client %s loaded from disk", clientKey); |
| client = new AndroidClientProxy(service, storage, clientConfig); |
| clientMap.put(clientKey, client); |
| } |
| } |
| return client; |
| } |
| } |
| |
| /** |
| * Sets the GCM registration ID that should be used for all managed clients (new and existing). |
| */ |
| void informRegistrationIdChanged() { |
| synchronized (lock) { |
| // Propagate the value to all existing clients |
| for (AndroidClientProxy proxy : clientMap.values()) { |
| proxy.getChannel().informRegistrationIdChanged(); |
| } |
| } |
| } |
| |
| /** |
| * Releases all managed clients and drops them from the managed set. |
| */ |
| void releaseAll() { |
| synchronized (lock) { |
| for (AndroidClientProxy clientProxy : clientMap.values()) { |
| clientProxy.release(); |
| } |
| clientMap.clear(); |
| } |
| } |
| |
| /** |
| * Returns an android storage instance for managing client state. |
| */ |
| |
| protected AndroidStorage createAndroidStorage(Context context, String clientKey) { |
| synchronized (lock) { |
| return new AndroidStorage(context, clientKey); |
| } |
| } |
| |
| |
| static ClientConfigP setConfigForTest(ClientConfigP newConfig) { |
| logger.info("Setting client configuration: %s", newConfig); |
| ClientConfigP currentConfig = clientConfig; |
| clientConfig = newConfig; |
| return clientConfig; |
| } |
| |
| |
| public static void setRegistrationIdForTest(String registrationIdForTest) { |
| AndroidClientManager.registrationIdForTest = registrationIdForTest; |
| } |
| |
| /** Returns whether all loaded clients are stopped. */ |
| public boolean areAllClientsStopped() { |
| synchronized (lock) { |
| for (AndroidClientProxy proxy : clientMap.values()) { |
| if (proxy.isStarted()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** Returns whether the client with key {@code clientKey} is in memory. */ |
| public boolean isLoadedForTest(String clientKey) { |
| synchronized (lock) { |
| return TypedUtil.containsKey(clientMap, clientKey); |
| } |
| } |
| } |