You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
music-synthesizer-for-android/android/src/com/levien/synthesizer/android/Storage.java

279 lines
11 KiB

/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.levien.synthesizer.android;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Environment;
import android.widget.EditText;
import android.widget.Toast;
import com.levien.synthesizer.core.music.Music.Score;
/**
* A collection of functions for storing and retrieving scores.
*/
public class Storage {
/**
* Opens the score stored with the name "_default", which should always be the current score
* being edited. This may be called at any time to restore saved state, since Android Activities
* can come and go.
*
* @param score - The mutable score to populate with the stored data.
* @param context - Android application context.
*/
public static void openDefaultScore(Score.Builder score, Context context) throws IOException {
openScore(score, "_default", context);
}
/**
* Saves a score with the name "_default", which should always be the current score being edited.
* This may be called at any time to save state, since Android Activities can come and go.
*
* @param score - The score data to save.
* @param context - Android application context.
*/
public static void saveDefaultScore(Score score, Context context) throws IOException {
saveScore(score, "_default", true, context);
}
/**
* Opens the score with the given name. The name should be name of a valid score file in storage.
* The file must be in the root external files directory for the app.
*
* @param score - The mutable score to update with the data from storage.
* @param name - The name of the file, minus the ".pb" extension.
* @param context - The Android application context.
*/
public static void openScore(Score.Builder score, String name, Context context) throws IOException {
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
!Environment.MEDIA_MOUNTED_READ_ONLY.equals(Environment.getExternalStorageState())) {
throw new IOException("External storage is not readable.");
}
File path = context.getExternalFilesDir(null);
File file = new File(path, name + ".pb");
FileInputStream in = new FileInputStream(file);
score.clear();
score.mergeFrom(in);
in.close();
}
/**
* Saves the score with the given name. Files are stored in the root external files directory for
* the app.
*
* @param score - The score to save.
* @param name - The name of the file, without any extension.
* @param overwrite - If true, replace the existing file, if one already exists.
* @param context - The Android application context.
* @throws IOException - On any kind of IO error, or if name is "", or the file already exists.
*/
public static void saveScore(Score score,
String name,
boolean overwrite,
Context context) throws IOException {
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
throw new IOException("External storage is not writeable.");
}
File path = context.getExternalFilesDir(null);
name = cleanupName(name);
if (name.length() == 0) {
throw new IOException("Can't save score without a name.");
}
File file = new File(path, name + ".pb");
if (!overwrite && file.exists()) {
throw new IOException("File already exists.");
}
FileOutputStream out = new FileOutputStream(file);
score.writeTo(out);
out.close();
}
/**
* Returns the list of all valid names of scores that are currently in storage.
*/
public static String[] getScoreNames(Context context) throws IOException {
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
!Environment.MEDIA_MOUNTED_READ_ONLY.equals(Environment.getExternalStorageState())) {
throw new IOException("External storage is not readable.");
}
File path = context.getExternalFilesDir(null);
File[] files = path.listFiles();
ArrayList<String> names = new ArrayList<String>();
for (File file : files) {
names.add(file.getName().replaceAll("\\.pb", ""));
}
return names.toArray(new String[0]);
}
/**
* Returns true iff there is a score in storage with the given name.
*/
public static boolean scoreExists(String name, Context context) throws IOException {
String[] names = getScoreNames(context);
for (String existingName : names) {
if (name.equals(existingName)) {
return true;
}
}
return false;
}
/**
* Interface used to notify callers of openScoreWithDialog() when the opening has completed.
*/
public interface OpenScoreListener {
void onOpenScore(Score.Builder score);
}
/**
* Shows UI to allow the user to pick one of the current scores in storage, and then populates
* score with the data from that file.
*
* @param score - The mutable score to update.
* @param listener - A listener that is notified after the score is updated. Can be null.
* @param context - An Android application context.
*/
public static void openScoreWithDialog(final Score.Builder score,
final OpenScoreListener listener,
final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Open score...");
try {
final String[] scoreNames = getScoreNames(context);
builder.setItems(scoreNames, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
try {
openScore(score, scoreNames[which], context);
if (listener != null) {
listener.onOpenScore(score);
}
dialog.dismiss();
} catch (IOException e) {
Logger logger = Logger.getLogger(Storage.class.getName());
logger.log(Level.SEVERE,
"Error opening score \"" + scoreNames[which] + "\" with dialog.", e);
Toast.makeText(context,
"Unable to open \"" + scoreNames[which] + "\".",
Toast.LENGTH_SHORT).show();
}
}
});
} catch (IOException e) {
Logger logger = Logger.getLogger(Storage.class.getName());
logger.log(Level.SEVERE,
"Error getting score names.", e);
Toast.makeText(context,
"Unable to get existing score names.",
Toast.LENGTH_SHORT).show();
}
AlertDialog dialog = builder.create();
dialog.show();
}
/**
* Shows UI to allow the user to pick a name and save the given score in storage.
*
* @param score - The score to save.
* @param context - An Android application context.
*/
public static void saveScoreWithDialog(final Score score,
final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Save score as...");
builder.setMessage("Name: ");
final EditText input = new EditText(context);
builder.setView(input);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface nameDialog, int which) {
final String name = cleanupName(input.getText().toString());
if (name.length() == 0) {
Toast.makeText(context,
"Name must not be empty.",
Toast.LENGTH_SHORT).show();
} else {
try {
if (scoreExists(name, context)) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Overwrite?");
builder.setMessage(
"A score named " + name + " already exists. Would you like to overwrite it?");
builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface confirmDialog, int which) {
try {
saveScore(score, name, true, context);
confirmDialog.dismiss();
nameDialog.dismiss();
} catch (IOException e) {
Logger logger = Logger.getLogger(Storage.class.getName());
logger.log(Level.SEVERE,
"Error saving score \"" + name + "\" with dialog.", e);
Toast.makeText(context,
"Unable to save \"" + name + "\".",
Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog confirmDialog = builder.create();
confirmDialog.show();
} else {
saveScore(score, name, false, context);
nameDialog.dismiss();
}
} catch (IOException e) {
Logger logger = Logger.getLogger(Storage.class.getName());
logger.log(Level.SEVERE,
"Error saving score \"" + name + "\" with dialog.", e);
Toast.makeText(context,
"Unable to save \"" + name + "\".",
Toast.LENGTH_SHORT).show();
}
}
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
/**
* Internal method to turn a user input string into a valid file name.
*/
private static String cleanupName(String name) {
name = name.trim();
name = name.replaceAll("[^A-Za-z0-9_-]", "_");
return name;
}
}