blob: 2ecf317c2d0996a3e8f86dc45c99a9d7982d0433 [file] [log] [blame]
/*
* 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.
* <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;
}