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