summaryrefslogtreecommitdiff
path: root/core/java/android/os/CommonClock.java
blob: 7f41c5d98e9b34a5a6a6660bdcefeb91fc721bbd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.os;

import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.util.NoSuchElementException;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.CommonTimeUtils;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import static android.system.OsConstants.*;

/**
 * Used for accessing the android common time service's common clock and receiving notifications
 * about common time synchronization status changes.
 * @hide
 */
public class CommonClock {
    /**
     * Sentinel value returned by {@link #getTime()} and {@link #getEstimatedError()} when the
     * common time service is not able to determine the current common time due to a lack of
     * synchronization.
     */
    public static final long TIME_NOT_SYNCED = -1;

    /**
     * Sentinel value returned by {@link #getTimelineId()} when the common time service is not
     * currently synced to any timeline.
     */
    public static final long INVALID_TIMELINE_ID = 0;

    /**
     * Sentinel value returned by {@link #getEstimatedError()} when the common time service is not
     * currently synced to any timeline.
     */
    public static final int ERROR_ESTIMATE_UNKNOWN = 0x7FFFFFFF;

    /**
     * Value used by {@link #getState()} to indicate that there was an internal error while
     * attempting to determine the state of the common time service.
     */
    public static final int STATE_INVALID = -1;

    /**
     * Value used by {@link #getState()} to indicate that the common time service is in its initial
     * state and attempting to find the current timeline master, if any.  The service will
     * transition to either {@link #STATE_CLIENT} if it finds an active master, or to
     * {@link #STATE_MASTER} if no active master is found and this client becomes the master of a
     * new timeline.
     */
    public static final int STATE_INITIAL = 0;

    /**
     * Value used by {@link #getState()} to indicate that the common time service is in its client
     * state and is synchronizing its time to a different timeline master on the network.
     */
    public static final int STATE_CLIENT = 1;

    /**
     * Value used by {@link #getState()} to indicate that the common time service is in its master
     * state and is serving as the timeline master for other common time service clients on the
     * network.
     */
    public static final int STATE_MASTER = 2;

    /**
     * Value used by {@link #getState()} to indicate that the common time service is in its Ronin
     * state.  Common time service instances in the client state enter the Ronin state after their
     * timeline master becomes unreachable on the network.  Common time services who enter the Ronin
     * state will begin a new master election for the timeline they were recently clients of.  As
     * clients detect they are not the winner and drop out of the election, they will transition to
     * the {@link #STATE_WAIT_FOR_ELECTION} state.  When there is only one client remaining in the
     * election, it will assume ownership of the timeline and transition to the
     * {@link #STATE_MASTER} state.  During the election, all clients will allow their timeline to
     * drift without applying correction.
     */
    public static final int STATE_RONIN = 3;

    /**
     * Value used by {@link #getState()} to indicate that the common time service is waiting for a
     * master election to conclude and for the new master to announce itself before transitioning to
     * the {@link #STATE_CLIENT} state.  If no new master announces itself within the timeout
     * threshold, the time service will transition back to the {@link #STATE_RONIN} state in order
     * to restart the election.
     */
    public static final int STATE_WAIT_FOR_ELECTION = 4;

    /**
     * Name of the underlying native binder service
     */
    public static final String SERVICE_NAME = "common_time.clock";

    /**
     * Class constructor.
     * @throws android.os.RemoteException
     */
    public CommonClock()
    throws RemoteException {
        mRemote = ServiceManager.getService(SERVICE_NAME);
        if (null == mRemote)
            throw new RemoteException();

        mInterfaceDesc = mRemote.getInterfaceDescriptor();
        mUtils = new CommonTimeUtils(mRemote, mInterfaceDesc);
        mRemote.linkToDeath(mDeathHandler, 0);
        registerTimelineChangeListener();
    }

    /**
     * Handy class factory method.
     */
    static public CommonClock create() {
        CommonClock retVal;

        try {
            retVal = new CommonClock();
        }
        catch (RemoteException e) {
            retVal = null;
        }

        return retVal;
    }

    /**
     * Release all native resources held by this {@link android.os.CommonClock} instance.  Once
     * resources have been released, the {@link android.os.CommonClock} instance is disconnected from
     * the native service and will throw a {@link android.os.RemoteException} if any of its
     * methods are called.  Clients should always call release on their client instances before
     * releasing their last Java reference to the instance.  Failure to do this will cause
     * non-deterministic native resource reclamation and may cause the common time service to remain
     * active on the network for longer than it should.
     */
    public void release() {
        unregisterTimelineChangeListener();
        if (null != mRemote) {
            try {
                mRemote.unlinkToDeath(mDeathHandler, 0);
            }
            catch (NoSuchElementException e) { }
            mRemote = null;
        }
        mUtils = null;
    }

    /**
     * Gets the common clock's current time.
     *
     * @return a signed 64-bit value representing the current common time in microseconds, or the
     * special value {@link #TIME_NOT_SYNCED} if the common time service is currently not
     * synchronized.
     * @throws android.os.RemoteException
     */
    public long getTime()
    throws RemoteException {
        throwOnDeadServer();
        return mUtils.transactGetLong(METHOD_GET_COMMON_TIME, TIME_NOT_SYNCED);
    }

    /**
     * Gets the current estimation of common clock's synchronization accuracy from the common time
     * service.
     *
     * @return a signed 32-bit value representing the common time service's estimation of
     * synchronization accuracy in microseconds, or the special value
     * {@link #ERROR_ESTIMATE_UNKNOWN} if the common time service is currently not synchronized.
     * Negative values indicate that the local server estimates that the nominal common time is
     * behind the local server's time (in other words, the local clock is running fast) Positive
     * values indicate that the local server estimates that the nominal common time is ahead of the
     * local server's time (in other words, the local clock is running slow)
     * @throws android.os.RemoteException
     */
    public int getEstimatedError()
    throws RemoteException {
        throwOnDeadServer();
        return mUtils.transactGetInt(METHOD_GET_ESTIMATED_ERROR, ERROR_ESTIMATE_UNKNOWN);
    }

    /**
     * Gets the ID of the timeline the common time service is currently synchronizing its clock to.
     *
     * @return a long representing the unique ID of the timeline the common time service is
     * currently synchronizing with, or {@link #INVALID_TIMELINE_ID} if the common time service is
     * currently not synchronized.
     * @throws android.os.RemoteException
     */
    public long getTimelineId()
    throws RemoteException {
        throwOnDeadServer();
        return mUtils.transactGetLong(METHOD_GET_TIMELINE_ID, INVALID_TIMELINE_ID);
    }

    /**
     * Gets the current state of this clock's common time service in the the master election
     * algorithm.
     *
     * @return a integer indicating the current state of the this clock's common time service in the
     * master election algorithm or {@link #STATE_INVALID} if there is an internal error.
     * @throws android.os.RemoteException
     */
    public int getState()
    throws RemoteException {
        throwOnDeadServer();
        return mUtils.transactGetInt(METHOD_GET_STATE, STATE_INVALID);
    }

    /**
     * Gets the IP address and UDP port of the current timeline master.
     *
     * @return an InetSocketAddress containing the IP address and UDP port of the current timeline
     * master, or null if there is no current master.
     * @throws android.os.RemoteException
     */
    public InetSocketAddress getMasterAddr()
    throws RemoteException {
        throwOnDeadServer();
        return mUtils.transactGetSockaddr(METHOD_GET_MASTER_ADDRESS);
    }

    /**
     * The OnTimelineChangedListener interface defines a method called by the
     * {@link android.os.CommonClock} instance to indicate that the time synchronization service has
     * either synchronized with a new timeline, or is no longer a member of any timeline.  The
     * client application can implement this interface and register the listener with the
     * {@link #setTimelineChangedListener(OnTimelineChangedListener)} method.
     */
    public interface OnTimelineChangedListener  {
        /**
         * Method called when the time service's timeline has changed.
         *
         * @param newTimelineId a long which uniquely identifies the timeline the time
         * synchronization service is now a member of, or {@link #INVALID_TIMELINE_ID} if the the
         * service is not synchronized to any timeline.
         */
        void onTimelineChanged(long newTimelineId);
    }

    /**
     * Registers an OnTimelineChangedListener interface.
     * <p>Call this method with a null listener to stop receiving server death notifications.
     */
    public void setTimelineChangedListener(OnTimelineChangedListener listener) {
        synchronized (mListenerLock) {
            mTimelineChangedListener = listener;
        }
    }

    /**
     * The OnServerDiedListener interface defines a method called by the
     * {@link android.os.CommonClock} instance to indicate that the connection to the native media
     * server has been broken and that the {@link android.os.CommonClock} instance will need to be
     * released and re-created.  The client application can implement this interface and register
     * the listener with the {@link #setServerDiedListener(OnServerDiedListener)} method.
     */
    public interface OnServerDiedListener  {
        /**
         * Method called when the native media server has died.  <p>If the native common time
         * service encounters a fatal error and needs to restart, the binder connection from the
         * {@link android.os.CommonClock} instance to the common time service will be broken.  To
         * restore functionality, clients should {@link #release()} their old visualizer and create
         * a new instance.
         */
        void onServerDied();
    }

    /**
     * Registers an OnServerDiedListener interface.
     * <p>Call this method with a null listener to stop receiving server death notifications.
     */
    public void setServerDiedListener(OnServerDiedListener listener) {
        synchronized (mListenerLock) {
            mServerDiedListener = listener;
        }
    }

    protected void finalize() throws Throwable { release(); }

    private void throwOnDeadServer() throws RemoteException {
        if ((null == mRemote) || (null == mUtils))
            throw new RemoteException();
    }

    private final Object mListenerLock = new Object();
    private OnTimelineChangedListener mTimelineChangedListener = null;
    private OnServerDiedListener mServerDiedListener = null;

    private IBinder mRemote = null;
    private String mInterfaceDesc = "";
    private CommonTimeUtils mUtils;

    private IBinder.DeathRecipient mDeathHandler = new IBinder.DeathRecipient() {
        public void binderDied() {
            synchronized (mListenerLock) {
                if (null != mServerDiedListener)
                    mServerDiedListener.onServerDied();
            }
        }
    };

    private class TimelineChangedListener extends Binder {
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
            switch (code) {
                case METHOD_CBK_ON_TIMELINE_CHANGED:
                    data.enforceInterface(DESCRIPTOR);
                    long timelineId = data.readLong();
                    synchronized (mListenerLock) {
                        if (null != mTimelineChangedListener)
                            mTimelineChangedListener.onTimelineChanged(timelineId);
                    }
                    return true;
            }

            return super.onTransact(code, data, reply, flags);
        }

        private static final String DESCRIPTOR = "android.os.ICommonClockListener";
    };

    private TimelineChangedListener mCallbackTgt = null;

    private void registerTimelineChangeListener() throws RemoteException {
        if (null != mCallbackTgt)
            return;

        boolean success = false;
        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();
        mCallbackTgt = new TimelineChangedListener();

        try {
            data.writeInterfaceToken(mInterfaceDesc);
            data.writeStrongBinder(mCallbackTgt);
            mRemote.transact(METHOD_REGISTER_LISTENER, data, reply, 0);
            success = (0 == reply.readInt());
        }
        catch (RemoteException e) {
            success = false;
        }
        finally {
            reply.recycle();
            data.recycle();
        }

        // Did we catch a remote exception or fail to register our callback target?  If so, our
        // object must already be dead (or be as good as dead).  Clear out all of our state so that
        // our other methods will properly indicate a dead object.
        if (!success) {
            mCallbackTgt = null;
            mRemote = null;
            mUtils = null;
        }
    }

    private void unregisterTimelineChangeListener() {
        if (null == mCallbackTgt)
            return;

        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();

        try {
            data.writeInterfaceToken(mInterfaceDesc);
            data.writeStrongBinder(mCallbackTgt);
            mRemote.transact(METHOD_UNREGISTER_LISTENER, data, reply, 0);
        }
        catch (RemoteException e) { }
        finally {
            reply.recycle();
            data.recycle();
            mCallbackTgt = null;
        }
    }

    private static final int METHOD_IS_COMMON_TIME_VALID = IBinder.FIRST_CALL_TRANSACTION;
    private static final int METHOD_COMMON_TIME_TO_LOCAL_TIME = METHOD_IS_COMMON_TIME_VALID + 1;
    private static final int METHOD_LOCAL_TIME_TO_COMMON_TIME = METHOD_COMMON_TIME_TO_LOCAL_TIME + 1;
    private static final int METHOD_GET_COMMON_TIME = METHOD_LOCAL_TIME_TO_COMMON_TIME + 1;
    private static final int METHOD_GET_COMMON_FREQ = METHOD_GET_COMMON_TIME + 1;
    private static final int METHOD_GET_LOCAL_TIME = METHOD_GET_COMMON_FREQ + 1;
    private static final int METHOD_GET_LOCAL_FREQ = METHOD_GET_LOCAL_TIME + 1;
    private static final int METHOD_GET_ESTIMATED_ERROR = METHOD_GET_LOCAL_FREQ + 1;
    private static final int METHOD_GET_TIMELINE_ID = METHOD_GET_ESTIMATED_ERROR + 1;
    private static final int METHOD_GET_STATE = METHOD_GET_TIMELINE_ID + 1;
    private static final int METHOD_GET_MASTER_ADDRESS = METHOD_GET_STATE + 1;
    private static final int METHOD_REGISTER_LISTENER = METHOD_GET_MASTER_ADDRESS + 1;
    private static final int METHOD_UNREGISTER_LISTENER = METHOD_REGISTER_LISTENER + 1;

    private static final int METHOD_CBK_ON_TIMELINE_CHANGED = IBinder.FIRST_CALL_TRANSACTION;
}