package de.blau.android.prefs;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import de.blau.android.App;
import de.blau.android.R;
import de.blau.android.dialogs.Progress;
import de.blau.android.prefs.AdvancedPrefDatabase.PresetInfo;
import de.blau.android.presets.Preset;
import de.blau.android.presets.PresetIconManager;
import de.blau.android.services.util.StreamUtils;
import de.blau.android.util.SavingHelper;
import de.blau.android.util.Snack;
/** Provides an activity to edit the preset list. Downloads preset data when necessary. */
public class PresetEditorActivity extends URLListEditActivity {
private static final String DEBUG_TAG = "PresetEditorActivity";
private AdvancedPrefDatabase db;
private final static int MENU_RELOAD = 1;
public PresetEditorActivity() {
super();
addAdditionalContextMenuItem(MENU_RELOAD, R.string.preset_update);
}
public static void start(@NonNull Context context) {
Intent intent = new Intent(context, PresetEditorActivity.class);
context.startActivity(intent);
}
public static void startForResult(@NonNull Activity activity,
@NonNull String presetName,
@NonNull String presetUrl,
boolean enable,
int requestCode) {
Intent intent = new Intent(activity, PresetEditorActivity.class);
intent.setAction(ACTION_NEW);
intent.putExtra(EXTRA_NAME, presetName);
intent.putExtra(EXTRA_VALUE, presetUrl);
intent.putExtra(EXTRA_ENABLE, enable);
activity.startActivityForResult(intent, requestCode);
}
@Override
public void onCreate(Bundle savedInstanceState) {
Preferences prefs = new Preferences(this);
if (prefs.lightThemeEnabled()) {
setTheme(R.style.Theme_customLight);
}
db = new AdvancedPrefDatabase(this);
super.onCreate(savedInstanceState);
}
@Override
protected int getAddTextResId() {
return R.string.urldialog_add_preset;
}
@Override
protected void onLoadList(List<ListEditItem> items) {
PresetInfo[] presets = db.getPresets();
for (PresetInfo preset : presets) {
items.add(new ListEditItem(preset.id, preset.name, preset.url, false, preset.active));
}
}
@Override
protected void onItemClicked(ListEditItem item) {
if (item.active && db.getActivePresets().length == 1) { // at least one item needs to be selected
updateAdapter();
Snack.barWarning(this, R.string.toast_min_one_preset);
return;
}
item.active = !item.active;
db.setPresetState(item.id, item.active);
App.resetPresets();
// finish();
}
@Override
protected void onItemCreated(ListEditItem item) {
if (isAddingViaIntent()) {
item.enabled = getIntent().getExtras().getBoolean(EXTRA_ENABLE);
}
db.addPreset(item.id, item.name, item.value, item.enabled);
downloadPresetData(item);
if (!isAddingViaIntent()) {
App.resetPresets();
} else if (item.enabled) { // added a new preset and enabled it: need to rebuild presets
App.resetPresets();
}
}
@Override
protected void onItemEdited(ListEditItem item) {
db.setPresetInfo(item.id, item.name, item.value);
db.removePresetDirectory(item.id);
downloadPresetData(item);
App.resetPresets();
}
@Override
protected void onItemDeleted(ListEditItem item) {
db.deletePreset(item.id);
App.resetPresets();
}
@Override
public void onAdditionalMenuItemClick(int menuItemId, ListEditItem clickedItem) {
switch (menuItemId) {
case MENU_RELOAD:
onItemEdited(clickedItem);
break;
default:
Log.e(DEBUG_TAG, "Unknown menu item "+ menuItemId);
break;
}
}
/**
* Download data (XML, icons) for a certain preset
* @param item the item containing the preset to be downloaded
*/
private void downloadPresetData(final ListEditItem item) {
final File presetDir = db.getPresetDirectory(item.id);
//noinspection ResultOfMethodCallIgnored
presetDir.mkdir();
if (!presetDir.isDirectory()) throw new RuntimeException("Could not create preset directory " + presetDir.getAbsolutePath());
if (item.value.startsWith(Preset.APKPRESET_URLPREFIX)) {
PresetEditorActivity.super.sendResultIfApplicable(item);
return;
}
new AsyncTask<Void, Integer, Integer>() {
private ProgressDialog progress;
private boolean canceled = false;
private final int RESULT_TOTAL_FAILURE = 0;
private final int RESULT_TOTAL_SUCCESS = 1;
private final int RESULT_IMAGE_FAILURE = 2;
private final int RESULT_PRESET_NOT_PARSABLE = 3;
private final int RESULT_DOWNLOAD_CANCELED = 4;
private final int DOWNLOADED_PRESET_ERROR = -1;
private final int DOWNLOADED_PRESET_XML = 0;
private final int DOWNLOADED_PRESET_ZIP = 1;
@Override
protected void onPreExecute() {
// progress = new ProgressDialog(PresetEditorActivity.this);
// progress.setTitle(R.string.progress_title);
// progress.setIndeterminate(true);
// progress.setCancelable(true);
// progress.setMessage(PresetEditorActivity.this.getResources().getString(R.string.progress_download_message));
// progress.setOnCancelListener(new OnCancelListener() {
// @Override
// public void onCancel(DialogInterface dialog) {
// canceled = true;
// }
// });
// progress.show();
// FIXME allow canceling and switch to non-indeterminate mode
Progress.showDialog(PresetEditorActivity.this, Progress.PROGRESS_PRESET);
}
@Override
protected Integer doInBackground(Void... args) {
int downloadResult = download(item.value, Preset.PRESETXML);
if (downloadResult == DOWNLOADED_PRESET_ERROR) {
return RESULT_TOTAL_FAILURE;
} else if (downloadResult == DOWNLOADED_PRESET_ZIP) {
return RESULT_TOTAL_SUCCESS;
} // fall through to further processing
ArrayList<String> urls = Preset.parseForURLs(presetDir);
if (urls == null) {
Log.e(DEBUG_TAG, "Could not parse preset for URLs");
return RESULT_PRESET_NOT_PARSABLE;
}
boolean allImagesSuccessful = true;
int count = 0;
for (String url : urls) {
if (canceled) return RESULT_DOWNLOAD_CANCELED;
count++;
allImagesSuccessful &= (download(url, null) == DOWNLOADED_PRESET_XML);
publishProgress(count, url.length());
}
return allImagesSuccessful? RESULT_TOTAL_SUCCESS : RESULT_IMAGE_FAILURE;
}
/**
* Updates the progress bar
* @param values two integers, first is the current progress, second is the max progress
*/
@Override
protected void onProgressUpdate(Integer... values) {
// progress.setIndeterminate(false);
// progress.setMax(values[1]);
// progress.setProgress(values[0]);
}
/**
*
* @param url
* @param filename A filename where to save the file.
* If null, the URL will be hashed using the PresetIconManager hash function
* and the file will be saved to hashvalue.png
* (where "hashvalue" will be replaced with the URL hash).
* @return code indicating result
*/
private int download(String url, String filename) {
if (filename == null) {
filename = PresetIconManager.hash(url)+".png";
}
InputStream downloadStream = null;
OutputStream fileStream = null;
try {
Log.d(DEBUG_TAG, "Downloading " + url + " to " + presetDir + "/" + filename);
HttpURLConnection conn = (HttpURLConnection)((new URL(url)).openConnection());
conn.setInstanceFollowRedirects(true);
if (conn.getResponseCode() != 200) {
Log.w("PresetDownloader", "Could not download file " + url + " respose code " + conn.getResponseCode());
return DOWNLOADED_PRESET_ERROR;
}
String contentType = conn.getContentType();
boolean zip = contentType != null && contentType.equalsIgnoreCase("application/zip");
final String FILE_NAME_TEMPORARY_ARCHIVE = "temp.zip";
if (zip) {
filename = FILE_NAME_TEMPORARY_ARCHIVE;
}
downloadStream = conn.getInputStream();
fileStream = new FileOutputStream(new File(presetDir, filename));
StreamUtils.copy(downloadStream, fileStream);
if (zip && unpackZip(presetDir.getPath() + "/",filename)) {
//noinspection ResultOfMethodCallIgnored
(new File(presetDir, FILE_NAME_TEMPORARY_ARCHIVE)).delete();
return DOWNLOADED_PRESET_ZIP;
}
return DOWNLOADED_PRESET_XML;
} catch (Exception e) {
Log.e("PresetDownloader", "Could not download file " + url + " " + e.getMessage());
return DOWNLOADED_PRESET_ERROR;
} finally {
SavingHelper.close(downloadStream);
SavingHelper.close(fileStream);
}
}
@Override
protected void onPostExecute(Integer result) {
// progress.dismiss();
Progress.dismissDialog(PresetEditorActivity.this, Progress.PROGRESS_PRESET);
switch (result) {
case RESULT_TOTAL_SUCCESS:
Snack.barInfo(PresetEditorActivity.this, R.string.preset_download_successful);
PresetEditorActivity.super.sendResultIfApplicable(item);
break;
case RESULT_TOTAL_FAILURE:
msgbox(R.string.preset_download_failed);
break;
case RESULT_PRESET_NOT_PARSABLE:
db.removePresetDirectory(item.id);
msgbox(R.string.preset_download_parse_failed);
break;
case RESULT_IMAGE_FAILURE:
msgbox(R.string.preset_download_missing_images);
break;
case RESULT_DOWNLOAD_CANCELED:
break; // do nothing
default:
break;
}
}
/**
* Show a simple message box detailing the download result.
* The activity will end as soon as the box is closed.
* @param msgResID string resource id of message
*/
private void msgbox(int msgResID) {
AlertDialog.Builder box = new AlertDialog.Builder(PresetEditorActivity.this);
box.setMessage(getResources().getString(msgResID));
box.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
PresetEditorActivity.super.sendResultIfApplicable(item);
}
});
box.setPositiveButton(R.string.okay, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
PresetEditorActivity.super.sendResultIfApplicable(item);
}
});
box.show();
}
}.execute();
}
@Override
protected boolean canAutoClose() { // download needs to get done
return false;
}
/**
* Code from http://stackoverflow.com/questions/3382996/how-to-unzip-files-programmatically-in-android
* @param presetDir
* @param zipname
* @return
*/
private boolean unpackZip(String presetDir, String zipname)
{
InputStream is = null;
ZipInputStream zis = null;
try
{
String filename;
is = new FileInputStream(presetDir + zipname);
zis = new ZipInputStream(new BufferedInputStream(is));
ZipEntry ze;
byte[] buffer = new byte[1024];
int count;
while ((ze = zis.getNextEntry()) != null)
{
// zapis do souboru
filename = ze.getName();
Log.d(DEBUG_TAG, "Unzip " + filename);
// Need to create directories if not exists, or
// it will generate an Exception...
if ("".equals(filename)) {
continue;
} else if (filename.indexOf('/') > 0 && !filename.endsWith("/")) {
int slash = filename.lastIndexOf('/');
String path = filename.substring(0, slash);
File fmd = new File(presetDir + path);
if (!fmd.exists()) {
fmd.mkdirs();
}
} else if (ze.isDirectory()) {
File fmd = new File(presetDir + filename);
//noinspection ResultOfMethodCallIgnored
if (!fmd.exists()) {
fmd.mkdirs();
}
continue;
}
FileOutputStream fout = null;
try {
fout= new FileOutputStream(presetDir + filename);
// cteni zipu a zapis
while ((count = zis.read(buffer)) != -1)
{
fout.write(buffer, 0, count);
}
} finally {
SavingHelper.close(fout);
}
zis.closeEntry();
}
} catch(IOException e) {
Log.e(DEBUG_TAG,"Unzipping failed with " + e.getMessage());
e.printStackTrace();
return false;
} finally {
SavingHelper.close(zis);
SavingHelper.close(is);
}
return true;
}
}