blob: 2ecf317c2d0996a3e8f86dc45c99a9d7982d0433 [file] [log] [blame]
John Grossman37237832012-01-12 11:05:37 -08001/*
2 * Copyright (C) 2012 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 */
16package android.os;
17
John Grossman37237832012-01-12 11:05:37 -080018import java.net.InetSocketAddress;
19import java.util.NoSuchElementException;
John Grossman37237832012-01-12 11:05:37 -080020import android.os.Binder;
21import android.os.CommonTimeUtils;
22import android.os.IBinder;
23import android.os.Parcel;
24import android.os.RemoteException;
25import android.os.ServiceManager;
John Grossman37237832012-01-12 11:05:37 -080026
27/**
28 * Used for accessing the android common time service's common clock and receiving notifications
29 * about common time synchronization status changes.
30 * @hide
31 */
32public class CommonClock {
33 /**
34 * Sentinel value returned by {@link #getTime()} and {@link #getEstimatedError()} when the
35 * common time service is not able to determine the current common time due to a lack of
36 * synchronization.
37 */
38 public static final long TIME_NOT_SYNCED = -1;
39
40 /**
41 * Sentinel value returned by {@link #getTimelineId()} when the common time service is not
42 * currently synced to any timeline.
43 */
44 public static final long INVALID_TIMELINE_ID = 0;
45
46 /**
47 * Sentinel value returned by {@link #getEstimatedError()} when the common time service is not
48 * currently synced to any timeline.
49 */
50 public static final int ERROR_ESTIMATE_UNKNOWN = 0x7FFFFFFF;
51
52 /**
53 * Value used by {@link #getState()} to indicate that there was an internal error while
54 * attempting to determine the state of the common time service.
55 */
56 public static final int STATE_INVALID = -1;
57
58 /**
59 * Value used by {@link #getState()} to indicate that the common time service is in its initial
60 * state and attempting to find the current timeline master, if any. The service will
61 * transition to either {@link #STATE_CLIENT} if it finds an active master, or to
62 * {@link #STATE_MASTER} if no active master is found and this client becomes the master of a
63 * new timeline.
64 */
65 public static final int STATE_INITIAL = 0;
66
67 /**
68 * Value used by {@link #getState()} to indicate that the common time service is in its client
69 * state and is synchronizing its time to a different timeline master on the network.
70 */
71 public static final int STATE_CLIENT = 1;
72
73 /**
74 * Value used by {@link #getState()} to indicate that the common time service is in its master
75 * state and is serving as the timeline master for other common time service clients on the
76 * network.
77 */
78 public static final int STATE_MASTER = 2;
79
80 /**
81 * Value used by {@link #getState()} to indicate that the common time service is in its Ronin
82 * state. Common time service instances in the client state enter the Ronin state after their
83 * timeline master becomes unreachable on the network. Common time services who enter the Ronin
84 * state will begin a new master election for the timeline they were recently clients of. As
85 * clients detect they are not the winner and drop out of the election, they will transition to
86 * the {@link #STATE_WAIT_FOR_ELECTION} state. When there is only one client remaining in the
87 * election, it will assume ownership of the timeline and transition to the
88 * {@link #STATE_MASTER} state. During the election, all clients will allow their timeline to
89 * drift without applying correction.
90 */
91 public static final int STATE_RONIN = 3;
92
93 /**
94 * Value used by {@link #getState()} to indicate that the common time service is waiting for a
95 * master election to conclude and for the new master to announce itself before transitioning to
96 * the {@link #STATE_CLIENT} state. If no new master announces itself within the timeout
97 * threshold, the time service will transition back to the {@link #STATE_RONIN} state in order
98 * to restart the election.
99 */
100 public static final int STATE_WAIT_FOR_ELECTION = 4;
101
102 /**
103 * Name of the underlying native binder service
104 */
105 public static final String SERVICE_NAME = "common_time.clock";
106
107 /**
108 * Class constructor.
109 * @throws android.os.RemoteException
110 */
111 public CommonClock()
112 throws RemoteException {
113 mRemote = ServiceManager.getService(SERVICE_NAME);
114 if (null == mRemote)
115 throw new RemoteException();
116
117 mInterfaceDesc = mRemote.getInterfaceDescriptor();
118 mUtils = new CommonTimeUtils(mRemote, mInterfaceDesc);
119 mRemote.linkToDeath(mDeathHandler, 0);
120 registerTimelineChangeListener();
121 }
122
123 /**
124 * Handy class factory method.
125 */
126 static public CommonClock create() {
127 CommonClock retVal;
128
129 try {
130 retVal = new CommonClock();
131 }
132 catch (RemoteException e) {
133 retVal = null;
134 }
135
136 return retVal;
137 }
138
139 /**
140 * Release all native resources held by this {@link android.os.CommonClock} instance. Once
141 * resources have been released, the {@link android.os.CommonClock} instance is disconnected from
142 * the native service and will throw a {@link android.os.RemoteException} if any of its
143 * methods are called. Clients should always call release on their client instances before
144 * releasing their last Java reference to the instance. Failure to do this will cause
145 * non-deterministic native resource reclamation and may cause the common time service to remain
146 * active on the network for longer than it should.
147 */
148 public void release() {
149 unregisterTimelineChangeListener();
150 if (null != mRemote) {
151 try {
152 mRemote.unlinkToDeath(mDeathHandler, 0);
153 }
154 catch (NoSuchElementException e) { }
155 mRemote = null;
156 }
157 mUtils = null;
158 }
159
160 /**
161 * Gets the common clock's current time.
162 *
163 * @return a signed 64-bit value representing the current common time in microseconds, or the
164 * special value {@link #TIME_NOT_SYNCED} if the common time service is currently not
165 * synchronized.
166 * @throws android.os.RemoteException
167 */
168 public long getTime()
169 throws RemoteException {
170 throwOnDeadServer();
171 return mUtils.transactGetLong(METHOD_GET_COMMON_TIME, TIME_NOT_SYNCED);
172 }
173
174 /**
175 * Gets the current estimation of common clock's synchronization accuracy from the common time
176 * service.
177 *
178 * @return a signed 32-bit value representing the common time service's estimation of
179 * synchronization accuracy in microseconds, or the special value
180 * {@link #ERROR_ESTIMATE_UNKNOWN} if the common time service is currently not synchronized.
181 * Negative values indicate that the local server estimates that the nominal common time is
182 * behind the local server's time (in other words, the local clock is running fast) Positive
183 * values indicate that the local server estimates that the nominal common time is ahead of the
184 * local server's time (in other words, the local clock is running slow)
185 * @throws android.os.RemoteException
186 */
187 public int getEstimatedError()
188 throws RemoteException {
189 throwOnDeadServer();
190 return mUtils.transactGetInt(METHOD_GET_ESTIMATED_ERROR, ERROR_ESTIMATE_UNKNOWN);
191 }
192
193 /**
194 * Gets the ID of the timeline the common time service is currently synchronizing its clock to.
195 *
196 * @return a long representing the unique ID of the timeline the common time service is
197 * currently synchronizing with, or {@link #INVALID_TIMELINE_ID} if the common time service is
198 * currently not synchronized.
199 * @throws android.os.RemoteException
200 */
201 public long getTimelineId()
202 throws RemoteException {
203 throwOnDeadServer();
204 return mUtils.transactGetLong(METHOD_GET_TIMELINE_ID, INVALID_TIMELINE_ID);
205 }
206
207 /**
208 * Gets the current state of this clock's common time service in the the master election
209 * algorithm.
210 *
211 * @return a integer indicating the current state of the this clock's common time service in the
212 * master election algorithm or {@link #STATE_INVALID} if there is an internal error.
213 * @throws android.os.RemoteException
214 */
215 public int getState()
216 throws RemoteException {
217 throwOnDeadServer();
218 return mUtils.transactGetInt(METHOD_GET_STATE, STATE_INVALID);
219 }
220
221 /**
222 * Gets the IP address and UDP port of the current timeline master.
223 *
224 * @return an InetSocketAddress containing the IP address and UDP port of the current timeline
225 * master, or null if there is no current master.
226 * @throws android.os.RemoteException
227 */
228 public InetSocketAddress getMasterAddr()
229 throws RemoteException {
230 throwOnDeadServer();
231 return mUtils.transactGetSockaddr(METHOD_GET_MASTER_ADDRESS);
232 }
233
234 /**
235 * The OnTimelineChangedListener interface defines a method called by the
236 * {@link android.os.CommonClock} instance to indicate that the time synchronization service has
237 * either synchronized with a new timeline, or is no longer a member of any timeline. The
238 * client application can implement this interface and register the listener with the
239 * {@link #setTimelineChangedListener(OnTimelineChangedListener)} method.
240 */
241 public interface OnTimelineChangedListener {
242 /**
243 * Method called when the time service's timeline has changed.
244 *
245 * @param newTimelineId a long which uniquely identifies the timeline the time
246 * synchronization service is now a member of, or {@link #INVALID_TIMELINE_ID} if the the
247 * service is not synchronized to any timeline.
248 */
249 void onTimelineChanged(long newTimelineId);
250 }
251
252 /**
253 * Registers an OnTimelineChangedListener interface.
254 * <p>Call this method with a null listener to stop receiving server death notifications.
255 */
256 public void setTimelineChangedListener(OnTimelineChangedListener listener) {
257 synchronized (mListenerLock) {
258 mTimelineChangedListener = listener;
259 }
260 }
261
262 /**
263 * The OnServerDiedListener interface defines a method called by the
264 * {@link android.os.CommonClock} instance to indicate that the connection to the native media
265 * server has been broken and that the {@link android.os.CommonClock} instance will need to be
266 * released and re-created. The client application can implement this interface and register
267 * the listener with the {@link #setServerDiedListener(OnServerDiedListener)} method.
268 */
269 public interface OnServerDiedListener {
270 /**
271 * Method called when the native media server has died. <p>If the native common time
272 * service encounters a fatal error and needs to restart, the binder connection from the
273 * {@link android.os.CommonClock} instance to the common time service will be broken. To
274 * restore functionality, clients should {@link #release()} their old visualizer and create
275 * a new instance.
276 */
277 void onServerDied();
278 }
279
280 /**
281 * Registers an OnServerDiedListener interface.
282 * <p>Call this method with a null listener to stop receiving server death notifications.
283 */
284 public void setServerDiedListener(OnServerDiedListener listener) {
285 synchronized (mListenerLock) {
286 mServerDiedListener = listener;
287 }
288 }
289
290 protected void finalize() throws Throwable { release(); }
291
292 private void throwOnDeadServer() throws RemoteException {
293 if ((null == mRemote) || (null == mUtils))
294 throw new RemoteException();
295 }
296
297 private final Object mListenerLock = new Object();
298 private OnTimelineChangedListener mTimelineChangedListener = null;
299 private OnServerDiedListener mServerDiedListener = null;
300
301 private IBinder mRemote = null;
302 private String mInterfaceDesc = "";
303 private CommonTimeUtils mUtils;
304
305 private IBinder.DeathRecipient mDeathHandler = new IBinder.DeathRecipient() {
306 public void binderDied() {
307 synchronized (mListenerLock) {
308 if (null != mServerDiedListener)
309 mServerDiedListener.onServerDied();
310 }
311 }
312 };
313
314 private class TimelineChangedListener extends Binder {
315 @Override
316 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
317 throws RemoteException {
318 switch (code) {
319 case METHOD_CBK_ON_TIMELINE_CHANGED:
320 data.enforceInterface(DESCRIPTOR);
321 long timelineId = data.readLong();
322 synchronized (mListenerLock) {
323 if (null != mTimelineChangedListener)
324 mTimelineChangedListener.onTimelineChanged(timelineId);
325 }
326 return true;
327 }
328
329 return super.onTransact(code, data, reply, flags);
330 }
331
332 private static final String DESCRIPTOR = "android.os.ICommonClockListener";
333 };
334
335 private TimelineChangedListener mCallbackTgt = null;
336
337 private void registerTimelineChangeListener() throws RemoteException {
338 if (null != mCallbackTgt)
339 return;
340
341 boolean success = false;
342 android.os.Parcel data = android.os.Parcel.obtain();
343 android.os.Parcel reply = android.os.Parcel.obtain();
344 mCallbackTgt = new TimelineChangedListener();
345
346 try {
347 data.writeInterfaceToken(mInterfaceDesc);
348 data.writeStrongBinder(mCallbackTgt);
349 mRemote.transact(METHOD_REGISTER_LISTENER, data, reply, 0);
350 success = (0 == reply.readInt());
351 }
352 catch (RemoteException e) {
353 success = false;
354 }
355 finally {
356 reply.recycle();
357 data.recycle();
358 }
359
360 // Did we catch a remote exception or fail to register our callback target? If so, our
361 // object must already be dead (or be as good as dead). Clear out all of our state so that
362 // our other methods will properly indicate a dead object.
363 if (!success) {
364 mCallbackTgt = null;
365 mRemote = null;
366 mUtils = null;
367 }
368 }
369
370 private void unregisterTimelineChangeListener() {
371 if (null == mCallbackTgt)
372 return;
373
374 android.os.Parcel data = android.os.Parcel.obtain();
375 android.os.Parcel reply = android.os.Parcel.obtain();
376
377 try {
378 data.writeInterfaceToken(mInterfaceDesc);
379 data.writeStrongBinder(mCallbackTgt);
380 mRemote.transact(METHOD_UNREGISTER_LISTENER, data, reply, 0);
381 }
382 catch (RemoteException e) { }
383 finally {
384 reply.recycle();
385 data.recycle();
386 mCallbackTgt = null;
387 }
388 }
389
390 private static final int METHOD_IS_COMMON_TIME_VALID = IBinder.FIRST_CALL_TRANSACTION;
391 private static final int METHOD_COMMON_TIME_TO_LOCAL_TIME = METHOD_IS_COMMON_TIME_VALID + 1;
392 private static final int METHOD_LOCAL_TIME_TO_COMMON_TIME = METHOD_COMMON_TIME_TO_LOCAL_TIME + 1;
393 private static final int METHOD_GET_COMMON_TIME = METHOD_LOCAL_TIME_TO_COMMON_TIME + 1;
394 private static final int METHOD_GET_COMMON_FREQ = METHOD_GET_COMMON_TIME + 1;
395 private static final int METHOD_GET_LOCAL_TIME = METHOD_GET_COMMON_FREQ + 1;
396 private static final int METHOD_GET_LOCAL_FREQ = METHOD_GET_LOCAL_TIME + 1;
397 private static final int METHOD_GET_ESTIMATED_ERROR = METHOD_GET_LOCAL_FREQ + 1;
398 private static final int METHOD_GET_TIMELINE_ID = METHOD_GET_ESTIMATED_ERROR + 1;
399 private static final int METHOD_GET_STATE = METHOD_GET_TIMELINE_ID + 1;
400 private static final int METHOD_GET_MASTER_ADDRESS = METHOD_GET_STATE + 1;
401 private static final int METHOD_REGISTER_LISTENER = METHOD_GET_MASTER_ADDRESS + 1;
402 private static final int METHOD_UNREGISTER_LISTENER = METHOD_REGISTER_LISTENER + 1;
403
404 private static final int METHOD_CBK_ON_TIMELINE_CHANGED = IBinder.FIRST_CALL_TRANSACTION;
405}