public class DatabaseAPI {
  private Context context;
  private DatabaseInterface databaseInterface;

  public DatabaseAPI(Context c) {
    this.context = c;
    this.databaseInterface = new DatabaseInterface(this.context);
    this.databaseInterface.open();
  }

  // Get all recipes in database.
  public ArrayList<Recipe> getRecipeList() {
    ArrayList<Recipe> list = this.databaseInterface.getRecipeList();

    for (Recipe r : list) {
      r.update();
    }

    return list;
  }

  // Create recipe with the given name
  public Recipe createRecipeWithName(String name) {
    Recipe r = new Recipe(name);

    long id = this.databaseInterface.addRecipeToDatabase(r);
    r = this.databaseInterface.getRecipeWithId(id);

    return r;
  }

  // Creates recipe from an existing one.
  public Recipe createRecipeFromExisting(Recipe r) {
    long id = this.databaseInterface.addRecipeToDatabase(r);
    r = this.databaseInterface.getRecipeWithId(id);

    return r;
  }

  // Updates existing recipe
  public boolean updateRecipe(Recipe r) {
    r.update();
    return this.databaseInterface.updateExistingRecipe(r);
  }

  // Updates existing ingredient
  public boolean updateIngredient(Ingredient i, long dbid) {
    return this.databaseInterface.updateExistingIngredientInDatabase(i, dbid);
  }

  // Deletes the given recipe if it exists in the database.
  public boolean deleteRecipe(Recipe r) {
    // Delete all ingredients for this recipe.
    for (Ingredient i : r.getIngredientList()) {
      this.deleteIngredientWithId(i.getId(), i.getDatabaseId());
    }

    // Delete the MashProfile.
    this.deleteMashProfileFromDatabase(r.getMashProfile(), Constants.DATABASE_USER_RECIPES);

    return this.databaseInterface.deleteRecipeIfExists(r.getId());
  }

  // Deletes all recipes, and their ingredients
  public boolean deleteAllRecipes() {
    boolean bool = true;

    for (Recipe r : getRecipeList()) {
      for (Ingredient i : r.getIngredientList()) {
        deleteIngredientWithId(i.getId(), Constants.DATABASE_USER_RECIPES);
      }
      bool = deleteRecipe(r);
    }
    return bool;
  }

  // Deletes the given ingredient, in the given database
  public boolean deleteIngredientWithId(long id, long dbid) {
    // Find the ingredient's owner, and touch it to indicate the recipe has changed.
    Ingredient i = this.getIngredientWithId(id);
    if (i != null) {
      this.databaseInterface.touchRecipe(i.getOwnerId());
    }

    Log.d("Database", "Trying to delete ingredient from database: " + dbid);
    boolean b = this.databaseInterface.deleteIngredientIfExists(id, dbid);
    if (b) {
      Log.d("Database", "Successfully deleted ingredient");
    }
    else {
      Log.d("Database", "Failed to delete ingredient");
    }
    return b;
  }

  // Gets the recipe with the given ID
  public Recipe getRecipeWithId(long id) throws ItemNotFoundException {
    // If we receive a special ID, handle that here
    if (id == Constants.INVALID_ID) {
      throw new ItemNotFoundException("Passed ID with value Utils.INVALID_ID");
    }

    // Actually perform the lookup
    return this.databaseInterface.getRecipeWithId(id);
  }

  // Gets the ingredient with the given ID
  public Ingredient getIngredientWithId(long id) {
    return this.databaseInterface.getIngredientWithId(id);
  }

  // Adds a list of ingredients to the specified virtual ingredient database
  public void addIngredientList(long dbid, ArrayList<Ingredient> list, long ownerId) {
    for (Ingredient i : list) {
      this.databaseInterface.addIngredientToDatabase(i, ownerId, dbid);
    }
  }

  // Adds a single ingredient to the specified virtual ingredient database
  public void addIngredient(long dbid, Ingredient i, long ownerId) {
    this.databaseInterface.addIngredientToDatabase(i, ownerId, dbid);
  }

  // Returns all ingredients in the given virtual database with the given ingredient type
  public ArrayList<Ingredient> getIngredients(long dbid, String type) {
    return this.databaseInterface.getIngredients(dbid, type);
  }

  // Returns all ingredients in the given database with the given ingredient type
  public ArrayList<Ingredient> getIngredients(long dbid) {
    return this.databaseInterface.getIngredients(dbid);
  }

  public ArrayList<BeerStyle> getStyles(long dbid) {
    return this.databaseInterface.getBeerStyles(dbid);
  }

  public void addStyleList(long databaseId, ArrayList<BeerStyle> list, long ownerID) {
    for (BeerStyle s : list) {
      this.databaseInterface.addStyleToDatabase(s, databaseId, ownerID);
    }
  }

  public void addMashProfileList(long dbid, ArrayList<MashProfile> list, long ownerId) {
    for (MashProfile p : list) {
      this.databaseInterface.addMashProfileToDatabase(p, ownerId, dbid);
    }
  }

  public long addMashProfile(long dbid, MashProfile p, long ownerId) {
    return this.databaseInterface.addMashProfileToDatabase(p, ownerId, dbid);
  }

  public void updateMashProfile(MashProfile p, long ownerId, long dbid) {
    this.databaseInterface.updateMashProfile(p, ownerId, dbid);
  }

  // Returns all mash profiles in the given database
  public ArrayList<MashProfile> getMashProfiles(long dbid) {
    return this.databaseInterface.getMashProfiles(dbid);
  }

  // Deletes the given mash profile
  public boolean deleteMashProfileFromDatabase(MashProfile p, long dbid) {
    for (MashStep s : p.getMashStepList()) {
      this.databaseInterface.deleteMashStep(s.getId());
    }
    return this.databaseInterface.deleteMashProfile(p.getId(), dbid);
  }

  // Deletes the given style.
  public boolean deleteStyle(BeerStyle s, long database) {
    return this.databaseInterface.deleteStyle(s, database);
  }
} public class AddRecipeActivity extends AddEditActivity {

  // Rows to be displayed
  public View efficiencyView;
  public View batchSizeView;
  public View boilSizeView;
  public Spinner styleSpinner;
  public Spinner typeSpinner;
  public Spinner profileSpinner;

  // Titles
  public TextView efficiencyViewTitle;
  public TextView batchSizeViewTitle;
  public TextView boilSizeViewTitle;

  // Contents
  public TextView efficiencyViewText;
  public TextView batchSizeViewText;
  public TextView boilSizeViewText;

  // Data storage declarations
  public BeerStyle style;
  public MashProfile profile;
  public String type;
  public double efficiency;

  // Spinner array declarations
  public ArrayList<BeerStyle> styleArray;
  public ArrayList<String> typeArray;
  public ArrayList<MashProfile> profileArray;

  // Callbacks
  public BooleanCallback boilVolumeCallback;

  // Whether or not to open the DisplayRecipeActivity when the new 
  // recipe is saved.  This defaults to true.
  public boolean displayOnCreate = true;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Disable delete button for this view
    findViewById(R.id.delete_button).setVisibility(View.GONE);

    // Create the ingredient handler.
    this.ingredientHandler = new IngredientHandler(AddRecipeActivity.this);

    // Initialize each of the rows to be displayed
    mainView = (ViewGroup) findViewById(R.id.main_layout);
    efficiencyView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    batchSizeView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    ;
    boilSizeView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);

    // Set onClickListeners for edit text views
    efficiencyView.setOnClickListener(onClickListener);
    batchSizeView.setOnClickListener(onClickListener);
    boilSizeView.setOnClickListener(onClickListener);

    /************************************************************************
     ************* Add views to main view  **********************************
     *************************************************************************/
    mainView.removeAllViews();
    mainView.addView(nameView);
    mainView.addView(styleSpinner);
    mainView.addView(typeSpinner);
    mainView.addView(profileSpinner);
    mainView.addView(timeView);
    mainView.addView(efficiencyView);
    mainView.addView(batchSizeView);
    mainView.addView(boilSizeView);

    /************************************************************************
     ************* Get titles, set values   **********************************
     *************************************************************************/
    nameViewTitle.setText("Recipe Name");
    timeViewTitle.setText("Boil Time (mins)");

    efficiencyViewTitle = (TextView) efficiencyView.findViewById(R.id.title);
    efficiencyViewTitle.setText("Predicted Efficiency");

    batchSizeViewTitle = (TextView) batchSizeView.findViewById(R.id.title);
    batchSizeViewTitle.setText("Batch Size (" + Units.getVolumeUnits() + ")");

    boilSizeViewTitle = (TextView) boilSizeView.findViewById(R.id.title);
    boilSizeViewTitle.setText("Boil Size (" + Units.getVolumeUnits() + ")");

    /************************************************************************
     ************* Get content views, set values   **************************
     *************************************************************************/
    nameViewText.setText(mRecipe.getRecipeName());
    timeViewText.setText(String.format("%d", mRecipe.getBoilTime()));

    efficiencyViewText = (TextView) efficiencyView.findViewById(R.id.text);
    efficiencyViewText.setText(String.format("%2.2f", mRecipe.getEfficiency()));

    batchSizeViewText = (TextView) batchSizeView.findViewById(R.id.text);
    batchSizeViewText.setText(String.format("%2.2f", mRecipe.getDisplayBatchSize()));

    boilSizeViewText = (TextView) boilSizeView.findViewById(R.id.text);
    boilSizeViewText.setText(String.format("%2.2f", mRecipe.getDisplayBoilSize()));
  }

  @Override
  public void onMissedClick(View v) {
    AlertDialog alert;
    if (v.equals(efficiencyView)) {
      alert = alertBuilder.editTextFloatAlert(efficiencyViewText, efficiencyViewTitle).create();
    }
    else if (v.equals(batchSizeView)) {
      alert = alertBuilder.editTextFloatAlert(batchSizeViewText, batchSizeViewTitle).create();
    }
    else if (v.equals(boilSizeView)) {
      alert = alertBuilder.editTextFloatCheckBoxAlert(boilSizeViewText, boilSizeViewTitle,
                                                      mRecipe.getCalculateBoilVolume(),
                                                      boilVolumeCallback).create();
    }
    else {
      return;
    }

    // Force keyboard open and show popup
    alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    alert.show();
  }

  @Override
  public void getValuesFromIntent() {
    super.getValuesFromIntent();

    // Determine if we should display the recipe, or just finish upon save.
    displayOnCreate = getIntent().getBooleanExtra(Constants.DISPLAY_ON_CREATE, true);
  }

  @Override
  public void createCallback() {
    // Default callback, called when alertBuilders are finished.
    // Allows us to update fields that are dependent on other fields which may have been changed
    // by the user..
    callback = new Callback() {
      @Override
      public void call() {
        try {
          // Get new values
          Log.d("AddRecipeActivity", "Calling callback.");
          acquireValues();
        } catch (Exception e) {
          Log.d("AddRecipeActivity", "Exception in callback from alert dialog");
          e.printStackTrace();
        }
        // Update fields here
        boilSizeViewText.setText(String.format("%2.2f", mRecipe.getDisplayBoilSize()));
      }
    };

    // Callback for when boilVolume is updated.  We need to check if the option to auto-calc
    // boil volume has changed via user selection.
    boilVolumeCallback = new BooleanCallback() {
      @Override
      public void call(boolean b) {
        Log.d("AddRecipeActivity", "Boil volume checkbox pressed.");
        mRecipe.setCalculateBoilVolume(b);
        boilSizeViewText.setText(String.format("%2.2f", mRecipe.getDisplayBoilSize()));
      }
    };
  }

  @Override
  public void onRecipeNotFound() {
    Log.d("AddRecipeActivity", "onRecipeNotFound because we're creating one!");
    // Set values
    mRecipe = new Recipe("New Recipe");
    style = mRecipe.getStyle();
    profile = mRecipe.getMashProfile();
    type = mRecipe.getType();
    efficiency = mRecipe.getEfficiency();
  }

  @Override
  public void createSpinner() {
    // Set up style spinner here
    styleSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainView, false);
    BeerStyleSpinnerAdapter styleAdapter = new BeerStyleSpinnerAdapter(this, styleArray);
    styleAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    styleSpinner.setAdapter(styleAdapter);

    // Set up mash profile spinner
    profileSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainView, false);
    MashProfileSpinnerAdapter profileAdapter = new MashProfileSpinnerAdapter(this, profileArray);
    profileAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    profileSpinner.setAdapter(profileAdapter);

    // Set up brew type spinner
    typeSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainView, false);
    SpinnerAdapter typeAdapter = new SpinnerAdapter(this, typeArray, "Recipe Type");
    typeAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    typeSpinner.setAdapter(typeAdapter);
  }

  @Override
  public void setInitialSpinnerSelection() {
    typeSpinner.setSelection(typeArray.indexOf(mRecipe.getType()));
    profileSpinner.setSelection(profileArray.indexOf(mRecipe.getMashProfile()));
    styleSpinner.setSelection(styleArray.indexOf(mRecipe.getStyle()));
  }

  @Override
  public void configureSpinnerListener() {
    // Handle beer style selector here
    styleSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position,
                                 long id) {
        style = styleArray.get(position);
        callback.call();
      }

      public void onNothingSelected(AdapterView<?> parentView) {
      }

    });

    // Handle mash profile selector here
    profileSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position,
                                 long id) {
        profile = profileArray.get(position);
        Log.d("AddRecipeActivity", "Got profile: " + profile.getName());
        callback.call();
      }

      public void onNothingSelected(AdapterView<?> parentView) {
      }

    });

    // Handle type selector here
    typeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position,
                                 long id) {
        type = typeArray.get(position);
        callback.call();

        if (type.equals(Recipe.EXTRACT)) {
          profileSpinner.setVisibility(View.GONE);
          efficiencyView.setVisibility(View.GONE);
        }
        else {
          profileSpinner.setVisibility(View.VISIBLE);
          efficiencyView.setVisibility(View.VISIBLE);
        }
      }

      public void onNothingSelected(AdapterView<?> parentView) {
      }

    });
  }

  @Override
  public void getList() {
    // Get array of styles and mash profiles
    styleArray = this.ingredientHandler.getStylesList();
    profileArray = this.ingredientHandler.getMashProfileList();
    typeArray = new ArrayList<String>();
    typeArray.add(Recipe.ALL_GRAIN);
    typeArray.add(Recipe.PARTIAL_MASH);
    typeArray.add(Recipe.EXTRACT);

    // If it doesn't contain the current recipes style / profile,
    // then it is custom and we add it to the list.
    if (! styleArray.contains(mRecipe.getStyle())) {
      styleArray.add(0, mRecipe.getStyle());
    }
    if (! profileArray.contains(mRecipe.getMashProfile())) {
      profileArray.add(mRecipe.getMashProfile());
    }
  }

  @Override
  public void onCancelPressed() {
    setResult(Constants.RESULT_CANCELED, new Intent());
    finish();
  }

  @Override
  public void onDeletePressed() {

  }

  @Override
  public void acquireValues() throws Exception {
    super.acquireValues();

    efficiency = Double.parseDouble(efficiencyViewText.getText().toString().replace(",", "."));
    double batchSize = Double.parseDouble(batchSizeViewText.getText().toString().replace(",", "."));
    double boilSize = Double.parseDouble(boilSizeViewText.getText().toString().replace(",", "."));
    // Notes are no longer edited in the recipe details dialog
    //String notes = "";
    String brewDate = new SimpleDateFormat(Constants.BREW_DATE_FMT).format(new Date());

    mRecipe.setRecipeName(name);
    mRecipe.setBrewDate(brewDate);
    mRecipe.setVersion(Utils.getXmlVersion());
    mRecipe.setType(type);
    mRecipe.setStyle(style);
    mRecipe.setMashProfile(profile);
    mRecipe.setBrewer("Unknown Brewer"); // TODO
    mRecipe.setDisplayBatchSize(batchSize);
    mRecipe.setDisplayBoilSize(boilSize);
    mRecipe.setBoilTime(time);
    mRecipe.setEfficiency(efficiency);
    mRecipe.setBatchTime(1);
    //mRecipe.setNotes(notes);
    //mRecipe.setTasteNotes(notes);
  }

  @Override
  public boolean validateValues() {
    if (mRecipe.getDisplayBatchSize() <= 0) {
      Log.d("AddRecipeActivity", "Recipe batch size <= 0 - invalid input");
      new AlertDialog.Builder(this)
              .setTitle("Invalid Batch Size")
              .setMessage("Recipe batch size must be greater than zero.")
              .setPositiveButton("OK", null).show();
      return false;
    }

    // Values are all valid.
    return true;
  }

  @Override
  public void onFinished() {
    // Create the recipe in the database.
    Recipe r = new DatabaseAPI(getApplicationContext()).createRecipeFromExisting(mRecipe);

    if (this.displayOnCreate) {
      // Open up the display recipe activity.
      Intent intent = new Intent(AddRecipeActivity.this, DisplayRecipeActivity.class);
      intent.putExtra(Constants.KEY_RECIPE, r);
      intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
      startActivity(intent);
      finish();
    }
    else {
      // Otherwise, just finish the activity.
      finish();
    }
  }
} public class BrewTimerActivity extends AppCompatActivity {

  // Stores the recipe which is being brewed - provided by the caller via the Intent which
  // started this Activity.
  private Recipe mRecipe;

  // Holds the index of the current active brew step.  E.g, the step which is currently
  // being performed.
  private int currentActivePosition;

  // Holds the index of the brew step currently being viewed, independent of the step which is
  // being performed (if there is one).
  private int currentSelectedPosition;

  // Holds the currently SELECTED (not actively counting down) fragment and instruction.
  BrewTimerStepFragment f;
  Instruction inst;

  // Adapter
  BrewTimerCollectionPagerAdapter cpAdapter;

  // Formatter for countdown timer views.
  java.text.DecimalFormat nft;

  // Indicates if timer is running, paused, or stopped.  Can be one of Constants.RUNNING,
  // Constants.STOPPED, or Constants.PAUSED.
  private int timerState;

  // Views
  ViewGroup mainLayout;
  ViewPager mViewPager;
  TextView hoursView;
  TextView minutesView;
  TextView secondsView;
  View timerControls;
  View countdownTimerView;
  TextView stepStatusView;

  // Buttons to control the timer.
  ImageButton stopButton;
  ImageButton playPauseButton;
  ImageButton goToCurrentButton;

  // LayoutInflater to inflate views.
  LayoutInflater inflater;

  // Application context
  private Context appContext;

  // Receive timer updates using a CountdownReceiver.  The timer is run in the BrewTimerService,
  // which advertises the timer ticks received by this CountDownReceiver.  The receiver then
  // updates the UI to indicate the new time.
  private CountdownReceiver countdownReceiver = new CountdownReceiver() {
    @Override
    public void onNewTime(int seconds) {

      // Set the display
      setTimerFromSeconds(seconds);

      // Time for this step is up
      if (seconds == 0) {
        onStepComplete();
      }
      else {
        // We're receiving ticks, set the button to be "pause"
        playPauseButton.setImageResource(R.drawable.ic_pause_black_48dp);
        timerState = Constants.RUNNING;
      }
    }
  };

  private BroadcastReceiver queryReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      Log.d("BrewTimerActivity", "Received query response");
      Recipe qRecipe = intent.getParcelableExtra(Constants.KEY_RECIPE);
      int qCurrentStep = intent.getIntExtra(Constants.KEY_STEP_NUMBER, 0);
      int qSeconds = intent.getIntExtra(Constants.KEY_SECONDS, 0);
      int qTimerState = intent.getIntExtra(Constants.KEY_TIMER_STATE, Constants.STOPPED);

      // Set timer state
      timerState = qTimerState;

      // If this is not the recipe used by the current timer, alert the user and cancel the
      // activity.  We can't have two brew timers running at the same time.
      if (! qRecipe.equals(mRecipe)) {
        Log.e("BrewTimerActivity", "Currently running timer is not for this recipe, aborting");

        new AlertDialog.Builder(BrewTimerActivity.this)
                .setTitle("Cannot open timer")
                .setMessage("The brew timer is already running for another recipe.")
                .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int which) {
                    finishWithoutStoppingService();
                  }
                }).show();
        return;
      }

      if (qCurrentStep != 0) {
        Log.d("BrewTimerActivity", "Setting active step panel to be #" + qCurrentStep);
        mViewPager.setCurrentItem(qCurrentStep);
      }

      if (qSeconds != 0) {
        Log.d("BrewTimerActivity", "Setting timer based on queried seconds: " + qSeconds);
        setTimerFromSeconds(qSeconds);
      }

      // Update status bar text.
      setStatusText();
    }
  };

  @Override
  public void onCreate(Bundle savedInstanceState) {
    Log.d("BrewTimerActivity", "Begin onCreate() for BrewTimerActivity");

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_brew_timer);

    // Set title for this activity.
    setTitle("Timer");

    // Timer is not yet running
    timerState = Constants.STOPPED;
    currentActivePosition = 0;

    // Configure formatter used for timer values
    nft = new java.text.DecimalFormat("#00.###");
    nft.setDecimalSeparatorAlwaysShown(false);

    // Set icon as back button
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    // Get inflater
    inflater = getLayoutInflater();

    // Get main layout
    mainLayout = (LinearLayout) findViewById(R.id.main_layout);

    // Get context
    appContext = getApplicationContext();

    // Get recipe from the caller.
    mRecipe = getIntent().getParcelableExtra(Constants.KEY_RECIPE);

    // Get the current timer state from the caller.  If no timer state is provided, default
    // to STOPPED.  A timer state will be provided when this activity is being created after
    // the user pressed the timer notification.
    timerState = getIntent().getIntExtra(Constants.KEY_TIMER_STATE, Constants.STOPPED);
    Log.d("BrewTimerActivity", "Initial timer state: " + timerState);

    // Get current step - if the timer is already running, this may be non-zero, in which case
    // we must set the pager adapter to the correct step after we create it.
    currentActivePosition = getIntent().getIntExtra(Constants.KEY_STEP_NUMBER, 0);
    currentSelectedPosition = currentActivePosition;

    // Inflate timer controls
    timerControls = inflater.inflate(R.layout.view_timer_controls, mainLayout, false);
    countdownTimerView = timerControls.findViewById(R.id.countdown_timer);

    // Get countdown views
    hoursView = (TextView) timerControls.findViewById(R.id.hours);
    minutesView = (TextView) timerControls.findViewById(R.id.minutes);
    secondsView = (TextView) timerControls.findViewById(R.id.seconds);

    // Set font face to be fixed width
    hoursView.setTypeface(Typeface.MONOSPACE);
    minutesView.setTypeface(Typeface.MONOSPACE);
    secondsView.setTypeface(Typeface.MONOSPACE);

    // Get buttons
    stopButton = (ImageButton) timerControls.findViewById(R.id.stop_button);
    playPauseButton = (ImageButton) timerControls.findViewById(R.id.play_pause_button);
    goToCurrentButton = (ImageButton) timerControls.findViewById(R.id.go_to_current_button);

    // Get view to display step status
    stepStatusView = (TextView) timerControls.findViewById(R.id.step_status);

    // Set default button backgrounds
    stopButton.setImageResource(R.drawable.ic_stop_black_48dp);
    playPauseButton.setImageResource(R.drawable.ic_play_arrow_black_48dp);

    // ViewPager and pagerAdapter for slidy tabs!
    cpAdapter = new BrewTimerCollectionPagerAdapter(getSupportFragmentManager(), mRecipe, appContext);
    mViewPager = (ViewPager) findViewById(R.id.pager);
    mViewPager.setAdapter(cpAdapter);

    // Set on page change listener
    ViewPager.OnPageChangeListener onPageChangeListener = new ViewPager.OnPageChangeListener() {
      @Override
      public void onPageScrolled(int position, float offset, int offsetPixels) {
      }

      @Override
      public void onPageSelected(int position) {
        Log.d("BrewTimerActivity", "Step #" + position + " selected.");

        // Acquire objects corresponding to the currently selected BrewStep page.
        f = (BrewTimerStepFragment) cpAdapter.getItem(position);
        inst = f.getInstruction();
        currentSelectedPosition = position;

        // If we're stopped, update some of our state whenever we switch instructions.  If we're
        // stopped, it means we can change the current active step and update the timerView
        // based on the remaining time for that step.
        if (timerState == Constants.STOPPED) {
          Log.d("BrewTimerActivity", "Timer is stopped - update.");
          currentActivePosition = mViewPager.getCurrentItem();
          setTimerFromCurrentStep();

          // If this instruction doesn't use a timer, hide the timer view.  Instructions might
          // not use timers if they are not time based. For example, sparge or cool steps.
          if (! inst.showTimer()) {
            Log.d("BrewTimerActivity", "Hiding timer for instruction: " + inst.getInstructionType());
            setTimerToNull();
          }
          else {
            Log.d("BrewTimerActivity", "Update timer view with new remaining time.");
            hoursView.setText(nft.format(Utils.getHours(inst.getTimeToNextStep(), inst.getDurationUnits())) + "");
            minutesView.setText(nft.format(Utils.getMinutes(inst.getTimeToNextStep(), inst.getDurationUnits())) + "");
            secondsView.setText(nft.format(0) + "");
          }
          goToCurrentButton.setImageResource(R.drawable.ic_check_black_48dp);
        }

        // Adjust fields based on the item we're looking at
        if (position == currentActivePosition) {
          goToCurrentButton.setImageResource(R.drawable.ic_check_black_48dp);
        }
        else if (position < currentActivePosition) {
          goToCurrentButton.setImageResource(R.drawable.ic_navigate_next_black_48dp);
        }
        else if (position > currentActivePosition) {
          goToCurrentButton.setImageResource(R.drawable.ic_navigate_before_black_48dp);
        }

        // Set text on the status bar
        setStatusText();
      }

      @Override
      public void onPageScrollStateChanged(int state) {

      }
    };
    mViewPager.addOnPageChangeListener(onPageChangeListener);

    // Set the view pager to the current active step, if there is one.  We need to trigger the
    // OnPageChangeListener to be called, so first setCurrentItem to a different page, and
    // then set it to the desired one.  We store the desired active step so that we can change
    // it back.
    Log.d("BrewTimerActivity", "Set the current brew step to be step #" + currentActivePosition);
    mViewPager.setCurrentItem(currentActivePosition);
    onPageChangeListener.onPageSelected(currentActivePosition);

    // Add views
    mainLayout.addView(timerControls);

    // Register listener for timer updates
    registerReceiver(countdownReceiver, new IntentFilter(Constants.BROADCAST_REMAINING_TIME));

    // Register listener for query responses
    registerReceiver(queryReceiver, new IntentFilter(Constants.BROADCAST_QUERY_RESP));

    // If the service is running, query it for current data
    if (BrewTimerService.isRunning) {
      Log.d("BrewTimerActivity", "BrewTimerService is running, query it for current info.");
      Intent i = new Intent();
      i.setAction(Constants.BROADCAST_TIMER_CONTROLS);
      i.putExtra(Constants.KEY_COMMAND, Constants.COMMAND_QUERY);
      appContext.sendBroadcast(i);

      // If the brew timer service is running, stop any alarms that might be active.  This might
      // occur if the BrewTimerActivity was destroyed while the service was still running, and
      // is being re-opened after a step has completed.
      stopAlarm();
    }

    Log.d("BrewTimerActivity", "End onCreate() for BrewTimerActivity");
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case android.R.id.home:
        // Behave in the same way as if back was pressed.
        this.onBackPressed();
        return true;
    }
    return super.onOptionsItemSelected(item);
  }

  @Override
  public void onBackPressed() {
    // If the back button is pressed and the brew timer is running, ask
    // the user if they really want to exit the activity.  Otherwise, just
    // go back.
    if (this.timerState == Constants.STOPPED) {
      // Timer is not running - we can just exit the activity as normal.
      Log.d("BrewTimerService", "Back button pressed, finish().");
      finish();
    }
    else {
      // The timer is not in stopped state.  Ask the user if they really
      // want to leave!  If they pick yes, the timer will be canceled.
      Log.d("BrewTimerService", "Back button pressed, display alert.");
      new AlertDialog.Builder(BrewTimerActivity.this)
              .setTitle("Cancel Brew Timer?")
              .setMessage("Leaving this screen will cancel the current brew timer. Continue?")
              .setNegativeButton("Cancel", null)
              .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                  finish();
                }
              }).show();
    }
  }

  /**
   * The default finish() method.  This ends the activity, bringing the BrewTimerService with it.
   */
  @Override
  public void finish() {
    // Override finish to also stop the timer.
    if (timerState != Constants.STOPPED) {
      this.stop();
    }
    super.finish();
  }

  /**
   * Use this to finish the BrewTimerActivity without stopping the service.
   */
  public void finishWithoutStoppingService() {
    super.finish();
  }

  @Override
  public void onResume() {
    super.onResume();
    mRecipe = getIntent().getParcelableExtra(Constants.KEY_RECIPE);

    // Set title
    setTitle("Brew Timer");

    // ViewPager and pagerAdapter for slidy tabs!
    cpAdapter = new BrewTimerCollectionPagerAdapter(getSupportFragmentManager(), mRecipe, appContext);
    mViewPager.setAdapter(cpAdapter);
    mViewPager.setCurrentItem(this.currentActivePosition);
  }

  public void togglePlayPause() {
    if (timerState == Constants.RUNNING) {
      pause();
    }
    else {
      play();
    }
  }

  public void play() {
    mViewPager.setCurrentItem(currentActivePosition);
    stopAlarm();

    // If the timer is 0, don't start!
    if (getTimerSeconds() != 0) {
      // Set timer state
      timerState = Constants.RUNNING;
      playPauseButton.setImageResource(R.drawable.ic_pause_black_48dp);

      Intent i = new Intent(this, BrewTimerService.class);
      i.putExtra(Constants.KEY_TITLE, inst.getBrewTimerTitle());
      i.putExtra(Constants.KEY_SECONDS, getTimerSeconds());
      i.putExtra(Constants.KEY_RECIPE, mRecipe);
      i.putExtra(Constants.KEY_STEP_NUMBER, currentActivePosition);
      startService(i);
    }
    else {
      Log.d("BrewTimerService", "Play pressed when time remaining == 0.  Moving to next step.");
      currentActivePosition = currentActivePosition + 1;
      mViewPager.setCurrentItem(currentActivePosition);
      setTimerFromCurrentStep();
    }
    setStatusText();
  }

  public void pause() {
    // Tell the timer service that we're paused
    Intent i = new Intent();
    i.setAction(Constants.BROADCAST_TIMER_CONTROLS);
    i.putExtra(Constants.KEY_COMMAND, Constants.COMMAND_PAUSE);
    appContext.sendBroadcast(i);

    // Set the timer state
    timerState = Constants.PAUSED;

    // Jump to the current step
    mViewPager.setCurrentItem(currentActivePosition);

    // Set the play/pause button to display the "Play" image
    playPauseButton.setImageResource(R.drawable.ic_play_arrow_black_48dp);

    // Set appropriate text on status bar
    setStatusText();
  }

  public void stop() {
    // Set the currently displayed page to be the first instruction.
    mViewPager.setCurrentItem(0);
    currentActivePosition = 0;

    // Tell the timer serviced that we've stopped
    Intent i = new Intent();
    i.setAction(Constants.BROADCAST_TIMER_CONTROLS);
    i.putExtra(Constants.KEY_COMMAND, Constants.COMMAND_STOP);
    appContext.sendBroadcast(i);

    // Set the timer state
    timerState = Constants.STOPPED;

    // Set timer to initial duration
    setTimerFromCurrentStep();

    // Set play/pause button to display the "Play" image
    playPauseButton.setImageResource(R.drawable.ic_play_arrow_black_48dp);

    // Stop any alarms
    stopAlarm();

    // Stop the timer service
    stopService(new Intent(this, BrewTimerService.class));

    // Set appropriate text on status bar
    setStatusText();
  }

  public void setStatusText() {
    if (currentSelectedPosition == currentActivePosition) {
      if (timerState == Constants.RUNNING) {
        stepStatusView.setText("IN PROGRESS");
        stepStatusView.setTextColor(Color.parseColor(ColorHandler.GREEN));
      }
      else if (timerState == Constants.PAUSED) {
        if (getTimerSeconds() == 0) {
          // If the timer is paused and the remaining time is 0, then it means
          // this step is complete.
          stepStatusView.setText("COMPLETE");
          stepStatusView.setTextColor(Color.parseColor(ColorHandler.GREEN));
        }
        else {
          // Otherwise, it means that the timer is just paused, and that this step is not
          // yet complete.
          stepStatusView.setText("PAUSED");
          stepStatusView.setTextColor(Color.parseColor(ColorHandler.RED));
        }
      }
      else if (timerState == Constants.STOPPED) {
        stepStatusView.setText("");
      }
    }
    else if (currentSelectedPosition < currentActivePosition) {
      if (timerState == Constants.RUNNING || timerState == Constants.PAUSED) {
        stepStatusView.setText("COMPLETE");
        stepStatusView.setTextColor(Color.parseColor(ColorHandler.GREEN));
      }
      else if (timerState == Constants.STOPPED) {
        stepStatusView.setText("");
      }
    }
    else if (currentSelectedPosition > currentActivePosition) {
      if (timerState == Constants.STOPPED) {
        stepStatusView.setText("");
      }
      else {
        stepStatusView.setText("PENDING");
        stepStatusView.setTextColor(Color.parseColor(ColorHandler.YELLOW));
      }
    }
  }

  public void onStepComplete() {
    // When we get to the next step, raise alarm, pause timer, switch views, update displayed time.
    pause();

    // Turn screen on
    Window wind;
    wind = this.getWindow();
    wind.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    wind.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
    wind.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
  }

  public int getTimerSeconds() {
    int seconds = 0;

    try {
      seconds += Integer.parseInt(hoursView.getText().toString()) * 3600;
      seconds += Integer.parseInt(minutesView.getText().toString()) * 60;
      seconds += Integer.parseInt(secondsView.getText().toString());
    } catch (Exception e) {
      Log.d("BrewTimerActivity", "Couldn't get timer seconds, returning 0");
      return 0;
    }

    return seconds;
  }

  public void setTimerFromSeconds(int time) {
    int hours = 0, minutes = 0, seconds = 0;

    hours = (int) (time / 3600);
    minutes = (int) ((time - 3600 * hours) / 60);
    seconds = time - (hours * 3600) - (minutes * 60);

    hoursView.setText(nft.format(hours) + "");
    minutesView.setText(nft.format(minutes) + "");
    secondsView.setText(nft.format(seconds) + "");
  }

  public void setTimerFromCurrentStep() {
    if (! inst.showTimer()) {
      setTimerToNull();
    }
    else {
      hoursView.setText(nft.format(Utils.getHours(inst.getTimeToNextStep(), inst.getDurationUnits())) + "");
      minutesView.setText(nft.format(Utils.getMinutes(inst.getTimeToNextStep(), inst.getDurationUnits())) + "");
      secondsView.setText(nft.format(0) + "");
    }
  }

  public void setTimerToNull() {
    hoursView.setText("--");
    minutesView.setText("--");
    secondsView.setText("--");
  }

  public void stopAlarm() {
    // Tell the timer service to stop the alarm.
    Intent i = new Intent();
    i.setAction(Constants.BROADCAST_TIMER_CONTROLS);
    i.putExtra(Constants.KEY_COMMAND, Constants.COMMAND_STOP_ALARM);
    appContext.sendBroadcast(i);
  }

  @Override
  protected void onDestroy() {
    // Stop any running alarm
    stopAlarm();

    // Unregister from our receivers
    unregisterReceiver(queryReceiver);
    unregisterReceiver(countdownReceiver);
    super.onDestroy();
  }

  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.play_pause_button: {
        togglePlayPause();
        break;
      }
      case R.id.go_to_current_button: {
        mViewPager.setCurrentItem(currentActivePosition);
        stopAlarm();
        break;
      }
      case R.id.stop_button: {
        stop();
        break;
      }
    }
  }
} public class DisplayRecipeActivity extends AppCompatActivity {

  private Recipe mRecipe;
  private int currentItem; // For storing current page
  DisplayRecipeCollectionPagerAdapter cpAdapter;
  ViewPager mViewPager;
  ViewPager.OnPageChangeListener pageListener;
  Menu menu;

  // Alert builder
  public AlertBuilder alertBuilder;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_display_recipe);

    // Set icon as back button
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    // Get recipe from intent
    mRecipe = getIntent().getParcelableExtra(Constants.KEY_RECIPE);

    // Create alert builder with no callback.
    alertBuilder = new AlertBuilder(this, null);

    // Set on page change listener
    pageListener = new ViewPager.OnPageChangeListener() {
      @Override
      public void onPageScrolled(int position, float offset, int offsetPixels) {
      }

      @Override
      public void onPageSelected(int position) {
        updateOptionsMenu();
      }

      @Override
      public void onPageScrollStateChanged(int state) {
      }
    };

    // Set to the current item (default to IngredientView)
    currentItem = 0;

    // Update user interface
    updateUI();
  }

  /**
   * TODO: This creates an entire new pager adapter adapter in order to update the UI.  It would be
   * nice if we could just update things in place, without having to create / destroy so many
   * objects.
   */
  public void updatePagerAdater() {
    cpAdapter = new DisplayRecipeCollectionPagerAdapter(getSupportFragmentManager(), mRecipe, getApplicationContext());

    // Set Adapter and onPageChangeListener.
    mViewPager = (ViewPager) findViewById(R.id.pager);
    mViewPager.setAdapter(cpAdapter);
    mViewPager.addOnPageChangeListener(pageListener);

    // Set the current item
    mViewPager.setCurrentItem(currentItem);
  }

  /**
   * Updates the UI after (potentially) changes have been made to the Recipe being viewed.
   */
  private void updateUI() {
    // Update the PagerAdapter.
    updatePagerAdater();

    // Set title based on recipe name
    setTitle(mRecipe.getRecipeName());

    // Update which options menu is displayed.
    updateOptionsMenu();
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    this.menu = menu;
    menu.removeItem(R.id.menu_add_ing);
    menu.removeItem(R.id.menu_edit_recipe);
    menu.removeItem(R.id.menu_timer);
    menu.removeItem(R.id.menu_profile_dropdown);

    BiermachtFragment f = (BiermachtFragment) cpAdapter.getItem(mViewPager.getCurrentItem());

    if (f instanceof IngredientViewFragment) {
      Log.d("DisplayRecipeActivity", "Displaying fragment_ingredient_menu");
      getMenuInflater().inflate(R.menu.fragment_ingredient_menu, menu);
    }
    else if (f instanceof InstructionViewFragment) {
      Log.d("DisplayRecipeActivity", "Displaying fragment_instruction_menu");
      getMenuInflater().inflate(R.menu.fragment_instruction_menu, menu);
    }
    else if (f instanceof DetailsViewFragment) {
      Log.d("DisplayRecipeActivity", "Displaying fragment_details_menu");
      getMenuInflater().inflate(R.menu.fragment_details_menu, menu);
    }
    else if (f instanceof ProfileViewFragment) {
      Log.d("DisplayRecipeActivity", "Displaying fragment_profile_menu");
      getMenuInflater().inflate(R.menu.fragment_profile_menu, menu);
      if (mRecipe.getType().equals(Recipe.EXTRACT)) {
        Log.d("DisplayRecipeActivity", "Including menu_edit_mash_profile");
        menu.findItem(R.id.menu_edit_mash_profile).setVisible(false);
      }
    }
    else {
      // No cases were matched - return false.
      Log.e("DisplayRecipeActivity", "Unable to select menu.");
      return false;
    }
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    Intent i;

    // Pass the given event to the currently selected Fragment to handle.  If handled, return.
    if (cpAdapter.getItem(mViewPager.getCurrentItem()).onOptionsItemSelected(item)) {
      return true;
    }

    // Otherwise, switch on the item ID.
    switch (item.getItemId()) {
      case android.R.id.home:
        finish();
        return true;

      case R.id.add_fermentable:
        i = new Intent(this.getApplicationContext(), AddFermentableActivity.class);
        i.putExtra(Constants.KEY_RECIPE, mRecipe);
        startActivity(i);
        return true;

      case R.id.add_hop:
        i = new Intent(getApplicationContext(), AddHopsActivity.class);
        i.putExtra(Constants.KEY_RECIPE, mRecipe);
        startActivity(i);
        return true;

      case R.id.add_yeast:
        i = new Intent(getApplicationContext(), AddYeastActivity.class);
        i.putExtra(Constants.KEY_RECIPE, mRecipe);
        startActivity(i);
        return true;

      case R.id.add_misc:
        i = new Intent(getApplicationContext(), AddMiscActivity.class);
        i.putExtra(Constants.KEY_RECIPE, mRecipe);
        startActivity(i);
        return true;

      case R.id.menu_timer:
        // If we have no instructions, don't go to timer.
        if (mRecipe.getInstructionList().size() == 0) {
          String msg = "Brew timer requires some instructions.  Add to your recipe to generate instructions!";
          alertBuilder.notification("No Instructions", msg).show();
          return false;
        }

        // We have instructions, can go to timer.
        i = new Intent(getApplicationContext(), BrewTimerActivity.class);
        i.putExtra(Constants.KEY_RECIPE, mRecipe);
        startActivity(i);
        return true;

      case R.id.menu_edit_recipe:
        i = new Intent(getApplicationContext(), EditRecipeActivity.class);
        i.putExtra(Constants.KEY_RECIPE, mRecipe);
        startActivity(i);
        return true;

      case R.id.menu_edit_mash_profile:
        i = new Intent(getApplicationContext(), EditMashProfileActivity.class);
        i.putExtra(Constants.KEY_RECIPE, mRecipe);
        i.putExtra(Constants.KEY_PROFILE_ID, mRecipe.getMashProfile().getId());
        i.putExtra(Constants.KEY_PROFILE, mRecipe.getMashProfile());
        startActivity(i);
        return true;

      case R.id.menu_edit_fermentation_profile:
        i = new Intent(getApplicationContext(), EditFermentationProfileActivity.class);
        i.putExtra(Constants.KEY_RECIPE, mRecipe);
        startActivity(i);
        return true;
    }
    return super.onOptionsItemSelected(item);
  }

  @Override
  protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
  }

  public void onResume() {
    super.onResume();
    Log.d("DisplayRecipeActivity", "onResume: Getting recipe from database");
    // Changes may have been made to this Recipe in another activity - get the Recipe
    // from the database and update the UI.
    try {
      mRecipe = new DatabaseAPI(getApplicationContext()).getRecipeWithId(mRecipe.getId());
    } catch (Exception e) {
      e.printStackTrace();
    }

    updateUI();
  }

  public void updateOptionsMenu() {
    if (menu != null) {
      onCreateOptionsMenu(menu);
    }
  }

  public void displayStyle(View v) {
    // Called when the display style button is pressed on the profile fragment.
    // Launches the DisplayStyleActivity.
    Intent i = new Intent(getApplicationContext(), DisplayStyleActivity.class);
    i.putExtra(Constants.KEY_STYLE, mRecipe.getStyle());
    startActivity(i);
  }

  public void handleClick(View v) {
    // Pass to the currently selected fragment to handle.
    ((BiermachtFragment) this.cpAdapter.getItem(mViewPager.getCurrentItem())).handleClick(v);
  }

  @Override
  public void onPause() {
    super.onPause();
    // Save the current page we're looking at
    this.currentItem = mViewPager.getCurrentItem();
  }

  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
  }
} public class DisplayStyleActivity extends AppCompatActivity {

  private BeerStyle style;
  private int currentItem;                           // For storing current page
  DisplayStyleCollectionPagerAdapter cpAdapter;
  ViewPager mViewPager;
  ViewPager.OnPageChangeListener pageListener;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_display_recipe);

    // Set icon as back button
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    // Get recipe from intent
    style = getIntent().getParcelableExtra(Constants.KEY_STYLE);

    // Set on page change listener
    pageListener = new ViewPager.OnPageChangeListener() {
      @Override
      public void onPageScrolled(int position, float offset, int offsetPixels) {
      }

      @Override
      public void onPageSelected(int position) {
      }

      @Override
      public void onPageScrollStateChanged(int state) {
      }
    };

    // Set to the current item.
    currentItem = 0;

    // Update user interface
    updateUI();
  }

  /**
   * TODO: This creates an entire new pager adapter adapter in order to update the UI.  It would be
   * nice if we could just update things in place, without having to create / destroy so many
   * objects.
   */
  public void updatePagerAdater() {
    cpAdapter = new DisplayStyleCollectionPagerAdapter(getSupportFragmentManager(), style, getApplicationContext());

    // Set Adapter and onPageChangeListener.
    mViewPager = (ViewPager) findViewById(R.id.pager);
    mViewPager.setAdapter(cpAdapter);
    mViewPager.addOnPageChangeListener(pageListener);

    // Set the current item
    mViewPager.setCurrentItem(currentItem);
  }

  /**
   * Updates the UI after (potentially) changes have been made to the Recipe being viewed.
   */
  private void updateUI() {
    // Update the PagerAdapter.
    updatePagerAdater();

    // Set title based on recipe name
    setTitle(style.getName());
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case android.R.id.home:
        finish();
        return true;
    }
    return super.onOptionsItemSelected(item);
  }

  @Override
  public void onPause() {
    super.onPause();
    // Save the current page we're looking at
    this.currentItem = mViewPager.getCurrentItem();
  }
} public class EditFermentationProfileActivity extends AppCompatActivity implements OnClickListener {

  // Main Layout
  private LinearLayout mainLayout;
  private LayoutInflater inflater;
  private LinearLayout primaryLayout;
  private LinearLayout secondaryLayout;
  private LinearLayout tertiaryLayout;

  // Data entry view declarations
  private Spinner numStagesSpinner;

  // Recipe
  private Recipe mRecipe;

  // Arrays
  private ArrayList<String> numStagesArray;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_add_edit);
    inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    // Get recipe from calling activity
    long id = getIntent().getLongExtra(Constants.KEY_RECIPE_ID, Constants.INVALID_ID);
    mRecipe = getIntent().getParcelableExtra(Constants.KEY_RECIPE);

    // Set icon as back button
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    // Disable delete button for this view
    findViewById(R.id.delete_button).setVisibility(View.GONE);

    // Initialize views and stuff
    mainLayout = (LinearLayout) findViewById(R.id.main_layout);
    numStagesSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainLayout, false);
    primaryLayout = (LinearLayout) inflater.inflate(R.layout.view_edit_fermentation_stage,
                                                    mainLayout, false);
    secondaryLayout = (LinearLayout) inflater.inflate(R.layout.view_edit_fermentation_stage,
                                                      mainLayout, false);
    tertiaryLayout = (LinearLayout) inflater.inflate(R.layout.view_edit_fermentation_stage,
                                                     mainLayout, false);

    // Set units appropriately
    TextView tempUnitsView;
    tempUnitsView = (TextView) primaryLayout.findViewById(R.id.temperature_units);
    tempUnitsView.setText(Units.getTemperatureUnits());
    tempUnitsView = (TextView) secondaryLayout.findViewById(R.id.temperature_units);
    tempUnitsView.setText(Units.getTemperatureUnits());
    tempUnitsView = (TextView) tertiaryLayout.findViewById(R.id.temperature_units);
    tempUnitsView.setText(Units.getTemperatureUnits());

    // Set titles
    TextView title;
    title = (TextView) primaryLayout.findViewById(R.id.title);
    title.setText("Primary:");
    title = (TextView) secondaryLayout.findViewById(R.id.title);
    title.setText("Secondary:");
    title = (TextView) tertiaryLayout.findViewById(R.id.title);
    title.setText("Tertiary:");

    // Add sublayouts
    mainLayout.addView(numStagesSpinner);
    mainLayout.addView(primaryLayout);
    mainLayout.addView(secondaryLayout);
    mainLayout.addView(tertiaryLayout);

    // Default values
    View v = new View(this);
    TextView ageView;
    TextView tempView;
    for (int i = Recipe.STAGE_PRIMARY; i <= Recipe.STAGE_TERTIARY; i++) {
      if (i == Recipe.STAGE_PRIMARY) {
        v = primaryLayout;
      }
      if (i == Recipe.STAGE_SECONDARY) {
        v = secondaryLayout;
      }
      if (i == Recipe.STAGE_TERTIARY) {
        v = tertiaryLayout;
      }

      ageView = (TextView) v.findViewById(R.id.age_edit_text);
      tempView = (TextView) v.findViewById(R.id.temp_edit_text);
      ageView.setText(mRecipe.getFermentationAge(i) + "");
      tempView.setText(String.format("%2.0f", mRecipe.getDisplayFermentationTemp(i)));
    }

    // Arraylist of Profiles
    numStagesArray = new ArrayList<String>();
    numStagesArray.add("1");
    numStagesArray.add("2");
    numStagesArray.add("3");

    // Set up num stages spinner
    SpinnerAdapter profAdapter = new SpinnerAdapter(this, numStagesArray, "Number of Stages");
    profAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    numStagesSpinner.setAdapter(profAdapter);

    // Determine the correct selection for the num stages spinner
    numStagesSpinner.setSelection(mRecipe.getFermentationStages() - 1);

    // Handle mash profile selector here
    numStagesSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position,
                                 long id) {

        // Set number of stages
        mRecipe.setFermentationStages(position + 1);

        // Figure out visibility
        secondaryLayout.setVisibility(View.VISIBLE);
        tertiaryLayout.setVisibility(View.VISIBLE);

        if (position == 0)
        // Only one stage
        {
          secondaryLayout.setVisibility(View.GONE);
          tertiaryLayout.setVisibility(View.GONE);
        }
        else if (position == 1)
        // Two stages
        {
          tertiaryLayout.setVisibility(View.GONE);
        }

      }

      public void onNothingSelected(AdapterView<?> parentView) {
        // Blag
      }

    });
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.activity_add_new_recipe, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    Intent i;
    switch (item.getItemId()) {
      case android.R.id.home:
        finish();
        return true;
    }
    return super.onOptionsItemSelected(item);
  }

  public void onClick(View v) {

    // Cancel Button Pressed
    if (v.getId() == R.id.cancel_button) {
      finish();
    }

    // Submit Button pressed
    if (v.getId() == R.id.submit_button) {
      boolean readyToGo = true;
      try {
        View view = new View(this);
        TextView ageView;
        TextView tempView;
        for (int i = Recipe.STAGE_PRIMARY; i <= mRecipe.getFermentationStages(); i++) {
          if (i == Recipe.STAGE_PRIMARY) {
            view = primaryLayout;
          }
          if (i == Recipe.STAGE_SECONDARY) {
            view = secondaryLayout;
          }
          if (i == Recipe.STAGE_TERTIARY) {
            view = tertiaryLayout;
          }

          ageView = (TextView) view.findViewById(R.id.age_edit_text);
          tempView = (TextView) view.findViewById(R.id.temp_edit_text);
          mRecipe.setFermentationAge(i, Integer.parseInt(ageView.getText().toString()));
          mRecipe.setDisplayFermentationTemp(i, Double.parseDouble(tempView.getText().toString()
                                                                           .replace(",", ".")));
        }
      } catch (Exception e) {
        readyToGo = false;
      }

      if (readyToGo) {
        mRecipe.save(getApplicationContext());
        finish();
      }
    }
  }
} public class EditMashProfileActivity extends AddMashProfileActivity {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Disable delete button for this view
    findViewById(R.id.delete_button).setVisibility(View.GONE);

    // Remove views we don't want.
    mainView.removeView(spinnerView);
    mainView.removeView(nameView);
  }

  @Override
  public void getValuesFromIntent() {
    super.getValuesFromIntent();

    // Acquire profile
    mProfile = getIntent().getParcelableExtra(Constants.KEY_PROFILE);

    // Set the profile.
    mRecipe.setMashProfile(mProfile);

    // Initialize data containers
    name = mProfile.getName();
  }

  @Override
  public void _getValuesFromIntent() {
    // When editing a mash profile, we don't need to do any special acquisition from the intent.
    // As such, override this method to do nothing.
  }

  @Override
  public void acquireValues() throws Exception {
    super.acquireValues();
    mRecipe.setMashProfile(mProfile);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.activity_edit_mash_profile, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case android.R.id.home:
        finish();
        return true;

      case R.id.menu_save:
        saveMashProfileAlert().show();
        return true;
    }
    return true;
  }

  @Override
  public void onFinished() {
    // onFinished is called when the User has chosen to save changes made to the active profile.
    // Save the profile, then exit the activity.
    mProfile.save(getApplicationContext(), Constants.DATABASE_USER_RECIPES);
    finish();
  }

  @Override
  public void onCancelPressed() {
    // If cancel is pressed, do not save the current profile.  Just finish the activity
    // without committing any changes.
    finish();
  }

  @Override
  public void onDeletePressed() {
    // This activity is for editing a MashProfile which is attached to a User's recipe.
    // As such, these can not be deleted (the user should change their recipe to be extract
    // if their recipe does not use a mash profile).
  }

  /**
   * Returns a Builder for an AlertDialog which prompts the user if they would like to save the
   * current recipe's mash profile to be used in future recipes.
   *
   * @return AlertDialog.Builder
   */
  private AlertDialog.Builder saveMashProfileAlert() {
    return new AlertDialog.Builder(this)
            .setTitle("Save Mash Profile?")
            .setMessage("Save this profile for use in other recipes?")
            .setPositiveButton("Save", new DialogInterface.OnClickListener() {
              public void onClick(DialogInterface dialog, int which) {
                mProfile.setName(mRecipe.getRecipeName() + "'s profile");
                new DatabaseAPI(getApplicationContext())
                        .addMashProfile(Constants.DATABASE_USER_RESOURCES,
                                        mProfile,
                                        mProfile.getOwnerId());
                onMashProfileSavedAlert().create().show();
              }
            })
            .setNegativeButton(R.string.cancel, null);
  }

  /**
   * Returns a Builder for an AlertDialog which informs the User that the active Mash Profile has
   * been saved for use in other recipes.
   *
   * @return AlertDialog.Builder
   */
  private AlertDialog.Builder onMashProfileSavedAlert() {
    return new AlertDialog.Builder(this)
            .setTitle("Profile Saved")
            .setMessage("Your mash profile has been saved! Make changes in the Mash Profile Editor.")
            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
              public void onClick(DialogInterface dialog, int which) {
              }
            });
  }
} public class EditRecipeNotesActivity extends AddEditActivity {

  // Notes views
  private View notesView;
  private EditText notesViewEditText;
  private View tasteNotesView;
  private EditText tasteNotesViewEditText;

  // Title bars
  public View notesTitleView;
  public TextView notesTitleViewText;
  public ImageButton notesTitleViewButton;

  public View tasteNotesTitleView;
  public TextView tasteNotesTitleViewText;
  public ImageButton tasteNotesTitleViewButton;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Disable delete button for this view
    findViewById(R.id.delete_button).setVisibility(View.GONE);

    // Change submit button to say "save"
    submitButton.setText("Save");

    // Create notes edit text
    notesView = inflater.inflate(R.layout.view_recipe_notes, mainView, false);
    notesViewEditText = (EditText) notesView.findViewById(R.id.edit_text);

    tasteNotesView = inflater.inflate(R.layout.view_recipe_notes, mainView, false);
    tasteNotesViewEditText = (EditText) tasteNotesView.findViewById(R.id.edit_text);

    // Set values
    notesViewEditText.setText(mRecipe.getNotes());
    tasteNotesViewEditText.setText(mRecipe.getTasteNotes());

    // Title bars
    notesTitleView = inflater.inflate(R.layout.view_title, mainView, false);
    notesTitleViewText = (TextView) notesTitleView.findViewById(R.id.title);
    notesTitleViewButton = (ImageButton) notesTitleView.findViewById(R.id.button);

    tasteNotesTitleView = inflater.inflate(R.layout.view_title, mainView, false);
    tasteNotesTitleViewText = (TextView) tasteNotesTitleView.findViewById(R.id.title);
    tasteNotesTitleViewButton = (ImageButton) tasteNotesTitleView.findViewById(R.id.button);

    // Set button to be invisible
    notesTitleViewButton.setVisibility(View.INVISIBLE);
    tasteNotesTitleViewButton.setVisibility(View.INVISIBLE);

    // Set title text
    notesTitleViewText.setText("Recipe notes");
    tasteNotesTitleViewText.setText("Taste notes");

    // Remove all views
    mainView.removeAllViews();

    // Add views
    mainView.addView(notesTitleView);
    mainView.addView(notesView);
    mainView.addView(tasteNotesTitleView);
    mainView.addView(tasteNotesView);
  }

  @Override
  public void getValuesFromIntent() {
    super.getValuesFromIntent();
  }

  @Override
  public void setInitialSpinnerSelection() {

  }

  @Override
  public void getList() {

  }

  @Override
  public void createSpinner() {

  }

  @Override
  public void onMissedClick(View v) {

  }

  @Override
  public void configureSpinnerListener() {

  }

  @Override
  public void acquireValues() throws Exception {
    super.acquireValues();
    String notes = notesViewEditText.getText().toString();
    String tasteNotes = tasteNotesViewEditText.getText().toString();

    mRecipe.setNotes(notes);
    mRecipe.setTasteNotes(tasteNotes);
  }

  @Override
  public void onFinished() {
    mRecipe.save(getApplicationContext());
    finish();
  }

  @Override
  public void onCancelPressed() {
    setResult(Constants.RESULT_CANCELED, new Intent());
    finish();
  }

  @Override
  public void onDeletePressed() {

  }
} public class AddFermentableActivity extends AddEditIngredientActivity {

  // Holds the currently selected fermentable
  Fermentable fermentable;

  // Editable rows to display
  public View colorView;
  public View gravityView;
  public Spinner fermentableTypeSpinner;

  // Titles from rows
  public TextView colorViewTitle;
  public TextView gravityViewTitle;

  // Content from rows
  public TextView colorViewText;
  public TextView gravityViewText;

  // Storage for acquired values
  double gravity;
  double color;
  String type;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Disable delete button for this view
    findViewById(R.id.delete_button).setVisibility(View.GONE);

    // Initialize views and such here
    colorView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    gravityView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);

    /************************************************************************
     ************* Add views *************************************************
     *************************************************************************/
    this.registerViews(Arrays.asList(fermentableTypeSpinner, colorView, gravityView));
    this.setViews(Arrays.asList(searchableListView, timeView, amountView, colorView, gravityView));

    /************************************************************************
     ************* Get titles, set values   **********************************
     *************************************************************************/
    colorViewTitle = (TextView) colorView.findViewById(R.id.title);
    colorViewTitle.setText("SRM Color");

    gravityViewTitle = (TextView) gravityView.findViewById(R.id.title);
    gravityViewTitle.setText("Gravity Contribution");

    searchableListViewTitle.setText("Fermentable");
    amountViewTitle.setText("Amount " + "(" + Units.getFermentableUnits() + ")");

    // Acquire text views
    colorViewText = (TextView) colorView.findViewById(R.id.text);
    gravityViewText = (TextView) gravityView.findViewById(R.id.text);

    // Set button text
    submitButton.setText(R.string.add);

    // Set initial position for searchable list
    setInitialSearchableListSelection();
  }

  @Override
  public void onMissedClick(View v) {
    super.onMissedClick(v);
    Log.d("AddFermentableActivity", "Checking views for: " + v);

    if (v.equals(colorView)) {
      dialog = alertBuilder.editTextFloatAlert(colorViewText, colorViewTitle).create();
    }
    else if (v.equals(gravityView)) {
      dialog = alertBuilder.editTextFloatAlert(gravityViewText, gravityViewTitle).create();
    }
    else {
      Log.d("AddFermentableActivity", "View not found: " + v);
      return;
    }

    // Force keyboard open and show popup
    dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    dialog.show();
  }

  @Override
  public void getList() {
    Log.d("AddFermentableActivityt", "Getting fermentables list");
    ingredientList = new ArrayList<Ingredient>();
    ingredientList.addAll(ingredientHandler.getFermentablesList());

    // Add a placeholder ingredient.  When selected, allows user to create
    // a new custom ingredient.
    Log.d("AddFermentableActivity", "Adding placeholder ingredient");
    PlaceholderIngredient i = new PlaceholderIngredient("Create new");
    i.setShortDescription("Create a custom fermentable");
    ingredientList.add(0, i);
  }

  @Override
  public void setInitialSpinnerSelection() {
    spinnerView.setSelection(0);
  }

  @Override
  public void createSpinner() {
    this.adapter = new IngredientSpinnerAdapter(this, ingredientList, "Fermentable", true);
    adapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    spinnerView.setAdapter(this.adapter);

    fermentableTypeSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainView,
                                                        false);
    SpinnerAdapter adapter = new SpinnerAdapter(this, Constants.FERMENTABLE_TYPES, "Type");
    adapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    fermentableTypeSpinner.setAdapter(adapter);

    // Handle fermentable type selections
    fermentableTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position,
                                 long id) {
        type = Constants.FERMENTABLE_TYPES.get(position);
        Log.d("AddFermentableActivity", "Fermentable type selcted: " + type);
        if (mRecipe.getType().equals(Recipe.EXTRACT)) {
          timeView.setVisibility(View.VISIBLE);
          if (type.equals(Fermentable.TYPE_EXTRACT) || type.equals(Fermentable.TYPE_SUGAR)) {
            gravityViewText.setText("1.044");
            timeViewTitle.setText(R.string.boil_time);
            timeViewText.setText(mRecipe.getBoilTime() + "");
          }
          else if (type.equals(Fermentable.TYPE_GRAIN)) {
            gravityViewText.setText("1.037");
            timeViewTitle.setText(R.string.steep_time);
            timeViewText.setText(15 + "");
          }
          else {
            timeViewTitle.setText("Time");
          }
        }
        else {
          timeView.setVisibility(View.GONE);
        }
      }

      public void onNothingSelected(AdapterView<?> parentView) {
      }
    });
  }

  @Override
  public void configureSearchableListListener() {
    searchableListListener = new AdapterView.OnItemClickListener() {

      public void onItemClick(AdapterView<?> parentView, View selectedItemView, int position,
                              long id) {
        // Handle the placeholder case
        if (filteredList.get(position).getType().equals(Ingredient.PLACEHOLDER)) {
          // Cancel the dialog
          cancelDialog();

          // Switch into AddCustomFermentableActivity
          Intent intent = new Intent(AddFermentableActivity.this, AddCustomFermentableActivity
                  .class);
          intent.putExtra(Constants.KEY_RECIPE, mRecipe);
          intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
          startActivity(intent);
          finish();
          return;
        }

        // Not a placeholder
        fermentable = (Fermentable) filteredList.get(position);

        // Set whether we show boil or steep
        if (mRecipe.getType().equals(Recipe.EXTRACT)) {
          if (fermentable.getFermentableType().equals(Fermentable.TYPE_EXTRACT)) {
            timeViewTitle.setText(R.string.boil_time);
            timeViewText.setText(mRecipe.getBoilTime() + "");
          }
          else if (fermentable.getFermentableType().equals(Fermentable.TYPE_GRAIN)) {
            timeViewTitle.setText(R.string.steep_time);
            timeViewText.setText(15 + "");
          }
          else {
            timeViewTitle.setText("Time");
          }
        }
        else {
          // TODO: Support extract / adjunct times for all-grain recipes.
          timeView.setVisibility(View.GONE);
        }

        nameViewText.setText(fermentable.getName());
        searchableListViewText.setText(fermentable.getName());
        colorViewText.setText(String.format("%2.2f", fermentable.getLovibondColor()));
        gravityViewText.setText(String.format("%2.3f", fermentable.getGravity()));
        amountViewText.setText(1 + "");
        timeViewText.setText(String.format("%d", mRecipe.getBoilTime()));
        type = fermentable.getFermentableType();

        // Cancel dialog
        cancelDialog();
      }
    };
  }

  @Override
  public void onDeletePressed() {
    // Must be overriden
  }

  @Override
  public void onCancelPressed() {
    finish();
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.activity_add_ingredient, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {

    switch (item.getItemId()) {
      case android.R.id.home:
        finish();
        return true;
    }
    return super.onOptionsItemSelected(item);
  }

  @Override
  public void acquireValues() throws Exception {
    super.acquireValues();
    Log.d("AddFermentableActivity", "Acquiring values for: " + fermentable.getName());

    color = Double.parseDouble(colorViewText.getText().toString().replace(",", "."));
    gravity = Double.parseDouble(gravityViewText.getText().toString().replace(",", "."));

    fermentable.setName(name);
    fermentable.setTime(time);
    fermentable.setDisplayAmount(amount);
    fermentable.setLovibondColor(color);
    fermentable.setGravity(gravity);
    fermentable.setFermentableType(type);
  }

  @Override
  public void onFinished() {
    mRecipe.addIngredient(fermentable);
    mRecipe.save(AddFermentableActivity.this);
    finish();
  }
} public class AddHopsActivity extends AddEditIngredientActivity {

  // Editable rows to display
  public Spinner useSpinner;
  public Spinner formSpinner;
  public View alphaAcidView;
  public View descriptionView;

  // Titles from rows
  public TextView alphaAcidViewTitle;
  public TextView descriptionViewTitle;

  // Content from rows
  public TextView alphaAcidViewText;
  public TextView descriptionViewText;

  // Spinner array declarations
  public ArrayList<String> formArray;
  public ArrayList<String> useArray;

  // Holds the currently selected hop, and hop being edited
  Hop hop;

  // Storage for acquired values
  String use;
  String form;
  double alpha;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Disable delete button for this view
    findViewById(R.id.delete_button).setVisibility(View.GONE);

    // Initialize views and such here
    alphaAcidView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    descriptionView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    useSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainView, false);
    formSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainView, false);

    /************************************************************************
     ************* Add views *************************************************
     *************************************************************************/
    this.registerViews(Arrays.asList(alphaAcidView, descriptionView, useSpinner, formSpinner));
    this.setViews(Arrays.asList(searchableListView, amountView, timeView, formSpinner,
                                useSpinner, alphaAcidView));

    // Inflate title views.
    alphaAcidViewTitle = (TextView) alphaAcidView.findViewById(R.id.title);
    descriptionViewTitle = (TextView) descriptionView.findViewById(R.id.title);

    // Configure titles
    alphaAcidViewTitle.setText("Alpha Acids (%)");
    amountViewTitle.setText("Amount " + "(" + Units.getHopUnits() + ")");
    searchableListViewTitle.setText("Hop");
    descriptionViewTitle.setText("Description");

    // Get content views
    alphaAcidViewText = (TextView) alphaAcidView.findViewById(R.id.text);
    descriptionViewText = (TextView) descriptionView.findViewById(R.id.text);

    // Hop form spinner
    formArray = Constants.HOP_FORMS;
    SpinnerAdapter hopFormAdapter = new SpinnerAdapter(this, formArray, "Form");
    hopFormAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    formSpinner.setAdapter(hopFormAdapter);

    // Set up hop use spinner
    useArray = Constants.HOP_USES;
    SpinnerAdapter hopUseAdapter = new SpinnerAdapter(this, useArray, "Usage");
    hopUseAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    useSpinner.setAdapter(hopUseAdapter);

    // Set listeners
    useSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position,
                                 long id) {
        use = useArray.get(position);

        if (use.equals(Hop.USE_DRY_HOP)) {
          timeViewTitle.setText("Time (days)");
          timeViewText.setText(5 + "");
          timeView.setVisibility(View.VISIBLE);
        }
        else if (use.equals(Hop.USE_FIRST_WORT)) {
          // First wort hops are included in the full boil.
          timeViewText.setText(String.format("%d", mRecipe.getBoilTime()));
          timeView.setVisibility(View.GONE);
        }
        else {
          timeViewTitle.setText("Time (mins)");
          timeViewText.setText(String.format("%d", mRecipe.getBoilTime()));
          timeView.setVisibility(View.VISIBLE);
        }
      }

      public void onNothingSelected(AdapterView<?> parentView) {
      }
    });

    formSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position,
                                 long id) {
        form = formArray.get(position);
      }

      public void onNothingSelected(AdapterView<?> parentView) {
      }
    });

    // Set selections
    formSpinner.setSelection(0);
    useSpinner.setSelection(0);

    // Set button text
    submitButton.setText(R.string.add);

    // Set initial position for searchable list
    setInitialSearchableListSelection();
  }

  @Override
  public void onMissedClick(View v) {
    super.onMissedClick(v);

    if (v.equals(alphaAcidView)) {
      dialog = alertBuilder.editTextFloatAlert(alphaAcidViewText, alphaAcidViewTitle).create();
    }
    else if (v.equals(descriptionView)) {
      dialog = alertBuilder.editTextStringAlert(descriptionViewText, descriptionViewTitle).create();
    }
    else {
      return;
    }

    // Force keyboard open and show popup
    dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    dialog.show();
  }

  @Override
  public void getList() {
    Log.d("AddHopsActivity", "Getting hops list");
    ingredientList = new ArrayList<Ingredient>();
    ingredientList.addAll(ingredientHandler.getHopsList());
    Log.d("AddHopsActivity", "Found " + ingredientList.size() + " hop(s)");

    // Add a placeholder ingredient.  When selected, allows user to create
    // a new custom ingredient.
    Log.d("AddHopsActivity", "Adding placeholder ingredient");
    PlaceholderIngredient i = new PlaceholderIngredient("Create new");
    i.setShortDescription("Create a custom hop");
    ingredientList.add(0, i);
    Log.d("AddHopsActivity", "Total with placeholder: " + ingredientList.size() + " hop(s)");
  }

  @Override
  public void setInitialSpinnerSelection() {
    spinnerView.setSelection(0);
  }

  @Override
  public void createSpinner() {
    // Ingredient Spinner
    adapter = new IngredientSpinnerAdapter(this, ingredientList, "Hop", true);
    adapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    spinnerView.setAdapter(adapter);
  }

  @Override
  public void configureSpinnerListener() {
    // We don't use the spinner for this activity.
  }

  @Override
  public void configureSearchableListListener() {
    searchableListListener = new OnItemClickListener() {

      public void onItemClick(AdapterView<?> parentView, View selectedItemView, int position,
                              long id) {
        // Handle the placeholder case
        if (filteredList.get(position).getType().equals(Ingredient.PLACEHOLDER)) {
          // Cancel the dialog
          cancelDialog();

          // Switch into AddCustom
          Intent intent = new Intent(AddHopsActivity.this, AddCustomHopsActivity.class);
          intent.putExtra(Constants.KEY_RECIPE, mRecipe);
          intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
          startActivity(intent);
          finish();
          return;
        }

        // Not a placeholder
        hop = (Hop) filteredList.get(position);

        setValues(hop);

        // Cancel dialog
        if (dialog != null) {
          dialog.cancel();
          dialog = null;
        }
      }
    };
  }

  public void setValues(Hop h) {
    nameViewText.setText(h.getName());
    searchableListViewText.setText(h.getName());
    timeViewText.setText(mRecipe.getBoilTime() + "");
    alphaAcidViewText.setText(String.format("%2.2f", h.getAlphaAcidContent()));
    descriptionViewText.setText(h.getDescription());

    // If an amount has been specified then show it.  Otherewise,
    // default to 1.0oz.
    if (hop.getDisplayAmount() != 0) {
      amountViewText.setText(String.format("%2.2f", hop.getDisplayAmount()));
    }
    else {
      amountViewText.setText("1.0");
    }
  }

  @Override
  public void onDeletePressed() {
    // Must be overriden.  Add activities don't have a delete button.
  }

  @Override
  public void onCancelPressed() {
    finish();
  }

  @Override
  public void acquireValues() throws Exception {
    super.acquireValues();
    alpha = Double.parseDouble(alphaAcidViewText.getText().toString().replace(",", "."));
    String description = descriptionViewText.getText().toString();

    hop.setName(name);
    hop.setAlphaAcidContent(alpha);
    hop.setDisplayAmount(amount);
    hop.setUse(use);
    hop.setForm(form);
    hop.setDisplayTime(time);
    hop.setDescription(description);
  }

  @Override
  public void onFinished() {
    mRecipe.addIngredient(hop);
    mRecipe.save(AddHopsActivity.this);
    finish();
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.activity_add_hops, menu);
    return true;
  }
} public class AddMiscActivity extends AddEditIngredientActivity {

  // Holds the currently selected misc, and misc being edited
  public Misc misc;

  // Editable rows to display
  public Spinner typeSpinner;
  public Spinner useSpinner;
  public Spinner unitsSpinner;

  // Spinner array declarations
  public ArrayList<String> typeArray;
  public ArrayList<String> useArray;
  public ArrayList<String> unitsArray;

  // Data storage for spinners
  public String type;
  public String use;
  public String units;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Disable delete button for this view
    findViewById(R.id.delete_button).setVisibility(View.GONE);

    // Initialize views and such here
    typeSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainView, false);
    useSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainView, false);
    unitsSpinner = (Spinner) inflater.inflate(R.layout.row_layout_spinner, mainView, false);

    /************************************************************************
     ************* Add views *************************************************
     *************************************************************************/
    this.registerViews(Arrays.asList((View) useSpinner, (View) typeSpinner, (View) unitsSpinner));
    this.setViews(Arrays.asList(searchableListView, amountView, timeView, useSpinner, typeSpinner));

    // Set titles
    searchableListViewTitle.setText("Misc");

    // Set up type spinner
    typeArray = new ArrayList<String>();
    typeArray.add(Misc.TYPE_FINING);
    typeArray.add(Misc.TYPE_FLAVOR);
    typeArray.add(Misc.TYPE_HERB);
    typeArray.add(Misc.TYPE_SPICE);
    typeArray.add(Misc.TYPE_WATER_AGENT);
    typeArray.add(Misc.TYPE_OTHER);

    // Misc type spinner
    SpinnerAdapter miscTypeAdapter = new SpinnerAdapter(this, typeArray, "Misc Type");
    miscTypeAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    typeSpinner.setAdapter(miscTypeAdapter);

    // Set up use spinner
    useArray = new ArrayList<String>();
    useArray.add(Misc.USE_BOIL);
    useArray.add(Misc.USE_BOTTLING);
    useArray.add(Misc.USE_MASH);
    useArray.add(Misc.USE_PRIMARY);
    useArray.add(Misc.USE_SECONDARY);

    // Misc use spinner.
    SpinnerAdapter miscUseAdapter = new SpinnerAdapter(this, useArray, "Misc Use");
    miscUseAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    useSpinner.setAdapter(miscUseAdapter);

    // Set up units spinner
    unitsArray = new ArrayList<String>();
    unitsArray.add(Units.TEASPOONS_FORMAL);
    unitsArray.add(Units.CUPS_FORMAL);
    unitsArray.add(Units.GRAMS_FORMAL);
    unitsArray.add(Units.GALLONS_FORMAL);
    unitsArray.add(Units.LITERS_FORMAL);
    unitsArray.add(Units.OUNCES_FORMAL);
    unitsArray.add(Units.ITEMS_FORMAL);
    unitsArray.add(Units.KILOGRAMS_FORMAL);
    unitsArray.add(Units.MILLILITERS_FORMAL);
    unitsArray.add(Units.PACKAGES_FORMAL);
    unitsArray.add(Units.POUNDS_FORMAL);
    unitsArray.add(Units.UNITS_FORMAL);
    Collections.sort(unitsArray);

    // Units spinner adapter
    SpinnerAdapter miscUnitsAdapter = new SpinnerAdapter(this, unitsArray, "Units");
    miscUnitsAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    unitsSpinner.setAdapter(miscUnitsAdapter);

    // Handle units selector here
    unitsSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
        units = unitsArray.get(position);
        Log.d("AddMiscActivity", "Misc units selected: " + units);
      }

      public void onNothingSelected(AdapterView<?> parentView) {
      }

    });

    // Handle type selector here
    typeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
        type = typeArray.get(position);
        Log.d("AddMiscActivity", "Misc type selected: " + type);
      }

      public void onNothingSelected(AdapterView<?> parentView) {
      }

    });

    // Handle use selector here
    useSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {

      public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
        // Can't do anything if the misc is null.
        if (misc == null) {
          Log.d("AddMiscActivity", "No misc available");
          return;
        }

        use = useArray.get(position);
        misc.setUse(use);
        Log.d("AddMiscActivity", "Misc use selected: " + use);

        if (use.equals(Misc.USE_BOTTLING)) {
          timeView.setVisibility(View.GONE);
        }
        else {
          timeView.setVisibility(View.VISIBLE);
          timeViewTitle.setText(use + " Time" + " (" + misc.getTimeUnits() + ")");
        }
      }

      public void onNothingSelected(AdapterView<?> parentView) {
      }

    });

    // Set button text
    submitButton.setText(R.string.add);

    // Set selections
    unitsSpinner.setSelection(0);
    typeSpinner.setSelection(0);
    useSpinner.setSelection(0);

    // Set initial position for searchable list
    setInitialSearchableListSelection();
  }

  @Override
  public void onMissedClick(View v) {
    super.onMissedClick(v);
  }

  @Override
  public void getList() {
    // Get the list of ingredients to show
    Log.d("AddMiscActivity", "Getting miscs list");
    ingredientList = new ArrayList<Ingredient>();
    ingredientList.addAll(ingredientHandler.getMiscsList());

    // Add a placeholder ingredient.  When selected, allows user to create
    // a new custom ingredient.
    Log.d("AddMiscActivity", "Adding placeholder ingredient");
    PlaceholderIngredient i = new PlaceholderIngredient("Create new");
    i.setShortDescription("Create a custom misc");
    ingredientList.add(0, i);
  }

  @Override
  public void createSpinner() {
    // Set up spinner
    Log.d("AddMiscActivity", "Creating misc spinner");
    adapter = new IngredientSpinnerAdapter(this, ingredientList, "Misc Selector", true);
    adapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    spinnerView.setAdapter(adapter);
  }

  @Override
  public void setInitialSpinnerSelection() {
    spinnerView.setSelection(0);
  }

  @Override
  public void configureSearchableListListener() {

    searchableListListener = new OnItemClickListener() {

      public void onItemClick(AdapterView<?> parentView, View selectedItemView, int position, long id) {
        // Handle the placeholder case
        if (filteredList.get(position).getType().equals(Ingredient.PLACEHOLDER)) {
          // Cancel the dialog
          cancelDialog();

          // Switch into AddCustom
          Intent intent = new Intent(AddMiscActivity.this, AddCustomMiscActivity.class);
          intent.putExtra(Constants.KEY_RECIPE, mRecipe);
          intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
          startActivity(intent);
          finish();
          return;
        }

        // Not a placeholder
        misc = (Misc) filteredList.get(position);
        setValues(misc);

        // Cancel dialog
        cancelDialog();
      }
    };
  }

  public void setValues(Misc m) {
    Log.d("AddMiscActivity", "Setting text view values from misc: " + misc.getName());
    nameViewText.setText(m.getName());
    searchableListViewText.setText(m.getName());
    timeViewText.setText(String.format("%d", mRecipe.getBoilTime()));
    amountViewText.setText(String.format("%2.2f", m.getDisplayAmount()));
    typeSpinner.setSelection(typeArray.indexOf(m.getMiscType()));
    useSpinner.setSelection(useArray.indexOf(m.getUse()));
    unitsSpinner.setSelection(unitsArray.indexOf(Units.toFormal(m.getDisplayUnits())));
    amountViewTitle.setText("Amount (" + m.getDisplayUnits() + ")");
    timeViewTitle.setText(m.getUse() + " time");

    // Set units here
    units = Units.toFormal(m.getDisplayUnits());
  }

  @Override
  public void onFinished() {
    mRecipe.addIngredient(misc);
    mRecipe.save(this);
    finish();
  }

  @Override
  public void acquireValues() throws Exception {
    super.acquireValues();

    misc.setName(name);
    misc.setTime(time);
    misc.setDisplayUnits(Units.toAbbreviation(units));
    misc.setDisplayAmount(amount, misc.getDisplayUnits());
    misc.setMiscType(type);
    misc.setUse(use);
  }

  @Override
  public void onCancelPressed() {
    finish();
  }

  @Override
  public void onDeletePressed() {

  }
} public class AddYeastActivity extends AddEditIngredientActivity {

  // Holds currently selected yeast.
  public Yeast yeast;

  //Rows
  public View attenuationView;
  public View labView;
  public View productIdView;

  // Titles from rows
  public TextView attenuationViewTitle;
  public TextView labViewTitle;
  public TextView productIdViewTitle;

  // Content from rows
  public TextView attenuationViewText;
  public TextView labViewText;
  public TextView productIdViewText;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Disable delete button for this view
    findViewById(R.id.delete_button).setVisibility(View.GONE);

    // Initialize views and such here
    attenuationView = (View) inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    labView = (View) inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    productIdView = (View) inflater.inflate(R.layout.row_layout_edit_text, mainView, false);

    // Set the onClickListener for each row
    attenuationView.setOnClickListener(onClickListener);
    labView.setOnClickListener(onClickListener);
    productIdView.setOnClickListener(onClickListener);

    // Remove views we don't want
    mainView.removeView(timeView);

    // Add views we want
    mainView.addView(attenuationView);

    // Set titles
    attenuationViewTitle = (TextView) attenuationView.findViewById(R.id.title);
    labViewTitle = (TextView) labView.findViewById(R.id.title);
    productIdViewTitle = (TextView) productIdView.findViewById(R.id.title);

    attenuationViewTitle.setText("Attenuation (%)");
    labViewTitle.setText("Laboratory");
    productIdViewTitle.setText("Product ID");
    amountViewTitle.setText("Amount (L)");
    searchableListViewTitle.setText("Yeast");

    // Get text views
    attenuationViewText = (TextView) attenuationView.findViewById(R.id.text);
    labViewText = (TextView) labView.findViewById(R.id.text);
    productIdViewText = (TextView) productIdView.findViewById(R.id.text);

    // Set button text
    submitButton.setText(R.string.add);

    // Set initial position for searchable list
    setInitialSearchableListSelection();
  }

  @Override
  public void onMissedClick(View v) {
    super.onMissedClick(v);

    if (v.equals(attenuationView)) {
      dialog = alertBuilder.editTextFloatAlert(attenuationViewText, attenuationViewTitle).create();
    }
    else if (v.equals(labView)) {
      dialog = alertBuilder.editTextStringAlert(labViewText, labViewTitle).create();
    }
    else if (v.equals(productIdView)) {
      dialog = alertBuilder.editTextStringAlert(productIdViewText, productIdViewTitle).create();
    }
    else {
      return;
    }

    // Force keyboard open and show popup
    dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    dialog.show();
  }

  @Override
  public void getList() {
    // Get the list of ingredients to show
    ingredientList = new ArrayList<Ingredient>();
    ingredientList.addAll(ingredientHandler.getYeastsList());
  }

  @Override
  public void createSpinner() {
    // Set up type spinner
    adapter = new IngredientSpinnerAdapter(this, ingredientList, "Yeast", true);
    adapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
    spinnerView.setAdapter(adapter);
  }

  @Override
  public void configureSearchableListListener() {
    searchableListListener = new OnItemClickListener() {

      public void onItemClick(AdapterView<?> parentView, View selectedItemView, int position,
                              long id) {
        yeast = (Yeast) filteredList.get(position);
        setValues(yeast);

        // Cancel dialog
        if (dialog != null) {
          dialog.cancel();
          dialog = null;
        }
      }
    };
  }

  public void setValues(Yeast y) {
    nameViewText.setText(y.getName());
    searchableListViewText.setText(y.getName());
    attenuationViewText.setText(String.format("%2.0f", y.getAttenuation()));
    amountViewText.setText(String.format("%2.2f", y.getBeerXmlStandardAmount()));
    labViewText.setText(y.getLaboratory());
    productIdViewText.setText(y.getProductId());
  }

  @Override
  public void acquireValues() throws Exception {
    super.acquireValues();
    double attenuation = Double.parseDouble(attenuationViewText.getText().toString()
                                                    .replace(",", "."));
    String lab = labViewText.getText().toString();
    String pid = productIdViewText.getText().toString();

    yeast.setName(name);
    yeast.setAttenuation(attenuation);
    yeast.setBeerXmlStandardAmount(amount);
    yeast.setTime(time);
    yeast.setLaboratory(lab);
    yeast.setProductId(pid);
  }

  @Override
  public void onFinished() {
    mRecipe.addIngredient(yeast);
    mRecipe.save(this);
    finish();
  }

  public void onDeletePressed() {
    // Nothing
  }

  @Override
  public void onCancelPressed() {
    finish();
  }
} public class EditRecipeActivity extends AddRecipeActivity {

  // Rows to be displayed
  private View brewDateView;
  private View measuredOGView;
  private View measuredFGView;
  private View measuredBatchSizeView;

  // Titles
  private TextView brewDateViewTitle;
  private TextView measuredOGViewTitle;
  private TextView measuredFGViewTitle;
  private TextView measuredBatchSizeViewTitle;

  // Contents
  public static TextView brewDateViewText;
  private TextView measuredOGViewText;
  private TextView measuredFGViewText;
  private TextView measuredBatchSizeViewText;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initialize views and stuff
    brewDateView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    measuredFGView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    measuredOGView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);
    measuredBatchSizeView = inflater.inflate(R.layout.row_layout_edit_text, mainView, false);

    // Set onClickListeners for edit text views
    brewDateView.setOnClickListener(onClickListener);
    measuredFGView.setOnClickListener(onClickListener);
    measuredOGView.setOnClickListener(onClickListener);
    measuredBatchSizeView.setOnClickListener(onClickListener);

    // Add views to main view
    mainView.addView(brewDateView);
    mainView.addView(measuredOGView);
    mainView.addView(measuredFGView);
    mainView.addView(measuredBatchSizeView);

    // Titles
    brewDateViewTitle = (TextView) brewDateView.findViewById(R.id.title);
    brewDateViewTitle.setText("Brew Date");

    measuredFGViewTitle = (TextView) measuredFGView.findViewById(R.id.title);
    measuredFGViewTitle.setText("Measured Final Gravity");

    measuredOGViewTitle = (TextView) measuredOGView.findViewById(R.id.title);
    measuredOGViewTitle.setText("Measured Original Gravity");

    measuredBatchSizeViewTitle = (TextView) measuredBatchSizeView.findViewById(R.id.title);
    measuredBatchSizeViewTitle.setText("Measured Batch Size (" + Units.getVolumeUnits() + ")");

    // Texts
    brewDateViewText = (TextView) brewDateView.findViewById(R.id.text);
    brewDateViewText.setText(mRecipe.getBrewDate());

    measuredFGViewText = (TextView) measuredFGView.findViewById(R.id.text);
    measuredFGViewText.setText(String.format("%2.3f", mRecipe.getMeasuredFG()));

    measuredOGViewText = (TextView) measuredOGView.findViewById(R.id.text);
    measuredOGViewText.setText(String.format("%2.3f", mRecipe.getMeasuredOG()));

    measuredBatchSizeViewText = (TextView) measuredBatchSizeView.findViewById(R.id.text);
    measuredBatchSizeViewText.setText(String.format("%2.3f", mRecipe.getDisplayMeasuredBatchSize
            ()));

    // Set button text
    submitButton.setText(R.string.save);
  }

  @Override
  public void getValuesFromIntent() {
    super.getValuesFromIntent();
    mRecipe = getIntent().getParcelableExtra(Constants.KEY_RECIPE);
    style = mRecipe.getStyle();
    profile = mRecipe.getMashProfile();
    type = mRecipe.getType();
    efficiency = mRecipe.getEfficiency();
  }

  @Override
  public void onMissedClick(View v) {
    super.onMissedClick(v);

    AlertDialog alert;
    if (v.equals(measuredFGView)) {
      alert = alertBuilder.editTextFloatAlert(measuredFGViewText, measuredFGViewTitle).create();
    }
    else if (v.equals(measuredOGView)) {
      alert = alertBuilder.editTextFloatAlert(measuredOGViewText, measuredOGViewTitle).create();
    }
    else if (v.equals(measuredBatchSizeView)) {
      alert = alertBuilder.editTextFloatAlert(measuredBatchSizeViewText,
                                              measuredBatchSizeViewTitle).create();
    }
    else if (v.equals(brewDateView)) {
      // Show the brew date picker and return.
      DialogFragment newFragment = new DatePickerFragment();
      Bundle args = new Bundle();
      newFragment.setArguments(args);
      newFragment.show(this.getSupportFragmentManager(), "datePicker");
      return;
    }
    else {
      Log.d("EditRecipeActivity", "onMissedClick did not handle the clicked view");
      return; // In case its none of those views...
    }

    // Force keyboard open and show popup
    alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    alert.show();
  }

  @Override
  public void onFinished() {
    mRecipe.save(this);
    finish();
  }

  @Override
  public void acquireValues() throws Exception {
    super.acquireValues();
    double measuredOg = Double.parseDouble(measuredOGViewText.getText().toString()
                                                   .replace(",", "."));
    double measuredFg = Double.parseDouble(measuredFGViewText.getText().toString()
                                                   .replace(",", "."));
    double measuredBatchSize = Double.parseDouble(measuredBatchSizeViewText.getText().toString()
                                                          .replace(",", "."));
    String brewDate = brewDateViewText.getText().toString();

    mRecipe.setBrewDate(brewDate);
    mRecipe.setMeasuredFG(measuredFg);
    mRecipe.setMeasuredOG(measuredOg);
    mRecipe.setDisplayMeasuredBatchSize(measuredBatchSize);
  }

  /**
   * DatePicker dialog for when the user decides to modify the brew date.
   */
  public static class DatePickerFragment extends DialogFragment
          implements DatePickerDialog.OnDateSetListener {

    public DatePickerFragment() {
      // This fragment has no options menu.
      setHasOptionsMenu(false);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
      // Use the current date as the default date in the picker
      final Calendar c = Calendar.getInstance();
      int year = c.get(Calendar.YEAR);
      int month = c.get(Calendar.MONTH);
      int day = c.get(Calendar.DAY_OF_MONTH);

      // Create a new instance of DatePickerDialog and return it
      return new DatePickerDialog(getActivity(), this, year, month, day);
    }

    public void onDateSet(DatePicker view, int year, int month, int day) {
      // Parse the date.
      Date date = new GregorianCalendar(year, month, day).getTime();
      String dateString = new SimpleDateFormat(Constants.BREW_DATE_FMT).format(date);

      // Update the text view.
      brewDateViewText.setText(dateString);
    }
  }
} public class DisplayRecipeCollectionPagerAdapter extends FragmentStatePagerAdapter {
  Recipe r;
  Context c;
  ArrayList<Fragment> fragmentList;
  IngredientViewFragment ingredientFragment;
  InstructionViewFragment instructionFragment;
  DetailsViewFragment detailsFragment;
  ProfileViewFragment profileFragment;

  public DisplayRecipeCollectionPagerAdapter(FragmentManager fm, Recipe r, Context c) {
    super(fm);
    this.r = r;
    this.c = c;
    this.ingredientFragment = IngredientViewFragment.instance(r);
    this.instructionFragment = InstructionViewFragment.instance(r);
    this.detailsFragment = DetailsViewFragment.instance(r);
    this.profileFragment = ProfileViewFragment.instance(r);

    // Keep in list.
    this.fragmentList = new ArrayList<>();
    this.fragmentList.add(ingredientFragment);
    this.fragmentList.add(detailsFragment);
    this.fragmentList.add(instructionFragment);
    this.fragmentList.add(profileFragment);
  }

  @Override
  public Fragment getItem(int i) {
    return this.fragmentList.get(i);
  }

  @Override
  public int getCount() {
    return this.fragmentList.size();
  }

  @Override
  public CharSequence getPageTitle(int position) {
    BiermachtFragment f = (BiermachtFragment) this.fragmentList.get(position);
    return f.name();
  }

  @Override
  public void restoreState(Parcelable p, ClassLoader cl) {
    // Override this to do nothing.  It is not needed, and throws exceptions for "reasons".
    return;
  }
} public class RecipeArrayAdapter extends ArrayAdapter<Recipe> {

  // Fields
  private List<Recipe> list;
  private RecipesFragment frag;
  private ViewStorage vs;
  private String color;
  private LayoutInflater inflater;
  private View row;
  private HashMap<Integer, Boolean> checked;

  // Constructor
  public RecipeArrayAdapter(Context context, List<Recipe> list, RecipesFragment fr) {
    super(context, android.R.layout.simple_list_item_1, list);
    this.list = list;
    this.frag = fr;
    this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    this.checked = new HashMap<Integer, Boolean>();
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    // View to return
    row = convertView;

    if (row == null) {
      // If the row does not yet exist, inflate a new row_layout_recipe.
      row = inflater.inflate(R.layout.row_layout_recipe, parent, false);

      // Create storage for the component views.
      vs = new ViewStorage();
      vs.textView = (TextView) row.findViewById(R.id.label);
      vs.styleView = (TextView) row.findViewById(R.id.beer_style);
      vs.volView = (TextView) row.findViewById(R.id.batch_volume);
      vs.imageView = (ImageView) row.findViewById(R.id.row_icon);
      vs.unitsView = (TextView) row.findViewById(R.id.unit_text);
      row.setTag(vs);
    }

    // Get the component views for this row.
    vs = (ViewStorage) row.getTag();

    // Set the correct values for these views.
    vs.textView.setText(list.get(position).getRecipeName());
    vs.styleView.setText((list.get(position).getStyle().getName()));
    vs.volView.setText(String.format("%2.2f", list.get(position).getDisplayBatchSize()));
    vs.unitsView.setText(list.get(position).getVolumeUnits());

    if (this.checked.get(position) != null && this.checked.get(position)) {
      // This item is checked - make the background selected.
      row.setBackgroundResource(R.color.theme_grey);
    }
    else {
      // Otherwise, set the background to be transparent.
      row.setBackgroundResource(R.color.transparent);
    }

    // If we're running as a tablet, we should do some extra stuff here.
    if (frag.isTablet) {
      // If currently selected, set the background to indicate it.
      // Otherwise, set the background to transparent.
      if (position == frag.currentSelectedIndex) {
        row.setBackgroundResource(R.drawable.selector_tablet);
      }
      else {
        // Set background color transparent.
        row.setBackgroundResource(R.color.transparent);
      }
    }

    // Set beer color
    color = ColorHandler.getSrmColor(list.get(position).getColor());
    vs.imageView.setColorFilter(Color.parseColor(color));

    return row;
  }

  /**
   * Marks the given position in the list as checked.  This will cause the list item to be
   * highlighted.
   *
   * @param pos
   *         Position to mark
   * @param checked
   *         Whether to mark as checked or un-checked.
   */
  public void setChecked(int pos, boolean checked) {
    this.checked.put(pos, checked);
  }

  /**
   * Un-checks all items in the list.
   */
  public void clearChecks() {
    this.checked = new HashMap<Integer, Boolean>();
  }

  // A private class for storing views for a given row.
  private class ViewStorage {
    public TextView textView;
    public ImageView imageView;
    public TextView styleView;
    public TextView volView;
    public TextView unitsView;
  }
} public abstract class Ingredient implements Parcelable {

  // Beer XML 1.0 Required Fields (To be inherited) =================
  // ================================================================
  private String name;                    // Ingredient name
  private int version;                   // bXML Version being used
  public double amount;                  // Amount in beerXML standard units
  public int time;                       // Time ingredient is used - units vary based on use

  // Custom Fields ==================================================
  // ================================================================
  private long id;                        // Lookup ID for database
  private long ownerId;                   // ID of recipe that contains this
  private double inventory;               // Amount in inventory (standard units)
  private long databaseId;                // Which virtual database to store this in

  // Static values =================================================
  // ===============================================================
  public static final String FERMENTABLE = "Fermentable";
  public static final String HOP = "Hop";
  public static final String YEAST = "Yeast";
  public static final String MISC = "Misc";
  public static final String WATER = "Water";
  public static final String PLACEHOLDER = "Placeholder";

  // Ingredient uses
  public static final String USE_BOIL = "Boil";
  public static final String USE_MASH = "Mash";
  public static final String USE_PRIMARY = "Primary";
  public static final String USE_SECONDARY = "Secondary";
  public static final String USE_BOTTLING = "Bottling";
  public static final String USE_DRY_HOP = "Dry Hop";
  public static final String USE_FIRST_WORT = "First Wort";
  public static final String USE_AROMA = "Aroma";
  public static final String USE_OTHER = "other";

  // Public constructors
  public Ingredient(String name) {
    this.name = name;
    this.id = Constants.INVALID_ID;
    this.ownerId = Constants.INVALID_ID;
    this.databaseId = Constants.DATABASE_USER_RECIPES;
    this.amount = 0;
    this.time = 0;
    this.inventory = 0;
  }

  public Ingredient(Parcel p) {
    name = p.readString();
    version = p.readInt();
    amount = p.readDouble();
    id = p.readLong();
    ownerId = p.readLong();
    inventory = p.readDouble();
    databaseId = p.readLong();
    time = p.readInt();
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    // Should be called by all sub-classes
    p.writeString(name);
    p.writeInt(version);
    p.writeDouble(amount);
    p.writeLong(id);
    p.writeLong(ownerId);
    p.writeDouble(inventory);
    p.writeLong(databaseId);
    p.writeInt(time);
  }

  public static final Parcelable.Creator<Ingredient> CREATOR =
          new Parcelable.Creator<Ingredient>() {
            @Override
            public Ingredient createFromParcel(Parcel p) {
              Ingredient i = null;
              try {
                i = new Hop(p);
              } catch (Exception e) {
                Log.d("Ingredient", "Not a hop");
              }
              try {
                i = new Misc(p);
              } catch (Exception e) {
                Log.d("Ingredient", "Not a Misc");
              }
              try {
                i = new Fermentable(p);
              } catch (Exception e) {
                Log.d("Ingredient", "Not a Fermentable");
              }
              try {
                i = new Yeast(p);
              } catch (Exception e) {
                Log.d("Ingredient", "Not a Yeast");
              }

              return i;
            }

            @Override
            public Ingredient[] newArray(int size) {
              return new Ingredient[]{};
            }
          };

  // Abstract methods of Ingredient
  public abstract String getType();

  public abstract String getShortDescription();

  public abstract void setShortDescription(String description);

  // Get beerXML 1.0 Units
  public abstract String getBeerXmlStandardUnits();

  // Get display units
  public abstract String getDisplayUnits();

  // Set display units
  public abstract void setDisplayUnits(String s);

  // Returns display amount
  public abstract double getDisplayAmount();

  // Takes display amount
  public abstract void setDisplayAmount(double amt);

  // Returns amount in units specified by beerXML 1.0
  public abstract double getBeerXmlStandardAmount();

  // Takes amount in beerXML 1.0 units
  public abstract void setBeerXmlStandardAmount(double amt);

  public abstract int hashCode();

  public abstract boolean equals(Object o);

  public abstract int compareTo(Ingredient other);

  public abstract void setTime(int time);

  public abstract int getTime();

  @Override
  public String toString() {
    return name;
  }

  public String getUse() {
    return Ingredient.USE_OTHER;
  }

  public long getDatabaseId() {
    return this.databaseId;
  }

  public void setDatabaseId(long i) {
    this.databaseId = i;
  }

  public double getBeerXmlStandardInventory() {
    return this.inventory;
  }

  public void setBeerXmlStandardInventory(double d) {
    this.inventory = d;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public long getOwnerId() {
    return ownerId;
  }

  public void setOwnerId(long ownerId) {
    this.ownerId = ownerId;
  }

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public int getVersion() {
    return version;
  }

  public void setVersion(int version) {
    this.version = version;
  }
} public class MashStep implements Parcelable {
  // ================================================================
  // Beer XML 1.0 Required Fields ===================================
  // ================================================================
  private String name;              // profile name
  private int version;              // XML Version -- 1
  private String type;              // infusion, temp, decoc
  private double infuseAmount;      // Amount
  private double stepTemp;          // Temp for this step in C
  private double stepTime;          // Time for this step

  // ================================================================
  // Beer XML 1.0 Optional Fields ===================================
  // ================================================================
  private double rampTime;            // Time to ramp temp
  private double endTemp;             // Final temp for long steps
  private String description;         // Description of step
  private double waterToGrainRatio;   // Water to grain ratio (L/kg)
  private double decoctAmount;        // Amount of mash to decoct. (L)

  // ================================================================
  // Custom Fields ==================================================
  // ================================================================
  private long ownerId;             // id for parent mash profile
  private long id;                  // id for use in database
  public int order;                 // Order in step list
  private double infuseTemp;        // Temperature of infuse water
  private boolean calcInfuseTemp;   // Auto calculate the infusion temperature if true.
  private boolean calcInfuseAmt;    // Auto calculate the infusion amount if true.
  private boolean calcDecoctAmt;    // Auto calculate the amount to decoct if true.
  private Recipe recipe;            // Reference to the recipe that owns this mash.

  // ================================================================
  // Static values ==================================================
  // ================================================================
  public static String INFUSION = "Infusion";
  public static String TEMPERATURE = "Temperature";
  public static String DECOCTION = "Decoction";

  // Basic Constructor
  public MashStep(Recipe r) {
    this.setName("Mash Step (" + (r.getMashProfile().getMashStepList().size() + 1) + ")");
    this.setVersion(1);
    this.setType(MashStep.INFUSION);
    this.setDisplayInfuseAmount(0);
    this.setBeerXmlStandardStepTemp(65.555556);
    this.setStepTime(60);
    this.setRampTime(0);
    this.setBeerXmlStandardEndTemp(0.0);
    this.setDescription("");
    this.setBeerXmlStandardWaterToGrainRatio(2.60793889);
    this.infuseTemp = 0.0;
    this.id = - 1;
    this.ownerId = - 1;
    this.order = 1;
    this.decoctAmount = 0;
    this.calcInfuseAmt = true;
    this.calcInfuseTemp = true;
    this.calcDecoctAmt = true;
    this.recipe = r;
  }

  // Only use this when we don't have a mash profile to
  // use!
  public MashStep() {
    this(new Recipe());
  }

  public MashStep(Parcel p) {
    // Beer XML 1.0 Required Fields ===================================
    // ================================================================
    name = p.readString();
    version = p.readInt();
    type = p.readString();
    infuseAmount = p.readDouble();
    stepTemp = p.readDouble();
    stepTime = p.readDouble();

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    rampTime = p.readDouble();
    endTemp = p.readDouble();
    description = p.readString();
    waterToGrainRatio = p.readDouble();
    decoctAmount = p.readDouble();

    // Custom Fields ==================================================
    // ================================================================
    ownerId = p.readLong();
    id = p.readLong();
    order = p.readInt();
    infuseTemp = p.readDouble();
    calcInfuseTemp = p.readInt() == 0 ? false : true;
    calcInfuseAmt = p.readInt() == 0 ? false : true;
    calcDecoctAmt = p.readInt() == 0 ? false : true;
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    // Beer XML 1.0 Required Fields ===================================
    // ================================================================
    p.writeString(name);                // profile name
    p.writeInt(version);              // XML Version -- 1
    p.writeString(type);                    // infusion, temp, decoc
    p.writeDouble(infuseAmount);            // Amount
    p.writeDouble(stepTemp);        // Temp for this step in C
    p.writeDouble(stepTime);        // Time for this step

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    p.writeDouble(rampTime);        // Time to ramp temp
    p.writeDouble(endTemp);             // Final temp for long steps
    p.writeString(description);         // Description of step
    p.writeDouble(waterToGrainRatio);   // Water to grain ratio (L/kg)
    p.writeDouble(decoctAmount);        // Amount of water to decoct.

    // Custom Fields ==================================================
    // ================================================================
    p.writeLong(ownerId);        // id for parent mash profile
    p.writeLong(id);                    // id for use in database
    p.writeInt(order);                  // Order in step list
    p.writeDouble(infuseTemp);
    p.writeInt(calcInfuseTemp ? 1 : 0);
    p.writeInt(calcInfuseAmt ? 1 : 0);
    p.writeInt(calcDecoctAmt ? 1 : 0);
  }

  public static final Parcelable.Creator<MashStep> CREATOR =
          new Parcelable.Creator<MashStep>() {
            @Override
            public MashStep createFromParcel(Parcel p) {
              return new MashStep(p);
            }

            @Override
            public MashStep[] newArray(int size) {
              return new MashStep[size];
            }
          };

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

  @Override
  public boolean equals(Object o) {
    // Non-MashStep objects cannot equal a MashStep.
    if (! (o instanceof MashStep)) {
      return false;
    }

    // Comparing to a MashStep - cast the given Object.
    MashStep other = (MashStep) o;

    // Both are MashStep objects - compare important fields.
    if (! this.getName().equals(other.getName())) {
      Log.d("MashStep", "MashStep.equals(): " + this.getName() + " != " + other.getName());
      return false;
    }
    else if (this.getStepTime() != other.getStepTime()) {
      Log.d("MashStep", "MashStep.equals(): " + this.getStepTime() + " != " + other.getStepTime());
      return false;
    }
    else if (this.getBeerXmlStandardStepTemp() != other.getBeerXmlStandardStepTemp()) {
      Log.d("MashStep", "MashStep.equals(): " + this.getBeerXmlStandardStepTemp() + " != " + other.getBeerXmlStandardStepTemp());
      return false;
    }
    else if (! this.getType().equals(other.getType())) {
      Log.d("MashStep", "MashStep.equals(): " + this.getType() + " != " + other.getType());
      return false;
    }

    // All index fields match - these objects are equal.
    return true;
  }

  @Override
  public String toString() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return this.name;
  }

  public void setVersion(int version) {
    this.version = version;
  }

  public int getVersion() {
    return this.version;
  }

  public void setType(String type) {
    this.type = type;
  }

  public String getType() {
    return this.type;
  }

  public double getDisplayDecoctAmount() {
    // If we are autocalculating.
    if (this.calcDecoctAmt) {
      return this.calculateDecoctAmount();
    }

    // We're not auto-calculating.
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      return Units.litersToGallons(this.decoctAmount);
    }
    else {
      return this.decoctAmount;
    }
  }

  public void setDisplayDecoctAmount(double amt) {
    Log.d("MashStep", "Setting display decoction amount: " + amt);
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.decoctAmount = Units.gallonsToLiters(amt);
    }
    else {
      this.decoctAmount = amt;
    }
  }

  public void setBeerXmlDecoctAmount(double amt) {
    this.decoctAmount = amt;
  }

  public double getBeerXmlDecoctAmount() {
    return this.decoctAmount;
  }

  public void setDisplayInfuseAmount(double amt) {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.infuseAmount = Units.gallonsToLiters(amt);
    }
    else {
      this.infuseAmount = amt;
    }
  }

  public double getDisplayAmount() {
    if (this.getType().equals(DECOCTION)) {
      Log.d("MashStep", "Returning display decoction amount: " + this.getDisplayDecoctAmount());
      return this.getDisplayDecoctAmount();
    }
    else if (this.getType().equals(INFUSION)) {
      Log.d("MashStep", "Returning display infusion amount: " + this.getDisplayInfuseAmount());
      return this.getDisplayInfuseAmount();
    }
    else if (this.getType().equals(TEMPERATURE)) {
      Log.d("MashStep", "Temperature mash, returning 0 for display amount");
      return 0;
    }
    Log.d("MashStep", "Invalid type: " + this.getType() + ".  Returning -1 for display amount");
    return - 1;
  }

  public double getDisplayInfuseAmount() {
    // No infuse amount for decoction steps. Ever.
    if (this.getType().equals(MashStep.DECOCTION)) {
      return 0;
    }

    // If we are autocalculating.
    if (this.calcInfuseAmt) {
      return this.calculateInfuseAmount();
    }

    // If we're not auto-calculating.
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      return Units.litersToGallons(this.infuseAmount);
    }
    else {
      return this.infuseAmount;
    }
  }

  /* Calculates the infusion amount based on
  /* water to grain ratio, water temp, water to add,
  /* and the step temperature.
  /* Also sets this.infuseAmount to the correct value. */
  public double calculateInfuseAmount() {
    // We perform different calculations if this is the initial infusion.
    double amt = - 1;
    if (this.firstInList() &&
            ! this.recipe.getMashProfile().getMashType().equals(MashProfile.MASH_TYPE_BIAB)) {
      // Initial infusion for a non-biab mash. Water is constant * amount of grain.
      amt = this.getBeerXmlStandardWaterToGrainRatio() * this.getBeerXmlStandardMashWeight();
    }
    else if (this.firstInList() &&
            this.recipe.getMashProfile().getMashType().equals(MashProfile.MASH_TYPE_BIAB)) {
      // Initial infusion for a BIAB mash.
      // Water is boil volume + grain absorption.
      amt = this.recipe.getBeerXmlStandardBoilSize();

      // Estimate ~1 liter per kg absobtion.
      // TODO: Get this from equipment profile.
      amt += this.getBeerXmlStandardMashWeight();
    }
    else {
      // The actual temperature of the water being infused.
      double actualInfuseTemp = calcInfuseTemp ? calculateBXSInfuseTemp() : getBeerXmlStandardInfuseTemp();

      // Not initial infusion. Calculate water to add to reach appropriate temp.
      try {
        amt = (this.getBeerXmlStandardStepTemp() - getPreviousStep().getBeerXmlStandardStepTemp());
      } catch (Exception e) {
        e.printStackTrace();
      }
      amt = amt * (.41 * this.getBeerXmlStandardMashWeight() + this.getBXSTotalWaterInMash());
      amt = amt / (actualInfuseTemp - this.getBeerXmlStandardStepTemp());
    }

    // Set BXL amount so that database is consistent.
    this.infuseAmount = amt;

    // Use appropriate units.
    if (Units.getVolumeUnits().equals(Units.LITERS)) {
      return amt;
    }
    else {
      return Units.litersToGallons(amt);
    }
  }

  /* Calculates the decoction amount */
  public double calculateDecoctAmount() {
    Log.d("MashStep", "Auto-calculating decoct amount");
    /**
     * F = (TS – TI) / (TB – TI – X)
     *
     * Where f is the fraction, TS is the target step temperature, TI is the initial (current) temperature,
     * TB is the temperature of the boiling mash and X is an equipment dependent parameter (typically 18F or 10C).
     */
    double target = this.getBeerXmlStandardStepTemp();
    double initial = 0;
    try {
      initial = this.getPreviousStep().getBeerXmlStandardStepTemp();
    } catch (Exception e) {
      // Attempted to calculate decoct amount for first step in MashProfile.  This isn't
      // allowed (the first step must be an infusion step).  Handle this
      // somewhat gracefully, as this can occur temporarily during recipe formulation.
      e.printStackTrace();
      return 0;
    }
    double boiling = 99; // Boiling temperature of mash (C).
    double X = 10; // TODO: Equipment factor - accounts for heat absorbtion of mash tun.
    double grainDensity = .43; // .43kg / 1L
    double waterVolume = getBXSTotalWaterInMash(); // L
    double grainMass = (waterVolume / waterToGrainRatio); // kg
    double grainVolume = grainMass / grainDensity; // L
    double totalVolume = waterVolume + grainVolume; // L

    // This calculates the ratio of "mash to boil" / "mash to leave in tun".
    double fraction = (target - initial) / (boiling - initial - X);

    // Convert this ratio to "Mash to boil" / "Total Mash"
    fraction = (fraction) / (1 + fraction);

    // Return the fraction of mash, adjusted for the selected units.
    if (Units.getUnitSystem().equals(Units.METRIC)) {
      return fraction * totalVolume; // Return Liters of mash.
    }
    else {
      return fraction * Units.litersToGallons(totalVolume); // Return Gallons of mash.
    }
  }

  /**
   * Calculates the infusion temperature for both initial infusion, and water adds.
   * http://www.howtobrew.com/section3/chapter16-3.html
   */
  public double calculateInfuseTemp() {
    // We perform different calculations if this is the initial infusion.
    double temp = 0;
    if (this.firstInList()) {
      // Initial infusion.
      // TODO: For now, we don't have equipment so we combine tun / grain temp for calculation.
      double tunTemp = .7 * this.recipe.getMashProfile().getBeerXmlStandardGrainTemp() +
              .3 * this.recipe.getMashProfile().getBeerXmlStandardTunTemp();
      temp = (.41) / (this.getBeerXmlStandardWaterToGrainRatio());
      temp = temp * (this.getBeerXmlStandardStepTemp() - tunTemp) + this.getBeerXmlStandardStepTemp();
    }
    else {
      // Not initial infusion.  Assume boiling water to make
      // calculation easier.  If the next step has a LOWER temperature,
      // use room temperature water (72F).
      try {
        if (getPreviousStep().getBeerXmlStandardStepTemp() < this.getBeerXmlStandardStepTemp()) {
          temp = 100;
        }
        else {
          temp = 22.2222;
        }
      } catch (Exception e) {
        temp = - 1;
      }
    }

    // Set the infuse temperature.
    this.infuseTemp = temp;

    // Use appropriate units.
    if (Units.getTemperatureUnits().equals(Units.CELSIUS)) {
      return temp;
    }
    else {
      return Units.celsiusToFahrenheit(temp);
    }
  }

  private double calculateBXSInfuseTemp() {
    if (Units.getTemperatureUnits().equals(Units.CELSIUS)) {
      return this.calculateInfuseTemp();
    }
    else {
      return Units.fahrenheitToCelsius(this.calculateInfuseTemp());
    }
  }

  public boolean firstInList() {
    return this.getOrder() == 0;
  }

  public void setBeerXmlStandardInfuseAmount(double amt) {
    this.infuseAmount = amt;
  }

  public double getBeerXmlStandardInfuseAmount() {
    if (this.calcInfuseAmt) {
      calculateInfuseAmount();
    }
    return this.infuseAmount;
  }

  public double getDisplayInfuseTemp() {
    if (this.calcInfuseTemp) {
      return Math.round(this.calculateInfuseTemp());
    }

    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Math.round(Units.celsiusToFahrenheit(this.infuseTemp));
    }
    else {
      return Math.round(this.infuseTemp);
    }
  }

  public void setDisplayInfuseTemp(double d) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.infuseTemp = Units.fahrenheitToCelsius(d);
    }
    else {
      this.infuseTemp = d;
    }
  }

  public double getBeerXmlStandardInfuseTemp() {
    return this.infuseTemp;
  }

  public void setBeerXmlStandardInfuseTemp(double d) {
    this.infuseTemp = d;
  }

  public void setAutoCalcInfuseTemp(boolean b) {
    this.calcInfuseTemp = b;
  }

  public void setAutoCalcInfuseAmt(boolean b) {
    this.calcInfuseAmt = b;
  }

  public void setAutoCalcDecoctAmt(boolean b) {
    this.calcDecoctAmt = b;
  }

  public boolean getAutoCalcDecoctAmt() {
    return this.calcDecoctAmt;
  }

  public boolean getAutoCalcInfuseTemp() {
    return this.calcInfuseTemp;
  }

  public boolean getAutoCalcInfuseAmt() {
    return this.calcInfuseAmt;
  }

  public void setDisplayStepTemp(double temp) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.stepTemp = Units.fahrenheitToCelsius(temp);
    }
    else {
      this.stepTemp = temp;
    }
  }

  public double getDisplayStepTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(this.stepTemp);
    }
    else {
      return this.stepTemp;
    }
  }

  public void setBeerXmlStandardStepTemp(double temp) {
    this.stepTemp = temp;
  }

  public double getBeerXmlStandardStepTemp() {
    return this.stepTemp;
  }

  public double getBeerXmlStandardWaterToGrainRatio() {
    // If this is the first in the list, use the configured value.
    // Otherwise, we need to calculate it based on the water added.
    if (this.firstInList() && ! this.recipe.getMashProfile().getMashType().equals(MashProfile.MASH_TYPE_BIAB)) {
      return this.waterToGrainRatio;
    }
    return (this.getBeerXmlStandardInfuseAmount() + this.getBXSTotalWaterInMash()) / this.getBeerXmlStandardMashWeight();
  }

  public double getDisplayWaterToGrainRatio() {
    if (Units.getUnitSystem().equals(Units.IMPERIAL)) {
      return Units.LPKGtoQPLB(getBeerXmlStandardWaterToGrainRatio());
    }
    else {
      return getBeerXmlStandardWaterToGrainRatio();
    }
  }

  public void setBeerXmlStandardWaterToGrainRatio(double d) {
    // Don't update if less than 0. Impossible value.
    if (d <= 0) {
      return;
    }
    this.waterToGrainRatio = d;
  }

  public void setDisplayWaterToGrainRatio(double d) {
    if (d <= 0) {
      return;
    }
    if (Units.getUnitSystem().equals(Units.IMPERIAL)) {
      this.waterToGrainRatio = Units.QPLBtoLPKG(d);
    }
    else {
      this.waterToGrainRatio = d;
    }
  }

  public void setOrder(int i) {
    // Order is privately used for ordering mash steps
    // when they are received from the database.  Once they
    // are out of the db, we use the order in the list as the order.
    // When saved, the orders will be updated in the database.
    this.order = i;
  }

  public int getOrder() {
    return this.recipe.getMashProfile().getMashStepList().indexOf(this);
  }

  public void setDescription(String s) {
    this.description = s;
  }

  public String getDescription() {
    return this.description;
  }

  public void setStepTime(double time) {
    this.stepTime = time;
  }

  public double getStepTime() {
    return this.stepTime;
  }

  public void setRampTime(double time) {
    this.rampTime = time;
  }

  public double getRampTime() {
    return this.rampTime;
  }

  public void setBeerXmlStandardEndTemp(double temp) {
    this.endTemp = temp;
  }

  public double getBeerXmlStandardEndTemp() {
    return this.endTemp;
  }

  public void setId(long id) {
    this.id = id;
  }

  public long getId() {
    return this.id;
  }

  public void setOwnerId(long id) {
    this.ownerId = id;
  }

  public long getOwnerId() {
    return this.ownerId;
  }

  public void setRecipe(Recipe r) {
    this.recipe = r;
  }

  public Recipe getRecipe() {
    return this.recipe;
  }

  public double getBeerXmlStandardMashWeight() {
    // If there are no ingredients for this recipe yet, we need to use something!
    // Fake it, assume 12 pounds of grain by default.
    double weight = BrewCalculator.TotalBeerXmlMashWeight(this.recipe);
    return weight == 0 ? Units.poundsToKilos(12) : weight;
  }

  public MashStep getPreviousStep() throws Exception {
    if (this.firstInList()) {
      throw new Exception(); // TODO: This should throw a specific exception.
    }

    int idx = this.recipe.getMashProfile().getMashStepList().indexOf(this);
    return this.recipe.getMashProfile().getMashStepList().get(idx - 1);
  }

  public double getBXSTotalWaterInMash() {
    double amt = 0;

    for (MashStep step : this.recipe.getMashProfile().getMashStepList()) {
      if (step.equals(this)) {
        break;
      }
      amt += step.getBeerXmlStandardInfuseAmount();
    }
    Log.d("MashStep", "Step " + this.getName() + " has " + Units.litersToGallons(amt) + " gal in mash.");
    return amt;
  }
} public class Recipe implements Parcelable {
  // ===============================================================
  // Static values =================================================
  // ===============================================================
  public static final String EXTRACT = "Extract";
  public static final String ALL_GRAIN = "All Grain";
  public static final String PARTIAL_MASH = "Partial Mash";
  public static final int STAGE_PRIMARY = 1;
  public static final int STAGE_SECONDARY = 2;
  public static final int STAGE_TERTIARY = 3;
  public static final Parcelable.Creator<Recipe> CREATOR =
          new Parcelable.Creator<Recipe>() {
            @Override
            public Recipe createFromParcel(Parcel p) {
              return new Recipe(p);
            }

            @Override
            public Recipe[] newArray(int size) {
              return new Recipe[]{};
            }
          };

  // ================================================================
  // Beer XML 1.0 Required Fields ===================================
  // ================================================================
  private String name;                 // Recipe name
  private int version;                 // XML Version -- 1
  private String type;                 // Extract, Grain, Mash
  private BeerStyle style;             // Stout, Pilsner, etc.
  private String brewer;               // Brewer's name
  private double batchSize;            // Target size (L)
  private double boilSize;             // Pre-boil vol (L)
  private int boilTime;                // In Minutes
  private double efficiency;           // 100 for extract
  private ArrayList<Hop> hops;         // Hops used
  private ArrayList<Fermentable> fermentables;  // Fermentables used
  private ArrayList<Yeast> yeasts;     // Yeasts used
  private ArrayList<Misc> miscs;       // Misc ingredients used
  private ArrayList<Water> waters;     // Waters used
  private MashProfile mashProfile;     // Mash profile for non-extracts

  // ================================================================
  // Beer XML 1.0 Optional Fields ===================================
  // ================================================================
  private double OG;                // Original Gravity
  private double FG;                // Final Gravity
  private int fermentationStages;   // # of Fermentation stages
  private int primaryAge;           // Time in primary in days
  private double primaryTemp;       // Temp in primary in C
  private int secondaryAge;         // Time in Secondary in days
  private double secondaryTemp;     // Temp in secondary in C
  private int tertiaryAge;          // Time in tertiary in days
  private double tertiaryTemp;      // Temp in tertiary in C
  private String tasteNotes;        // Taste notes
  private int tasteRating;          // Taste score out of 50
  private int bottleAge;            // Bottle age in days
  private double bottleTemp;        // Bottle temp in C
  private boolean isForceCarbonated;// True if force carb is used
  private double carbonation;       // Volumes of carbonation
  private String brewDate;          // Date brewed
  private String primingSugarName;  // Name of sugar for priming
  private double primingSugarEquiv; // Equivalent amount of priming sugar to be used
  private double kegPrimingFactor;  // factor - use less sugar when kegging vs bottles
  private double carbonationTemp;   // Carbonation temperature in C
  private int calories;             // Calories (KiloCals)
  private String lastModified;      // Date last modified in database.

  // ================================================================
  // Custom Fields ==================================================
  // ================================================================
  private long id;                   // id for use in database
  private String notes;              // User input notes
  private int batchTime;             // Total length in weeks
  private double ABV;                // Alcohol by volume
  private double bitterness;         // Bitterness in IBU
  private double color;              // Color - SRM
  private InstructionGenerator instructionGenerator;
  private double measuredOG;         // Brew day stat: measured OG
  private double measuredFG;         // Brew stat: measured FG
  private double measuredVol;        // Measured final volume (L) of batch.
  private double steepTemp;          // Temperature to steep grains.

  // ================================================================
  // Fields for auto-calculation ====================================
  // ================================================================
  private boolean calculateBoilVolume;            // Calculate the boil volume automatically
  private boolean calculateStrikeVolume;          // Calculate strike vol automatically
  private boolean calculateStrikeTemp;            // Calculate strike temp automatically

  // Public constructors
  public Recipe(String s) {
    // ================================================================
    // Beer XML 1.0 Required Fields ===================================
    // ================================================================
    this.name = s;
    this.setVersion(1);
    this.setType(ALL_GRAIN);
    this.style = Constants.BEERSTYLE_OTHER;
    this.setBrewer("Unknown Brewer");
    this.setDisplayBatchSize(5);
    this.setDisplayBoilSize(2.5);
    this.setBoilTime(60);
    this.setEfficiency(70);
    this.hops = new ArrayList<Hop>();
    this.fermentables = new ArrayList<Fermentable>();
    this.yeasts = new ArrayList<Yeast>();
    this.miscs = new ArrayList<Misc>();
    this.waters = new ArrayList<Water>();
    this.mashProfile = new MashProfile(this);

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    this.OG = 1;
    this.setFG(1);
    this.setFermentationStages(1);
    this.primaryAge = 14;
    this.secondaryAge = 0;
    this.tertiaryAge = 0;
    this.primaryTemp = 21;
    this.secondaryTemp = 21;
    this.tertiaryTemp = 21;
    this.bottleAge = 14;
    this.brewDate = "";

    // Custom Fields ==================================================
    // ================================================================
    this.id = - 1;
    this.notes = "";
    this.tasteNotes = "";
    this.batchTime = 60;
    this.ABV = 0;
    this.bitterness = 0;
    this.color = 0;
    this.instructionGenerator = new InstructionGenerator(this);
    this.measuredOG = 0;
    this.measuredFG = 0;
    this.measuredVol = 0;
    this.steepTemp = Units.fahrenheitToCelsius(155);
    this.lastModified = new SimpleDateFormat(Constants.LAST_MODIFIED_DATE_FMT).format(new Date());

    // Fields for auto-calculation ====================================
    // ================================================================
    calculateBoilVolume = true;
    calculateStrikeVolume = false;
    calculateStrikeTemp = false;

    update();
  }

  // Constructor with no arguments!
  public Recipe() {
    this("New Recipe");
  }

  public Recipe(Parcel p) {
    hops = new ArrayList<Hop>();
    fermentables = new ArrayList<Fermentable>();
    yeasts = new ArrayList<Yeast>();
    miscs = new ArrayList<Misc>();
    waters = new ArrayList<Water>();

    name = p.readString();            // Recipe name
    version = p.readInt();          // XML Version -- 1
    type = p.readString();                // Extract, Grain, Mash
    style = p.readParcelable(BeerStyle.class.getClassLoader());    // Stout, Pilsner, etc.
    brewer = p.readString();            // Brewer's name
    batchSize = p.readDouble();
    boilSize = p.readDouble();
    boilTime = p.readInt();            // In Minutes
    efficiency = p.readDouble();          // 100 for extract
    p.readTypedList(hops, Hop.CREATOR);              // Hops used
    p.readTypedList(fermentables, Fermentable.CREATOR);      // Fermentables used
    p.readTypedList(yeasts, Yeast.CREATOR);            // Yeasts used
    p.readTypedList(miscs, Misc.CREATOR);           // Misc ingredients used
    p.readTypedList(waters, Water.CREATOR);           // Waters used
    mashProfile = p.readParcelable(MashProfile.class.getClassLoader()); // Mash profile for non-extracts
    mashProfile.setRecipe(this);

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    OG = p.readDouble();            // Original Gravity
    FG = p.readDouble();            // Final Gravity
    fermentationStages = p.readInt();   // # of Fermentation stages
    primaryAge = p.readInt();        // Time in primary in days
    primaryTemp = p.readDouble();      // Temp in primary in C
    secondaryAge = p.readInt();      // Time in Secondary in days
    secondaryTemp = p.readDouble();    // Temp in secondary in C
    tertiaryAge = p.readInt();      // Time in tertiary in days
    tertiaryTemp = p.readDouble();    // Temp in tertiary in C
    tasteNotes = p.readString();        // Taste notes
    tasteRating = p.readInt();          // Taste score out of 50
    bottleAge = p.readInt();            // Bottle age in days
    bottleTemp = p.readDouble();        // Bottle temp in C
    isForceCarbonated = p.readInt() > 0;  // True if force carb is used
    carbonation = p.readDouble();       // Volumes of carbonation
    brewDate = p.readString();          // Date brewed
    primingSugarName = p.readString();  // Name of sugar for priming
    primingSugarEquiv = p.readDouble(); // Equivalent amount of priming sugar to be used
    kegPrimingFactor = p.readDouble();  // factor - use less sugar when kegging vs bottles
    carbonationTemp = p.readDouble();   // Carbonation temperature in C
    calories = p.readInt();
    lastModified = p.readString();

    // Custom Fields ==================================================
    // ================================================================
    id = p.readLong();                  // id for use in database
    notes = p.readString();       // User input notes
    batchTime = p.readInt();            // Total length in weeks
    ABV = p.readDouble();                // Alcohol by volume
    bitterness = p.readDouble();         // Bitterness in IBU
    color = p.readDouble();              // Color - SRM
    // Instruction generator not included in parcel
    measuredOG = p.readDouble();         // Brew day stat: measured OG
    measuredFG = p.readDouble();         // Brew stat: measured FG
    measuredVol = p.readDouble();        // Brew stat: measured volume
    steepTemp = p.readDouble();          // Temperature to steep grains for extract recipes.

    // Fields for auto-calculation ====================================
    // ================================================================
    calculateBoilVolume = p.readInt() > 0;
    calculateStrikeVolume = p.readInt() > 0;
    calculateStrikeTemp = p.readInt() > 0;

    // Create instruction generator
    instructionGenerator = new InstructionGenerator(this);
    update();
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    p.writeString(name);            // Recipe name
    p.writeInt(version);          // XML Version -- 1
    p.writeString(type);                // Extract, Grain, Mash
    p.writeParcelable(style, flags);    // Stout, Pilsner, etc.
    p.writeString(brewer);            // Brewer's name
    p.writeDouble(batchSize);           // Target size (L)
    p.writeDouble(boilSize);        // Pre-boil vol (L)
    p.writeInt(boilTime);            // In Minutes
    p.writeDouble(efficiency);          // 100 for extract
    p.writeTypedList(hops);             // Hops used
    p.writeTypedList(fermentables);     // Fermentables used
    p.writeTypedList(yeasts);           // Yeasts used
    p.writeTypedList(miscs);            // Misc ingredients used
    p.writeTypedList(waters);           // Waters used
    p.writeParcelable(mashProfile, flags); // Mash profile for non-extracts

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    p.writeDouble(OG);            // Original Gravity
    p.writeDouble(FG);            // Final Gravity
    p.writeInt(fermentationStages);   // # of Fermentation stages
    p.writeInt(primaryAge);        // Time in primary in days
    p.writeDouble(primaryTemp);      // Temp in primary in C
    p.writeInt(secondaryAge);      // Time in Secondary in days
    p.writeDouble(secondaryTemp);    // Temp in secondary in C
    p.writeInt(tertiaryAge);      // Time in tertiary in days
    p.writeDouble(tertiaryTemp);    // Temp in tertiary in C
    p.writeString(tasteNotes);        // Taste notes
    p.writeInt(tasteRating);          // Taste score out of 50
    p.writeInt(bottleAge);            // Bottle age in days
    p.writeDouble(bottleTemp);        // Bottle temp in C
    p.writeInt(isForceCarbonated ? 1 : 0);  // True if force carb is used
    p.writeDouble(carbonation);       // Volumes of carbonation
    p.writeString(brewDate);          // Date brewed
    p.writeString(primingSugarName);  // Name of sugar for priming
    p.writeDouble(primingSugarEquiv); // Equivalent amount of priming sugar to be used
    p.writeDouble(kegPrimingFactor);  // factor - use less sugar when kegging vs bottles
    p.writeDouble(carbonationTemp);   // Carbonation temperature in C
    p.writeInt(calories);             // Calories (KiloCals)
    p.writeString(lastModified);

    // Custom Fields ==================================================
    // ================================================================
    p.writeLong(id);                  // id for use in database
    p.writeString(notes);              // User input notes
    p.writeInt(batchTime);             // Total length in weeks
    p.writeDouble(ABV);                // Alcohol by volume
    p.writeDouble(bitterness);         // Bitterness in IBU
    p.writeDouble(color);              // Color - SRM
    // Instruction generator not included in parcel
    p.writeDouble(measuredOG);         // Brew day stat: measured OG
    p.writeDouble(measuredFG);         // Brew stat: measured FG
    p.writeDouble(measuredVol);        // Brew stat: measured volume
    p.writeDouble(steepTemp);          // Steep temperature for extract recipes.

    // Fields for auto-calculation ====================================
    // ================================================================
    p.writeInt(calculateBoilVolume ? 1 : 0);
    p.writeInt(calculateStrikeVolume ? 1 : 0);
    p.writeInt(calculateStrikeTemp ? 1 : 0);
  }

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

  /**
   * Recipe objects are identified by the recipe name and the recipe's ID, as used when stored in
   * the database.  If a recipe has not been stored in the database, it will not necessarily have a
   * unique ID.
   */
  @Override
  public boolean equals(Object o) {
    // If the given object is not a Recipe, it cannot be equal.
    if (! (o instanceof Recipe)) {
      return false;
    }

    // The given object is a recipe - cast it.
    Recipe other = (Recipe) o;

    // Check index fields.
    if (! other.getRecipeName().equals(this.getRecipeName())) {
      // If the given recipe does not have the same name, it is not equal.
      return false;
    }
    else if (other.getId() != this.getId()) {
      // If the given recipe does not have the same ID, it is not equal.
      return false;
    }

    // Otherwise, the two recipes are equal.
    return true;
  }

  @Override
  public String toString() {
    return this.getRecipeName();
  }

  // Public methods
  public void update() {
    setColor(BrewCalculator.Color(this));
    setOG(BrewCalculator.OriginalGravity(this));
    setBitterness(BrewCalculator.Bitterness(this));
    setFG(BrewCalculator.FinalGravity(this));
    setABV(BrewCalculator.AlcoholByVolume(this));
    this.instructionGenerator.generate();
  }

  public String getRecipeName() {
    return this.name;
  }

  public void setRecipeName(String name) {
    this.name = name;
  }

  public void addIngredient(Ingredient i) {
    Log.d(getRecipeName() + "::addIngredient", "Adding ingredient: " + i.getName());
    if (i.getType().equals(Ingredient.HOP)) {
      addHop(i);
    }
    else if (i.getType().equals(Ingredient.FERMENTABLE)) {
      addFermentable(i);
    }
    else if (i.getType().equals(Ingredient.MISC)) {
      addMisc(i);
    }
    else if (i.getType().equals(Ingredient.YEAST)) {
      addYeast(i);
    }
    else if (i.getType().equals(Ingredient.WATER)) {
      addWater(i);
    }

    update();
  }

  public void removeIngredientWithId(long id) {
    for (Ingredient i : getIngredientList()) {
      if (i.getId() == id) {
        Log.d("Recipe", "Removing ingredient " + i.getName());
        removeIngredient(i);
      }
    }
  }

  public void removeIngredient(Ingredient i) {
    if (i.getType().equals(Ingredient.HOP)) {
      hops.remove(i);
    }
    else if (i.getType().equals(Ingredient.FERMENTABLE)) {
      fermentables.remove(i);
    }
    else if (i.getType().equals(Ingredient.MISC)) {
      miscs.remove(i);
    }
    else if (i.getType().equals(Ingredient.YEAST)) {
      yeasts.remove(i);
    }
    else if (i.getType().equals(Ingredient.WATER)) {
      waters.remove(i);
    }

    if (getIngredientList().contains(i)) {
      Log.d("Recipe", "Failed to remove ingredient");
    }
    else {
      Log.d("Recipe", "Successfully removed ingredient");
    }

    update();
  }

  public MashProfile getMashProfile() {
    return this.mashProfile;
  }

  public void setMashProfile(MashProfile profile) {
    this.mashProfile = profile;
    this.mashProfile.setRecipe(this);
    update();
  }

  public String getNotes() {
    return notes;
  }

  public void setNotes(String notes) {
    this.notes = notes;
  }

  public BeerStyle getStyle() {
    return style;
  }

  public void setStyle(BeerStyle beerStyle) {
    this.style = beerStyle;
  }

  public ArrayList<Ingredient> getIngredientList() {
    ArrayList<Ingredient> list = new ArrayList<Ingredient>();
    list.addAll(hops);
    list.addAll(fermentables);
    list.addAll(yeasts);
    list.addAll(miscs);
    list.addAll(waters);

    Collections.sort(list, new RecipeIngredientsComparator());
    return list;
  }

  public void setIngredientsList(ArrayList<Ingredient> ingredientsList) {

    for (Ingredient i : ingredientsList) {
      if (i.getType().equals(Ingredient.HOP)) {
        addHop(i);
      }
      else if (i.getType().equals(Ingredient.FERMENTABLE)) {
        addFermentable(i);
      }
      else if (i.getType().equals(Ingredient.MISC)) {
        addMisc(i);
      }
      else if (i.getType().equals(Ingredient.YEAST)) {
        addYeast(i);
      }
      else if (i.getType().equals(Ingredient.WATER)) {
        addWater(i);
      }
    }

    update();
  }

  private void addWater(Ingredient i) {
    Water w = (Water) i;
    waters.add(w);
  }

  private void addYeast(Ingredient i) {
    Yeast y = (Yeast) i;
    yeasts.add(y);
  }

  private void addMisc(Ingredient i) {
    Misc m = (Misc) i;
    miscs.add(m);
  }

  private void addFermentable(Ingredient i) {
    Log.d(getRecipeName() + "::addFermentable", "Adding fermentable: " + i.getName());
    Fermentable f = (Fermentable) i;
    this.fermentables.add(f);
  }

  private void addHop(Ingredient i) {
    Hop h = (Hop) i;
    this.hops.add(h);
  }

  public ArrayList<Instruction> getInstructionList() {
    return this.instructionGenerator.getInstructions();
  }

  public double getOG() {
    return OG;
  }

  public void setOG(double gravity) {
    gravity = (double) Math.round(gravity * 1000) / 1000;
    this.OG = gravity;
  }

  public double getBitterness() {
    bitterness = (double) Math.round(bitterness * 10) / 10;
    return bitterness;
  }

  public void setBitterness(double bitterness) {
    bitterness = (double) Math.round(bitterness * 10) / 10;
    this.bitterness = bitterness;
  }

  public double getColor() {
    color = (double) Math.round(color * 10) / 10;
    return color;
  }

  public void setColor(double color) {
    color = (double) Math.round(color * 10) / 10;
    this.color = color;
  }

  public double getABV() {
    ABV = (double) Math.round(ABV * 10) / 10;
    return ABV;
  }

  public void setABV(double aBV) {
    ABV = (double) Math.round(ABV * 10) / 10;
    ABV = aBV;
  }

  public int getBatchTime() {
    return batchTime;
  }

  public void setBatchTime(int batchTime) {
    this.batchTime = batchTime;
  }

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public String getVolumeUnits() {
    return Units.getVolumeUnits();
  }

  public double getDisplayBatchSize() {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      return Units.litersToGallons(this.batchSize);
    }
    else {
      return this.batchSize;
    }
  }

  public void setDisplayBatchSize(double size) {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.batchSize = Units.gallonsToLiters(size);
    }
    else {
      this.batchSize = size;
    }
  }

  public double getDisplayMeasuredBatchSize() {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      return Units.litersToGallons(this.measuredVol);
    }
    else {
      return this.measuredVol;
    }
  }

  public void setDisplayMeasuredBatchSize(double size) {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.measuredVol = Units.gallonsToLiters(size);
    }
    else {
      this.measuredVol = size;
    }
  }

  public double getBeerXmlStandardBatchSize() {
    return this.batchSize;
  }

  public void setBeerXmlStandardBatchSize(double v) {
    this.batchSize = v;
  }

  public double getBeerXmlMeasuredBatchSize() {
    return this.measuredVol;
  }

  public void setBeerXmlMeasuredBatchSize(double d) {
    this.measuredVol = d;
  }

  public int getBoilTime() {
    return boilTime;
  }

  public void setBoilTime(int boilTime) {
    this.boilTime = boilTime;
  }

  public double getEfficiency() {
    if (this.getType().equals(EXTRACT)) {
      return 100;
    }
    return efficiency;
  }

  public void setEfficiency(double efficiency) {
    this.efficiency = efficiency;
  }

  public String getBrewer() {
    return brewer;
  }

  public void setBrewer(String brewer) {
    this.brewer = brewer;
  }

  public double getDisplayBoilSize() {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      if (this.calculateBoilVolume) {
        return Units.litersToGallons(calculateBoilVolume());
      }
      else {
        return Units.litersToGallons(this.boilSize);
      }
    }
    else {
      if (this.calculateBoilVolume) {
        return calculateBoilVolume();
      }
      else {
        return this.boilSize;
      }
    }
  }

  public void setDisplayBoilSize(double size) {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.boilSize = Units.gallonsToLiters(size);
    }
    else {
      this.boilSize = size;
    }
  }

  private double calculateBoilVolume() {
    // TODO: Get parameters from equipment profile.
    double TRUB_LOSS = Units.gallonsToLiters(.3);     // Liters lost
    double SHRINKAGE = .04;                           // Percent lost
    double EVAP_LOSS = Units.gallonsToLiters(1.5);    // Evaporation loss (L/hr)

    if (this.type.equals(Recipe.ALL_GRAIN)) {
      return batchSize * (1 + SHRINKAGE) + TRUB_LOSS + (EVAP_LOSS * Units.minutesToHours(boilTime));
    }
    else {
      return (batchSize / 3) * (1 + SHRINKAGE) + TRUB_LOSS + (EVAP_LOSS * Units.minutesToHours
              (boilTime));
    }
  }

  public double getBeerXmlStandardBoilSize() {
    return boilSize;
  }

  public void setBeerXmlStandardBoilSize(double boilSize) {
    this.boilSize = boilSize;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

  public double getFG() {
    this.FG = (double) Math.round(FG * 1000) / 1000;
    return this.FG;
  }

  public void setFG(double fG) {
    fG = (double) Math.round(fG * 1000) / 1000;
    this.FG = fG;
  }

  public int getFermentationStages() {
    return fermentationStages;
  }

  public void setFermentationStages(int fermentationStages) {
    this.fermentationStages = fermentationStages;
  }

  public int getTotalFermentationDays() {
    return this.primaryAge + this.secondaryAge + this.tertiaryAge;
  }

  public int getVersion() {
    return version;
  }

  public void setVersion(int version) {
    this.version = version;
  }

  public ArrayList<Misc> getMiscList() {
    return miscs;
  }

  public ArrayList<Fermentable> getFermentablesList() {
    return fermentables;
  }

  public ArrayList<Hop> getHopsList() {
    return hops;
  }

  public ArrayList<Yeast> getYeastsList() {
    return yeasts;
  }

  public ArrayList<Water> getWatersList() {
    return waters;
  }

  /**
   * Generates a list of hops in this recipe with the given use.
   *
   * @param use
   *         One of Ingredient.USE_*
   * @return An ArrayList of Ingredients.
   */
  public ArrayList<Ingredient> getHops(String use) {
    ArrayList<Ingredient> list = new ArrayList<Ingredient>();
    for (Hop h : this.getHopsList()) {
      if (h.getUse().equals(use)) {
        list.add(h);
      }
    }
    return list;
  }

  public double getMeasuredOG() {
    return this.measuredOG;
  }

  public void setMeasuredOG(double d) {
    this.measuredOG = d;
  }

  public double getMeasuredFG() {
    return this.measuredFG;
  }

  public void setMeasuredFG(double d) {
    this.measuredFG = d;
  }

  public int getDisplayCoolToFermentationTemp() {
    // Metric - imperial conversion is performed in Yeast
    for (Yeast y : this.getYeastsList()) {
      return y.getDisplayFermentationTemp();
    }
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return 65;
    }
    else {
      return (int) Units.fahrenheitToCelsius(65);
    }
  }

  public double getDisplaySteepTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(steepTemp);
    }
    else {
      return steepTemp;
    }
  }

  public void setDisplaySteepTemp(double t) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.steepTemp = Units.fahrenheitToCelsius(t);
    }
    else {
      this.steepTemp = t;
    }
  }

  public void setNumberFermentationStages(int stages) {
    this.fermentationStages = stages;
  }

  public void setFermentationAge(int stage, int age) {
    switch (stage) {
      case STAGE_PRIMARY:
        this.primaryAge = age;
      case STAGE_SECONDARY:
        this.secondaryAge = age;
      case STAGE_TERTIARY:
        this.tertiaryAge = age;
    }
  }

  public int getFermentationAge(int stage) {
    switch (stage) {
      case STAGE_PRIMARY:
        return this.primaryAge;
      case STAGE_SECONDARY:
        return this.secondaryAge;
      case STAGE_TERTIARY:
        return this.tertiaryAge;
      default:
        return 7;
    }
  }

  public void setBeerXmlStandardFermentationTemp(int stage, double temp) {
    switch (stage) {
      case STAGE_PRIMARY:
        this.primaryTemp = temp;
      case STAGE_SECONDARY:
        this.secondaryTemp = temp;
      case STAGE_TERTIARY:
        this.tertiaryTemp = temp;
    }
  }

  public double getBeerXmlStandardFermentationTemp(int stage) {
    switch (stage) {
      case STAGE_PRIMARY:
        return this.primaryTemp;
      case STAGE_SECONDARY:
        return this.secondaryTemp;
      case STAGE_TERTIARY:
        return this.tertiaryTemp;
      default:
        return 21;
    }
  }

  public void setDisplayFermentationTemp(int stage, double temp) {
    switch (stage) {
      case STAGE_PRIMARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          this.primaryTemp = Units.fahrenheitToCelsius(temp);
        }
        else {
          this.primaryTemp = temp;
        }
        break;

      case STAGE_SECONDARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          this.secondaryTemp = Units.fahrenheitToCelsius(temp);
        }
        else {
          this.secondaryTemp = temp;
        }
        break;

      case STAGE_TERTIARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          this.tertiaryTemp = Units.fahrenheitToCelsius(temp);
        }
        else {
          this.secondaryTemp = temp;
        }
        break;
    }
  }

  public double getDisplayFermentationTemp(int stage) {
    switch (stage) {
      case STAGE_PRIMARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          return Units.celsiusToFahrenheit(this.primaryTemp);
        }
        else {
          return this.primaryTemp;
        }

      case STAGE_SECONDARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          return Units.celsiusToFahrenheit(this.secondaryTemp);
        }
        else {
          return this.secondaryTemp;
        }

      case STAGE_TERTIARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          return Units.celsiusToFahrenheit(this.tertiaryTemp);
        }
        else {
          return this.tertiaryTemp;
        }

      default:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          return Units.celsiusToFahrenheit(21);
        }
        else {
          return 21;
        }
    }
  }

  public String getTasteNotes() {
    if (this.tasteNotes == null) {
      this.tasteNotes = "";
    }
    return this.tasteNotes;
  }

  public void setTasteNotes(String s) {
    this.tasteNotes = s;
  }

  public int getTasteRating() {
    return this.tasteRating;
  }

  public void setTasteRating(int i) {
    this.tasteRating = i;
  }

  public int getBottleAge() {
    return this.bottleAge;
  }

  public void setBottleAge(int i) {
    this.bottleAge = i;
  }

  public double getBeerXmlStandardBottleTemp() {
    return this.bottleTemp;
  }

  public void setBeerXmlStandardBottleTemp(double d) {
    this.bottleTemp = d;
  }

  public double getDisplayBottleTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(this.bottleTemp);
    }
    else {
      return this.bottleTemp;
    }
  }

  public void setDisplayBottleTemp(double d) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.bottleTemp = Units.fahrenheitToCelsius(d);
    }
    else {
      this.bottleTemp = d;
    }
  }

  public boolean isForceCarbonated() {
    return this.isForceCarbonated;
  }

  public void setIsForceCarbonated(boolean b) {
    this.isForceCarbonated = b;
  }

  public double getCarbonation() {
    return this.carbonation;
  }

  public void setCarbonation(double d) {
    this.carbonation = d;
  }

  public void setLastModified(String s) {
    this.lastModified = s;
  }

  public Date getLastModified() {
    try {
      Date d = new SimpleDateFormat(Constants.LAST_MODIFIED_DATE_FMT).parse(this.lastModified);
      Log.d("Recipe", this.toString() + " was last modified " + d.toString());
      return d;
    } catch (ParseException e) {
      Log.w("Recipe", "Failed to parse lastModified: " + this.lastModified);
      return new Date();
    } catch (NullPointerException e) {
      // No modified date was ever set - likely a recipe from before this
      // feature existed.
      Log.w("Recipe", "No last modified for recipe: " + this.toString());
      return new Date();
    }
  }

  public String getBrewDate() {
    if (this.brewDate == null) {
      this.brewDate = "";
    }
    return this.brewDate;
  }

  public void setBrewDate(String s) {
    this.brewDate = s;
  }

  /* There is no standardized date format in beerXML.  Thus, we need
   * to try and parse as many formats as possible.  This method takes the given raw
   * date string and returns the best effort Date object.  If not we're unable to parse
   * the date, then this method returns today's date.
   */
  public Date getBrewDateDate() {
    // First, try the common date formats to speed things up.  We'll resort to a full search
    // of known formats if these fail.
    String d = this.getBrewDate();

    // This format is common for BeerSmith recipes.
    try {
      return new SimpleDateFormat("MM/dd/yyyy").parse(d);
    } catch (ParseException e) {
      // Do nothing.
    }

    // This format is used by Biermacht.
    try {
      return new SimpleDateFormat("dd MMM yyyy").parse(d);
    } catch (ParseException e) {
      // Do nothing.
    }

    // This takes a long time, so only do it as a last resort.
    // Look through a bunch of known formats to figure out what it is.
    String fmt = DateUtil.determineDateFormat(d);
    if (fmt == null) {
      Log.w("Recipe", "Failed to parse date: " + d);
      return new Date();
    }
    try {
      return new SimpleDateFormat(fmt).parse(d);
    } catch (ParseException e) {
      Log.e("Recipe", "Failed to parse date: " + d);
      e.printStackTrace();
      return new Date();
    }
  }

  public String getPrimingSugarName() {
    return this.primingSugarName;
  }

  public void setPrimingSugarName(String s) {
    this.primingSugarName = s;
  }

  public double getPrimingSugarEquiv() {
    // TODO
    return 0.0;
  }

  public void setPrimingSugarEquiv(double d) {
    this.primingSugarEquiv = d;
  }

  public double getBeerXmlStandardCarbonationTemp() {
    return this.carbonationTemp;
  }

  public void setBeerXmlStandardCarbonationTemp(double d) {
    this.carbonationTemp = d;
  }

  public double getDisplayCarbonationTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(this.carbonationTemp);
    }
    else {
      return this.carbonationTemp;
    }
  }

  public void setDisplayCarbonationTemp(double d) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.carbonationTemp = Units.fahrenheitToCelsius(d);
    }
    else {
      this.carbonationTemp = d;
    }
  }

  public double getKegPrimingFactor() {
    return this.kegPrimingFactor;
  }

  public void setKegPrimingFactor(double d) {
    this.kegPrimingFactor = d;
  }

  public int getCalories() {
    return this.calories;
  }

  public void setCalories(int i) {
    this.calories = i;
  }

  public boolean getCalculateBoilVolume() {
    return this.calculateBoilVolume;
  }

  public void setCalculateBoilVolume(boolean b) {
    this.calculateBoilVolume = b;
  }

  public boolean getCalculateStrikeVolume() {
    return this.calculateStrikeVolume;
  }

  public void setCalculateStrikeVolume(boolean b) {
    this.calculateStrikeVolume = b;
  }

  public boolean getCalculateStrikeTemp() {
    return this.calculateStrikeTemp;
  }

  public void setCalculateStrikeTemp(boolean b) {
    this.calculateStrikeTemp = b;
  }

  public double getMeasuredABV() {
    if (this.getMeasuredFG() > 0 && this.getMeasuredOG() > this.getMeasuredFG()) {
      return (this.getMeasuredOG() - this.getMeasuredFG()) * 131;
    }
    else {
      return 0;
    }
  }

  public double getMeasuredEfficiency() {
    double potGravP, measGravP;
    double eff = 100;
    double measBatchSize = this.getBeerXmlMeasuredBatchSize();

    // If the user hasn't input a measured batch size, assume the recipe went as planned
    // and that the target final batch size was hit.
    if (measBatchSize == 0) {
      Log.d("Recipe", "No measured batch size, try using recipe batch size");
      measBatchSize = this.getBeerXmlStandardBatchSize();
    }

    if (! this.getType().equals(Recipe.EXTRACT)) {
      eff = getEfficiency();
    }

    // Computation only valid if measured gravity is greater than 1, and batch size is non-zero.
    // Theoretically, measured gravity could be less than 1, but we don't support that yet.
    if ((this.getMeasuredOG() > 1) && (batchSize > 0)) {
      // Calculate potential milli-gravity points.  Adjust the value returned by the
      // brew calculator, because it takes the expected efficiency into account (which
      // we don't want here).
      potGravP = (BrewCalculator.OriginalGravity(this) - 1) / (eff / 100);

      // Adjust potential gravity points to account for measured batch size.
      potGravP = potGravP * (getBeerXmlStandardBatchSize() / measBatchSize);

      // Calculate the measured milli-gravity points.
      measGravP = this.getMeasuredOG() - 1;

      // Return the efficiency.
      return 100 * measGravP / potGravP;
    }
    else {
      return 0;
    }
  }

  public void save(Context c) {
    Log.d(getRecipeName() + "::save", "Saving with id: " + this.getId());
    new DatabaseAPI(c).updateRecipe(this);
  }
} public class Constants {

  // New Objects
  public static final BeerStyle BEERSTYLE_OTHER = new BeerStyle("Other");

  // Master recipe's ID.  This is the ID of the dummy recipe we create on first use.
  // This recipe is used as a placeholder when a user-created recipe is not available.
  // For example, when a custom ingredient is added to the database, but is not in a recipe.
  public static final long MASTER_RECIPE_ID = 1;

  // Keys for passing objects
  public static final String KEY_RECIPE_ID = "biermacht.brews.recipe.id";
  public static final String KEY_RECIPE = "biermacht.brews.recipe";
  public static final String KEY_PROFILE_ID = "biermacht.brews.mash.profile.id";
  public static final String KEY_PROFILE = "biermacht.brews.mash.profile";
  public static final String KEY_INGREDIENT_ID = "biermacht.brews.ingredient.id";
  public static final String KEY_INGREDIENT = "biermacht.brews.ingredient";
  public static final String KEY_MASH_STEP = "biermacht.brews.mash.step";
  public static final String KEY_MASH_STEP_ID = "biermacht.brews.mash.step.id";
  public static final String KEY_MASH_STEP_LIST = "biermacht.brews.mash.profile.steps";
  public static final String KEY_MASH_PROFILE = "biermacht.brews.mash.profile";
  public static final String KEY_INSTRUCTION = "biermacht.brews.instruction";
  public static final String KEY_DATABASE_ID = "biermacht.brews.database.id";
  public static final String KEY_SECONDS = "biermacht.brews.remaining.seconds";
  public static final String KEY_TITLE = "biermacht.brews.title";
  public static final String KEY_COMMAND = "biermacht.brews.command";
  public static final String KEY_STEP_NUMBER = "biermacht.brews.stepnumber";
  public static final String KEY_TIMER_STATE = "biermacht.brews.timer.state";
  public static final String KEY_STYLE = "biermacht.brews.style";

  public static final int INVALID_ID = - 1;

  public static final String BREW_DATE_FMT = "dd MMM yyyy";
  public static final String LAST_MODIFIED_DATE_FMT = "dd MMM yyyy HH:mm:ss";

  // Indicates if a recipe should be displayed after it is created.
  public static final String DISPLAY_ON_CREATE = "biermacht.brews.recipe.display.on.create";

  // Valid commands
  public static final String COMMAND_START = "biermacht.brews.commands.start";
  public static final String COMMAND_STOP = "biermacht.brews.commands.stop";
  public static final String COMMAND_PAUSE = "biermacht.brews.commands.pause";
  public static final String COMMAND_QUERY = "biermacht.brews.commands.query";
  public static final String COMMAND_STOP_ALARM = "biermacht.brews.commands.stop.alarm";

  // Database identifiers used to slice SQLite database into
  // multiple zones, each with a different purpose.

  // System DB - imported from assets.  Contains the selection of "default" ingredients /
  // profiles / styles / etc that come with the application.
  public static final long DATABASE_SYSTEM_RESOURCES = 2;

  // User DB - custom resources added by the user. Constains the selection of ingredients / profiles
  // styles / etc that a user has specifically added to the application.
  public static final long DATABASE_USER_RESOURCES = 1;

  // User DB - stores recipes and specific instances of the "rubber-stamp"
  // ingredients / profiles / styles / etc from one of SYSTEM_ADDED or USER_ADDED
  // that are related to those recipes.
  public static final long DATABASE_USER_RECIPES = 0;

  // No owner ID for use in database
  public static final long OWNER_NONE = - 1;

  // Broadcast types
  public static final String BROADCAST_TIMER = "com.biermacht.brews.broadcast.timer";
  public static final String BROADCAST_REMAINING_TIME = "com.biermacht.brews.broadcast.timer.remaining";
  public static final String BROADCAST_TIMER_CONTROLS = "com.biermacht.brews.broadcast.timer.controls";
  public static final String BROADCAST_QUERY_RESP = "com.biermacht.brews.broadcast.query.resp";

  // Shared preferences Constants
  public static final String PREFERENCES = "com.biermacht.brews.preferences";
  public static final String PREF_USED_BEFORE = "com.biermacht.brews.usedBefore";
  public static final String PREF_LAST_OPENED = "com.biermacht.brews.lastOpened";
  public static final String PREF_BREWER_NAME = "com.biermacht.brews.brewerName";
  public static final String PREF_MEAS_SYSTEM = "com.biermacht.brews.measurementSystem";
  public static final String PREF_FIXED_RATIOS = "com.biermacht.brews.waterToGrainRatiosFixed";
  public static final String PREF_HYDROMETER_CALIBRATION_TEMP = "com.biermacht.brews.hydrometerCalibrationTemp";
  public static final String PREF_SORT_STRATEGY = "com.biermacht.brews.recipe_sort_strategy";

  // Value of this preference indicates the last time db contents were updated.
  public static final String PREF_NEW_CONTENTS_VERSION = "com.biermacht.brews.newIngredientsVersion";

  //  Incremented when new database contents are added.
  public static int NEW_DB_CONTENTS_VERSION = 5;

  // Valid recipe sort strategies.
  public static final String SORT_STRATEGY_ALPHABETICAL = "Name (A to Z)";
  public static final String SORT_STRATEGY_REV_ALPHABETICAL = "Name (Z to A)";
  public static final String SORT_STRATEGY_BREW_DATE = "Created (Newest first)";
  public static final String SORT_STRATEGY_REV_BREW_DATE = "Created (Oldest first)";
  public static final String SORT_STRATEGY_MODIFIED = "Modified (Latest first)";

  // Activity for result return codes
  public static final int RESULT_DELETED = 1;
  public static final int RESULT_OK = 2;
  public static final int RESULT_CANCELED = 3;

  // Request codes for activities
  public static final int REQUEST_NEW_MASH_STEP = 1;
  public static final int REQUEST_EDIT_MASH_STEP = 2;
  public static final int REQUEST_EDIT_RECIPE = 3;
  public static final int REQUEST_IMPORT_FILE = 4;
  public static final int REQUEST_CONNECT_TO_DRIVE = 5;
  public static final int REQUEST_DRIVE_FILE_OPEN = 6;
  public static final int REQUEST_DRIVE_FILE_CREATE = 7;

  // Possible timer states
  public static int PAUSED = 0;
  public static int RUNNING = 1;
  public static int STOPPED = 2;

  // Constant messages
  public static String MESSAGE_AUTO_CALC_W2GR = "Water-to-grain ratio is calculated automatically for this step.";

  // Other Constants
  private static final String[] hop_uses = {Hop.USE_BOIL, Hop.USE_AROMA, Hop.USE_DRY_HOP, Hop.USE_FIRST_WORT};
  private static final String[] hop_forms = {Hop.FORM_PELLET, Hop.FORM_WHOLE, Hop.FORM_PLUG};
  private static final String[] ferm_types = {Fermentable.TYPE_GRAIN, Fermentable.TYPE_EXTRACT, Fermentable.TYPE_SUGAR, Fermentable.TYPE_ADJUNCT};
  private static final String[] step_types = {MashStep.INFUSION, MashStep.DECOCTION, MashStep.TEMPERATURE};
  private static final String[] unit_systems = {Units.IMPERIAL, Units.METRIC};
  private static final String[] mash_types = {MashProfile.MASH_TYPE_DECOCTION, MashProfile.MASH_TYPE_INFUSION, MashProfile.MASH_TYPE_TEMPERATURE, MashProfile.MASH_TYPE_BIAB};
  private static final String[] sparge_types = {MashProfile.SPARGE_TYPE_BATCH, MashProfile.SPARGE_TYPE_FLY, MashProfile.SPARGE_TYPE_BIAB};
  private static final String[] sort_strats = {SORT_STRATEGY_ALPHABETICAL, SORT_STRATEGY_REV_ALPHABETICAL, SORT_STRATEGY_BREW_DATE, SORT_STRATEGY_REV_BREW_DATE, SORT_STRATEGY_MODIFIED};

  public static final ArrayList<String> HOP_USES = new ArrayList<String>(Arrays.asList(hop_uses));
  public static final ArrayList<String> HOP_FORMS = new ArrayList<String>(Arrays.asList(hop_forms));
  public static final ArrayList<String> FERMENTABLE_TYPES = new ArrayList<String>(Arrays.asList(ferm_types));
  public static final ArrayList<String> MASH_STEP_TYPES = new ArrayList<String>(Arrays.asList(step_types));
  public static final ArrayList<String> UNIT_SYSTEMS = new ArrayList<String>(Arrays.asList(unit_systems));
  public static final ArrayList<String> MASH_TYPES = new ArrayList<String>(Arrays.asList(mash_types));
  public static final ArrayList<String> SPARGE_TYPES = new ArrayList<String>(Arrays.asList(sparge_types));
  public static final ArrayList<String> RECIPE_SORT_STRATEGIES = new ArrayList(Arrays.asList(sort_strats));

} public abstract class DriveActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

  public GoogleApiClient driveClient;
  private ProgressDialog progressDialog;
  private int action;
  private ArrayList<Recipe> recipes;

  private static final int NONE = 0;
  private static final int FILE_OPEN = 1;
  private static final int FILE_WRITE = 2;

  // Callback for after creating Drive contents.
  final ResultCallback<DriveApi.DriveContentsResult> driveContentsCallback = new
          ResultCallback<DriveApi.DriveContentsResult>() {
            @Override
            public void onResult(DriveApi.DriveContentsResult result) {

              if (! result.getStatus().isSuccess()) {
                Log.e("DriveActivity", "driveContentsCallback error: " + result.toString());
                return;
              }
              final DriveContents driveContents = result.getDriveContents();

              // Write content to DriveContents
              OutputStream outputStream = driveContents.getOutputStream();
              Writer writer = new OutputStreamWriter(outputStream);
              Recipe r = new Recipe();
              try {
                writer.write(new RecipeXmlWriter(getApplicationContext()).getXmlText(recipes));
                writer.close();
              } catch (IOException e) {
                Log.e("DriveActivity", e.getMessage());
              }

              // Determine the default title for the file.
              String title = RecipeXmlWriter.generateFileName("all-recipes-");
              if (recipes.size() == 1) {
                title = RecipeXmlWriter.generateFileName(recipes.get(0).getRecipeName() + "-");
              }

              // Start intent to pick and create file.
              MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
                      .setTitle(title)
                      .setMimeType("text/xml").build();
              IntentSender intentSender = Drive.DriveApi
                      .newCreateFileActivityBuilder()
                      .setInitialMetadata(metadataChangeSet)
                      .setInitialDriveContents(result.getDriveContents())
                      .setActivityTitle("Save Recipe(s)")
                      .build(driveClient);
              try {
                startIntentSenderForResult(intentSender,
                                           Constants.REQUEST_DRIVE_FILE_CREATE,
                                           null, 0, 0, 0);
              } catch (IntentSender.SendIntentException e) {
                Log.e("DriveActivity", "Exception starting Drive file create intent");
                e.printStackTrace();
              }
            }
          };

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    // Create the progress dialog view - displayed when connecting to Google APIs.
    progressDialog = new ProgressDialog(this);
    progressDialog.setMessage("Connecting to Google APIs...");
    progressDialog.setIndeterminate(false);
    progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
    progressDialog.setCancelable(false);

    // Create the connection driveClient.
    driveClient = new GoogleApiClient.Builder(this)
            .addApi(Drive.API)
            .addScope(Drive.SCOPE_FILE)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build();
  }

  // Abstract methods to be overridden by the subclass.  These handle drive events.
  public abstract void onDriveFilePicked(Intent data);

  public abstract void onDriveFileWritten(Intent data);

  public void connectToDrive() {
    Log.d("DriveActivity", "connectToDrive()");
    driveClient.connect();
    progressDialog.show();
  }

  @Override
  protected void onStop() {
    Log.d("DriveActivity", "onStop()");
    driveClient.disconnect();
    super.onStop();
  }

  public void pickFile() {
    Log.d("DriveActivity", "pickFile()");
    if (! driveClient.isConnected()) {
      this.action = FILE_OPEN;
      connectToDrive();
    }
    else {
      _pickFile();
    }
  }

  private void _pickFile() {
    Log.d("DriveActivity", "Starting Google Drive file picker intent");
    this.action = NONE;
    IntentSender i = Drive.DriveApi
            .newOpenFileActivityBuilder()
            .build(driveClient);
    try {
      this.startIntentSenderForResult(i, Constants.REQUEST_DRIVE_FILE_OPEN, null, 0, 0, 0);
    } catch (IntentSender.SendIntentException e) {
      e.printStackTrace();
    }
  }

  public void writeFile(ArrayList<Recipe> recipes) {
    // Store the given recipes so we can write them later.
    Log.d("DriveActivity", "writeFile() " + recipes.toString());
    this.recipes = recipes;

    if (! driveClient.isConnected()) {
      this.action = FILE_WRITE;
      connectToDrive();
    }
    else {
      _writeFile();
    }
  }

  private void _writeFile() {
    Log.d("DriveActivity", "Writing file to Google Drive.");
    this.action = NONE;
    IntentSender i = Drive.DriveApi
            .newOpenFileActivityBuilder()
            .build(driveClient);

    Drive.DriveApi.newDriveContents(driveClient)
            .setResultCallback(driveContentsCallback);
  }

  protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    switch (requestCode) {
      case Constants.REQUEST_CONNECT_TO_DRIVE:
        Log.d("DriveActivity", "Result from Google API: " + resultCode);
        handleConnectToDrive(resultCode, data);
        break;
      case Constants.REQUEST_DRIVE_FILE_OPEN:
        Log.d("DriveActivity", "Result from Google Drive file access: " + resultCode);
        handleDriveFileOpen(resultCode, data);
        break;
      case Constants.REQUEST_DRIVE_FILE_CREATE:
        Log.d("DriveActivity", "Result from Google Drive file create: " + resultCode);
        handleDriveFileWritten(resultCode, data);
        break;
      default:
        Log.d("DriveActivity", String.format("Unhandled: requestCode: %d, resultCode: %d", requestCode, resultCode));
        super.onActivityResult(requestCode, resultCode, data);
        break;
    }
  }

  private void handleConnectToDrive(int resultCode, Intent data) {
    Log.d("DriveActivity", "handleConnectToDrive() event, rc=" + resultCode);
    if (resultCode == RESULT_OK) {
      // Make sure the app is not already connected or attempting to connect
      if (! this.driveClient.isConnecting() &&
              ! this.driveClient.isConnected()) {
        this.driveClient.connect();
      }
    }
  }

  private void handleDriveFileOpen(int resultCode, Intent data) {
    Log.d("DriveActivity", "handleDriveFileOpen() event, rc=" + resultCode);
    if (resultCode == RESULT_OK) {
      // Pass to the onFilePicked method for the subclass to handle.
      this.onDriveFilePicked(data);
    }
  }

  private void handleDriveFileWritten(int resultCode, Intent data) {
    Log.d("DriveActivity", "handleDriveFileWritten() event, rc=" + resultCode);
    if (resultCode == RESULT_OK) {
      // Pass to the onDriveFileWritten method for the subclass to handle.
      this.onDriveFileWritten(data);
    }

    // Clear recipes.
    this.recipes = null;
  }

  @Override
  public void onConnected(Bundle bundle) {
    Log.d("DriveApiHelper", "Connected to Google APIs");
    if (progressDialog.isShowing()) {
      Log.d("DriveApiHelper", "Dismissing progress dialog");
      progressDialog.dismiss();
    }

    // Based on the action, call the correct method.
    switch (this.action) {
      case FILE_OPEN:
        _pickFile();
        break;
      case FILE_WRITE:
        _writeFile();
        break;
      default:
        Log.e("DriveApiHelper", "Unrecognized action: " + this.action);
        break;
    }
  }

  @Override
  public void onConnectionSuspended(int i) {
    Log.d("DriveActivity", "onConnectionSuspended(): " + i);
  }

  @Override
  public void onConnectionFailed(ConnectionResult result) {
    Log.d("DriveActivity", "Google API Connection failed");
    if (result.hasResolution()) {
      Log.d("DriveActivity", "Failure has a resolution, starting");
      try {
        result.startResolutionForResult(this, Constants.REQUEST_CONNECT_TO_DRIVE);
      } catch (IntentSender.SendIntentException e) {
        driveClient.connect();
      }
    }
    else {
      Log.e("DriveActivity", "No resolution, showing error dialog");
      GoogleApiAvailability.getInstance().getErrorDialog(this, result.getErrorCode(),
                                                         Constants.REQUEST_CONNECT_TO_DRIVE).show();
    }

  }
} public class Units {

  // Unit systems
  public static final String IMPERIAL = "Imperial";
  public static final String METRIC = "Metric";

  // Imperial Units
  public static final String OUNCES = "oz";
  public static final String GALLONS = "gal";
  public static final String POUNDS = "lbs";
  public static final String TEASPOONS = "tsp";
  public static final String FAHRENHEIT = "\u2109";
  public static final String CUP = "Cup";
  public static final String CUPS = "Cups";
  public static final String QUARTS_PER_POUND = "qt/lb";
  public static final String QUARTS = "qt";
  public static final String PINTS = "pt";
  public static final String TABLESPOONS = "tbps";

  // Metric Units
  public static final String KILOGRAMS = "kg";
  public static final String GRAMS = "grams";
  public static final String MILLIGRAMS = "mg";
  public static final String LITERS = "liters";
  public static final String MILLILITERS = "ml";
  public static final String CELSIUS = "\u2103";
  public static final String LITERS_PER_KG = "L/kg";

  // Agnostic Units
  public static final String PACKAGES = "pkg";
  public static final String ITEMS = "items";
  public static final String DAYS = "days";
  public static final String MINUTES = "mins";
  public static final String HOURS = "hours";
  public static final String PLATO = "plato";
  public static final String GRAVITY = "sg";
  public static final String UNITS = "units";

  // Formal units - Full spellings for displaying in lists, etc.
  // Make sure to add these to the converter below!
  public static final String TEASPOONS_FORMAL = "Teaspoons";
  public static final String CUPS_FORMAL = "Cups";
  public static final String GRAMS_FORMAL = "Grams";
  public static final String GALLONS_FORMAL = "Gallons";
  public static final String LITERS_FORMAL = "Liters";
  public static final String OUNCES_FORMAL = "Ounces";
  public static final String ITEMS_FORMAL = "Items";
  public static final String KILOGRAMS_FORMAL = "Kilograms";
  public static final String MILLILITERS_FORMAL = "Milliliters";
  public static final String PACKAGES_FORMAL = "Packages";
  public static final String POUNDS_FORMAL = "Pounds";
  public static final String UNITS_FORMAL = "Units";

  // SharedPreferences to use.
  private static SharedPreferences preferences = BiermachtApplication.getContext().getSharedPreferences(Constants.PREFERENCES, Context.MODE_PRIVATE);

  public static String toAbbreviation(String s) {
    if (s.toLowerCase().equals(TEASPOONS_FORMAL.toLowerCase())) {
      return TEASPOONS;
    }
    if (s.toLowerCase().equals(CUPS_FORMAL.toLowerCase())) {
      return CUPS;
    }
    if (s.toLowerCase().equals(GRAMS_FORMAL.toLowerCase())) {
      return GRAMS;
    }
    if (s.toLowerCase().equals(GALLONS_FORMAL.toLowerCase())) {
      return GALLONS;
    }
    if (s.toLowerCase().equals(LITERS_FORMAL.toLowerCase())) {
      return LITERS;
    }
    if (s.toLowerCase().equals(OUNCES_FORMAL.toLowerCase())) {
      return OUNCES;
    }
    if (s.toLowerCase().equals(ITEMS_FORMAL.toLowerCase())) {
      return ITEMS;
    }
    if (s.toLowerCase().equals(KILOGRAMS_FORMAL.toLowerCase())) {
      return KILOGRAMS;
    }
    if (s.toLowerCase().equals(MILLILITERS_FORMAL.toLowerCase())) {
      return MILLILITERS;
    }
    if (s.toLowerCase().equals(PACKAGES_FORMAL.toLowerCase())) {
      return PACKAGES;
    }
    if (s.toLowerCase().equals(POUNDS_FORMAL.toLowerCase())) {
      return POUNDS;
    }
    else {
      return UNITS;
    }
  }

  public static String toFormal(String s) {
    if (s.toLowerCase().equals(TEASPOONS.toLowerCase())) {
      return TEASPOONS_FORMAL;
    }
    if (s.toLowerCase().equals(CUPS.toLowerCase())) {
      return CUPS_FORMAL;
    }
    if (s.toLowerCase().equals(GRAMS.toLowerCase())) {
      return GRAMS_FORMAL;
    }
    if (s.toLowerCase().equals(GALLONS.toLowerCase())) {
      return GALLONS_FORMAL;
    }
    if (s.toLowerCase().equals(LITERS.toLowerCase())) {
      return LITERS_FORMAL;
    }
    if (s.toLowerCase().equals(OUNCES.toLowerCase())) {
      return OUNCES_FORMAL;
    }
    if (s.toLowerCase().equals(ITEMS.toLowerCase())) {
      return ITEMS_FORMAL;
    }
    if (s.toLowerCase().equals(KILOGRAMS.toLowerCase())) {
      return KILOGRAMS_FORMAL;
    }
    if (s.toLowerCase().equals(MILLILITERS.toLowerCase())) {
      return MILLILITERS_FORMAL;
    }
    if (s.toLowerCase().equals(PACKAGES.toLowerCase())) {
      return PACKAGES_FORMAL;
    }
    if (s.toLowerCase().equals(POUNDS.toLowerCase())) {
      return POUNDS_FORMAL;
    }
    else {
      return UNITS_FORMAL;
    }
  }

  public static String getUnitsFromDisplayAmount(String s) {
    Log.d("Units", "Getting units from display amount: " + s);
    String unit = "";
    ArrayList<String> temp = new ArrayList<String>(Arrays.asList(s.split(" ")));
    if (temp.size() == 2) {
      unit = temp.get(temp.size() - 1);
    }

    // Assign a Units class standard unit here
    if (unit.equals("tsp") || unit.equals("teaspoons")) {
      unit = Units.TEASPOONS;
    }
    if (unit.equalsIgnoreCase("qt") || unit.equalsIgnoreCase("quarts") || unit.equalsIgnoreCase
            ("quart") || unit.equalsIgnoreCase("qts")) {
      unit = Units.QUARTS;
    }
    if (unit.equals("g") || unit.equals("grams")) {
      unit = Units.GRAMS;
    }
    if (unit.equals("oz") || unit.equals("ounces")) {
      unit = Units.OUNCES;
    }
    if (unit.contains("gal")) {
      unit = Units.GALLONS;
    }
    if (unit.equals("item") || unit.equals("items")) {
      unit = Units.ITEMS;
    }
    if (unit.contains("package") || unit.equals("pkg")) {
      unit = Units.PACKAGES;
    }
    if (unit.equalsIgnoreCase(PLATO)) {
      unit = Units.PLATO;
    }
    if (unit.equalsIgnoreCase(GRAVITY)) {
      unit = Units.GRAVITY;
    }
    if (unit.equalsIgnoreCase("F")) {
      unit = Units.FAHRENHEIT;
    }
    if (unit.equalsIgnoreCase("C")) {
      unit = Units.CELSIUS;
    }
    if (unit.equalsIgnoreCase("qt/lb")) {
      unit = Units.QUARTS_PER_POUND;
    }
    if (unit.equalsIgnoreCase(LITERS_PER_KG)) {
      unit = Units.LITERS_PER_KG;
    }

    Log.d("Units", "Got units: " + unit);
    return unit;
  }

  public static double getAmountFromDisplayAmount(String s) {
    Log.d("Units", "Getting amount from display amount: " + s);
    ArrayList<String> temp = new ArrayList<String>(Arrays.asList(s.split(" ")));
    if (getUnitsFromDisplayAmount(s).equals("")) {
      Log.d("Units", "Unable to determine units from: " + s);
      return 0;
    }
    else if (temp.size() <= 2 && temp.size() > 0) {
      try {
        Log.d("Units", "Returning amount: " + Double.parseDouble(temp.get(0).replace(",", ".")));
        return Double.parseDouble(temp.get(0).replace(",", "."));
      } catch (Exception e) {
        e.printStackTrace();
        Log.d("Units", "Error parsing display amount: " + s);
      }
    }
    else {
      Log.d("Units", "Received bad display amount: " + s);
    }
    Log.d("Units", "Failed to get amount. Returning 0");
    return 0;
  }

  public static int toSeconds(double time, String units) {
    if (units.equals(MINUTES)) {
      return (int) (time * 60);
    }
    if (units.equals(HOURS)) {
      return (int) (time * 3600);
    }

    return 0;
  }

  // Functions for converting units below
  public static double platoToGravity(double p) {
    return p / (258.6 - ((p / 258.2) * 227.1)) + 1;
  }

  public static double fahrenheitToCelsius(double f) {
    return (f - 32) / 1.8;
  }

  public static double celsiusToFahrenheit(double c) {
    return c * 1.8 + 32;
  }

  public static double LPKGtoQPLB(double d) {
    return d * .479305709;
  }

  public static double QPLBtoLPKG(double d) {
    return d / .479305709;
  }

  public static double litersToGallons(double l) {
    return l * .264172052;
  }

  public static double cupsToLiters(double c) {
    return c * .236;
  }

  public static double litersToCups(double l) {
    return l / .236;
  }

  public static double litersToMillis(double l) {
    return l * 1000;
  }

  public static double millisToLiters(double m) {
    return m / 1000;
  }

  public static double minutesToDays(double m) {
    return m / 1440;
  }

  public static double minutesToHours(double m) {
    return m / 60;
  }

  public static double daysToMinutes(double d) {
    return d * 1440;
  }

  public static double gallonsToLiters(double g) {
    return g / .264172052;
  }

  public static double litersToOunces(double l) {
    return l / .0295735;
  }

  public static double ouncesToLiters(double o) {
    return o * .0295735;
  }

  public static double litersToTeaspoons(double l) {
    return l * 202.884;
  }

  public static double litersToTablespoons(double l) {
    return l * 67.628;
  }

  public static double teaspoonsToLiters(double t) {
    return t / 202.884;
  }

  public static double tablespoonsToLiters(double t) {
    return t / 67.628;
  }

  public static double quartsToLiters(double q) {
    return q * .946353;
  }

  public static double pintsToLiters(double p) {
    return p / 2.11338;
  }

  public static double litersToQuarts(double l) {
    return l / .946353;
  }

  public static double litersToPints(double l) {
    return 1 / 2.11338;
  }

  public static double kilosToPounds(double k) {
    return k * 2.204;
  }

  public static double kilosToOunces(double k) {
    return k * 35.2739619;
  }

  public static double ouncesToKilos(double o) {
    return o / 35.2739619;
  }

  public static double poundsToKilos(double p) {
    return p / 2.204;
  }

  public static double kilosToGrams(double k) {
    return k * 1000;
  }

  public static double gramsToKilos(double g) {
    return g / 1000;
  }

  public static double milligramsToKilos(double mg) {
    return mg / 1000 / 1000;
  }

  public static double kilosToMilligrams(double kg) {
    return kg * 1000 * 1000;
  }

  public static double ouncesToGrams(double o) {
    return o * 28.3495231;
  }

  public static double gramsToOunces(double g) {
    return g / 28.3495231;
  }

  // Methods to return the units for each measurement system
  public static String getUnitSystem() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return IMPERIAL;
    }
    else {
      return METRIC;
    }
  }

  public static String getHopUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return OUNCES;
    }
    else {
      return GRAMS;
    }
  }

  public static String getFermentableUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return POUNDS;
    }
    else {
      return KILOGRAMS;
    }
  }

  public static String getWaterToGrainUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return QUARTS_PER_POUND;
    }
    else {
      return LITERS_PER_KG;
    }
  }

  public static String getStrikeVolumeUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return QUARTS;
    }
    else {
      return LITERS;
    }
  }

  public static String getTemperatureUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return FAHRENHEIT;
    }
    else {
      return CELSIUS;
    }
  }

  public static String getVolumeUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals(IMPERIAL)) {
      return GALLONS;
    }
    else {
      return LITERS;
    }
  }

  public static String getWeightUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return POUNDS;
    }
    else {
      return KILOGRAMS;
    }
  }

  public static double toLiters(double amount, String unit) {
    if (unit.equals(GALLONS)) {
      return gallonsToLiters(amount);
    }
    if (unit.equals(QUARTS)) {
      return quartsToLiters(amount);
    }
    if (unit.equals(MILLILITERS)) {
      return millisToLiters(amount);
    }
    if (unit.equals(CUPS) || unit.equals(CUP)) {
      return cupsToLiters(amount);
    }
    if (unit.equals(TEASPOONS)) {
      return teaspoonsToLiters(amount);
    }
    if (unit.equals(OUNCES)) {
      return ouncesToLiters(amount);
    }
    if (unit.equals(LITERS)) {
      return amount;
    }
    return - 1;
  }

  public static String getMetricEquivalent(String imp, boolean isWeight) {
    if (imp.equals(Units.GALLONS)) {
      return LITERS;
    }
    else if (imp.equals(Units.TEASPOONS)) {
      return MILLILITERS;
    }
    else if (imp.equals(Units.OUNCES)) {
      if (isWeight) {
        return GRAMS;
      }
      else {
        return MILLILITERS;
      }
    }
    else if (imp.equals(Units.POUNDS)) {
      return KILOGRAMS;
    }
    else if (imp.equals(Units.CUP) || imp.equals(Units.CUPS)) {
      return LITERS;
    }
    else {
      return imp;
    }
  }
} public class Utils {
  /**
   * Determines if the given value is within the given range
   *
   * @param a
   * @param low
   * @param high
   * @return
   */
  public static boolean isWithinRange(double a, double low, double high) {
    if (a <= high && a >= low) {
      return true;
    }
    else {
      return false;
    }
  }

  public static int getHours(double time, String units) {
    int num_hours = 0;
    if (units.equals(Units.MINUTES)) {
      for (int i = 60; i <= time; i += 60) {
        num_hours++;
      }
    }
    if (units.equals(Units.HOURS)) {
      num_hours = (int) time;
    }
    return num_hours;
  }

  public static int getMinutes(double time, String units) {
    int num_minutes = 0;
    int num_hours = 0;

    if (units.equals(Units.HOURS)) {
      time = 60 * time;
      units = Units.MINUTES;
    }

    if (units.equals(Units.MINUTES)) {
      num_hours = getHours(time, units);
      num_minutes = (int) (time - (60 * num_hours));
    }

    return num_minutes;
  }

  public static String convertStreamToString(InputStream is) throws Exception {
    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    StringBuilder sb = new StringBuilder();
    String line = null;
    while ((line = reader.readLine()) != null) {
      sb.append(line).append("\n");
    }
    reader.close();
    return sb.toString();
  }

  /**
   * @return Beer XML standard version in use
   */
  public static int getXmlVersion() {
    return 1;
  }

  public static Recipe scaleRecipe(Context c, Recipe r, double newVolume) {
    double oldVolume = r.getDisplayBatchSize();
    double ratio = newVolume / oldVolume;

    r.setDisplayBatchSize(newVolume);
    r.setDisplayBoilSize(r.getDisplayBoilSize() * ratio);

    for (Ingredient i : r.getIngredientList()) {
      if (i instanceof Misc) {
        ((Misc) i).setDisplayAmount(i.getDisplayAmount() * ratio, i.getDisplayUnits());
      }
      else {
        i.setDisplayAmount(i.getDisplayAmount() * ratio);
      }
      new DatabaseAPI(c).updateIngredient(i, Constants.DATABASE_USER_RECIPES);
    }

    r.save(c);
    return r;
  }

  /**
   * This method adjusts the height of the given listView to match the combined height of all if its
   * children and the dividers between list items.  This is used to set the height of the mash step
   * list such that it does not scroll, since it is encompassed by a ScrollView.
   *
   * @param listView
   *         ListView to adjust.
   */
  public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
      return;
    }

    int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.UNSPECIFIED);
    int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
    View view = null;
    for (int i = 0; i < listAdapter.getCount(); i++) {
      view = listAdapter.getView(i, view, listView);

      view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);

      if (i == 0) {
        view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT));
        //totalHeight += view.getMeasuredHeight();
      }

      totalHeight += view.getMeasuredHeight();
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
  }

  public static double parseDouble(String s, Double defaultValue) {
    try {
      return Double.parseDouble(s);
    } catch (Exception e) {
      Log.e("Utils", "Exception parsing double: " + s);
      return defaultValue;
    }
  }

  public static float parseFloat(String s, float defaultValue) {
    try {
      return Float.parseFloat(s);
    } catch (NumberFormatException e) {
      Log.e("Utils", "Exception parsing float: " + s);
      return defaultValue;
    }
  }

  public static int parseInt(String s, int defaultValue) {
    try {
      return Integer.parseInt(s);
    } catch (NumberFormatException e) {
      Log.e("Utils", "Exception parsing int: " + s);
      return defaultValue;
    }
  }
} public class RecipeDateComparator<T> implements Comparator<T> {

  public int compare(T t1, T t2) {
    Recipe r1 = ((Recipe) t1);
    Recipe r2 = ((Recipe) t2);

    return r2.getBrewDateDate().compareTo(r1.getBrewDateDate());
  }

} public class RecipeModifiedComparator<T> implements Comparator<T> {

  public int compare(T t1, T t2) {
    Recipe r1 = ((Recipe) t1);
    Recipe r2 = ((Recipe) t2);

    return r2.getLastModified().compareTo(r1.getLastModified());
  }

} public class RecipeNameComparator<Recipe> implements Comparator<Recipe> {

  public int compare(Recipe s1, Recipe s2) {
    return s1.toString().compareToIgnoreCase(s2.toString());
  }

} public interface BiermachtFragment {
  // Method to call when a click event occurs
  public void handleClick(View v);

  // Method to call when this fragmet should update.
  public void update();

  // Method to call when an options item is selected.
  public boolean onOptionsItemSelected(MenuItem i);

  // Returns the name of this fragment (user visible).
  public String name();
} public class RecipeXmlWriter {
  private Context c;
  private String lastFileLocation;

  public RecipeXmlWriter(Context c) {
    this.c = c;
    this.lastFileLocation = "";
  }

  public void writeRecipe(Recipe r, String filePrefix) {
    ArrayList<Recipe> list = new ArrayList<Recipe>();
    list.add(r);
    this.writeRecipes(list, filePrefix);
  }

  public Document generateDocument(List<Recipe> list) throws ParserConfigurationException {
    // Open the document.
    DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder docBuilder;

    // This throws the exceptions!
    docBuilder = docFactory.newDocumentBuilder();

    // Create root element.
    Document doc = docBuilder.newDocument();
    Element rootElement = doc.createElement("RECIPES");
    doc.appendChild(rootElement);

    for (Recipe r : list) {
      // Element for this recipe.
      Element recipeElement = doc.createElement("RECIPE");

      // Create a mapping of name -> value
      Map<String, String> map = new HashMap<String, String>();
      map.put("NAME", r.getRecipeName());
      map.put("VERSION", r.getVersion() + "");
      map.put("TYPE", r.getType());
      map.put("EQUIPMENT", "");
      map.put("BREWER", "");
      map.put("BATCH_SIZE", r.getBeerXmlStandardBatchSize() + "");
      map.put("BOIL_SIZE", r.getBeerXmlStandardBoilSize() + "");
      map.put("BOIL_TIME", r.getBoilTime() + "");
      map.put("EFFICIENCY", r.getEfficiency() + "");
      map.put("NOTES", r.getNotes());
      map.put("OG", r.getOG() + "");
      map.put("FG", r.getFG() + "");
      map.put("DISPLAY_OG", r.getMeasuredOG() + "");
      map.put("DISPLAY_FG", r.getMeasuredFG() + "");
      map.put("FERMENTATION_STAGES", r.getFermentationStages() + "");
      map.put("PRIMARY_AGE", r.getFermentationAge(Recipe.STAGE_PRIMARY) + "");
      map.put("SECONDARY_AGE", r.getFermentationAge(Recipe.STAGE_SECONDARY) + "");
      map.put("TERTIARY_AGE", r.getFermentationAge(Recipe.STAGE_TERTIARY) + "");
      map.put("PRIMARY_TEMP", r.getBeerXmlStandardFermentationTemp(Recipe.STAGE_PRIMARY) + "");
      map.put("SECONDARY_TEMP", r.getBeerXmlStandardFermentationTemp(Recipe.STAGE_SECONDARY) + "");
      map.put("TERTIARY_TEMP", r.getBeerXmlStandardFermentationTemp(Recipe.STAGE_TERTIARY) + "");
      map.put("AGE", r.getBottleAge() + "");

      for (Map.Entry<String, String> e : map.entrySet()) {
        String fieldName = e.getKey();
        String fieldValue = e.getValue();
        Element element = doc.createElement(fieldName);
        element.setTextContent(fieldValue);
        recipeElement.appendChild(element);
      }

      // Add elements to recipe.
      recipeElement.appendChild(this.getHopsChild(doc, r.getHopsList()));
      recipeElement.appendChild(this.getFermentablesChild(doc, r.getFermentablesList()));
      recipeElement.appendChild(this.getMiscsChild(doc, r.getMiscList()));
      recipeElement.appendChild(this.getYeastsChild(doc, r.getYeastsList()));
      recipeElement.appendChild(this.getWatersChild(doc, r.getWatersList()));
      recipeElement.appendChild(this.getMashChild(doc, r.getMashProfile()));
      recipeElement.appendChild(this.getStyleChild(doc, r.getStyle()));

      // Add recipe to root <RECIPES> element.
      rootElement.appendChild(recipeElement);
    }
    return doc;
  }

  public String getXmlText(List<Recipe> list) {
    // Get the generated XML doc.
    Document doc;
    try {
      doc = generateDocument(list);
    } catch (ParserConfigurationException e) {
      e.printStackTrace();
      return null;
    }

    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = null;

    try {
      transformer = tf.newTransformer();
    } catch (TransformerConfigurationException e) {
      e.printStackTrace();
    }

    try {
      transformer.transform(domSource, result);
    } catch (TransformerException e) {
      e.printStackTrace();
    }

    writer.flush();
    return writer.toString();
  }

  public static String generateFileName(String prefix) {
    // Generate date string
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
    String dateString = dateFormat.format(new Date());

    return prefix + dateString + ".xml";
  }

  public void writeRecipes(List<Recipe> list, String filePrefix) {
    // Get the generated XML doc.
    Document doc;
    try {
      doc = generateDocument(list);
    } catch (ParserConfigurationException e) {
      e.printStackTrace();
      return;
    }

    // Write to XML file.
    TransformerFactory transformerFactory = TransformerFactory.newInstance();
    Transformer transformer = null;
    try {
      transformer = transformerFactory.newTransformer();
    } catch (TransformerConfigurationException e) {
      e.printStackTrace();
      return;
    }
    DOMSource source = new DOMSource(doc);

    // Create file object
    File file = null;
    try {
      file = getStoragePath(generateFileName(filePrefix));
    } catch (IOException e) {
      e.printStackTrace();
    }
    StreamResult result = new StreamResult(file);
    Log.d("WriteXmlFile", "Writing XML to:" + file);

    try {
      transformer.transform(source, result);
    } catch (TransformerException e) {
      e.printStackTrace();
    }
    this.lastFileLocation = file.getAbsolutePath();
  }

  /**
   * Creates and returns a style document for the given BeerStyle object.
   */
  public Element getStyleChild(Document d, BeerStyle s) {
    // Create the element.
    Element rootElement = d.createElement("STYLE");

    // Create a mapping of name -> value
    Map<String, String> map = new HashMap<String, String>();
    map.put("NAME", s.getName());
    map.put("CATEGORY", s.getCategory());
    map.put("VERSION", "1"); // FIXME: Actually set version.
    map.put("CATEGORY_NUMBER", s.getCatNum() + "");
    map.put("STYLE_LETTER", s.getStyleLetter());
    map.put("STYLE_GUIDE", s.getStyleGuide());
    map.put("TYPE", s.getType());
    map.put("OG_MIN", String.format("%2.8f", s.getMinOg()));
    map.put("OG_MAX", String.format("%2.8f", s.getMaxOg()));
    map.put("FG_MIN", String.format("%2.8f", s.getMinFg()));
    map.put("FG_MAX", String.format("%2.8f", s.getMaxFg()));
    map.put("IBU_MIN", String.format("%2.8f", s.getMinIbu()));
    map.put("IBU_MAX", String.format("%2.8f", s.getMaxIbu()));
    map.put("COLOR_MIN", String.format("%2.8f", s.getMinColor()));
    map.put("COLOR_MAX", String.format("%2.8f", s.getMaxColor()));
    map.put("CARB_MIN", String.format("%2.8f", s.getMinCarb()));
    map.put("CARB_MAX", String.format("%2.8f", s.getMaxCarb()));
    map.put("ABV_MIN", String.format("%2.8f", s.getMinAbv()));
    map.put("ABV_MAX", String.format("%2.8f", s.getMaxAbv()));
    map.put("NOTES", s.getNotes());
    map.put("PROFILE", s.getProfile());
    map.put("INGREDIENTS", s.getIngredients());
    map.put("EXAMPLES", s.getExamples());

    for (Map.Entry<String, String> e : map.entrySet()) {
      String fieldName = e.getKey();
      String fieldValue = e.getValue();
      Element element = d.createElement(fieldName);
      element.setTextContent(fieldValue);
      rootElement.appendChild(element);
    }

    return rootElement;
  }

  public Element getHopsChild(Document d, ArrayList<Hop> l) {
    // Create the element.
    Element hopsElement = d.createElement("HOPS");

    for (Hop h : l) {
      Element hopElement = d.createElement("HOP");

      // Create fields of element
      Element nameElement = d.createElement("NAME");
      Element versionElement = d.createElement("VERSION");
      Element alphaElement = d.createElement("ALPHA");
      Element amountElement = d.createElement("AMOUNT");
      Element useElement = d.createElement("USE");
      Element timeElement = d.createElement("TIME");
      Element notesElement = d.createElement("NOTES");
      Element typeElement = d.createElement("TYPE");
      Element formElement = d.createElement("FORM");

      // Assign values
      nameElement.setTextContent(h.getName());
      versionElement.setTextContent(h.getVersion() + "");
      alphaElement.setTextContent(h.getAlphaAcidContent() + "");
      amountElement.setTextContent(h.getBeerXmlStandardAmount() + "");
      timeElement.setTextContent(h.getTime() + "");
      notesElement.setTextContent(h.getDescription());
      typeElement.setTextContent(h.getType());
      formElement.setTextContent(h.getForm());
      useElement.setTextContent(h.getUse());

      // Attach to element.
      hopElement.appendChild(nameElement);
      hopElement.appendChild(versionElement);
      hopElement.appendChild(alphaElement);
      hopElement.appendChild(amountElement);
      hopElement.appendChild(useElement);
      hopElement.appendChild(timeElement);
      hopElement.appendChild(notesElement);
      hopElement.appendChild(typeElement);
      hopElement.appendChild(formElement);

      // Attach to list of elements.
      hopsElement.appendChild(hopElement);
    }

    return hopsElement;
  }

  public Element getFermentablesChild(Document d, ArrayList<Fermentable> l) {
    // Create the element.
    Element fermentablesElement = d.createElement("FERMENTABLES");

    for (Fermentable f : l) {
      Element fermentableElement = d.createElement("FERMENTABLE");

      // Create fields of element
      Element nameElement = d.createElement("NAME");
      Element versionElement = d.createElement("VERSION");
      Element typeElement = d.createElement("TYPE");
      Element amountElement = d.createElement("AMOUNT");
      Element yieldElement = d.createElement("YIELD");
      Element colorElement = d.createElement("COLOR");
      Element addAfterBoilElement = d.createElement("ADD_AFTER_BOIL");

      // Assign values
      nameElement.setTextContent(f.getName());
      versionElement.setTextContent(f.getVersion() + "");
      typeElement.setTextContent(f.getFermentableType());
      amountElement.setTextContent(f.getBeerXmlStandardAmount() + "");
      yieldElement.setTextContent(f.getYield() + "");
      colorElement.setTextContent(f.getLovibondColor() + "");
      addAfterBoilElement.setTextContent(f.isAddAfterBoil() + "");

      // Attach to element.
      fermentableElement.appendChild(nameElement);
      fermentableElement.appendChild(versionElement);
      fermentableElement.appendChild(typeElement);
      fermentableElement.appendChild(amountElement);
      fermentableElement.appendChild(yieldElement);
      fermentableElement.appendChild(colorElement);
      fermentableElement.appendChild(addAfterBoilElement);

      // Attach to list of elements.
      fermentablesElement.appendChild(fermentableElement);
    }

    return fermentablesElement;
  }

  public Element getMiscsChild(Document d, ArrayList<Misc> l) {
    // Create the element.
    Element miscsElement = d.createElement("MISCS");

    for (Misc m : l) {
      miscsElement.appendChild(this.getMiscChild(d, m));
    }

    return miscsElement;
  }

  public Element getMiscChild(Document d, Misc m) {
    // Create the element.
    Element rootElement = d.createElement("MISC");

    // Create a mapping of name -> value
    Map<String, String> map = new HashMap<String, String>();
    map.put("NAME", m.getName());
    map.put("VERSION", m.getVersion() + "");
    map.put("TYPE", m.getType());
    map.put("USE", m.getUse());
    map.put("AMOUNT", String.format("%2.8f", m.getBeerXmlStandardAmount()));
    map.put("DISPLAY_AMOUNT", m.getDisplayAmount() + " " + m.getDisplayUnits());
    map.put("DISPLAY_TIME", m.getTime() + " " + m.getTimeUnits());
    map.put("AMOUNT_IS_WEIGHT", m.amountIsWeight() ? "true" : "false");
    map.put("NOTES", m.getShortDescription());
    map.put("USE_FOR", m.getUseFor());

    for (Map.Entry<String, String> e : map.entrySet()) {
      String fieldName = e.getKey();
      String fieldValue = e.getValue();
      Element element = d.createElement(fieldName);
      element.setTextContent(fieldValue);
      rootElement.appendChild(element);
    }

    return rootElement;
  }

  public Element getYeastsChild(Document d, ArrayList<Yeast> l) {
    // Create the element.
    Element yeastsElement = d.createElement("YEASTS");

    for (Yeast y : l) {
      Element yeastElement = d.createElement("YEAST");

      // Create fields of element
      Element nameElement = d.createElement("NAME");
      Element versionElement = d.createElement("VERSION");
      Element typeElement = d.createElement("TYPE");
      Element formElement = d.createElement("FORM");
      Element amountElement = d.createElement("AMOUNT");
      Element laboratoryElement = d.createElement("LABORATORY");
      Element productIdElement = d.createElement("PRODUCT_ID");
      Element minTempElement = d.createElement("MIN_TEMPERATURE");
      Element maxTempElement = d.createElement("MAX_TEMPERATURE");
      Element attenuationElement = d.createElement("ATTENUATION");

      // Assign values
      nameElement.setTextContent(y.getName());
      versionElement.setTextContent(y.getVersion() + "");
      typeElement.setTextContent(y.getType());
      formElement.setTextContent(y.getForm());
      amountElement.setTextContent(y.getBeerXmlStandardAmount() + "");
      laboratoryElement.setTextContent(y.getLaboratory());
      productIdElement.setTextContent(y.getProductId());
      minTempElement.setTextContent(y.getBeerXmlStandardFermentationTemp() + "");
      maxTempElement.setTextContent(y.getBeerXmlStandardFermentationTemp() + "");
      attenuationElement.setTextContent(y.getAttenuation() + "");

      // Attach to element.
      yeastElement.appendChild(nameElement);
      yeastElement.appendChild(versionElement);
      yeastElement.appendChild(typeElement);
      yeastElement.appendChild(amountElement);
      yeastElement.appendChild(laboratoryElement);
      yeastElement.appendChild(productIdElement);
      yeastElement.appendChild(minTempElement);
      yeastElement.appendChild(maxTempElement);
      yeastElement.appendChild(attenuationElement);

      // Attach to list of elements.
      yeastsElement.appendChild(yeastElement);
    }

    return yeastsElement;
  }

  public Element getWatersChild(Document d, ArrayList<Water> l) {
    return d.createElement("WATERS");
  }

  public Element getMashChild(Document d, MashProfile m) {
    // Create the element.
    Element rootElement = d.createElement("MASH");

    // Create a mapping of name -> value
    Map<String, String> map = new HashMap<String, String>();
    map.put("NAME", m.getName());
    map.put("VERSION", m.getVersion() + "");
    map.put("GRAIN_TEMP", String.format("%2.8f", m.getBeerXmlStandardGrainTemp()));
    map.put("TUN_TEMP", String.format("%2.8f", m.getBeerXmlStandardTunTemp()));
    map.put("SPARGE_TEMP", String.format("%2.8f", m.getBeerXmlStandardSpargeTemp()));
    map.put("NOTES", m.getNotes());
    map.put("PH", String.format("%2.8f", m.getpH()));
    map.put("TUN_WEIGHT", String.format("%2.8f", m.getBeerXmlStandardTunWeight()));
    map.put("TUN_SPECIFIC_HEAT", String.format("%2.8f", m.getBeerXmlStandardTunSpecHeat()));
    map.put("EQUIP_ADJUST", "FALSE"); // FIXME: Actually set this.

    for (Map.Entry<String, String> e : map.entrySet()) {
      String fieldName = e.getKey();
      String fieldValue = e.getValue();
      Element element = d.createElement(fieldName);
      element.setTextContent(fieldValue);
      rootElement.appendChild(element);
    }

    // Add mash steps
    rootElement.appendChild(getMashStepsChild(d, m));

    return rootElement;
  }

  public Element getMashStepsChild(Document d, MashProfile m) {
    // Create the element.
    Element rootElement = d.createElement("MASH_STEPS");

    for (MashStep s : m.getMashStepList()) {
      rootElement.appendChild(getMashStepChild(d, s));
    }

    return rootElement;
  }

  public Element getMashStepChild(Document d, MashStep s) {
    // Create the element.
    Element rootElement = d.createElement("MASH_STEP");

    // Create a mapping of name -> value
    Map<String, String> map = new HashMap<String, String>();
    map.put("NAME", s.getName());
    map.put("VERSION", s.getVersion() + "");
    map.put("TYPE", s.getType());
    map.put("INFUSE_AMOUNT", String.format("%2.8f", s.getBeerXmlStandardInfuseAmount()));
    map.put("STEP_TEMP", String.format("%2.8f", s.getBeerXmlStandardStepTemp()));
    map.put("STEP_TIME", String.format("%2.8f", s.getStepTime()));
    map.put("RAMP_TIME", String.format("%2.8f", s.getRampTime()));
    map.put("END_TIME", String.format("%2.8f", s.getBeerXmlStandardEndTemp()));

    for (Map.Entry<String, String> e : map.entrySet()) {
      String fieldName = e.getKey();
      String fieldValue = e.getValue();
      Element element = d.createElement(fieldName);
      element.setTextContent(fieldValue);
      rootElement.appendChild(element);
    }

    return rootElement;
  }

  public File getStoragePath(String fileName) throws IOException {
    // Get the directory for the app's private directory on the SD card.  If there is none, try
    // the internal storage directory.
    File sdCard = Environment.getExternalStorageDirectory();
    File filePath = new File(sdCard.getAbsolutePath() + "/BiermachtBrews");
    File file = new File(filePath.getAbsolutePath() + "/", fileName);
    Log.d("XmlWriter", "Attempting to create file: " + file.getAbsolutePath());
    if (! filePath.mkdirs()) {
      Log.e("XmlWriter", "Cannot create directory.");
    }
    if (! file.exists()) {
      if (! file.createNewFile()) {
        Log.e("XmlWriter", "File not created.");
      }
    }
    else {
      Log.d("XmlWriter", "File already exists.");
    }
    return file;
  }

  public String getSavedFileLocation() {
    return this.lastFileLocation;
  }
}
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.biermacht.brews.R;
import com.biermacht.brews.database.DatabaseAPI;
import com.biermacht.brews.frontend.AddRecipeActivity;
import com.biermacht.brews.frontend.BrewTimerActivity;
import com.biermacht.brews.frontend.DisplayRecipeActivity;
import com.biermacht.brews.frontend.DisplayStyleActivity;
import com.biermacht.brews.frontend.EditFermentationProfileActivity;
import com.biermacht.brews.frontend.EditMashProfileActivity;
import com.biermacht.brews.frontend.EditRecipeNotesActivity;
import com.biermacht.brews.frontend.IngredientActivities.AddFermentableActivity;
import com.biermacht.brews.frontend.IngredientActivities.AddHopsActivity;
import com.biermacht.brews.frontend.IngredientActivities.AddMiscActivity;
import com.biermacht.brews.frontend.IngredientActivities.AddYeastActivity;
import com.biermacht.brews.frontend.IngredientActivities.EditRecipeActivity;
import com.biermacht.brews.frontend.adapters.DisplayRecipeCollectionPagerAdapter;
import com.biermacht.brews.frontend.adapters.RecipeArrayAdapter;
import com.biermacht.brews.ingredient.Ingredient;
import com.biermacht.brews.recipe.MashStep;
import com.biermacht.brews.recipe.Recipe;
import com.biermacht.brews.utils.Constants;
import com.biermacht.brews.utils.DriveActivity;
import com.biermacht.brews.utils.Units;
import com.biermacht.brews.utils.Utils;
import com.biermacht.brews.utils.comparators.RecipeDateComparator;
import com.biermacht.brews.utils.comparators.RecipeModifiedComparator;
import com.biermacht.brews.utils.comparators.RecipeNameComparator;
import com.biermacht.brews.utils.interfaces.BiermachtFragment;
import com.biermacht.brews.xml.RecipeXmlWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
            .setTitle("Confirm Delete")
            .setMessage("Do you really want to delete '" + r.getRecipeName() + "'?")
            .setIcon(android.R.drawable.ic_delete)
            .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {

              public void onClick(DialogInterface dialog, int which) {
                Log.d("RecipesFragment", "Deleting recipe: " + r);
                recipeList.remove(r);
                mAdapter.notifyDataSetChanged();
                databaseApi.deleteRecipe(r);
                if (isTablet) {
                  // If we're running on a tablet, we should set the current selected item to 0
                  // and update the details view.  Otherwise, the details view will remain stuck on the 
                  // current (now deleted) recipe.
                  currentSelectedIndex = (currentSelectedIndex > 0) ? currentSelectedIndex - 1 : 0;
                  if (recipeList.size() != 0) {
                    // Only update the details view if there is a recipe to show.
                    updateTabletDetailsView(recipeList.get(currentSelectedIndex));
                  }
                  else {
                    // Otherwise, set the correct views to show.
                    setCorrectView();
                  }
                }

                // If the last recipe was just deleted, we need to update which
                // view is being displayed.
                setCorrectView();

                // Dismiss the CAB
                mActionMode.finish();
                Log.d("RecipesFragment", "Recipe deleted");

                // Show Snackbar to indicate deletion.
                Snackbar.make(listView, R.string.recipe_deleted, Snackbar.LENGTH_LONG).show();
              }

            })

            .setNegativeButton(android.R.string.no, null);
  }

  private AlertDialog.Builder scaleAlert(final Recipe r) {
    LayoutInflater factory = LayoutInflater.from(context);
    final LinearLayout alertView = (LinearLayout) factory.inflate(R.layout.alert_view_scale, null);
    final EditText editText = (EditText) alertView.findViewById(R.id.new_volume_edit_text);

    return new AlertDialog.Builder(context)
            .setTitle("Scale Recipe")
            .setView(alertView)
            .setPositiveButton(R.string.scale, new DialogInterface.OnClickListener() {

              public void onClick(DialogInterface dialog, int which) {
                double newVolume = Double.parseDouble(editText.getText().toString()
                                                              .replace(",", "."));
                Utils.scaleRecipe(getActivity(), r, newVolume);
                mAdapter.notifyDataSetChanged();

                // Dismiss the CAB
                mActionMode.finish();

                // Show Snackbar to indicate completion.
                Snackbar.make(listView, R.string.recipe_scaled, Snackbar.LENGTH_LONG).show();
              }

            })

            .setNegativeButton(R.string.cancel, null);
  }

  @Override
  public void handleClick(View v) {
    // Currently, the handleClick method is only valid when running on
    // a tablet.  If we hit this method when not running on a tablet,
    // we should just return.
    if (! isTablet) {
      Log.d("RecipesFragment", "Not on tablet, do nothing");
      return;
    }

    // Get the current selected recipe.
    Recipe r = recipeList.get(currentSelectedIndex);

    if (v.getId() == R.id.add_ingredient_button) {
      // The user has pressed the add-ingredient button.  Display
      // options for which type of ingredient to add.  This button should 
      // only be present on tablets.
      Log.d("RecipesFragment", "User pressed add-ingredient button");
      currentAlert = ingredientSelectAlert().show();
    }
    else if (v.getId() == R.id.add_fermentable_button) {
      // User has chosen to add a fermentable - start the 
      // add fermentable activity.
      Intent i = new Intent(getActivity(), AddFermentableActivity.class);
      i.putExtra(Constants.KEY_RECIPE, r);
      startActivity(i);

      // Dismiss the currentAlert, which should be the ingredientSelectorAlert()
      currentAlert.dismiss();
    }
    else if (v.getId() == R.id.add_hops_button) {
      // User has chosen to add a hop - start the add hop activity.
      Intent i = new Intent(getActivity(), AddHopsActivity.class);
      i.putExtra(Constants.KEY_RECIPE, r);
      startActivity(i);

      // Dismiss the currentAlert, which should be the ingredientSelectorAlert()
      currentAlert.dismiss();
    }
    else if (v.getId() == R.id.add_yeast_button) {
      // User has chosen to add a yeast - start the add yeast activity.
      Intent i = new Intent(getActivity(), AddYeastActivity.class);
      i.putExtra(Constants.KEY_RECIPE, r);
      startActivity(i);

      // Dismiss the currentAlert, which should be the ingredientSelectorAlert()
      currentAlert.dismiss();
    }
    else if (v.getId() == R.id.add_misc_button) {
      // User has chosen to add a misc - start the add misc activity.
      Intent i = new Intent(getActivity(), AddMiscActivity.class);