2 * Copyright 2011 Google Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.google.ipc.invalidation.ticl.android;
19 import com.google.ipc.invalidation.external.client.SystemResources.Logger;
20 import com.google.ipc.invalidation.external.client.android.service.AndroidClientException;
21 import com.google.ipc.invalidation.external.client.android.service.AndroidLogger;
22 import com.google.ipc.invalidation.external.client.android.service.Response.Status;
23 import com.google.ipc.invalidation.ticl.InvalidationClientCore;
24 import com.google.ipc.invalidation.util.TypedUtil;
25 import com.google.protos.ipc.invalidation.ClientProtocol.ClientConfigP;
27 import android.accounts.Account;
28 import android.content.Context;
29 import android.content.Intent;
31 import java.util.HashMap;
36 * Manages active client instances for the Android invalidation service. The client manager contains
37 * the code to create, persist, load, and lookup client instances, as well as handling the
38 * propagation of any C2DM registration notifications to active clients.
41 public class AndroidClientManager {
44 private static final Logger logger = AndroidLogger.forTag("InvClientManager");
47 * The client configuration used creating new invalidation client instances. This is normally
48 * a constant but may be varied for testing.
50 private static ClientConfigP clientConfig = InvalidationClientCore.createConfig().build();
52 /** The invalidation service associated with this manager */
53 private final AndroidInvalidationService service;
56 * When set, this registration ID is used rather than the ID returned by
57 * {@code GCMRegistrar.getRegistrationId()}.
59 private static String registrationIdForTest;
61 /** A map from client key to client proxy instances for in-memory client instances */
62 private final Map<String, AndroidClientProxy> clientMap =
63 new HashMap<String, AndroidClientProxy>();
65 /** All client manager operations are synchronized on this lock */
66 private final Object lock = new Object();
68 /** Creates a new client manager instance associated with the provided service */
69 AndroidClientManager(AndroidInvalidationService service) {
70 this.service = service;
74 * Returns the number of managed clients.
76 int getClientCount() {
78 return clientMap.size();
83 * Creates a new Android client proxy with the provided attributes. Before creating, will check to
84 * see if there is an existing client with attributes that match and return it if found. If there
85 * is an existing client with the same key but attributes that do not match, an exception will be
86 * thrown. If no client with a matching key exists, a new client proxy will be created and
89 * @param clientKey key that uniquely identifies the client on the device.
90 * @param clientType client type.
91 * @param account user account associated with the client.
92 * @param authType authentication type for the client.
93 * @param eventIntent intent that can be used to bind to an event listener for the client.
94 * @return an android invalidation client instance representing the client.
96 AndroidClientProxy create(String clientKey, int clientType, Account account, String authType,
100 // First check to see if an existing client is found
101 AndroidClientProxy proxy = lookup(clientKey);
103 if (!proxy.getAccount().equals(account) || !proxy.getAuthType().equals(authType)) {
104 throw new AndroidClientException(
105 Status.INVALID_CLIENT, "Account does not match existing client");
110 // If not found, create a new client proxy instance to represent the client.
111 AndroidStorage store = createAndroidStorage(service, clientKey);
112 store.create(clientType, account, authType, eventIntent);
113 proxy = new AndroidClientProxy(service, store, clientConfig);
114 if (registrationIdForTest != null) {
115 proxy.getChannel().setRegistrationIdForTest(registrationIdForTest);
117 clientMap.put(clientKey, proxy);
118 logger.fine("Client %s created", clientKey);
124 * Retrieves an existing client that matches the provided key, loading it if necessary. If no
125 * matching client can be found, an exception is thrown.
127 * @param clientKey the client key for the client to retrieve.
128 * @return the matching client instance
130 AndroidClientProxy get(String clientKey) {
131 synchronized (lock) {
132 return lookup(clientKey);
137 * Removes any client proxy instance associated with the provided key from memory but leaves the
138 * instance persisted. The client may subsequently be loaded again by calling {@code #get}.
140 * @param clientKey the client key of the instance to remove from memory.
142 void remove(String clientKey) {
143 synchronized (lock) {
144 // Remove the proxy from the managed set and release any associated resources
145 AndroidClientProxy proxy = clientMap.remove(clientKey);
153 * Looks up the client proxy instance associated with the provided key and returns it (or {@code
154 * null} if not found).
156 * @param clientKey the client key to look up
157 * @return the client instance or {@code null}.
160 AndroidClientProxy lookup(String clientKey) {
161 synchronized (lock) {
162 // See if the client is already resident in memory
163 AndroidClientProxy client = clientMap.get(clientKey);
164 if (client == null) {
165 // Attempt to load the client from the store
166 AndroidStorage storage = createAndroidStorage(service, clientKey);
167 if (storage.load()) {
168 logger.fine("Client %s loaded from disk", clientKey);
169 client = new AndroidClientProxy(service, storage, clientConfig);
170 clientMap.put(clientKey, client);
178 * Sets the GCM registration ID that should be used for all managed clients (new and existing).
180 void informRegistrationIdChanged() {
181 synchronized (lock) {
182 // Propagate the value to all existing clients
183 for (AndroidClientProxy proxy : clientMap.values()) {
184 proxy.getChannel().informRegistrationIdChanged();
190 * Releases all managed clients and drops them from the managed set.
193 synchronized (lock) {
194 for (AndroidClientProxy clientProxy : clientMap.values()) {
195 clientProxy.release();
202 * Returns an android storage instance for managing client state.
205 protected AndroidStorage createAndroidStorage(Context context, String clientKey) {
206 synchronized (lock) {
207 return new AndroidStorage(context, clientKey);
212 static ClientConfigP setConfigForTest(ClientConfigP newConfig) {
213 logger.info("Setting client configuration: %s", newConfig);
214 ClientConfigP currentConfig = clientConfig;
215 clientConfig = newConfig;
220 public static void setRegistrationIdForTest(String registrationIdForTest) {
221 AndroidClientManager.registrationIdForTest = registrationIdForTest;
224 /** Returns whether all loaded clients are stopped. */
225 public boolean areAllClientsStopped() {
226 synchronized (lock) {
227 for (AndroidClientProxy proxy : clientMap.values()) {
228 if (proxy.isStarted()) {
236 /** Returns whether the client with key {@code clientKey} is in memory. */
237 public boolean isLoadedForTest(String clientKey) {
238 synchronized (lock) {
239 return TypedUtil.containsKey(clientMap, clientKey);