/* * Copyright (C) 2013 jonas.oreland@gmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.runnerup.view; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnMultiChoiceClickListener; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.Button; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ExpandableListView; import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.TextView; import android.widget.Toast; import org.runnerup.R; import org.runnerup.common.util.Constants; import org.runnerup.content.WorkoutFileProvider; import org.runnerup.db.DBHelper; import org.runnerup.export.SyncManager; import org.runnerup.export.SyncManager.Callback; import org.runnerup.export.SyncManager.WorkoutRef; import org.runnerup.export.Synchronizer; import org.runnerup.export.Synchronizer.Status; import org.runnerup.workout.Workout; import org.runnerup.workout.WorkoutSerializer; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @TargetApi(Build.VERSION_CODES.FROYO) public class ManageWorkoutsActivity extends Activity implements Constants { SQLiteDatabase mDB = null; private String PHONE_STRING = "my phone"; public final static String WORKOUT_NAME = ""; final HashSet<SyncManager.WorkoutRef> pendingWorkouts = new HashSet<SyncManager.WorkoutRef>(); final ArrayList<ContentValues> providers = new ArrayList<ContentValues>(); final HashMap<String, ArrayList<SyncManager.WorkoutRef>> workouts = new HashMap<String, ArrayList<SyncManager.WorkoutRef>>(); WorkoutAccountListAdapter adapter = null; final HashSet<String> loadedProviders = new HashSet<String>(); boolean uploading = false; ExpandableListView list = null; CompoundButton currentlySelectedWorkout = null; Button downloadButton = null; Button deleteButton = null; Button shareButton = null; Button createButton = null; SyncManager syncManager = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.manage_workouts); PHONE_STRING = getResources().getString(R.string.my_phone); mDB = DBHelper.getReadableDatabase(this); syncManager = new SyncManager(this); adapter = new WorkoutAccountListAdapter(this); list = (ExpandableListView) findViewById(R.id.expandable_list_view); list.setAdapter(adapter); downloadButton = (Button) findViewById(R.id.download_workout_button); downloadButton.setOnClickListener(downloadButtonClick); deleteButton = (Button) findViewById(R.id.delete_workout_button); deleteButton.setOnClickListener(deleteButtonClick); createButton = (Button) findViewById(R.id.create_workout_button); createButton.setOnClickListener(createButtonClick); shareButton = (Button) findViewById(R.id.share_workout_button); shareButton.setOnClickListener(shareButtonClick); handleButtons(); requery(); listLocal(); Uri data = getIntent().getData(); if (data != null) { getIntent().setData(null); String fileName = getFilename(data); if (fileName == null) fileName = "noname"; try { importData(fileName, data); } catch (Exception e) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.Problem)); builder.setMessage(getString(R.string.Failed_to_import) + fileName); builder.setPositiveButton(getString(R.string.OK_darn), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); ManageWorkoutsActivity.this.finish(); return; } }); builder.show(); return; } return; } // launch home Activity (with FLAG_ACTIVITY_CLEAR_TOP) } private String getFilename(Uri data) { Log.i(getClass().getName(), "scheme: " + data.toString()); String name = null; if (ContentResolver.SCHEME_FILE.contentEquals(data.getScheme())) { name = data.getLastPathSegment(); } else if (ContentResolver.SCHEME_CONTENT.contentEquals(data.getScheme())) { String projection[] = { MediaStore.MediaColumns.DISPLAY_NAME }; Cursor c = getContentResolver().query(data, projection, null, null, null); if (c != null) { c.moveToFirst(); final int fileNameColumnId = c.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); if (fileNameColumnId >= 0) name = c.getString(fileNameColumnId); c.close(); } } return name; } private void importData(final String fileName, final Uri data) throws Exception { final ContentResolver cr = getContentResolver(); InputStream is = cr.openInputStream(data); if (is == null) { throw new Exception("Failed to get input stream"); } boolean convertRestToRecovery = true; // we just test to import, value of this doesnt matter Workout w = WorkoutSerializer.readJSON(new BufferedReader(new InputStreamReader(is)), convertRestToRecovery); is.close(); if (w == null) throw new Exception("Failed to parse content"); final boolean exists = WorkoutSerializer.getFile(this, fileName).exists(); final boolean selected[] = { false }; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.Import_workout) + fileName); builder.setPositiveButton(getString(R.string.Yes), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); String saveName = fileName; try { if (exists && selected[0] == false) { String name = ""; String tmp[] = fileName.split("\\."); if (tmp.length > 0) { for (int i = 0; i < tmp.length - 1; i++) name = name.concat(tmp[i]); } else { name = fileName; } String ending = tmp.length > 0 ? ("." + tmp[tmp.length - 1]) : ""; String newName = fileName; for (int i = 1; i < 25; i++) { newName = name + "-" + i + ending; if (!WorkoutSerializer.getFile(ManageWorkoutsActivity.this, newName).exists()) break; } saveName = newName; Toast.makeText(ManageWorkoutsActivity.this, getString(R.string.Saving_as) + saveName, Toast.LENGTH_SHORT).show(); } saveImport(saveName, cr.openInputStream(data)); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } launchMain(saveName); return; } }); builder.setNegativeButton(getString(R.string.No_way), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Do nothing but close the dialog dialog.dismiss(); finish(); return; } }); if (exists) { String items[] = { getString(R.string.Overwrite_existing) }; builder.setMultiChoiceItems(items, selected, new OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface arg0, int arg1, boolean arg2) { selected[arg1] = arg2; } }); } builder.show(); return; } private void saveImport(String file, InputStream is) throws IOException { File f = WorkoutSerializer.getFile(this, file); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(f)); BufferedInputStream in = new BufferedInputStream(is); byte buf[] = new byte[1024]; while (in.read(buf) > 0) { out.write(buf); } in.close(); out.close(); } protected void launchMain(String fileName) { SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); pref.edit().putString(getResources().getString(R.string.pref_advanced_workout), fileName).commit(); Intent intent = new Intent(this, MainLayout.class); intent.putExtra("mode", StartActivity.TAB_ADVANCED); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); finish(); return; } @Override protected void onResume() { super.onResume(); listLocal(); } private void handleButtons() { if (currentlySelectedWorkout == null) { downloadButton.setEnabled(false); deleteButton.setEnabled(false); shareButton.setEnabled(false); createButton.setEnabled(true); return; } WorkoutRef selected = (WorkoutRef) currentlySelectedWorkout.getTag(); if (PHONE_STRING.contentEquals(selected.synchronizer)) { downloadButton.setEnabled(false); deleteButton.setEnabled(true); shareButton.setEnabled(true); } else { downloadButton.setEnabled(true); deleteButton.setEnabled(false); shareButton.setEnabled(false); } } void listLocal() { ArrayList<SyncManager.WorkoutRef> newlist = new ArrayList<SyncManager.WorkoutRef>(); String[] list = org.runnerup.view.WorkoutListAdapter.load(this); if (list != null) { for (String s : list) { newlist.add(new SyncManager.WorkoutRef(PHONE_STRING, null, s)); } } workouts.remove(PHONE_STRING); workouts.put(PHONE_STRING, newlist); adapter.notifyDataSetChanged(); } @Override public void onDestroy() { super.onDestroy(); DBHelper.closeDB(mDB); syncManager.close(); } void requery() { ContentValues allSynchronizers[] = null; { /** * Accounts/reports */ String sql = "SELECT DISTINCT " + " acc._id, " // 0 + (" acc." + DB.ACCOUNT.NAME + ", ") + (" acc." + DB.ACCOUNT.DESCRIPTION + ", ") + (" acc." + DB.ACCOUNT.AUTH_METHOD + ", ") + (" acc." + DB.ACCOUNT.AUTH_CONFIG + ", ") + (" acc." + DB.ACCOUNT.ENABLED + ", ") + (" acc." + DB.ACCOUNT.FLAGS + " ") + (" FROM " + DB.ACCOUNT.TABLE + " acc "); Cursor c = mDB.rawQuery(sql, null); allSynchronizers = DBHelper.toArray(c); c.close(); } providers.clear(); ContentValues phone = new ContentValues(); phone.put(DB.ACCOUNT.NAME, PHONE_STRING); providers.add(phone); for (ContentValues tmp : allSynchronizers) { Synchronizer synchronizer = syncManager.add(tmp); if (synchronizer != null && synchronizer.checkSupport(Synchronizer.Feature.WORKOUT_LIST)) { providers.add(tmp); workouts.remove(synchronizer.getName()); workouts.put(synchronizer.getName(), new ArrayList<WorkoutRef>()); } } adapter.notifyDataSetChanged(); } @Override public void onBackPressed() { if (uploading == true) { /** * Ignore while uploading */ return; } super.onBackPressed(); } interface Filter<T> { boolean match(T t); } ArrayList<SyncManager.WorkoutRef> filter(List<SyncManager.WorkoutRef> list, Filter<SyncManager.WorkoutRef> f) { ArrayList<SyncManager.WorkoutRef> newlist = new ArrayList<SyncManager.WorkoutRef>(); return filter(list, newlist, f); } ArrayList<SyncManager.WorkoutRef> filter(List<SyncManager.WorkoutRef> list, ArrayList<WorkoutRef> newlist, Filter<SyncManager.WorkoutRef> f) { for (SyncManager.WorkoutRef w : list) { if (f.match(w)) newlist.add(w); } return newlist; } final OnClickListener createButtonClick = new OnClickListener() { @Override public void onClick(View view) { final Intent intent = new Intent(ManageWorkoutsActivity.this, CreateAdvancedWorkout.class); AlertDialog.Builder builder = new AlertDialog.Builder(ManageWorkoutsActivity.this); builder.setTitle(getString(R.string.Create_new_workout)); builder.setMessage(getString(R.string.Set_workout_name)); // Set an EditText view to get user input final EditText input = new EditText(ManageWorkoutsActivity.this); builder.setView(input); builder.setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String value = input.getText().toString(); intent.putExtra(WORKOUT_NAME, value); startActivity(intent); } }); builder.setNegativeButton(getString(R.string.Cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { dialog.dismiss(); } }); builder.show(); } }; final OnClickListener downloadButtonClick = new OnClickListener() { @Override public void onClick(View v) { if (currentlySelectedWorkout == null) return; final WorkoutRef selected = (WorkoutRef) currentlySelectedWorkout.getTag(); ArrayList<WorkoutRef> local = workouts.get(PHONE_STRING); if (contains(local, selected)) { AlertDialog.Builder builder = new AlertDialog.Builder(ManageWorkoutsActivity.this); builder.setTitle(getString(R.string.Downloading_1s_will_overwrite_2_workout_with_same_name, selected.workoutName, PHONE_STRING)); builder.setMessage(getString(R.string.Are_you_sure)); builder.setPositiveButton(getString(R.string.Yes), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); downloadWorkout(selected); return; } }); builder.setNegativeButton(getString(R.string.No), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Do nothing but close the dialog dialog.dismiss(); } }); builder.show(); return; } downloadWorkout(selected); } private void downloadWorkout(WorkoutRef selected) { uploading = true; HashSet<WorkoutRef> list = new HashSet<WorkoutRef>(); list.add((WorkoutRef) currentlySelectedWorkout.getTag()); syncManager.loadWorkouts(list, new SyncManager.Callback() { @Override public void run(String synchronizerName, Synchronizer.Status status) { uploading = false; currentlySelectedWorkout = null; listLocal(); handleButtons(); } }); } private boolean contains(ArrayList<WorkoutRef> local, WorkoutRef selected) { for (WorkoutRef w : local) { if (selected.workoutName.contentEquals(w.workoutName)) { return true; } } return false; } }; private final OnClickListener deleteButtonClick = new OnClickListener() { @Override public void onClick(View v) { if (currentlySelectedWorkout == null) return; final WorkoutRef selected = (WorkoutRef) currentlySelectedWorkout.getTag(); AlertDialog.Builder builder = new AlertDialog.Builder(ManageWorkoutsActivity.this); builder.setTitle(getString(R.string.Delete_workout) + selected.workoutName); builder.setMessage(getString(R.string.Are_you_sure)); builder.setPositiveButton(getString(R.string.Yes), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); deleteWorkout(selected); return; } }); builder.setNegativeButton(getString(R.string.No), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Do nothing but close the dialog dialog.dismiss(); } }); builder.show(); return; } }; private void deleteWorkout(WorkoutRef selected) { File f = WorkoutSerializer.getFile(this, selected.workoutName); //noinspection ResultOfMethodCallIgnored f.delete(); SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); if (selected.workoutName.contentEquals(pref.getString(getResources().getString(R.string.pref_advanced_workout), ""))) { pref.edit().putString(getResources().getString(R.string.pref_advanced_workout), "").commit(); } currentlySelectedWorkout = null; listLocal(); } public final OnCheckedChangeListener onWorkoutChecked = new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton arg0, boolean isChecked) { if (currentlySelectedWorkout != null) { currentlySelectedWorkout.setChecked(false); } if (isChecked) { currentlySelectedWorkout = arg0; } else { currentlySelectedWorkout = null; } handleButtons(); } }; OnClickListener loadWorkoutButtonClick = new OnClickListener() { public void onClick(View v) { uploading = true; syncManager.loadWorkouts(pendingWorkouts, new SyncManager.Callback() { @Override public void run(String synchronizerName, Status status) { uploading = false; listLocal(); } }); } }; private final OnClickListener shareButtonClick = new OnClickListener() { @Override public void onClick(View v) { if (currentlySelectedWorkout == null) return; final Activity context = ManageWorkoutsActivity.this; final WorkoutRef selected = (WorkoutRef) currentlySelectedWorkout.getTag(); final String name = selected.workoutName; final Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.RunnerUp_workout) + name); intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.HinHere_is_a_workout_I_think_you_might_like)); intent.setType(WorkoutFileProvider.MIME); Uri uri = Uri.parse("content://" + WorkoutFileProvider.AUTHORITY + "/" + name); intent.putExtra(Intent.EXTRA_STREAM, uri); context.startActivity(Intent.createChooser(intent, getString(R.string.Share_workout))); } }; @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SyncManager.CONFIGURE_REQUEST) { syncManager.onActivityResult(requestCode, resultCode, data); } requery(); } class WorkoutAccountListAdapter extends BaseExpandableListAdapter { final Context context; WorkoutAccountListAdapter(Context ctx) { context = ctx; } String getProvider(int index) { return providers.get(index).getAsString(DB.ACCOUNT.NAME); } @Override public Object getChild(int groupPosition, int childPosition) { return workouts.get(getProvider(groupPosition)).get(childPosition); } @Override public long getChildId(int groupPosition, int childPosition) { return 0; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view, ViewGroup parent) { if (view == null || !(view instanceof LinearLayout)) { LayoutInflater infalInflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = infalInflater.inflate(R.layout.manage_workouts_list_row, parent, false); } WorkoutRef workout = workouts.get(getProvider(groupPosition)).get(childPosition); RadioButton cb = (RadioButton) view.findViewById(R.id.download_workout_checkbox); TextView tv = (TextView) view.findViewById(R.id.download_workout_name); cb.setTag(workout); cb.setChecked(currentlySelectedWorkout != null && currentlySelectedWorkout.getTag() == workout); cb.setOnCheckedChangeListener(onWorkoutChecked); tv.setText(workout.workoutName); return view; } @Override public int getChildrenCount(int groupPosition) { return workouts.get(getProvider(groupPosition)).size(); } @Override public Object getGroup(int groupPosition) { return providers.get(groupPosition); } @Override public int getGroupCount() { return providers.size(); } @Override public long getGroupId(int groupPosition) { return 0; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { TextView view = null; if (convertView != null && convertView instanceof TextView) view = (TextView) convertView; else view = new TextView(context); view.setGravity(Gravity.CENTER_HORIZONTAL); view.setText(getProvider(groupPosition)); view.setBackgroundResource(android.R.drawable.btn_default_small); view.setTextAppearance(context, R.style.ButtonTextGrey); return view; } @Override public boolean hasStableIds() { return false; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return false; } int saveGroupPosition; @Override public void onGroupExpanded(int groupPosition) { String provider = getProvider(groupPosition); if (PHONE_STRING.contentEquals(provider)) { super.onGroupExpanded(groupPosition); return; } if (loadedProviders.contains(provider)) { super.onGroupExpanded(groupPosition); return; } uploading = true; saveGroupPosition = groupPosition; if (!syncManager.isConfigured(provider)) { syncManager.connect(onSynchronizerConfiguredCallback, provider, false); } else { onSynchronizerConfiguredCallback.run(provider, Synchronizer.Status.OK); } } final Callback onSynchronizerConfiguredCallback = new Callback() { @Override public void run(String synchronizerName, Status status) { Log.i(getClass().getName(), "status: " + status); if (status != Synchronizer.Status.OK) { uploading = false; return; } ArrayList<WorkoutRef> list = workouts.get(synchronizerName); list.clear(); HashSet<String> tmp = new HashSet<String>(); tmp.add(synchronizerName); syncManager.loadWorkoutList(list, onLoadWorkoutListCallback, tmp); } }; private void onGroupExpandedImpl() { super.onGroupExpanded(saveGroupPosition); } private final Callback onLoadWorkoutListCallback = new Callback() { @Override public void run(String synchronizerName, Status status) { uploading = false; if (status == Status.OK) { loadedProviders.add(getProvider(saveGroupPosition)); adapter.notifyDataSetChanged(); onGroupExpandedImpl(); } } }; @Override public void onGroupCollapsed(int groupPosition) { super.onGroupCollapsed(groupPosition); String provider = getProvider(groupPosition); if (currentlySelectedWorkout != null) { WorkoutRef ref = (WorkoutRef) currentlySelectedWorkout.getTag(); if (ref.synchronizer.contentEquals(provider)) { currentlySelectedWorkout.setChecked(false); currentlySelectedWorkout = null; } } } } }