app/src/main/java/com/google/android/stardroid/activities/util/ActivityLightLevelChanger.java
public interface NightModeable {
  void setNightMode(boolean nightMode);
}
app/src/main/java/com/google/android/stardroid/renderer/SkyRenderer.java
public class SkyRenderer implements GLSurfaceView.Renderer {
  private SkyBox mSkyBox = null;
  private OverlayManager mOverlayManager = null;

  private RenderState mRenderState = new RenderState();

  private Matrix4x4 mProjectionMatrix;
  private Matrix4x4 mViewMatrix;

  // Indicates whether the transformation matrix has changed since the last
  // time we started rendering
  private boolean mMustUpdateView = true;
  private boolean mMustUpdateProjection = true;

  private Set<Runnable> mUpdateClosures = new HashSet<>();

  private RendererObjectManager.UpdateListener mUpdateListener =
      new RendererObjectManager.UpdateListener() {
        public void queueForReload(RendererObjectManager rom, boolean fullReload) {
          mManagersToReload.add(new ManagerReloadData(rom, fullReload));
        }
      };

  // All managers - we need to reload all of these when we recreate the surface.
  private Set<RendererObjectManager> mAllManagers = new TreeSet<RendererObjectManager>();

  protected final TextureManager mTextureManager;

  private static class ManagerReloadData {
    ManagerReloadData(RendererObjectManager manager, boolean fullReload) {
      this.manager = manager;
      this.fullReload = fullReload;
    }
    public RendererObjectManager manager;
    public boolean fullReload;
  }
  // A list of managers which need to be reloaded before the next frame is rendered.  This may
  // be because they haven't ever been loaded yet, or because their objects have changed since
  // the last frame.
  private ArrayList<ManagerReloadData> mManagersToReload = new ArrayList<>();

  // Maps an integer indicating render order to a list of objects at that level.  The managers
  // will be rendered in order, with the lowest number coming first.
  private TreeMap<Integer, Set<RendererObjectManager>> mLayersToManagersMap = null;

  public SkyRenderer(Resources res) {
    mRenderState.setResources(res);

    mLayersToManagersMap = new TreeMap<Integer, Set<RendererObjectManager>>();

    mTextureManager = new TextureManager(res);

    // The skybox should go behind everything.
    mSkyBox = new SkyBox(Integer.MIN_VALUE, mTextureManager);
    mSkyBox.enable(false);
    addObjectManager(mSkyBox);

    // The overlays go on top of everything.
    mOverlayManager = new OverlayManager(Integer.MAX_VALUE, mTextureManager);
    addObjectManager(mOverlayManager);

    Log.d("SkyRenderer", "SkyRenderer::SkyRenderer()");
  }

  // Returns true if the buffers should be swapped, false otherwise.
  public void onDrawFrame(GL10 gl) {
    // Initialize any of the unloaded managers.
    for (ManagerReloadData data : mManagersToReload) {
      data.manager.reload(gl, data.fullReload);
    }
    mManagersToReload.clear();

    maybeUpdateMatrices(gl);

    // Determine which sky regions should be rendered.
    mRenderState.setActiveSkyRegions(
        SkyRegionMap.getActiveRegions(
            mRenderState.getLookDir(),
            mRenderState.getRadiusOfView(),
            (float) mRenderState.getScreenWidth() / mRenderState.getScreenHeight()));

    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

    for (Set<RendererObjectManager> managers : mLayersToManagersMap.values()) {
      for (RendererObjectManager rom : managers) {
        rom.draw(gl);
      }
    }
    checkForErrors(gl);

    // Queue updates for the next frame.
    for (Runnable update : mUpdateClosures) {
      update.run();
    }
  }

  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    Log.d("SkyRenderer", "surfaceCreated");

    gl.glEnable(GL10.GL_DITHER);

    /*
     * Some one-time OpenGL initialization can be made here
     * probably based on features of this particular context
     */
    gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
              GL10.GL_FASTEST);

    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    gl.glEnable(GL10.GL_CULL_FACE);
    gl.glShadeModel(GL10.GL_SMOOTH);
    gl.glDisable(GL10.GL_DEPTH_TEST);


    // Release references to all of the old textures.
    mTextureManager.reset();

    String extensions = gl.glGetString(GL10.GL_EXTENSIONS);
    Log.i("SkyRenderer", "GL extensions: " + extensions);

    // Determine if the phone supports VBOs or not, and set this on the GLBuffer.
    // TODO(jpowell): There are two extension strings which seem applicable.
    // There is GL_OES_vertex_buffer_object and GL_ARB_vertex_buffer_object.
    // I can't find any documentation which explains the difference between
    // these two.  Most phones which support one seem to support both,
    // except for the Nexus One, which only supports ARB but doesn't seem
    // to benefit from using VBOs anyway.  I should figure out what the
    // difference is and use ARB too, if I can.
    boolean canUseVBO = false;
    if (extensions.contains("GL_OES_vertex_buffer_object")) {
      canUseVBO = true;
    }
    // VBO support on the Cliq and Behold is broken and say they can
    // use them when they can't.  Explicitly disable it for these devices.
    final String[] badModels = {
        "MB200",
        "MB220",
        "Behold",
    };
    for (String model : badModels) {
      if (android.os.Build.MODEL.contains(model)) {
        canUseVBO = false;
        break;
      }
    }
    Log.i("SkyRenderer", "Model: " + android.os.Build.MODEL);
    Log.i("SkyRenderer", canUseVBO ? "VBOs enabled" : "VBOs disabled");
    GLBuffer.setCanUseVBO(canUseVBO);

    // Reload all of the managers.
    for (RendererObjectManager rom : mAllManagers) {
      rom.reload(gl, true);
    }
  }

  public void onSurfaceChanged(GL10 gl, int width, int height) {
    Log.d("SkyRenderer", "Starting sizeChanged, size = (" + width + ", " + height + ")");

    mRenderState.setScreenSize(width, height);
    mOverlayManager.resize(gl, width, height);

    // Need to set the matrices.
    mMustUpdateView = true;
    mMustUpdateProjection = true;

    Log.d("SkyRenderer", "Changing viewport size");

    gl.glViewport(0, 0, width, height);

    Log.d("SkyRenderer", "Done with sizeChanged");
  }

  public void setRadiusOfView(float degrees) {
    // Log.d("SkyRenderer", "setRadiusOfView(" + degrees + ")");
    mRenderState.setRadiusOfView(degrees);
    mMustUpdateProjection = true;
  }

  public void addUpdateClosure(Runnable update) {
    mUpdateClosures.add(update);
  }

  // Sets up from the perspective of the viewer.
  // ie, the zenith in celestial coordinates.
  public void setViewerUpDirection(Vector3 up) {
    mOverlayManager.setViewerUpDirection(up);
  }

  public void addObjectManager(RendererObjectManager m) {
    m.setRenderState(mRenderState);
    m.setUpdateListener(mUpdateListener);
    mAllManagers.add(m);

    // It needs to be reloaded before we try to draw it.
    mManagersToReload.add(new ManagerReloadData(m, true));

    // Add it to the appropriate layer.
    Set<RendererObjectManager> managers = mLayersToManagersMap.get(m.getLayer());
    if (managers == null) {
      managers = new TreeSet<RendererObjectManager>();
      mLayersToManagersMap.put(m.getLayer(), managers);
    }
    managers.add(m);
  }

  public void removeObjectManager(RendererObjectManager m) {
    mAllManagers.remove(m);

    Set<RendererObjectManager> managers = mLayersToManagersMap.get(m.getLayer());
    // managers shouldn't ever be null, so don't bother checking.  Let it crash if it is so we
    // know there's a bug.
    managers.remove(m);
  }

  public void enableSkyGradient(Vector3 sunPosition) {
    mSkyBox.setSunPosition(sunPosition);
    mSkyBox.enable(true);
  }

  public void disableSkyGradient() {
    mSkyBox.enable(false);
  }

  public void enableSearchOverlay(Vector3 target, String targetName) {
    mOverlayManager.enableSearchOverlay(target, targetName);
  }

  public void disableSearchOverlay() {
    mOverlayManager.disableSearchOverlay();
  }

  public void setNightVisionMode(boolean enabled) {
    mRenderState.setNightVisionMode(enabled);
  }

  // Used to set the orientation of the text.  The angle parameter is the roll
  // of the phone.  This angle is rounded to the nearest multiple of 90 degrees
  // to keep the text readable.
  public void setTextAngle(float angleInRadians) {
    final float TWO_OVER_PI = 2.0f / (float)Math.PI;
    final float PI_OVER_TWO = (float)Math.PI / 2.0f;

    float newAngle = Math.round(angleInRadians * TWO_OVER_PI) * PI_OVER_TWO;

    mRenderState.setUpAngle(newAngle);
  }

  public void setViewOrientation(float dirX, float dirY, float dirZ,
                                 float upX, float upY, float upZ) {
    // Normalize the look direction
    float dirLen = MathUtils.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ);
    float oneOverDirLen = 1.0f / dirLen;
    dirX *= oneOverDirLen;
    dirY *= oneOverDirLen;
    dirZ *= oneOverDirLen;

    // We need up to be perpendicular to the look direction, so we subtract
    // off the projection of the look direction onto the up vector
    float lookDotUp = dirX * upX + dirY * upY + dirZ * upZ;
    upX -= lookDotUp * dirX;
    upY -= lookDotUp * dirY;
    upZ -= lookDotUp * dirZ;

    // Normalize the up vector
    float upLen = MathUtils.sqrt(upX*upX + upY*upY + upZ*upZ);
    float oneOverUpLen = 1.0f / upLen;
    upX *= oneOverUpLen;
    upY *= oneOverUpLen;
    upZ *= oneOverUpLen;

    mRenderState.setLookDir(new Vector3(dirX, dirY, dirZ));
    mRenderState.setUpDir(new Vector3(upX, upY, upZ));

    mMustUpdateView = true;

    mOverlayManager.setViewOrientation(new Vector3(dirX, dirY, dirZ),
                                       new Vector3(upX, upY, upZ));
  }

  protected int getWidth() { return mRenderState.getScreenWidth(); }
  protected int getHeight() { return mRenderState.getScreenHeight(); }


  static void checkForErrors(GL10 gl) {
    checkForErrors(gl, false);
  }

  static void checkForErrors(GL10 gl, boolean printStackTrace) {
    int error = gl.glGetError();
    if (error != 0) {
      Log.e("SkyRenderer", "GL error: " + error);
      Log.e("SkyRenderer", GLU.gluErrorString(error));
      if (printStackTrace) {
        StringWriter writer = new StringWriter();
        new Throwable().printStackTrace(new PrintWriter(writer));
        Log.e("SkyRenderer", writer.toString());
      }
    }
  }

  private void updateView(GL10 gl) {
    // Get a vector perpendicular to both, pointing to the right, by taking
    // lookDir cross up.
    Vector3 lookDir = mRenderState.getLookDir();
    Vector3 upDir = mRenderState.getUpDir();
    Vector3 right = lookDir.times(upDir);

    mViewMatrix = Matrix4x4.createView(lookDir, upDir, right);

    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadMatrixf(mViewMatrix.getFloatArray(), 0);
  }

  private void updatePerspective(GL10 gl) {
    mProjectionMatrix = Matrix4x4.createPerspectiveProjection(
        mRenderState.getScreenWidth(),
        mRenderState.getScreenHeight(),
        mRenderState.getRadiusOfView() * 3.141593f / 360.0f);

    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadMatrixf(mProjectionMatrix.getFloatArray(), 0);

    // Switch back to the model view matrix.
    gl.glMatrixMode(GL10.GL_MODELVIEW);
  }

  private void maybeUpdateMatrices(GL10 gl) {
    boolean updateTransform = mMustUpdateView || mMustUpdateProjection;
    if (mMustUpdateView) {
      updateView(gl);
      mMustUpdateView = false;
    }
    if (mMustUpdateProjection) {
      updatePerspective(gl);
      mMustUpdateProjection = false;
    }
    if (updateTransform) {
      // Device coordinates are a square from (-1, -1) to (1, 1).  Screen
      // coordinates are (0, 0) to (width, height).  Both coordinates
      // are useful in different circumstances, so we'll pre-compute
      // matrices to do the transformations from world coordinates
      // into each of these.
      Matrix4x4 transformToDevice = Matrix4x4.times(mProjectionMatrix, mViewMatrix);

      Matrix4x4 translate = Matrix4x4.createTranslation(1, 1, 0);
      Matrix4x4 scale = Matrix4x4.createScaling(mRenderState.getScreenWidth() * 0.5f,
                                                mRenderState.getScreenHeight() * 0.5f, 1);

      Matrix4x4 transformToScreen =
          Matrix4x4.times(Matrix4x4.times(scale, translate),
                               transformToDevice);

      mRenderState.setTransformationMatrices(transformToDevice, transformToScreen);
    }
  }

  // WARNING!  These factory methods are invoked from another thread and
  // therefore cannot do any OpenGL operations or any nontrivial nontrivial
  // initialization.
  //
  // TODO(jpowell): This would be much safer if the renderer controller
  // schedules creation of the objects in the queue.
  public PointObjectManager createPointManager(int layer) {
    return new PointObjectManager(layer, mTextureManager);
  }

  public PolyLineObjectManager createPolyLineManager(int layer) {
    return new PolyLineObjectManager(layer, mTextureManager);
  }

  public LabelObjectManager createLabelManager(int layer) {
    return new LabelObjectManager(layer, mTextureManager);
  }

  public ImageObjectManager createImageManager(int layer) {
    return new ImageObjectManager(layer, mTextureManager);
  }
}
app/src/main/java/com/google/android/stardroid/activities/util/ActivityLightLevelManager.java
public class ActivityLightLevelManager implements OnSharedPreferenceChangeListener {
  private ActivityLightLevelChanger lightLevelChanger;
  private SharedPreferences sharedPreferences;
  private enum LightMode {DAY, NIGHT, AUTO}
  public static final String LIGHT_MODE_KEY = "lightmode";
  public ActivityLightLevelManager(ActivityLightLevelChanger lightLevelChanger,
                                   SharedPreferences sharedPreferences) {
    this.lightLevelChanger = lightLevelChanger;
    this.sharedPreferences = sharedPreferences;
  }

  public void onResume() {
    registerWithPreferences();
    LightMode currentMode = getLightModePreference();
    setActivityMode(currentMode);
  }

  private void setActivityMode(LightMode currentMode) {
    switch(currentMode) {
      case DAY:
        lightLevelChanger.setNightMode(false);
        break;
      case NIGHT:
        lightLevelChanger.setNightMode(true);
        break;
      case AUTO:
        throw new UnsupportedOperationException("not implemented yet");
    }
  }

  private LightMode getLightModePreference() {
    String preference = sharedPreferences.getString(LIGHT_MODE_KEY, LightMode.DAY.name());
    return LightMode.valueOf(preference);
  }

  private void registerWithPreferences() {
    sharedPreferences.registerOnSharedPreferenceChangeListener(this);
  }

  public void onPause() {
    unregisterWithPreferences();
  }

  private void unregisterWithPreferences() {
    sharedPreferences.registerOnSharedPreferenceChangeListener(this);
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    if (!LIGHT_MODE_KEY.equals(key)) {
      return;
    }
    setActivityMode(getLightModePreference());
  }
}
app/src/main/java/com/google/android/stardroid/control/ControllerGroup.java
public class ControllerGroup implements Controller {
  private final static String TAG = MiscUtil.getTag(ControllerGroup.class);
  private final ArrayList<Controller> controllers = new ArrayList<Controller>();
  private ZoomController zoomController;
  private ManualOrientationController manualDirectionController;
  private SensorOrientationController sensorOrientationController;
  private TimeTravelClock timeTravelClock = new TimeTravelClock();
  private TransitioningCompositeClock transitioningClock = new TransitioningCompositeClock(
      timeTravelClock, new RealClock());
  private TeleportingController teleportingController;
  private boolean usingAutoMode = true;
  private AstronomerModel model;

  // TODO(jontayler): inject everything else.
  @Inject
  ControllerGroup(Context context, SensorOrientationController sensorOrientationController,
                  LocationController locationController) {
    addController(locationController);
    this.sensorOrientationController = sensorOrientationController;
    addController(sensorOrientationController);
    manualDirectionController = new ManualOrientationController();
    addController(manualDirectionController);
    zoomController = new ZoomController();
    addController(zoomController);
    teleportingController = new TeleportingController();
    addController(teleportingController);
    setAutoMode(true);
  }

  @Override
  public void setEnabled(boolean enabled) {
    Log.i(TAG, "Enabling all controllers");
    for (Controller controller : controllers) {
      controller.setEnabled(enabled);
    }
  }

  @Override
  public void setModel(AstronomerModel model) {
    Log.i(TAG, "Setting model");
    for (Controller controller : controllers) {
      controller.setModel(model);
    }
    this.model = model;
    model.setAutoUpdatePointing(usingAutoMode);
    model.setClock(transitioningClock);
  }

  /**
   * Switches to time-travel model and start with the supplied time.
   * See {@link #useRealTime()}.
   */
  public void goTimeTravel(Date d) {
    transitioningClock.goTimeTravel(d);
  }

  /**
   * Gets the id of the string used to display the current speed of time travel.
   */
  public int getCurrentSpeedTag() {
    return timeTravelClock.getCurrentSpeedTag();
  }

  /**
   * Sets the model back to using real time.
   * See {@link #goTimeTravel(Date)}.
   */
  public void useRealTime() {
    transitioningClock.returnToRealTime();
  }

  /**
   * Increases the rate of time travel into the future (or decreases the rate of
   * time travel into the past) if in time travel mode.
   */
  public void accelerateTimeTravel() {
    timeTravelClock.accelerateTimeTravel();
  }

  /**
   * Decreases the rate of time travel into the future (or increases the rate of
   * time travel into the past) if in time travel mode.
   */
  public void decelerateTimeTravel() {
    timeTravelClock.decelerateTimeTravel();
  }

  /**
   * Pauses time, if in time travel mode.
   */
  public void pauseTime() {
    timeTravelClock.pauseTime();
  }

  /**
   * Are we in auto mode (aka sensor mode) or manual?
   */
  public boolean isAutoMode() {
    return usingAutoMode;
  }

  /**
   * Sets auto mode (true) or manual mode (false).
   */
  public void setAutoMode(boolean enabled) {
    manualDirectionController.setEnabled(!enabled);
    sensorOrientationController.setEnabled(enabled);
    if (model != null) {
      model.setAutoUpdatePointing(enabled);
    }
    usingAutoMode = enabled;
  }

  @Override
  public void start() {
    Log.i(TAG, "Starting controllers");
    for (Controller controller : controllers) {
      controller.start();
    }
  }

  @Override
  public void stop() {
    Log.i(TAG, "Stopping controllers");
    for (Controller controller : controllers) {
      controller.stop();
    }
  }

  /**
   * Moves the pointing right and left.
   * 
   * @param radians the angular change in the pointing in radians (only
   * accurate in the limit as radians tends to 0.)
   */
  public void changeRightLeft(float radians) {
    manualDirectionController.changeRightLeft(radians);
  }

  /**
   * Moves the pointing up and down.
   * 
   * @param radians the angular change in the pointing in radians (only
   * accurate in the limit as radians tends to 0.)
   */
  public void changeUpDown(float radians) {
    manualDirectionController.changeUpDown(radians);
  }

  /**
   * Rotates the view about the current center point.
   */
  public void rotate(float degrees) {
    manualDirectionController.rotate(degrees);
  }

  /**
   * Sends the astronomer's pointing to the new target.
   *
   * @param target the destination
   */
  public void teleport(Vector3 target) {
    teleportingController.teleport(target);
  }
  
  /**
   * Adds a new controller to this 
   */
  @VisibleForTesting
  public void addController(Controller controller) {
    controllers.add(controller);
  }

  public void zoomBy(float ratio) {
    zoomController.zoomBy(ratio);
  }
}
app/src/main/java/com/google/android/stardroid/renderer/RendererController.java
public class RendererController extends RendererControllerBase {
  /**
   * Used for grouping renderer calls into atomic units.
   */
  public static class AtomicSection extends RendererControllerBase {
    private Queuer mQueuer = new Queuer();
    private static int NEXT_ID = 0;
    private int mID;

    private AtomicSection(SkyRenderer renderer) {
      super(renderer);
      synchronized(AtomicSection.class) {
        mID = NEXT_ID++;
      }
    }

    @Override
    protected EventQueuer getQueuer() {
      return mQueuer;
    }

    @Override
    public String toString() {
      return "AtomicSection" + mID;
    }

    private Queue<Runnable> releaseEvents() {
      Queue<Runnable> queue = mQueuer.mQueue;
      mQueuer = new Queuer();
      return queue;
    }

    private static class Queuer implements EventQueuer {
      private Queue<Runnable> mQueue = new LinkedList<Runnable>();
      public void queueEvent(Runnable r) {
        mQueue.add(r);
      }
    }
  }

  private final EventQueuer mQueuer;

  @Override
  protected EventQueuer getQueuer() {
    return mQueuer;
  }

  public RendererController(SkyRenderer renderer, final GLSurfaceView view) {
    super(renderer);
    mQueuer = view::queueEvent;
  }

  @Override
  public String toString() {
    return "RendererController";
  }

  public AtomicSection createAtomic() {
    return new AtomicSection(mRenderer);
  }

  public void queueAtomic(final AtomicSection atomic) {
    String msg = "Applying " + atomic.toString();
    queueRunnable(msg, CommandType.Synchronization, new Runnable() { public void run() {
      Queue<Runnable> events = atomic.releaseEvents();
      for (Runnable r : events) {
        r.run();
      }
    }});
  }
}
app/src/main/java/com/google/android/stardroid/control/AstronomerModel.java
class Pointing {
  // Geocentric coordinates
  private final Vector3 lineOfSight;
  private final Vector3 perpendicular;

  public Pointing(Vector3 lineOfSight,
                  Vector3 perpendicular) {
    this.lineOfSight = lineOfSight.copyForJ();
    this.perpendicular = perpendicular.copyForJ();
  }

  public Pointing() {
    this(new Vector3(1, 0, 0),
         new Vector3(0, 1, 0));
  }

  /**
   * Gets the line of sight component of the pointing.
   * Warning: creates a copy - if you can reuse your own
   * GeocentricCoordinates object it might be more efficient to
   * use {@link #getLineOfSightX()} etc.
   */
  public Vector3 getLineOfSight() {
    return lineOfSight.copyForJ();
  }

  /**
   * Gets the perpendicular component of the pointing.
   * Warning: creates a copy - if you can reuse your own
   * GeocentricCoordinates object it might be more efficient to
   * use {@link #getLineOfSightX()} etc.
   */
  public Vector3 getPerpendicular() {
    return perpendicular.copyForJ();
  }

  public float getLineOfSightX() {
    return lineOfSight.x;
  }
  public float getLineOfSightY() {
    return lineOfSight.y;
  }
  public float getLineOfSightZ() {
    return lineOfSight.z;
  }
  public float getPerpendicularX() {
    return perpendicular.x;
  }
  public float getPerpendicularY() {
    return perpendicular.y;
  }
  public float getPerpendicularZ() {
    return perpendicular.z;
  }

  /**
   * Only the AstronomerModel should change this.
   */
  void updatePerpendicular(Vector3 newPerpendicular) {
    perpendicular.assign(newPerpendicular);
  }

  /**
   * Only the AstronomerModel should change this.
   */
  void updateLineOfSight(Vector3 newLineOfSight) {
    lineOfSight.assign(newLineOfSight);
  }
}
app/src/main/java/com/google/android/stardroid/activities/dialogs/NoSensorsDialogFragment.java
public class NoSensorsDialogFragment extends DialogFragment {
  private static final String TAG = MiscUtil.getTag(NoSensorsDialogFragment.class);
  @Inject Activity parentActivity;
  @Inject SharedPreferences preferences;

  public interface ActivityComponent {
    void inject(NoSensorsDialogFragment fragment);
  }

  @Override
  public Dialog onCreateDialog(Bundle savedInstanceState) {
    // Activities using this dialog MUST implement this interface.  Obviously.
    ((HasComponent<ActivityComponent>) getActivity()).getComponent().inject(this);

    LayoutInflater inflater = parentActivity.getLayoutInflater();
    final View view = inflater.inflate(R.layout.no_sensor_warning, null);
    return new AlertDialog.Builder(parentActivity)
        .setTitle(R.string.warning_dialog_title)
        .setView(view).setNegativeButton(android.R.string.ok,
            new DialogInterface.OnClickListener() {
              public void onClick(DialogInterface dialog, int whichButton) {
                Log.d(TAG, "No Sensor Dialog closed");
                preferences.edit().putBoolean(
                    ApplicationConstants.NO_WARN_ABOUT_MISSING_SENSORS,
                    ((CheckBox) view.findViewById(R.id.no_show_dialog_again)).isChecked()).commit();
                dialog.dismiss();
              }
            }).create();
  }
}
app/src/main/java/com/google/android/stardroid/activities/util/ActivityLightLevelChanger.java
public class ActivityLightLevelChanger {
  /**
   * Activities that have some kind of custom night mode (rather than just
   * dimming the screen) implement this.
   *
   * @author John Taylor
   *
   */
  public interface NightModeable {
    void setNightMode(boolean nightMode);
  }

  // This value is based on inspecting the Android source code for the
  // SettingsAppWidgetProvider:
  // http://hi-android.info/src/com/android/settings/widget/SettingsAppWidgetProvider.java.html
  // (We know that 0.05 is OK on the G1 and N1, but not some other phones, so we don't make this
  // as dim as we could...)
  private static final float BRIGHTNESS_DIM = (float) 20f / 255f;

  private NightModeable nightModeable;
  private Activity activity;

  /**
   * Wraps an activity with a setNightMode method.
   *
   * @param activity the activity under control
   * @param nightmodeable Allows an activity to have a custom night mode method.  May be null.
   */
  public ActivityLightLevelChanger(Activity activity, @Nullable NightModeable nightmodeable) {
    this.activity = activity;
    this.nightModeable = nightmodeable;
  }

  // current setting.
  public void setNightMode(boolean nightMode) {
    if (nightModeable != null) {
      nightModeable.setNightMode(nightMode);
    }
    Window window = activity.getWindow();
    WindowManager.LayoutParams params = window.getAttributes();
    if (nightMode) {
      params.screenBrightness = BRIGHTNESS_DIM;
      params.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;    // TODO(jontayler): look at this again - at present night mode can be brighter than the phone's
    } else {
      params.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
      params.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
    }
    window.setAttributes(params);
  }
}
app/src/main/java/com/google/android/stardroid/control/AstronomerModel.java
public interface AstronomerModel {
  /**
   * A POJO to hold the user's view direction.
   *
   * @author John Taylor
   */
  class Pointing {
    // Geocentric coordinates
    private final Vector3 lineOfSight;
    private final Vector3 perpendicular;

    public Pointing(Vector3 lineOfSight,
                    Vector3 perpendicular) {
      this.lineOfSight = lineOfSight.copyForJ();
      this.perpendicular = perpendicular.copyForJ();
    }

    public Pointing() {
      this(new Vector3(1, 0, 0),
           new Vector3(0, 1, 0));
    }

    /**
     * Gets the line of sight component of the pointing.
     * Warning: creates a copy - if you can reuse your own
     * GeocentricCoordinates object it might be more efficient to
     * use {@link #getLineOfSightX()} etc.
     */
    public Vector3 getLineOfSight() {
      return lineOfSight.copyForJ();
    }

    /**
     * Gets the perpendicular component of the pointing.
     * Warning: creates a copy - if you can reuse your own
     * GeocentricCoordinates object it might be more efficient to
     * use {@link #getLineOfSightX()} etc.
     */
    public Vector3 getPerpendicular() {
      return perpendicular.copyForJ();
    }

    public float getLineOfSightX() {
      return lineOfSight.x;
    }
    public float getLineOfSightY() {
      return lineOfSight.y;
    }
    public float getLineOfSightZ() {
      return lineOfSight.z;
    }
    public float getPerpendicularX() {
      return perpendicular.x;
    }
    public float getPerpendicularY() {
      return perpendicular.y;
    }
    public float getPerpendicularZ() {
      return perpendicular.z;
    }

    /**
     * Only the AstronomerModel should change this.
     */
    void updatePerpendicular(Vector3 newPerpendicular) {
      perpendicular.assign(newPerpendicular);
    }

    /**
     * Only the AstronomerModel should change this.
     */
    void updateLineOfSight(Vector3 newLineOfSight) {
      lineOfSight.assign(newLineOfSight);
    }
  }

  /**
   * If set to false, will not update the pointing automatically.
   */
  void setAutoUpdatePointing(boolean autoUpdatePointing);

  /**
   * Gets the field of view in degrees.
   */
  float getFieldOfView();

  void setFieldOfView(float degrees);

  enum ViewDirectionMode {STANDARD, ROTATE90, TELESCOPE}
  void setViewDirectionMode(ViewDirectionMode value);

  float getMagneticCorrection();

  /**
   * Returns the time, as UTC.
   */
  Date getTime();

  /**
   * Sets the clock that provides the time.
   */
  void setClock(Clock clock);

  /**
   * Returns the astronomer's current location on Earth.
   */
  LatLong getLocation();

  /**
   * Sets the user's current position on Earth.
   */
  void setLocation(LatLong location);

  /**
   * Gets the user's direction of view.
   */
  Pointing getPointing();

  /**
   * Sets the user's direction of view.
   */
  void setPointing(Vector3 lineOfSight, Vector3 perpendicular);

  /**
   * Gets the acceleration vector in the phone frame of reference.
   * 
   * <p>The returned object should not be modified.
   */
  Vector3 getPhoneUpDirection();

  /**
   * Sets the acceleration and magnetic field in the phone frame.
   * 
   * <p>The phone frame has x along the short side of the phone increasing to
   * the right, y along the long side increasing towards the top of the phone,
   * and z coming perpendicularly out of the phone increasing towards the user.
   */
  void setPhoneSensorValues(Vector3 acceleration, Vector3 magneticField);

  /**
   * Sets the phone's rotation vector from the fused gyro/mag field/accelerometer.
   * Alternative to {@link #setPhoneSensorValues(Vector3, Vector3)}
   */
  void setPhoneSensorValues(float[] rotationVector);

  /**
   * Returns the user's North in celestial coordinates.
   */
  Vector3 getNorth();

  /**
   * Returns the user's South in celestial coordinates.
   */
  Vector3 getSouth();

  /**
   * Returns the user's Zenith in celestial coordinates.
   */
  Vector3 getZenith();

  /**
   * Returns the user's Nadir in celestial coordinates.
   */
  Vector3 getNadir();

  /**
   * Returns the user's East in celestial coordinates.
   */
  Vector3 getEast();

  /**
   * Returns the user's West in celestial coordinates.
   */
  Vector3 getWest();

  void setMagneticDeclinationCalculator(MagneticDeclinationCalculator calculator);

  long getTimeMillis();
}
app/src/main/java/com/google/android/stardroid/activities/dialogs/NoSearchResultsDialogFragment.java
public class NoSearchResultsDialogFragment extends DialogFragment {
  private static final String TAG = MiscUtil.getTag(NoSearchResultsDialogFragment.class);
  @Inject Activity parentActivity;

  public interface ActivityComponent {
    void inject(NoSearchResultsDialogFragment fragment);
  }

  @Override
  public Dialog onCreateDialog(Bundle savedInstanceState) {
    // Activities using this dialog MUST implement this interface.  Obviously.
    ((HasComponent<ActivityComponent>) getActivity()).getComponent().inject(this);

    return new AlertDialog.Builder(parentActivity)
        .setTitle(R.string.no_search_title).setMessage(R.string.no_search_results_text2)
        .setNegativeButton(android.R.string.ok, new DialogInterface.OnClickListener() {
          public void onClick(DialogInterface dialog1, int whichButton) {
            Log.d(TAG, "No search results Dialog closed");
            dialog1.dismiss();
          }
        }).create();
  }
}
app/src/main/java/com/google/android/stardroid/activities/dialogs/MultipleSearchResultsDialogFragment.java
public class MultipleSearchResultsDialogFragment extends DialogFragment {
  private static final String TAG = MiscUtil.getTag(MultipleSearchResultsDialogFragment.class);
  @Inject DynamicStarMapActivity parentActivity;

  private ArrayAdapter<SearchResult> multipleSearchResultsAdaptor;

  public interface ActivityComponent {
    void inject(MultipleSearchResultsDialogFragment fragment);
  }

  @Override
  public Dialog onCreateDialog(Bundle savedInstanceState) {
    // Activities using this dialog MUST implement this interface.  Obviously.
    ((HasComponent<ActivityComponent>) getActivity()).getComponent().inject(this);

    // TODO(jontayler): inject
    multipleSearchResultsAdaptor = new ArrayAdapter<>(
        parentActivity, android.R.layout.simple_list_item_1, new ArrayList<SearchResult>());


    DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() {
      public void onClick(DialogInterface dialog, int whichButton) {
        if (whichButton == Dialog.BUTTON_NEGATIVE) {
          Log.d(TAG, "Many search results Dialog closed with cancel");
        } else {
          final SearchResult item = multipleSearchResultsAdaptor.getItem(whichButton);
          parentActivity.activateSearchTarget(item.coords(), item.getCapitalizedName());
        }
        dialog.dismiss();
      }
    };

    return new AlertDialog.Builder(parentActivity)
        .setTitle(R.string.many_search_results_title)
        .setNegativeButton(android.R.string.cancel, onClickListener)
        .setAdapter(multipleSearchResultsAdaptor, onClickListener)
        .create();
  }

  public void clearResults() {
    multipleSearchResultsAdaptor.clear();
  }

  public void add(SearchResult result) {
    multipleSearchResultsAdaptor.add(result);
  }
}
app/src/gms/java/com/google/android/stardroid/util/Analytics.java
public class Analytics implements AnalyticsInterface {
  /**
   * Analytics ID associated with http://stardroid-server.appspot.com
   */
  private final HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder();
  private FirebaseAnalytics firebaseAnalytics;
  private static final String TAG = MiscUtil.getTag(Analytics.class);

  @Inject
  Analytics(StardroidApplication application) {
    firebaseAnalytics = FirebaseAnalytics.getInstance(application);
    Task<String> appId = firebaseAnalytics.getAppInstanceId();
    appId.addOnCompleteListener(task -> Log.d(TAG, "Firebase ID " + task.getResult()));
  }

  @Override
  public void setEnabled(boolean enabled) {
    Log.d(TAG, enabled ? "Enabling stats collection" : "Disabling stats collection");
    firebaseAnalytics.setAnalyticsCollectionEnabled(enabled);
  }

  @Override
  public void trackEvent(String event, Bundle params) {
    Log.d(TAG, String.format("Logging event %s, %s", event, params));
    firebaseAnalytics.logEvent(event, params);
  }

  @Override
  public void setUserProperty(String propertyName, String propertyValue) {
    Log.d(TAG, String.format("Logging user property %s, %s", propertyName, propertyValue));
    firebaseAnalytics.setUserProperty(propertyName, propertyValue);
  }
}
app/src/main/java/com/google/android/stardroid/activities/util/FullscreenControlsManager.java
public class FullscreenControlsManager {
  private static final String TAG = MiscUtil.getTag(FullscreenControlsManager.class);
  /**
   * Whether or not the system UI should be auto-hidden after
   * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
   */
  private static final boolean AUTO_HIDE = true;

  /**
   * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
   * user interaction before hiding the system UI.
   */
  private static final int AUTO_HIDE_DELAY_MILLIS = 1000;

  /**
   * Some older devices needs a small delay between UI widget updates
   * and a change of the status and navigation bar.
   */
  private static final int UI_ANIMATION_DELAY = 300;
  public static final int INITIALLY_SHOW_CONTROLS_FOR_MILLIS = 1000;

  private Activity mActivity;
  private View mContentView;
  private List<View> mViewsToHide;
  private boolean mVisible;

  public FullscreenControlsManager(
          Activity parentActivity, View contentView,
          List<View> viewsToHide, List<View> viewsToTriggerHide) {
    mActivity = parentActivity;
    mVisible = true;
    mViewsToHide = new ArrayList(viewsToHide);
    mContentView = contentView;

    // Upon interacting with UI controls, delay any scheduled hide()
    // operations to prevent the jarring behavior of controls going away
    // while interacting with the UI.
    for (View buttonView : viewsToTriggerHide) {
      buttonView.setOnTouchListener(mDelayHideTouchListener);
    }
  }

  public void toggleControls() {
    Log.d(TAG, "Toggling the UI");
    toggle();
  }

  /**
   * Quickly exposes the controls so that the user knows they're there.
   */
  public void flashTheControls() {

    // Trigger the initial hide() shortly after the activity has been
    // created, to briefly hint to the user that UI controls
    // are available.
    delayedHide(INITIALLY_SHOW_CONTROLS_FOR_MILLIS);
  }

  public void delayHideTheControls() {
    delayedHide(AUTO_HIDE_DELAY_MILLIS);
  }

  /**
   * Touch listener to use for in-layout UI controls to delay hiding the
   * system UI. This is to prevent the jarring behavior of controls going away
   * while interacting with activity UI.
   */
  private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
      if (AUTO_HIDE) {
        delayedHide(AUTO_HIDE_DELAY_MILLIS);
      }
      return false;
    }
  };

  private void toggle() {
    if (mVisible) {
      hide();
    } else {
      show();
    }
  }

  private void hide() {
    // Hide UI first
    ActionBar actionBar = mActivity.getActionBar();
    if (actionBar != null) {
      actionBar.hide();
    }
    for (View view : mViewsToHide) {
      view.setVisibility(View.GONE);
    }
    mVisible = false;

    // Schedule a runnable to remove the status and navigation bar after a delay
    mHideHandler.removeCallbacks(mShowPart2Runnable);
    mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
  }

  private final Runnable mHidePart2Runnable = new Runnable() {
    @SuppressLint("InlinedApi")
    @Override
    public void run() {
      // Delayed removal of status and navigation bar

      // Note that some of these constants are new as of API 16 (Jelly Bean)
      // and API 19 (KitKat). It is safe to use them, as they are inlined
      // at compile-time and do nothing on earlier devices.
      mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
              | View.SYSTEM_UI_FLAG_FULLSCREEN
              | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
              | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
              | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
              | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
    }
  };

  @SuppressLint("InlinedApi")
  private void show() {
    // Show the system bar
    mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
    mVisible = true;

    // Schedule a runnable to display UI elements after a delay
    mHideHandler.removeCallbacks(mHidePart2Runnable);
    mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
  }

  private final Runnable mShowPart2Runnable = new Runnable() {
    @Override
    public void run() {
      // Delayed display of UI elements
      ActionBar actionBar = mActivity.getActionBar();
      if (actionBar != null) {
        actionBar.show();
      }
      for (View view : mViewsToHide) {
        view.setVisibility(View.VISIBLE);
      }
    }
  };

  private final Handler mHideHandler = new Handler();

  private final Runnable mHideRunnable = this::hide;

  /**
   * Schedules a call to hide() in [delay] milliseconds, canceling any
   * previously scheduled calls.
   */
  private void delayedHide(int delayMillis) {
    mHideHandler.removeCallbacks(mHideRunnable);
    mHideHandler.postDelayed(mHideRunnable, delayMillis);
  }
}
app/src/main/java/com/google/android/stardroid/util/AnalyticsInterface.java
public interface AnalyticsInterface {
  String PREF_KEY = "enable_analytics";

  // User properties
  String NEW_USER = "new_user_prop";  // Might be the same as the build-in prop - let's check
  String DEVICE_SENSORS = "device_sensors_prop"; // Alphabetically ordered list of relevant sensors
  String DEVICE_SENSORS_NONE = "none";
  String DEVICE_SENSORS_ACCELEROMETER = "accel";
  String DEVICE_SENSORS_GYRO = "gyro";
  String DEVICE_SENSORS_MAGNETIC = "mag";
  String DEVICE_SENSORS_ROTATION = "rot";
  // Phone claims to have a sensor, but then doesn't allow registration of a listener.
  String SENSOR_LIAR = "sensor_liar_prop";

  // Events & Categories
  String TOS_ACCEPTED_EVENT = "TOS_accepted_ev";
  String TOS_REJECTED_EVENT = "TOS_rejected_ev";
  String PREFERENCE_BUTTON_TOGGLE_EVENT = "preference_button_toggled_ev";
  String PREFERENCE_BUTTON_TOGGLE_VALUE = "preference_toggle_value";
  String PREFERENCE_CHANGE_EVENT = "preference_change_ev";
  String PREFERENCE_CHANGE_EVENT_VALUE = "value";
  String TOGGLED_MANUAL_MODE_LABEL = "toggled_manual_mode_ev";
  String MENU_ITEM_EVENT = "menu_item_pressed_ev";
  String MENU_ITEM_EVENT_VALUE = "menu_item";
  String TOGGLED_NIGHT_MODE_LABEL = "night_mode";
  String SEARCH_REQUESTED_LABEL = "search_requested";
  String SETTINGS_OPENED_LABEL = "settings_opened";
  String HELP_OPENED_LABEL = "help_opened";
  String CALIBRATION_OPENED_LABEL = "calibration_opened";
  String TIME_TRAVEL_OPENED_LABEL = "time_travel_opened";
  String GALLERY_OPENED_LABEL = "gallery_opened";
  String TOS_OPENED_LABEL = "TOS_opened";
  String DIAGNOSTICS_OPENED_LABEL = "diagnostics_opened";
  String SEARCH_EVENT = FirebaseAnalytics.Event.SEARCH;
  String SEARCH_TERM = FirebaseAnalytics.Param.SEARCH_TERM;
  String SEARCH_SUCCESS = "search_success";
  String START_EVENT = "start_up_event_ev";
  String START_EVENT_HOUR = "hour";

  String SESSION_LENGTH_EVENT = "session_length_ev";
  String SESSION_LENGTH_TIME_VALUE = "session_length";

  void setEnabled(boolean enabled);

  /**
   * Tracks an event.
   *
   * @see com.google.firebase.analytics.FirebaseAnalytics
   */
  void trackEvent(String event, Bundle params);

  void setUserProperty(String propertyName, String propertyValue);
}
app/src/main/java/com/google/android/stardroid/activities/dialogs/TimeTravelDialogFragment.java
public class TimeTravelDialogFragment extends android.app.DialogFragment {
  private static final String TAG = MiscUtil.getTag(TimeTravelDialogFragment.class);
  @Inject DynamicStarMapActivity parentActivity;

  public interface ActivityComponent {
    void inject(TimeTravelDialogFragment fragment);
  }

  @Override
  public Dialog onCreateDialog(Bundle savedInstanceState) {
    // Activities using this dialog MUST implement this interface.  Obviously.
    ((HasComponent<ActivityComponent>) getActivity()).getComponent().inject(this);

    return new TimeTravelDialog(parentActivity,
        parentActivity.getModel());
  }
}
app/src/main/java/com/google/android/stardroid/activities/dialogs/EulaDialogFragment.java
public class EulaDialogFragment extends DialogFragment {
  private static final String TAG = MiscUtil.getTag(EulaDialogFragment.class);
  @Inject Activity parentActivity;
  @Inject Analytics analytics;
  private EulaAcceptanceListener resultListener;

  public interface EulaAcceptanceListener {
    void eulaAccepted();
    void eulaRejected();
  }

  public interface ActivityComponent {
    void inject(EulaDialogFragment fragment);
  }

  public void setEulaAcceptanceListener(EulaAcceptanceListener resultListener) {
    this.resultListener = resultListener;
  }

  @Override
  public Dialog onCreateDialog(Bundle savedInstanceState) {
    Log.d(TAG, "onCreateDialog");
    // Activities using this dialog MUST implement this interface.  Obviously.
    ((HasComponent<ActivityComponent>) getActivity()).getComponent().inject(this);

    LayoutInflater inflater = parentActivity.getLayoutInflater();
    View view = inflater.inflate(R.layout.tos_view, null);

    String apologyText = parentActivity.getString(R.string.language_apology_text);
    Spanned formattedApologyText = Html.fromHtml(apologyText);
    TextView apologyTextView = (TextView) view.findViewById(R.id.language_apology_box_text);
    apologyTextView.setText(formattedApologyText, TextView.BufferType.SPANNABLE);

    String eulaText = parentActivity.getString(R.string.eula_text);
    Spanned formattedEulaText = Html.fromHtml(eulaText);
    TextView eulaTextView = (TextView) view.findViewById(R.id.eula_box_text);
    eulaTextView.setText(formattedEulaText, TextView.BufferType.SPANNABLE);

    AlertDialog.Builder tosDialogBuilder = new AlertDialog.Builder(parentActivity)
        .setTitle(R.string.menu_tos)
        .setView(view);
    if (resultListener != null) {
      tosDialogBuilder
          .setPositiveButton(R.string.dialog_accept,
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                  acceptEula(dialog);
                }
              })
          .setNegativeButton(R.string.dialog_decline,
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                  rejectEula(dialog);
                }
              });
    }
    return tosDialogBuilder.create();
  }

  private void acceptEula(DialogInterface dialog) {
    Log.d(TAG, "TOS Dialog closed.  User accepts.");
    dialog.dismiss();
    analytics.trackEvent(Analytics.TOS_ACCEPTED_EVENT, new Bundle());
    if (resultListener != null) {
      resultListener.eulaAccepted();
    }
  }

  private void rejectEula(DialogInterface dialog) {
    Log.d(TAG, "TOS Dialog closed.  User declines.");
    dialog.dismiss();
    analytics.trackEvent(Analytics.TOS_REJECTED_EVENT, new Bundle());
    if (resultListener != null) {
      resultListener.eulaRejected();
    }
  }

  private String getVersionName() {
    return ((StardroidApplication) parentActivity.getApplication()).getVersionName();
  }

  @Override
  public void onCancel(DialogInterface dialog) {
    rejectEula(dialog);
  }
}


Question: import android.app.FragmentManager;
import android.app.SearchManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.media.MediaPlayer;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import com.google.android.stardroid.ApplicationConstants;
import com.google.android.stardroid.R;
import com.google.android.stardroid.activities.dialogs.EulaDialogFragment;
import com.google.android.stardroid.activities.dialogs.HelpDialogFragment;
import com.google.android.stardroid.activities.dialogs.MultipleSearchResultsDialogFragment;
import com.google.android.stardroid.activities.dialogs.NoSearchResultsDialogFragment;
import com.google.android.stardroid.activities.dialogs.NoSensorsDialogFragment;
import com.google.android.stardroid.activities.dialogs.TimeTravelDialogFragment;
import com.google.android.stardroid.activities.util.ActivityLightLevelChanger;
import com.google.android.stardroid.activities.util.ActivityLightLevelChanger.NightModeable;
import com.google.android.stardroid.activities.util.ActivityLightLevelManager;
import com.google.android.stardroid.activities.util.FullscreenControlsManager;
import com.google.android.stardroid.activities.util.GooglePlayServicesChecker;
import com.google.android.stardroid.base.Lists;
import com.google.android.stardroid.control.AstronomerModel;
import com.google.android.stardroid.control.AstronomerModel.Pointing;
import com.google.android.stardroid.control.ControllerGroup;
import com.google.android.stardroid.control.MagneticDeclinationCalculatorSwitcher;
import com.google.android.stardroid.inject.HasComponent;
import com.google.android.stardroid.layers.LayerManager;
import com.google.android.stardroid.math.CoordinateManipulationsKt;
import com.google.android.stardroid.math.MathUtils;
import com.google.android.stardroid.math.Vector3;
import com.google.android.stardroid.renderer.RendererController;
import com.google.android.stardroid.renderer.SkyRenderer;
import com.google.android.stardroid.search.SearchResult;
import com.google.android.stardroid.touch.DragRotateZoomGestureDetector;
import com.google.android.stardroid.touch.GestureInterpreter;
import com.google.android.stardroid.touch.MapMover;
import com.google.android.stardroid.util.Analytics;
import com.google.android.stardroid.util.AnalyticsInterface;
import com.google.android.stardroid.util.MiscUtil;
import com.google.android.stardroid.util.SensorAccuracyMonitor;
import com.google.android.stardroid.views.ButtonLayerView;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
// 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.activities;






/**
 * The main map-rendering Activity.
 */
public class DynamicStarMapActivity extends InjectableActivity
    implements OnSharedPreferenceChangeListener, HasComponent<DynamicStarMapComponent> {
  private static final int TIME_DISPLAY_DELAY_MILLIS = 1000;
  private FullscreenControlsManager fullscreenControlsManager;

  @Override
  public DynamicStarMapComponent getComponent() {
    return daggerComponent;
  }

  /**
   * Passed to the renderer to get per-frame updates from the model.
   *
   * @author John Taylor
   */
  private static final class RendererModelUpdateClosure implements Runnable {
    private RendererController rendererController;
    private AstronomerModel model;
    private boolean viewDirectionMode;

    public RendererModelUpdateClosure(AstronomerModel model,
        RendererController rendererController, SharedPreferences sharedPreferences) {
      this.model = model;
      this.rendererController = rendererController;
      // TODO(jontayler): figure out why we need to do this here.
      updateViewDirectionMode(model, sharedPreferences);
    }

    @Override
    public void run() {
      Pointing pointing = model.getPointing();
      float directionX = pointing.getLineOfSightX();
      float directionY = pointing.getLineOfSightY();
      float directionZ = pointing.getLineOfSightZ();

      float upX = pointing.getPerpendicularX();
      float upY = pointing.getPerpendicularY();
      float upZ = pointing.getPerpendicularZ();

      rendererController.queueSetViewOrientation(directionX, directionY, directionZ, upX, upY, upZ);

      Vector3 up = model.getPhoneUpDirection();
      rendererController.queueTextAngle(MathUtils.atan2(up.x, up.y));
      rendererController.queueViewerUpDirection(model.getZenith().copyForJ());

      float fieldOfView = model.getFieldOfView();
      rendererController.queueFieldOfView(fieldOfView);
    }
  }

  private static void updateViewDirectionMode(AstronomerModel model, SharedPreferences sharedPreferences) {
    String viewDirectionMode =
        sharedPreferences.getString(ApplicationConstants.VIEW_MODE_PREFKEY, "STANDARD");
    switch(viewDirectionMode) {
      case "ROTATE90":
        model.setViewDirectionMode(AstronomerModel.ViewDirectionMode.ROTATE90);
        break;
      case "TELESCOPE":
        model.setViewDirectionMode(AstronomerModel.ViewDirectionMode.TELESCOPE);
        break;
      default:
        model.setViewDirectionMode(AstronomerModel.ViewDirectionMode.STANDARD);
    }
  }

  // Activity for result Ids
  public static final int GOOGLE_PLAY_SERVICES_REQUEST_CODE = 1;
  public static final int GOOGLE_PLAY_SERVICES_REQUEST_LOCATION_PERMISSION_CODE = 2;
  // End Activity for result Ids

  private static final float ROTATION_SPEED = 10;
  private static final String TAG = MiscUtil.getTag(DynamicStarMapActivity.class);

  private ImageButton cancelSearchButton;
  @Inject ControllerGroup controller;
  private GestureDetector gestureDetector;
  @Inject AstronomerModel model;
  private RendererController rendererController;
  private boolean nightMode = false;
  private boolean searchMode = false;
  private Vector3 searchTarget = CoordinateManipulationsKt.getGeocentricCoords(0, 0);

  @Inject SharedPreferences sharedPreferences;
  private GLSurfaceView skyView;
  private PowerManager.WakeLock wakeLock;
  private String searchTargetName;
  @Inject LayerManager layerManager;
  // TODO(widdows): Figure out if we should break out the
  // time dialog and time player into separate activities.
  private View timePlayerUI;
  private DynamicStarMapComponent daggerComponent;
  @Inject @Named("timetravel") Provider<MediaPlayer> timeTravelNoiseProvider;
  @Inject @Named("timetravelback") Provider<MediaPlayer> timeTravelBackNoiseProvider;
  private MediaPlayer timeTravelNoise;
  private MediaPlayer timeTravelBackNoise;
  @Inject Handler handler;
  @Inject Analytics analytics;
  @Inject GooglePlayServicesChecker playServicesChecker;
  @Inject FragmentManager fragmentManager;
  @Inject EulaDialogFragment eulaDialogFragmentNoButtons;
  @Inject TimeTravelDialogFragment timeTravelDialogFragment;
  @Inject HelpDialogFragment helpDialogFragment;
  @Inject NoSearchResultsDialogFragment noSearchResultsDialogFragment;
  @Inject MultipleSearchResultsDialogFragment multipleSearchResultsDialogFragment;
  @Inject NoSensorsDialogFragment noSensorsDialogFragment;
  @Inject SensorAccuracyMonitor sensorAccuracyMonitor;
  // A list of runnables to post on the handler when we resume.
  private List<Runnable> onResumeRunnables = new ArrayList<>();

  // We need to maintain references to these objects to keep them from
  // getting gc'd.
  @SuppressWarnings("unused")
  @Inject MagneticDeclinationCalculatorSwitcher magneticSwitcher;

  private DragRotateZoomGestureDetector dragZoomRotateDetector;
  @Inject Animation flashAnimation;
  private ActivityLightLevelManager activityLightLevelManager;
  private long sessionStartTime;

  @Override
  public void onCreate(Bundle icicle) {
    Log.d(TAG, "onCreate at " + System.currentTimeMillis());
    super.onCreate(icicle);

    daggerComponent = DaggerDynamicStarMapComponent.builder()
        .applicationComponent(getApplicationComponent())
        .dynamicStarMapModule(new DynamicStarMapModule(this)).build();
    daggerComponent.inject(this);

    sharedPreferences.registerOnSharedPreferenceChangeListener(this);

    // Set up full screen mode, hide the system UI etc.
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
                         WindowManager.LayoutParams.FLAG_FULLSCREEN);

    // TODO(jontayler): upgrade to
    // getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
    // when we reach API level 16.
    // http://developer.android.com/training/system-ui/immersive.html for the right way
    // to do it at API level 19.
    //getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);

    // Eventually we should check at the point of use, but this will do for now.  If the
    // user revokes the permission later then odd things may happen.
    playServicesChecker.maybeCheckForGooglePlayServices();

    initializeModelViewController();
    checkForSensorsAndMaybeWarn();

    // Search related
    setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);

    ActivityLightLevelChanger activityLightLevelChanger = new ActivityLightLevelChanger(this,
Answer:         new NightModeable() {