/* * 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.InetSocketAddress; import java.util.NoSuchElementException; import android.os.Binder; import android.os.CommonTimeUtils; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; /** * 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. *

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.

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. *

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; }