/**
 * Implementation of the {@link Layer} interface which reads its data from a file during the {@link
 * Layer#initialize} method.
 *
 * @author Brent Bryan
 * @author John Taylor
 */
public abstract class AbstractFileBasedLayer extends AbstractSourceLayer {
  private static final String TAG = MiscUtil.getTag(AbstractFileBasedLayer.class);
  private static final Executor BACKGROUND_EXECUTOR = Executors.newFixedThreadPool(1);

  private final AssetManager assetManager;
  private final String fileName;
  private final List<AstronomicalSource> fileSources = new ArrayList<>();

  public AbstractFileBasedLayer(AssetManager assetManager, Resources resources, String fileName) {
    super(resources, false);
    this.assetManager = assetManager;
    this.fileName = fileName;
  }

  @Override
  public void initialize() {
    BACKGROUND_EXECUTOR.execute(
        new Runnable() {
          public void run() {
            readSourceFile(fileName);
            AbstractFileBasedLayer.super.initialize();
          }
        });
  }

  @Override
  protected void initializeAstroSources(ArrayList<AstronomicalSource> sources) {
    sources.addAll(fileSources);
  }

  private void readSourceFile(String sourceFilename) {
    Log.d(TAG, "Loading Proto File: " + sourceFilename + "...");
    InputStream in = null;
    try {
      in = assetManager.open(sourceFilename, AssetManager.ACCESS_BUFFER);
      AstronomicalSourcesProto.Builder builder = AstronomicalSourcesProto.newBuilder();
      builder.mergeFrom(in);

      for (AstronomicalSourceProto proto : builder.build().getSourceList()) {
        fileSources.add(new ProtobufAstronomicalSource(proto, getResources()));
      }
      Log.d(TAG, "Found: " + fileSources.size() + " sources");
      String s =
          String.format(
              "Finished Loading: %s | Found %s sourcs.\n", sourceFilename, fileSources.size());
      Blog.d(this, s);

      refreshSources(EnumSet.of(UpdateType.Reset));
    } catch (IOException e) {
      Log.e(TAG, "Unable to open " + sourceFilename);
    } finally {
      Closeables.closeQuietly(in);
    }
  }
}
// TODO(brent): merge with AbstractLayer?
public abstract class AbstractSourceLayer extends AbstractLayer {
  private static final String TAG = MiscUtil.getTag(AbstractSourceLayer.class);

  private final ArrayList<TextSource> textSources = new ArrayList<TextSource>();
  private final ArrayList<ImageSource> imageSources = new ArrayList<ImageSource>();
  private final ArrayList<PointSource> pointSources = new ArrayList<PointSource>();
  private final ArrayList<LineSource> lineSources = new ArrayList<LineSource>();
  private final ArrayList<AstronomicalSource> astroSources = new ArrayList<AstronomicalSource>();

  private HashMap<String, SearchResult> searchIndex = new HashMap<String, SearchResult>();
  private PrefixStore prefixStore = new PrefixStore();
  private final boolean shouldUpdate;
  private SourceUpdateClosure closure;

  public AbstractSourceLayer(Resources resources, boolean shouldUpdate) {
    super(resources);
    this.shouldUpdate = shouldUpdate;
  }

  @Override
  public synchronized void initialize() {
    astroSources.clear();
    Log.d("RLP", "RLP this is initialization in Abstract source layer");
    initializeAstroSources(astroSources);

    for (AstronomicalSource astroSource : astroSources) {
      Sources sources = astroSource.initialize();
      textSources.addAll(sources.getLabels());
      imageSources.addAll(sources.getImages());
      pointSources.addAll(sources.getPoints());
      lineSources.addAll(sources.getLines());

      List<String> names = astroSource.getNames();
      if (!names.isEmpty()) {
        GeocentricCoordinates searchLoc = astroSource.getSearchLocation();
        for (String name : names) {
          searchIndex.put(name.toLowerCase(), new SearchResult(name, searchLoc));
          prefixStore.add(name.toLowerCase());
        }
      }
    }

    // update the renderer
    updateLayerForControllerChange();
  }

  @Override
  protected void updateLayerForControllerChange() {
    refreshSources(EnumSet.of(UpdateType.Reset));
    if (shouldUpdate) {
      if (closure == null) {
        closure = new SourceUpdateClosure(this);
      }
      addUpdateClosure(closure);
    }
  }

  /**
   * Subclasses should override this method and add all their {@link AstronomicalSource} to the
   * given {@link ArrayList}.
   */
  protected abstract void initializeAstroSources(ArrayList<AstronomicalSource> sources);

  /**
   * Redraws the sources on this layer, after first refreshing them based on the current state of
   * the {@link com.google.android.stardroid.control.AstronomerModel}.
   */
  protected void refreshSources() {
    refreshSources(EnumSet.noneOf(UpdateType.class));
  }

  /**
   * Redraws the sources on this layer, after first refreshing them based on the current state of
   * the {@link com.google.android.stardroid.control.AstronomerModel}.
   */
  protected synchronized void refreshSources(EnumSet<UpdateType> updateTypes) {
    for (AstronomicalSource astroSource : astroSources) {
      updateTypes.addAll(astroSource.update());
    }

    if (!updateTypes.isEmpty()) {
      redraw(updateTypes);
    }
  }

  /** Forcefully resets and redraws all sources on this layer everything on this layer. */
  @Override
  protected void redraw() {
    refreshSources(EnumSet.of(UpdateType.Reset));
  }

  private final void redraw(EnumSet<UpdateType> updateTypes) {
    super.redraw(textSources, pointSources, lineSources, imageSources, updateTypes);
  }

  @Override
  public List<SearchResult> searchByObjectName(String name) {
    Log.d(TAG, "Search planets layer for " + name);
    List<SearchResult> matches = new ArrayList<SearchResult>();
    SearchResult searchResult = searchIndex.get(name.toLowerCase());
    if (searchResult != null) {
      matches.add(searchResult);
    }
    Log.d(TAG, getLayerName() + " provided " + matches.size() + "results for " + name);
    return matches;
  }

  @Override
  public Set<String> getObjectNamesMatchingPrefix(String prefix) {
    Log.d(TAG, "Searching planets layer for prefix " + prefix);
    Set<String> results = prefixStore.queryByPrefix(prefix);
    Log.d(TAG, "Got " + results.size() + " results for prefix " + prefix + " in " + getLayerName());
    return results;
  }

  /** Implementation of the {@link UpdateClosure} interface used to update a layer */
  public static class SourceUpdateClosure extends AbstractUpdateClosure {
    private final AbstractSourceLayer layer;

    public SourceUpdateClosure(AbstractSourceLayer layer) {
      this.layer = layer;
    }

    @Override
    public void run() {
      layer.refreshSources();
    }
  }
}
示例#3
0
/**
 * Controls time as selected / created by the user in Time Travel mode. Includes control for
 * "playing" through time in both directions at different speeds.
 *
 * @author Dominic Widdows
 * @author John Taylor
 */
public class TimeTravelClock implements Clock {
  /** A data holder for the time stepping speeds. */
  private static class Speed {
    /** The speed in seconds per second. */
    public double rate;
    /** The id of the Speed's string label. */
    public int labelTag;

    public Speed(double rate, int labelTag) {
      this.rate = rate;
      this.labelTag = labelTag;
    }
  }

  public static final long STOPPED = 0;

  private static final Speed[] SPEEDS = {
    new Speed(-SECONDS_PER_WEEK, R.string.time_travel_week_speed_back),
    new Speed(-SECONDS_PER_DAY, R.string.time_travel_day_speed_back),
    new Speed(-SECONDS_PER_HOUR, R.string.time_travel_hour_speed_back),
    new Speed(-SECONDS_PER_10MINUTE, R.string.time_travel_10minute_speed_back),
    new Speed(-SECONDS_PER_MINUTE, R.string.time_travel_minute_speed_back),
    new Speed(-SECONDS_PER_SECOND, R.string.time_travel_second_speed_back),
    new Speed(STOPPED, R.string.time_travel_stopped),
    new Speed(SECONDS_PER_SECOND, R.string.time_travel_second_speed),
    new Speed(SECONDS_PER_MINUTE, R.string.time_travel_minute_speed),
    new Speed(SECONDS_PER_10MINUTE, R.string.time_travel_10minute_speed),
    new Speed(SECONDS_PER_HOUR, R.string.time_travel_hour_speed),
    new Speed(SECONDS_PER_DAY, R.string.time_travel_day_speed),
    new Speed(SECONDS_PER_WEEK, R.string.time_travel_week_speed),
  };

  private static final int STOPPED_INDEX = SPEEDS.length / 2;

  private int speedIndex = STOPPED_INDEX;

  private static final String TAG = MiscUtil.getTag(TimeTravelClock.class);
  private long timeLastSet;
  private long simulatedTime;

  /**
   * Sets the internal time.
   *
   * @param date Date to which the timeTravelDate will be set.
   */
  public synchronized void setTimeTravelDate(Date date) {
    pauseTime();
    timeLastSet = System.currentTimeMillis();
    simulatedTime = date.getTime();
  }

  /*
   * Controller logic for playing through time at different directions and
   * speeds.
   */

  /**
   * Increases the rate of time travel into the future (or decreases the rate of time travel into
   * the past.)
   */
  public synchronized void accelerateTimeTravel() {
    if (speedIndex < SPEEDS.length - 1) {
      Log.d(TAG, "Accelerating speed to: " + SPEEDS[speedIndex]);
      ++speedIndex;
    } else {
      Log.d(TAG, "Already at max forward speed");
    }
  }

  /**
   * Decreases the rate of time travel into the future (or increases the rate of time travel into
   * the past.)
   */
  public synchronized void decelerateTimeTravel() {
    if (speedIndex > 0) {
      Log.d(TAG, "Decelerating speed to: " + SPEEDS[speedIndex]);
      --speedIndex;
    } else {
      Log.d(TAG, "Already at maximum backwards speed");
    }
  }

  /** Pauses time. */
  public synchronized void pauseTime() {
    Log.d(TAG, "Pausing time");
    assert SPEEDS[STOPPED_INDEX].rate == 0.0;
    speedIndex = STOPPED_INDEX;
  }

  /** @return The current speed tag, a string describing the speed of time travel. */
  public int getCurrentSpeedTag() {
    return SPEEDS[speedIndex].labelTag;
  }

  @Override
  public long getTimeInMillisSinceEpoch() {
    long now = System.currentTimeMillis();
    long elapsedTimeMillis = now - timeLastSet;
    double rate = SPEEDS[speedIndex].rate;
    long timeDelta = (long) (rate * elapsedTimeMillis);
    if (Math.abs(rate) >= SECONDS_PER_DAY) {
      // For speeds greater than or equal to 1 day/sec we want to move in
      // increments of 1 day so that the map isn't dizzyingly fast.
      // This shows the slow annual procession of the stars.
      long days = (long) (timeDelta / MILLISECONDS_PER_DAY);
      if (days == 0) {
        return simulatedTime;
      }
      // Note that this assumes that time requests will occur right on the
      // day boundary.  If they occur later then the next time jump
      // might be a bit shorter than it should be.  Nevertheless the refresh
      // rate of the renderer is high enough that this should be unnoticeable.
      timeDelta = (long) (days * MILLISECONDS_PER_DAY);
    }
    timeLastSet = now;
    simulatedTime += timeDelta;
    return simulatedTime;
  }
}
/**
 * Base implementation of the {@link Layer} interface.
 *
 * @author John Taylor
 * @author Brent Bryan
 */
public abstract class AbstractLayer implements Layer {
  private static final String TAG = MiscUtil.getTag(AbstractLayer.class);

  private final ReentrantLock renderMapLock = new ReentrantLock();
  private final HashMap<Class<?>, RenderManager<?>> renderMap = Maps.newHashMap();
  private final Resources resources;

  private RendererController renderer;

  public AbstractLayer(Resources resources) {
    this.resources = resources;
  }

  protected Resources getResources() {
    return resources;
  }

  @Override
  public void registerWithRenderer(RendererController rendererController) {
    this.renderMap.clear();
    this.renderer = rendererController;
    updateLayerForControllerChange();
  }

  protected abstract void updateLayerForControllerChange();

  @Override
  public void setVisible(boolean visible) {
    Log.w(TAG, "RLP setVisible: entry");
    renderMapLock.lock();
    try {
      if (renderer == null) {
        Log.w(
            TAG, "RLP setVisible: Renderer not set - aborting " + this.getClass().getSimpleName());
        return;
      }

      AtomicSection atomic = renderer.createAtomic();
      for (Entry<Class<?>, RenderManager<?>> entry : renderMap.entrySet()) {
        entry.getValue().queueEnabled(visible, atomic);
      }
      renderer.queueAtomic(atomic);
    } finally {
      renderMapLock.unlock();
    }
  }

  protected void addUpdateClosure(UpdateClosure closure) {
    if (renderer != null) {
      renderer.addUpdateClosure(closure);
    }
  }

  protected void removeUpdateClosure(UpdateClosure closure) {
    if (renderer != null) {
      renderer.removeUpdateCallback(closure);
    }
  }

  /**
   * Forces a redraw of this layer, clearing all of the information about this layer in the renderer
   * and repopulating it.
   */
  protected abstract void redraw();

  protected void redraw(
      final ArrayList<TextSource> textSources,
      final ArrayList<PointSource> pointSources,
      final ArrayList<LineSource> lineSources,
      final ArrayList<ImageSource> imageSources) {
    redraw(textSources, pointSources, lineSources, imageSources, EnumSet.of(UpdateType.Reset));
  }

  /**
   * Updates the renderer (using the given {@link UpdateType}), with then given set of UI elements.
   * Depending on the value of {@link UpdateType}, current sources will either have their state
   * updated, or will be overwritten by the given set of UI elements.
   */
  protected void redraw(
      final ArrayList<TextSource> textSources,
      final ArrayList<PointSource> pointSources,
      final ArrayList<LineSource> lineSources,
      final ArrayList<ImageSource> imageSources,
      EnumSet<UpdateType> updateTypes) {

    //    Log.d(TAG, getLayerName() + "RLP Updating renderer: " + updateTypes);
    if (renderer == null) {
      Log.w(TAG, "RLP Renderer not set - aborting: " + this.getClass().getSimpleName());
      return;
    }

    renderMapLock.lock();
    try {
      // Blog.d(this, "Redraw: " + updateTypes);
      AtomicSection atomic = renderer.createAtomic();
      setSources(textSources, updateTypes, TextSource.class, atomic);
      setSources(pointSources, updateTypes, PointSource.class, atomic);
      setSources(lineSources, updateTypes, LineSource.class, atomic);
      setSources(imageSources, updateTypes, ImageSource.class, atomic);
      renderer.queueAtomic(atomic);
    } finally {
      renderMapLock.unlock();
    }
  }

  /**
   * Sets the objects on the {@link RenderManager} to the given values, creating (or disabling) the
   * {@link RenderManager} if necessary.
   */
  private <E> void setSources(
      ArrayList<E> sources, EnumSet<UpdateType> updateType, Class<E> clazz, AtomicSection atomic) {

    @SuppressWarnings("unchecked")
    RenderManager<E> manager = (RenderManager<E>) renderMap.get(clazz);
    if (sources == null || sources.isEmpty()) {
      if (manager != null) {
        // TODO(brent): we should really just disable this layer, but in a
        // manner that it will automatically be reenabled when appropriate.
        Blog.d(this, "       " + clazz.getSimpleName());
        manager.queueObjects(Collections.<E>emptyList(), updateType, atomic);
      }
      return;
    }

    if (manager == null) {
      manager = createRenderManager(clazz, atomic);
      renderMap.put(clazz, manager);
    }
    // Blog.d(this, "       " + clazz.getSimpleName() + " " + sources.size());
    manager.queueObjects(sources, updateType, atomic);
  }

  @SuppressWarnings("unchecked")
  <E> RenderManager<E> createRenderManager(Class<E> clazz, RendererControllerBase controller) {
    if (clazz.equals(ImageSource.class)) {
      return (RenderManager<E>) controller.createImageManager(getLayerId());

    } else if (clazz.equals(TextSource.class)) {
      return (RenderManager<E>) controller.createLabelManager(getLayerId());

    } else if (clazz.equals(LineSource.class)) {
      return (RenderManager<E>) controller.createLineManager(getLayerId());

    } else if (clazz.equals(PointSource.class)) {
      return (RenderManager<E>) controller.createPointManager(getLayerId());
    }
    throw new IllegalStateException("Unknown source type: " + clazz);
  }

  @Override
  public List<SearchResult> searchByObjectName(String name) {
    // By default, layers will return no search results.
    // Override this if the layer should be searchable.
    return Collections.emptyList();
  }

  @Override
  public Set<String> getObjectNamesMatchingPrefix(String prefix) {
    // By default, layers will return no search results.
    // Override this if the layer should be searchable.
    return Collections.emptySet();
  }

  /** Provides a string ID to the internationalized name of this layer. */
  // TODO(brent): could this be combined with getLayerId?  Not sure - they
  // serve slightly different purposes.
  protected abstract int getLayerNameId();

  @Override
  public String getPreferenceId() {
    return getPreferenceId(getLayerNameId());
  }

  protected String getPreferenceId(int layerNameId) {
    return "source_provider." + layerNameId;
  }

  @Override
  public String getLayerName() {
    return getStringFromId(getLayerNameId());
  }

  /** Return an internationalized string from a string resource id. */
  protected String getStringFromId(int resourceId) {
    return resources.getString(resourceId);
  }
}
示例#5
0
/**
 * If enabled, keeps the sky gradient up to date.
 *
 * @author John Taylor
 * @author Brent Bryan
 */
public class SkyGradientLayer implements Layer {
  private static final String TAG = MiscUtil.getTag(SkyGradientLayer.class);
  private static final long UPDATE_FREQUENCY_MS = 5L * TimeConstants.MILLISECONDS_PER_MINUTE;

  private final ReentrantLock rendererLock = new ReentrantLock();
  private final AstronomerModel model;
  private final Resources resources;

  private RendererController renderer;
  private long lastUpdateTimeMs = 0L;

  public SkyGradientLayer(AstronomerModel model, Resources resources) {
    this.model = model;
    this.resources = resources;
  }

  @Override
  public void initialize() {}

  @Override
  public void registerWithRenderer(RendererController controller) {
    this.renderer = controller;
    redraw();
  }

  @Override
  public void setVisible(boolean visible) {
    Log.d(TAG, "Setting showSkyGradient " + visible);
    if (visible) {
      redraw();
    } else {
      rendererLock.lock();
      try {
        renderer.queueDisableSkyGradient();
      } finally {
        rendererLock.unlock();
      }
    }
  }

  /** Redraws the sky shading gradient using the model's current time. */
  protected void redraw() {
    Date modelTime = model.getTime();
    if (Math.abs(modelTime.getTime() - lastUpdateTimeMs) > UPDATE_FREQUENCY_MS) {
      lastUpdateTimeMs = modelTime.getTime();

      RaDec sunPosition = SolarPositionCalculator.getSolarPosition(modelTime);
      // Log.d(TAG, "Enabling sky gradient with sun position " + sunPosition);
      rendererLock.lock();
      try {
        renderer.queueEnableSkyGradient(GeocentricCoordinates.getInstance(sunPosition));
      } finally {
        rendererLock.unlock();
      }
    }
  }

  @Override
  public int getLayerId() {
    return 0;
  }

  public String getPreferenceId() {
    return "source_provider." + getLayerNameId();
  }

  public String getLayerName() {
    return resources.getString(getLayerNameId());
  }

  private int getLayerNameId() {
    return R.string.show_sky_gradient;
  }

  @Override
  public List<SearchResult> searchByObjectName(String name) {
    return Collections.emptyList();
  }

  @Override
  public Set<String> getObjectNamesMatchingPrefix(String prefix) {
    return Collections.emptySet();
  }
}
/** Edit the user's preferences. */
public class EditSettingsActivity extends PreferenceActivity implements OnPreferenceChangeListener {
  /** These must match the keys in the preference_screen.xml file. */
  private static final String LONGITUDE = "longitude";

  private static final String LATITUDE = "latitude";
  private static final String LOCATION = "location";
  private static final String TAG = MiscUtil.getTag(EditSettingsActivity.class);
  private Geocoder geocoder;
  private ActivityLightLevelManager activityLightLevelManager;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    activityLightLevelManager =
        new ActivityLightLevelManager(
            new ActivityLightLevelChanger(this, null),
            PreferenceManager.getDefaultSharedPreferences(this));
    geocoder = new Geocoder(this);
    addPreferencesFromResource(R.xml.preference_screen);

    if (!StardroidApplication.getSupportsNewSensors()) {
      findPreference("fused_sensor").setEnabled(false);
    }

    findPreference("sensor_damping")
        .setEnabled(
            !PreferenceManager.getDefaultSharedPreferences(this).getBoolean("fused_sensor", false));
    findPreference("fused_sensor").setOnPreferenceChangeListener(this);

    Preference editPreference = findPreference(LOCATION);
    // TODO(johntaylor) if the lat long prefs get changed manually, we should really
    // reset the placename to "" too.
    editPreference.setOnPreferenceChangeListener(
        new OnPreferenceChangeListener() {

          public boolean onPreferenceChange(Preference preference, Object newValue) {
            Log.d(TAG, "Place to be updated to " + newValue);
            boolean success = setLatLongFromPlace(newValue.toString());
            return success;
          }
        });
  }

  public boolean onPreferenceChange(Preference preference, Object newValue) {
    if (preference.getKey().equals("fused_sensor")) {
      findPreference("sensor_damping").setEnabled(!newValue.toString().equals("true"));
    }
    return true;
  }

  @Override
  public void onStart() {
    super.onStart();
    Analytics.getInstance(this).trackPageView(Analytics.EDIT_SETTINGS_ACTIVITY);
  }

  @Override
  public void onResume() {
    super.onResume();
    activityLightLevelManager.onResume();
  }

  @Override
  public void onPause() {
    super.onPause();
    updatePreferences();
    activityLightLevelManager.onPause();
  }

  /**
   * Updates preferences on singletons, so we don't have to register preference change listeners for
   * them.
   */
  private void updatePreferences() {
    Log.d(TAG, "Updating preferences");
    Analytics.getInstance(this).setEnabled(findPreference(Analytics.PREF_KEY).isEnabled());
  }

  protected boolean setLatLongFromPlace(String place) {
    List<Address> addresses = new ArrayList<Address>();
    try {
      addresses = geocoder.getFromLocationName(place, 1);
    } catch (IOException e) {
      Toast.makeText(this, getString(R.string.location_unable_to_geocode), Toast.LENGTH_SHORT)
          .show();
      return false;
    }
    if (addresses.size() == 0) {
      showNotFoundDialog(place);
      return false;
    }
    // TODO(johntaylor) let the user choose, but for now just pick the first.
    Address first = addresses.get(0);
    setLatLong(first.getLatitude(), first.getLongitude());
    return true;
  }

  private void setLatLong(double latitude, double longitude) {
    EditTextPreference latPreference = (EditTextPreference) findPreference(LATITUDE);
    EditTextPreference longPreference = (EditTextPreference) findPreference(LONGITUDE);
    latPreference.setText(Double.toString(latitude));
    longPreference.setText(Double.toString(longitude));
    String message = String.format(getString(R.string.location_place_found), latitude, longitude);
    Log.d(TAG, message);
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
  }

  private void showNotFoundDialog(String place) {
    String message = String.format(getString(R.string.location_not_found), place);
    AlertDialog.Builder dialog =
        new AlertDialog.Builder(this)
            .setTitle(R.string.location_not_found_title)
            .setMessage(message)
            .setPositiveButton(
                android.R.string.ok,
                new OnClickListener() {
                  public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                  }
                });
    dialog.show();
  }
}