1/*
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
18import java.net.InetSocketAddress;
19import java.util.NoSuchElementException;
20import android.os.Binder;
21import android.os.CommonTimeUtils;
22import android.os.IBinder;
23import android.os.Parcel;
24import android.os.RemoteException;
25import android.os.ServiceManager;
26
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}
406