Upstream version 10.38.222.0
[platform/framework/web/crosswalk.git] / src / third_party / cacheinvalidation / src / java / com / google / ipc / invalidation / external / client / android / service / ServiceBinder.java
1 /*
2  * Copyright 2011 Google Inc.
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
17 package com.google.ipc.invalidation.external.client.android.service;
18
19 import com.google.common.base.Preconditions;
20 import com.google.ipc.invalidation.external.client.SystemResources.Logger;
21
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.ServiceConnection;
26 import android.os.IBinder;
27
28 import java.util.LinkedList;
29 import java.util.Queue;
30
31
32 /**
33  * Abstract base class that assists in making connections to a bound service. Subclasses can define
34  * a concrete binding to a particular bound service interface by binding to an explicit type on
35  * declaration, providing a public constructor, and providing an implementation of the
36  * {@link #asInterface} method.
37  * <p>
38  * This class has two main methods: {@link #runWhenBound} and {@link #release()}.
39  * {@code runWhenBound} submits a {@code receiver} to be invoked once the service is bound,
40  * initiating a bind if necessary. {@code release} releases the binding if one exists.
41  * <p>
42  * Interestingly, invocations of receivers passed to {@code runWhenBound} and calls to
43  * {@code release} will be executed in program order. I.e., a call to runWhenBound followed by
44  * a call to release will result in the receiver passed to runWhenBound being invoked before the
45  * release, even if the binder had to wait for the service to be bound.
46  * <p>
47  * It is legal to call runWhenBound after a call to release.
48  *
49  * @param <BoundService> the bound service interface associated with the binder.
50  *
51  */
52 public abstract class ServiceBinder<BoundService> {
53   /**
54    * Interface for a work unit to be executed when the service is bound.
55    * @param <ServiceType> the bound service interface type
56    */
57   public interface BoundWork<ServiceType> {
58     /** Function called with the bound service once the service is bound. */
59     void run(ServiceType service);
60   }
61   /** Logger */
62   private static final Logger logger = AndroidLogger.forTag("InvServiceBinder");
63
64   /** Intent that can be used to bind to the service */
65   private final Intent serviceIntent;
66
67   /** Class that represents the bound service interface */
68   private final Class<BoundService> serviceClass;
69
70   /** Name of the component that implements the service interface. */
71   private final String componentClassName;
72
73   /** Work waiting to be run when the service becomes bound. */
74   private final Queue<BoundWork<BoundService>> pendingWork =
75       new LinkedList<BoundWork<BoundService>>();
76
77   /** Used to synchronize. */
78   private final Object lock = new Object();
79
80   /** Bound service instance held by the binder or {@code null} if not bound. */
81   private BoundService serviceInstance = null;
82
83   /** Context to use when binding and unbinding. */
84   private final Context context;
85
86   /** Whether bindService has been called. */
87   private boolean hasCalledBind = false;
88
89   /** Whether we are currently executing an event from the queue. */
90   private boolean queueHandlingInProgress = false;
91
92   /** Number of times {@link #startBind()} has been called, for tests. */
93   private int numStartBindForTest = 0;
94
95   /**
96    * Service connection implementation that handles connection/disconnection
97    * events for the binder.
98    */
99   private final ServiceConnection serviceConnection = new ServiceConnection() {
100
101     @Override
102     public void onServiceConnected(ComponentName serviceName, IBinder binder) {
103       logger.fine("onServiceConnected: %s", serviceName);
104       synchronized (lock) {
105         // Once the service is bound, save it and run any work that was waiting for it.
106         serviceInstance = asInterface(binder);
107         handleQueue();
108       }
109     }
110
111     @Override
112     public void onServiceDisconnected(ComponentName serviceName) {
113       logger.fine("onServiceDisconnected: %s", serviceClass);
114       synchronized (lock) {
115         serviceInstance = null;
116       }
117     }
118   };
119
120   /**
121    * Constructs a new ServiceBinder that uses the provided intent to bind to the service of the
122    * specific type. Subclasses should expose a public constructor that passes the appropriate intent
123    * and type into this constructor.
124    *
125    * @param context context to use for (un)binding.
126    * @param serviceIntent intent that can be used to connect to the bound service.
127    * @param serviceClass interface exposed by the bound service.
128    * @param componentClassName name of component implementing the bound service. If non-null, then
129    *        an explicit binding to the named component within the same class is guaranteed.
130    */
131   protected ServiceBinder(Context context, Intent serviceIntent, Class<BoundService> serviceClass,
132       String componentClassName) {
133     this.context = Preconditions.checkNotNull(context);
134     this.serviceIntent = Preconditions.checkNotNull(serviceIntent);
135     this.serviceClass = Preconditions.checkNotNull(serviceClass);
136     this.componentClassName = componentClassName;
137   }
138
139   /** Returns the intent used to bind to the service */
140   public Intent getIntent() {
141     Intent bindIntent;
142     if (componentClassName == null) {
143       return serviceIntent;
144     }
145     bindIntent = new Intent(serviceIntent);
146     bindIntent.setClassName(context, componentClassName);
147     return bindIntent;
148   }
149
150   /** Runs {@code receiver} when the service becomes bound. */
151   public void runWhenBound(BoundWork<BoundService> receiver) {
152     synchronized (lock) {
153       pendingWork.add(receiver);
154       handleQueue();
155     }
156   }
157
158   /** Unbinds the service associated with the binder. No-op if not bound. */
159   public void release() {
160     synchronized (lock) {
161       if (!hasCalledBind) {
162         logger.fine("Release is a no-op since not bound: %s", serviceClass);
163         return;
164       }
165       // We need to release using a runWhenBound to avoid having a release jump ahead of
166       // pending work waiting for a bind (i.e., to preserve program order).
167       runWhenBound(new BoundWork<BoundService>() {
168         @Override
169         public void run(BoundService ignored) {
170           synchronized (lock) {
171             // Do the unbind.
172             logger.fine("Unbinding %s from %s", serviceClass, serviceInstance);
173             try {
174               context.unbindService(serviceConnection);
175             } catch (IllegalArgumentException exception) {
176               logger.fine("Exception unbinding from %s: %s", serviceClass,
177                   exception.getMessage());
178             }
179             // Clear the now-stale reference and reset hasCalledBind so that we will initiate a
180             // bind on a subsequent call to runWhenBound.
181             serviceInstance = null;
182             hasCalledBind = false;
183
184             // This isn't necessarily wrong, but it's slightly odd.
185             if (!pendingWork.isEmpty()) {
186               logger.info("Still have %s work items in release of %s", pendingWork.size(),
187                   serviceClass);
188             }
189           }
190         }
191       });
192     }
193   }
194
195   /**
196    * Returns {@code true} if the service binder is currently connected to the
197    * bound service.
198    */
199   public boolean isBoundForTest() {
200     synchronized (lock) {
201       return hasCalledBind;
202     }
203   }
204
205   @Override
206   public String toString() {
207     synchronized (lock) {
208       return this.getClass().getSimpleName() + "[" + serviceIntent + "]";
209     }
210   }
211
212   /** Returns the number of times {@code startBind} has been called, for tests. */
213   public int getNumStartBindForTest() {
214     return numStartBindForTest;
215   }
216
217   /**
218    * Recursively process the queue of {@link #pendingWork}. Initiates a bind to the service if
219    * required. Else, if the service instance is available, removes the head of the queue and invokes
220    * it with the service instance.
221    * <p>
222    * Note: this function differs from {@link #runWhenBound} only in that {@code runWhenBound}
223    * enqueues into {@link #pendingWork}.
224    */
225   private void handleQueue() {
226     if (queueHandlingInProgress) {
227       // Someone called back into runWhenBound from receiver.accept. We don't want to start another
228       // recursive call, since we're already handling the queue.
229       return;
230     }
231     if (pendingWork.isEmpty()) {
232       // Recursive base case.
233       return;
234     }
235     if (!hasCalledBind) {
236       // Initiate a bind if not bound.
237       Preconditions.checkState(serviceInstance == null,
238           "Bind not called but service instance is set: %s", serviceClass);
239
240       // May fail, but does its own logging. If it fails, we will never dispatch the work in the
241       // queue, but it's unclear what we can do in this case other than log.
242       startBind();
243       return;
244     }
245     if (serviceInstance == null) {
246       // Wait for the service to become bound if it is not yet available and a bind is in progress.
247       Preconditions.checkState(hasCalledBind, "No service instance and not waiting for bind: %s",
248           serviceClass);
249       return;
250     }
251
252     // Service is bound and available. Remove and invoke the head of the queue, then recurse to
253     // process the rest. We recurse because the head of the queue may have been a release(), which
254     // would have unbound the service, and we would need to reinvoke the binding code.
255     BoundWork<BoundService> work = pendingWork.remove();
256     queueHandlingInProgress = true;
257     work.run(serviceInstance);
258     queueHandlingInProgress = false;
259     handleQueue();
260   }
261
262   /**
263    * Binds to the service associated with the binder within the provided context. Returns whether
264    * binding was successfully initiated.
265    */
266   private boolean startBind() {
267     Preconditions.checkState(!hasCalledBind, "Bind already called for %s", serviceClass);
268     ++numStartBindForTest;
269     Intent bindIntent = getIntent();
270     if (!context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE)) {
271       logger.severe("Unable to bind to service: %s", bindIntent);
272       return false;
273     }
274     hasCalledBind = true;
275     return true;
276   }
277
278   /** Returns a bound service stub of the expected type. */
279   protected abstract BoundService asInterface(IBinder binder);
280 }