// Copyright 2010 Google Inc.
//
// 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 com.google.android.stardroid.control;
import com.google.android.stardroid.util.MiscUtil;
import android.util.Log;
import java.util.Date;
/**
* A clock that knows how to transition between a {@link TimeTravelClock}
* and another {@link Clock}. Usually this other
* Clock will be a {@link RealClock}.
*
* @author John Taylor
*
*/
public class TransitioningCompositeClock implements Clock {
public static final long TRANSITION_TIME_MILLIS = 2500L;
private static final String TAG = MiscUtil.getTag(TransitioningCompositeClock.class);
private Clock realClock;
private TimeTravelClock timeTravelClock;
private enum Mode {REAL_TIME, TRANSITION, TIME_TRAVEL};
private Mode mode = Mode.REAL_TIME;
private long startTime;
private long endTime;
private long startTransitionWallTime;
private Mode transitionTo;
/**
* Constructor.
*
* The realClock parameter serves two purposes - both as the clock to query
* when in realtime mode, and also to count the beats during the transition
* between realtime and timetravel modes to ensure a smooth transition.
*/
public TransitioningCompositeClock(TimeTravelClock timeTravelClock,
Clock realClock) {
this.timeTravelClock = timeTravelClock;
this.realClock = realClock;
}
public void goTimeTravel(Date targetDate) {
startTime = getTimeInMillisSinceEpoch();
endTime = targetDate.getTime();
timeTravelClock.setTimeTravelDate(targetDate);
mode = Mode.TRANSITION;
transitionTo = Mode.TIME_TRAVEL;
startTransitionWallTime = realClock.getTimeInMillisSinceEpoch();
}
public void returnToRealTime() {
startTime = getTimeInMillisSinceEpoch();
endTime = realClock.getTimeInMillisSinceEpoch() + TRANSITION_TIME_MILLIS;
mode = Mode.TRANSITION;
transitionTo = Mode.REAL_TIME;
startTransitionWallTime = realClock.getTimeInMillisSinceEpoch();
}
@Override
public long getTimeInMillisSinceEpoch() {
if (mode == Mode.TRANSITION) {
long elapsedTimeMillis = realClock.getTimeInMillisSinceEpoch() - startTransitionWallTime;
if (elapsedTimeMillis > TRANSITION_TIME_MILLIS) {
mode = transitionTo;
} else {
return (long) interpolate(startTime, endTime,
((double) elapsedTimeMillis) / TRANSITION_TIME_MILLIS);
}
}
switch(mode) {
case REAL_TIME:
return realClock.getTimeInMillisSinceEpoch();
case TIME_TRAVEL:
return timeTravelClock.getTimeInMillisSinceEpoch();
}
Log.e(TAG, "Mode is neither realtime or timetravel - this should never happen");
// While this will never happen - if it does let's just return real time.
return realClock.getTimeInMillisSinceEpoch();
}
/**
* An interpolation function to smoothly interpolate between start
* at lambda = 0 and end at lambda = 1
*/
public static double interpolate(double start, double end, double lambda) {
return (start + (3 * lambda * lambda - 2 * lambda * lambda * lambda) * (end - start));
}
}