Package net.tawacentral.roger.secrets

Source Code of net.tawacentral.roger.secrets.FileUtils$SecretsBackupAgent

// Copyright (c) 2009, Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package net.tawacentral.roger.secrets;

import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.FileBackupHelper;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import au.com.bytecode.opencsv.CSVReader;
import au.com.bytecode.opencsv.CSVWriter;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;

import net.tawacentral.roger.secrets.SecurityUtils.CipherInfo;

/**
* Helper class to manage reading and writing the secrets file.  The file
* is encrypted using the ciphers created by the SecurityUtils helper
* functions.
*
* Methods that touch the main secrets files are thread safe.  This allows
* the file to be saved in a background thread so that the UI is not blocked
* in the most common use cases.  Note that stopping the app and restarting
* it again may still cause the UI to block if the write take a long time.
*
* @author rogerta
*/
public class FileUtils {
  /** Return value for the getSaltAndRounds() function. */
  public static class SaltAndRounds {
    public SaltAndRounds(byte[] salt, int rounds) {
      this.salt = salt;
      this.rounds = rounds;
    }
    public byte[] salt;
    public int rounds;
  }

  /** Name of the secrets file. */
  public static final String SECRETS_FILE_NAME = "secrets";

  /** Name of the secrets backup file on the SD card. */
  public static final String SECRETS_FILE_NAME_SDCARD = "/sdcard/secrets";

  /** Name of the secrets CSV file on the SD card. */
  public static final String SECRETS_FILE_NAME_CSV = "/sdcard/secrets.csv";

  /** Name of the OI Safe CSV file on the SD card. */
  public static final String OI_SAFE_FILE_NAME_CSV = "/sdcard/oisafe.csv";

  private static final File SECRETS_FILE_CSV = new File(SECRETS_FILE_NAME_CSV);
  private static final File OI_SAFE_FILE_CSV = new File(OI_SAFE_FILE_NAME_CSV);

  // Secrets CSV column names
  public static final String COL_DESCRIPTION = "Description";
  public static final String COL_USERNAME = "Id";
  public static final String COL_PASSWORD = "PIN";
  public static final String COL_EMAIL = "Email";
  public static final String COL_NOTES= "Notes";

  private static final String EMPTY_STRING = "";
  private static final String INDENT = "   ";
  private static final String RP_PREFIX = "@";

  /** Tag for logging purposes. */
  public static final String LOG_TAG = "Secrets.FileUtils";

  /** Lock for accessing main secrets file. */
  private static final Object lock = new Object();

  private static final byte[] SIGNATURE = {0x22, 0x34, 0x56, 0x79};

  /** Does the secrets file exist? */
  public static boolean secretsExist(Context context) {
    // Instead of just checking for the existence of the secrets file
    // explicitly, I will check for the existence of any file in the
    // application's data directory.  This check is valid because:
    //
    //  - when the user runs the app for the first time, an empty secrets file
    //    is always written
    //  - there is at least one file in existences even during the save
    //    operation
    //
    // The benefit of making this assumption is that I don't need to acquire
    // the file lock in order to test for the existence of the secrets file.
    // This speeds up leaving the secrets list activity since the test for
    // existence does not need to wait for the save to finish.  This also
    // speeds the wake time when secrets was active at the time the phone
    // went to sleep.
    String[] filenames = context.fileList();
    return filenames.length > 0;
  }

  /** Does the secrets restore file exist on the SD card? */
  public static boolean restoreFileExist() {
    File file = new File(SECRETS_FILE_NAME_SDCARD);
    return file.exists();
  }

  /** Is the restore file too old? */
  public static boolean isRestoreFileTooOld() {
    File file = new File(SECRETS_FILE_NAME_SDCARD);
    if (!file.exists())
      return false;
   
    long lastModified = file.lastModified();
    long now = System.currentTimeMillis();
    long oneWeeks = 7 * 24 * 60 * 60 * 1000// One week.
   
    return (now - lastModified) > oneWeeks;
  }
 
  /** Is the restore point too old? */
  private static boolean isRestorePointTooOld(File file) {
    long lastModified = file.lastModified();
    long now = System.currentTimeMillis();
    long twoDays = 2 * 24 * 60 * 60 * 1000// 2 days.
   
    return (now - lastModified) > twoDays;
  }
 
  /**
   * Get all existing restore points, including the restore file on the SD card
   * if it exists.
   *
   * @param context Activity context in which the save is called.
   * @return A list of all possible restore points.
   */
  public static List<String> getRestorePoints(Context context) {
    String[] filenames = context.fileList();
    ArrayList<String> list = new ArrayList<String>(filenames.length + 1);
    if (restoreFileExist())
      list.add(SECRETS_FILE_NAME_SDCARD);
   
    for (String filename : filenames) {
      if (filename.startsWith(RP_PREFIX))
        list.add(filename);
    }
   
    return list;
  }
 
  /**
   * Cleanup any residual data files from a previous bad run, if any.  The
   * algorithm is as follows:
   *
   * - delete any file with "new" in the name.  These are possibly partial
   *   writes, so their contents is undefined.
   * - if no secrets file exists, rename the most recent auto restore point
   *   file to secrets.
   * - if too many auto restore point files exist, delete the extra ones.
   *   However, don't delete any auto-backups younger than 48 hours.
   *
   * @param context Activity context in which the save is called.
   */
  public static void cleanupDataFiles(Context context) {
    Log.d(LOG_TAG, "FileUtils.cleanupDataFiles");
    synchronized (lock) {
      String[] filenames = context.fileList();
      int oldCount = filenames.length;
      boolean secretsFileExists = context.getFileStreamPath(SECRETS_FILE_NAME)
          .exists();

      // Cleanup any partial saves and find the most recent auto-backup file.
      {
        File mostRecent = null;
        int mostRecentIndex = -1;
        for (int i = 0; i < filenames.length; ++i) {
          String filename = filenames[i];
          if (-1 != filename.indexOf("new")) {
            context.deleteFile(filename);
            --oldCount;
            filenames[i] = null;
          } else if (filename.startsWith(RP_PREFIX)) {
            if (!secretsFileExists) {
              File f = context.getFileStreamPath(filename);
              if (null == mostRecent ||
                  f.lastModified() > mostRecent.lastModified()) {
                mostRecent = f;
                mostRecentIndex = i;
              }
            }
          } else {
            --oldCount;
            filenames[i] = null;
          }
        }

        // If we don't have a secrets file but found an auto-backup file,
        // rename the more recent auto-backup to secrets.
        if (null != mostRecent) {
          mostRecent.renameTo(context.getFileStreamPath(SECRETS_FILE_NAME));
          --oldCount;
          filenames[mostRecentIndex] = null;
        }
      }

      // If there are too many old files, delete the oldest extra ones.
      while (oldCount > 10) {
        File oldest = null;
        int oldestIndex = -1;
       
        for (int i = 0; i < filenames.length; ++i) {
          String filename = filenames[i];
          if (null == filename)
            continue;
         
          File f = context.getFileStreamPath(filename);
          if (null == oldest || f.lastModified() < oldest.lastModified()) {
            oldest = f;
            oldestIndex = i;
          }
        }

        if (null != oldest) {
          // If the oldest file is not too old, then just break out of the
          // loop.  We don't want to delete any "old" files that are too
          // recent.
          if (!FileUtils.isRestorePointTooOld(oldest))
            break;
         
          oldest.delete();
          --oldCount;
          filenames[oldestIndex] = null;
        }
      }
    }
  }

  /**
   * Gets the salt and rounds already in use on this device, or null if none
   * exists.
   *
   * @param context Activity context in which the save is called.
   * @param path The file to read the salt and rounds from.  Can either be the
   *     string SECRETS_FILE_NAME_SDCARD, SECRETS_FILE_NAME, or the  name of a
   *     restore point.
   */
  public static SaltAndRounds getSaltAndRounds(Context context, String path) {
    // The salt is stored as a byte array at the start of the secrets file.
    FileInputStream input = null;
    try {
      input = SECRETS_FILE_NAME_SDCARD.equals(path)
          ? new FileInputStream(path)
          : context.openFileInput(path);
      return getSaltAndRounds(input);
    } catch (Exception ex) {
      Log.e(LOG_TAG, "getSaltAndRounds", ex);
    } finally {
      try {if (null != input) input.close();} catch (IOException ex) {}
    }
    return new SaltAndRounds(null, 0);
  }

  /**
   * Gets the salt and rounds already in use on this device, or null if none
   * exists.
   *
   * @param input The stream to read the salt and rounds from.
   *
   * @throws IOException
   */
  public static SaltAndRounds getSaltAndRounds(InputStream input)
      throws IOException {
    // The salt is stored as a byte array at the start of the secrets file.
    byte[] signature = new byte[SIGNATURE.length];
    byte[] salt = null;
    int rounds = 0;
    input.read(signature);
    if (Arrays.equals(signature, SIGNATURE)) {
      int length = input.read();
      salt = new byte[length];
      input.read(salt);
      rounds = input.read();
      if (rounds < 4 || rounds > 31) {
        salt = null;
        rounds = 0;
      }
    }

    return new SaltAndRounds(salt, rounds);
  }

  /**
   * Saves the secrets to file using the password retrieved from the user.
   *
   * @param context Activity context in which the save is called.
   * @param existing The file to save into.
   * @param cipher The encryption cipher to use with the file.
   * @param salt The salt used to create the cipher.
   * @param rounds The number of rounds for bcrypt.
   * @param secrets The list of secrets to save.
   * @return True if saved successfully.
   */
  public static int saveSecrets(Context context,
                                File existing,
                                Cipher cipher,
                                byte[] salt,
                                int rounds,
                                List<Secret> secrets) {
    Log.d(LOG_TAG, "FileUtils.saveSecrets");
    synchronized (lock) {
      Log.d(LOG_TAG, "FileUtils.saveSecrets: got lock");
     
      // To be as safe as possible, for example to handle low space conditions,
      // we will save the secrets to a file using the following steps:
      //
      //  1- write the secrets to a new temporary file (tempn)
      //     on error: delete tempn
      //  2- rename the existing secrets file, if any (to tempo)
      //     on error: delete tempn
      //  3- rename the new temporary file to the official file name
      //     on error: rename tempo back to existing, delete tempn
      //
      // The old file we hang around for a while.  The cleanupDataFiles()
      // method, which is called whenever Secrets is re-launched, will make
      // sure that the old file don't accumulate indefinitely.
      String prefix = MessageFormat.format(RP_PREFIX +
          "{0,date,yy.MM.dd}-{0,time,HH:mm}", new Date(),
          null);
      File parent = existing.getParentFile();
      File tempn = new File(parent, "new");
      File tempo = new File(parent, prefix);
      for (int i = 0; tempn.exists() || tempo.exists(); ++i) {
        tempn = new File(parent, "new" + i);
        tempo = new File(parent, prefix + i);
      }
      // Step 1
      ObjectOutputStream output = null;
      try {
        FileOutputStream fos = new FileOutputStream(tempn);
        writeSecrets(fos, cipher, salt, rounds, secrets);
      } catch (Exception ex) {
        Log.d(LOG_TAG, "FileUtils.saveSecrets: could not write secrets file");
        // NOTE: this delete() works, even though the file is still open.
        tempn.delete();
        return R.string.error_save_secrets;
      } finally {
        try {if (null != output) output.close();} catch (IOException ex) {}
      }

      // Step 2
      if (existing.exists() && !existing.renameTo(tempo)) {
        Log.d(LOG_TAG, "FileUtils.saveSecrets: could not move existing file");
        tempn.delete();
        return R.string.error_cannot_move_existing;
      }

      // Step 3
      if (!tempn.renameTo(existing)) {
        Log.d(LOG_TAG, "FileUtils.saveSecrets: could not move new file");
        tempo.renameTo(existing);
        tempn.delete();
        return R.string.error_cannot_move_new;
      }

      Log.d(LOG_TAG, "FileUtils.saveSecrets: done");
      return 0;
    }
  }

  /**
   * Backup the secrets to SD card using the password retrieved from the user.
   *
   * @param context Activity context in which the backup is called.
   * @param cipher The encryption cipher to use with the file.
   * @param salt The salt used to create the cipher.
   * @param rounds The number of rounds for bcrypt.
   * @param secrets The list of secrets to save.
   * @return True if saved successfully
   */
  public static boolean backupSecrets(Context context,
                                      Cipher cipher,
                                      byte[] salt,
                                      int rounds,
                                      List<Secret> secrets) {
    Log.d(LOG_TAG, "FileUtils.backupSecrets");

    if (null == cipher)
      return false;

    ObjectOutputStream output = null;
    boolean success = false;

    try {
      FileOutputStream fos = new FileOutputStream(SECRETS_FILE_NAME_SDCARD);
      writeSecrets(fos, cipher, salt, rounds, secrets);
      success = true;
    } catch (Exception ex) {
    } finally {
      try {if (null != output) output.close();} catch (IOException ex) {}
    }

    return success;
  }

  /**
   * Opens the secrets file using the password retrieved from the user and
   * the old encryption cipher.  This function is called only for backward
   * compatibility purposes, when Secrets encounters an error trying to load
   * the secrets using the current encryption method.
   *
   * @param context Activity context in which the load is called.
   * @param cipher Decryption cipher for old encryption.
   * @return A list of loaded secrets.
   */
  @SuppressWarnings("unchecked")
  public static ArrayList<Secret> loadSecretsV1(Context context,
                                                Cipher cipher) {
    if (null == cipher)
      return null;

    ArrayList<Secret> secrets = null;

    synchronized (lock) {
      ObjectInputStream input = null;

      try {
        input = new ObjectInputStream(
            new CipherInputStream(context.openFileInput(SECRETS_FILE_NAME),
                                  cipher));
        secrets = (ArrayList<Secret>) input.readObject();
      } catch (Exception ex) {
        Log.e(LOG_TAG, "loadSecretsV1", ex);
      } finally {
        try {if (null != input) input.close();} catch (IOException ex) {}
      }
    }

    return secrets;
  }

  /**
   * Opens the secrets file using the password retrieved from the user.
   *
   * @param context Activity context in which the load is called.
   * @return A list of loaded secrets.
   */
  public static ArrayList<Secret> loadSecrets(Context context) {
    Log.d(LOG_TAG, "FileUtils.loadSecrets");
    synchronized (lock) {
      Log.d(LOG_TAG, "FileUtils.loadSecrets: got lock");

      Cipher cipher = SecurityUtils.getDecryptionCipher();
      if (null == cipher)
        return null;

      ArrayList<Secret> secrets = null;
      InputStream input = null;

      try {
        input = context.openFileInput(SECRETS_FILE_NAME);
        secrets = readSecrets(input, cipher, SecurityUtils.getSalt(),
                              SecurityUtils.getRounds());
      } catch (Exception ex) {
        Log.e(LOG_TAG, "loadSecrets", ex);
      } finally {
        try {if (null != input) input.close();} catch (IOException ex) {}
      }
 
      Log.d(LOG_TAG, "FileUtils.loadSecrets: done");
      return secrets;
    }
  }

  /**
   * Restore the secrets from the SD card using the password retrieved from
   * the user.
   *
   * @param context Activity context in which the load is called.
   * @param rp A restore point name.  This should be one of the strings
   *     returned by the getRestorePoints() method.
   * @param info A CipherInfo structure describing the decryption cipher to use.
   */
  public static ArrayList<Secret> restoreSecrets(Context context,
                                                 String rp,
                                                 CipherInfo info) {
    Log.d(LOG_TAG, "FileUtils.restoreSecrets");

    ArrayList<Secret> secrets = null;
    InputStream input = null;

    try {
      input = SECRETS_FILE_NAME_SDCARD.equals(rp)
          ? new FileInputStream(rp)
          : context.openFileInput(rp);
      secrets = readSecrets(input, info.decryptCipher, info.salt, info.rounds);
    } catch (Exception ex) {
      Log.e(LOG_TAG, "restoreSecrets", ex);
    } finally {
      try {if (null != input) input.close();} catch (IOException ex) {}
    }

    return secrets;
  }

  /**
   * Writes the secrets to the given output stream encrypted with the given
   * cipher.
   *
   * @param output The output stream to write the secrets to.
   * @param cipher The cipher to encrypt the secrets with.
   * @param secrets The secrets to write.
   * @param rounds The number of rounds for bcrypt.
   * @throws IOException
   */
  private static void writeSecrets(OutputStream output,
                                   Cipher cipher,
                                   byte[] salt,
                                   int rounds,
                                   List<Secret> secrets) throws IOException {
    output.write(SIGNATURE);
    output.write(salt.length);
    output.write(salt);
    output.write(rounds);
    ObjectOutputStream oout = new ObjectOutputStream(
        new CipherOutputStream(output, cipher));
    try {
      oout.writeObject(secrets);
    } finally {
      try {if (null != oout) oout.close();} catch (IOException ex) {}
    }
  }
 
  /**
   * Read the secrets from the given input stream, decrypting with the given
   * cipher.
   *
   * @param input The input stream to read the secrets from.
   * @param cipher The cipher to decrypt the secrets with.
   * @return The secrets read from the stream.
   * @throws IOException
   * @throws ClassNotFoundException
   */
  @SuppressWarnings("unchecked")
  private static ArrayList<Secret> readSecrets(InputStream input,
                                               Cipher cipher,
                                               byte[] salt,
                                               int rounds)
      throws IOException, ClassNotFoundException {
    SaltAndRounds pair = getSaltAndRounds(input);
    if (!Arrays.equals(pair.salt, salt) || pair.rounds != rounds) {
      return null;
    }

    ObjectInputStream oin = new ObjectInputStream(
        new CipherInputStream(input, cipher));
    try {
      return (ArrayList<Secret>) oin.readObject();
    } finally {
      try {if (null != oin) oin.close();} catch (IOException ex) {}
    }
  }
 
  /** Deletes all secrets from the phone. */
  public static boolean deleteSecrets(Context context) {
    Log.d(LOG_TAG, "FileUtils.deleteSecrets");
    synchronized (lock) {
      String filenames[] = context.fileList();
      for (String filename : filenames) {
        context.deleteFile(filename);
      }
    }
   
    return true;
  }

  /**
   * Export secrets to a CSV file on the SD card.  See the description of
   * the importSecrets() method for more details about the format written.
   */
  public static boolean exportSecrets(Context context,List<Secret> secrets) {
    // An array to hold the rows that will be written to the CSV file.
    String[] row = new String[] {
        COL_DESCRIPTION, COL_USERNAME, COL_PASSWORD, COL_EMAIL, COL_NOTES
    };
    CSVWriter writer = null;
    boolean success = false;

    try {
      writer = new CSVWriter(new FileWriter(SECRETS_FILE_NAME_CSV));

      // Write descriptive headers.
      writer.writeNext(row);

      // Write out each secret.
      for (Secret secret : secrets) {
        row[0] = secret.getDescription();
        row[1] = secret.getUsername();
        row[2] = secret.getPassword(true)// true: forExport
        row[3] = secret.getEmail();
        row[4] = secret.getNote();

        // NOTE: writeNext() handles nulls in row[] gracefully.
        writer.writeNext(row);
        success = true;
      }
    } catch (Exception ex) {
      Log.e(LOG_TAG, "exportSecrets", ex);
    } finally {
      try {if (null != writer) writer.close();} catch (IOException ex) {}
    }

    return success;
  }

  /**
   * Returns the file that should be imported.  This method will look for a file
   * on the SD card whose name is either the secrets CSV file or the OI Safe
   * CSV file.  To support other formats, should add some code here to detect
   * those files.
   *
   * If more than one CSV file of exist, the one last modified is used.
   */
  public static File getFileToImport() {
    boolean haveSecretsCsv = SECRETS_FILE_CSV.exists();
    boolean haveOiSafeCsv = OI_SAFE_FILE_CSV.exists();
    File file = null;

    if (haveSecretsCsv && haveOiSafeCsv) {
      if (SECRETS_FILE_CSV.lastModified() > OI_SAFE_FILE_CSV.lastModified()) {
        file = SECRETS_FILE_CSV;
      } else {
        file = OI_SAFE_FILE_CSV;
      }
    } else if (haveSecretsCsv) {
      file = SECRETS_FILE_CSV;
    } else if (haveOiSafeCsv) {
      file = OI_SAFE_FILE_CSV;
    }

    return file;
  }

  /**
   * Import secrets from a CSV file.  This function will first clear the secrets
   * list argument before adding anything read from the file. If there are no
   * errors while reading, true is returned.
   *
   * Note that its possible for this function to return false, and yet the
   * secrets list is not empty.  This means that some, but not all, secrets
   * were able to be read from the file.
   *
   * For the moment, this function assumes that the first line is a description
   * so it is not imported.  At least 5 columns are assumed for each line:
   * description, username, password, email, and notes, in that order (this is
   * the default format as written by exportSecrets()).
   *
   * This function will attempt to detect OI Safe CSV files and import them
   * accordingly.  It does this by reading the first line of file, and looking
   * for column descriptions as exported by OI Safe 1.1.0.
   *
   * @param context Activity context for global services
   * @param file File to import
   * @param secrets List to append secrets read from the file
   * @return True if all secrets read successfully, and false otherwise
   */
  public static boolean importSecrets(Context context,
                                      File file,
                                      ArrayList<Secret> secrets) {
    secrets.clear();

    boolean isOiSafeCsv = false;
    boolean isSecretsScv = false;
    boolean success = false;
    CSVReader reader = null;

    try {
      reader = new CSVReader(new FileReader(file));

      // Use the first line to determine the type of csv file.  Secrets will
      // output 5 columns, with the names as used in the exportSecrets()
      // function.  OI Safe 1.1.0 is also detected.
      String headers[] = reader.readNext();
      if (null != headers) {
        isSecretsScv = isSecretsCsv(headers);
        if (!isSecretsScv)
          isOiSafeCsv = isOiSafeCsv(headers);
      }

      // Read all the rest of the lines as secrets.
      for (;;) {
        String[] row = reader.readNext();
        if (null == row)
          break;

        Secret secret = new Secret();
        if (isOiSafeCsv) {
          secret.setDescription(row[1]);
          secret.setUsername(row[3]);
          secret.setPassword(row[4]);
          secret.setEmail(EMPTY_STRING);

          // I will combine the category, website, and notes columns into
          // the notes field in secrets.
          int approxMaxLength = row[0].length() + row[2].length() +
                                row[5].length() + 32;
          StringBuilder builder = new StringBuilder(approxMaxLength);
          builder.append(row[5]).append("\n\n");
          builder.append("Category: ").append(row[0]).append('\n');
          if (null != row[2] && row[2].length() > 0)
            builder.append("Website: ").append(row[2]).append('\n');

          secret.setNote(builder.toString());
        } else {
          // If we get here, then this may be an unknown format.  For better
          // or for worse, this is a "best effort" to import that data.
          secret.setDescription(row[0]);
          secret.setUsername(row[1]);
          secret.setPassword(row[2]);
          secret.setEmail(row[3]);
          secret.setNote(row[4]);
        }

        secrets.add(secret);
      }

      // We'll only return complete success if we get here, and we detected
      // that we knew the file format.  This will give the user an indication
      // do look at the secrets if the format was not automatically detected.
      success = isOiSafeCsv || isSecretsScv;
    } catch (Exception ex) {
      Log.e(LOG_TAG, "importSecrets", ex);
    } finally {
      try {if (null != reader) reader.close();} catch (IOException ex) {}
    }

    return success;
  }

  /** Is it likely that the CSV file is in OI Safe format? */
  private static boolean isOiSafeCsv(String[] headers) {
    if (headers[0].equalsIgnoreCase("Category") &&
        headers[1].equalsIgnoreCase("Description") &&
        headers[2].equalsIgnoreCase("Website") &&
        headers[3].equalsIgnoreCase("Username") &&
        headers[4].equalsIgnoreCase("Password") &&
        headers[5].equalsIgnoreCase("Notes"))
      return true;

    return false;
  }

  /** Is it likely that the CSV file is in secrets format? */
  private static boolean isSecretsCsv(String[] headers) {
    if (headers[0].equalsIgnoreCase(COL_DESCRIPTION) &&
        headers[1].equalsIgnoreCase(COL_USERNAME) &&
        headers[2].equalsIgnoreCase(COL_PASSWORD) &&
        headers[3].equalsIgnoreCase(COL_EMAIL) &&
        headers[4].equalsIgnoreCase(COL_NOTES))
      return true;

    return false;
  }

  /** Returns a list of the supported CSV file names, newline separated. */
  public static String getCsvFileNames() {
    StringBuilder builder = new StringBuilder();
    builder.append(INDENT).append(SECRETS_FILE_CSV.getName()).append('\n');
    builder.append(INDENT).append(OI_SAFE_FILE_CSV.getName());

    return builder.toString();
  }

  static public class SecretsBackupAgent extends BackupAgentHelper {
    /** Tag for logging purposes. */
    public static final String LOG_TAG = "Secrets";
   
    /** Key in backup set for file data. */
    private static final String KEY ="file";
   
    @Override
    public void onCreate() {
      Log.d(LOG_TAG, "onCreate");
     
      FileBackupHelper helper = new FileBackupHelper(this,
          FileUtils.SECRETS_FILE_NAME);
      addHelper(KEY, helper);
    }
   
    @Override
    public void onBackup(ParcelFileDescriptor oldState,
                         BackupDataOutput data,
                         ParcelFileDescriptor newState) throws IOException {
      Log.d(LOG_TAG, "onBackup");
      synchronized (lock) {
        super.onBackup(oldState, data, newState);
      }
    }
   
    @Override
    public void onRestore(BackupDataInput data,
                          int appVersionCode,
                          ParcelFileDescriptor newStatethrows IOException {   
      Log.d(LOG_TAG, "onRestore");
      synchronized (lock) {
        super.onRestore(data, appVersionCode, newState);
      }
    }
  }
}
TOP

Related Classes of net.tawacentral.roger.secrets.FileUtils$SecretsBackupAgent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.