package com.sparseboolean.ifexplorer.ui; import gem.kevin.provider.ClipSourceProvider; import gem.kevin.provider.ClipSourceProvider.ClipSourceCallback; import gem.kevin.provider.MutualSourceProvider; import gem.kevin.util.DataUtil; import gem.kevin.util.FileUtil; import gem.kevin.widget.ClipItemAdapter; import gem.kevin.widget.ClipItemAdapter.ClipItemManager; import gem.kevin.widget.ClipItemAdapter.DeleteDelegate; import gem.kevin.widget.ClipItemAdapter.PasteDelegate; import gem.kevin.widget.FileClip; import gem.kevin.widget.MutualAdapter; import gem.kevin.widget.MutualSourceProcessor; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.text.format.Time; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.GridView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.sparseboolean.ifexplorer.IfExplorer.MultiChoiceConsumeOptions; import com.sparseboolean.ifexplorer.IfExplorer.MultiChoiceConsumer; import com.sparseboolean.ifexplorer.FileItem; import com.sparseboolean.ifexplorer.R; public class ClipHistoryAdapter extends ArrayAdapter<ClipItemAdapter> implements MultiChoiceConsumer, ClipSourceCallback, ClipItemManager { public class ClipGenerator implements MutualSourceProcessor { @Override public List<? extends Map<String, ?>> buildMutualAdapterData( HashSet<Object> rawSource) { ArrayList<HashMap<String, Object>> adaptableData = new ArrayList<HashMap<String, Object>>(); for (Object source : rawSource) { if (source instanceof FileItem) { String filePath = ((FileItem) source).getPath(); File file = new File(filePath); if (!file.exists()) { continue; } HashMap<String, Object> item = new HashMap<String, Object>(); item.put(ClipItemAdapter.CLIP_DATA_FIELD_FILE_NAME, file.getName()); item.put(ClipItemAdapter.CLIP_DATA_FIELD_FILE_PATH, file.getPath()); item.put(ClipItemAdapter.CLIP_DATA_FIELD_FILE_ICON, DataUtil.getFileIconResId(file)); adaptableData.add(item); } } return adaptableData; } } public interface ClipHistoryCallback { public String getClipPasteDir(); public void onClipHistoryChanged(ClipHistoryAdapter adapter); } private static class ClipGroupViewHolder { ImageView indicatorImageView; TextView titleTextView; GridView clipListView; View clipContentView; ImageButton executeButton; ImageButton removeButton; View oneshotTaskView; TextView oneshotStatus; ProgressBar oneshotProgress; ImageButton cancelButton; ListView repeatableTasksList; } private static final String TAG = "IF-ClipHistoryAdapter"; public static final String CLIP_COPY = "copy"; public static final String CLIP_CUT = "cut"; public static final String CLIP_DELETE = "delete"; public static final String CLIP_RENAME_SINGLE = "rename_single"; private ClipHistoryCallback mClipHistoryCallback; private Dialog mCurrentClearPrompt = null; private ClipGenerator mClipGenerator = new ClipGenerator(); private ArrayList<ClipItemAdapter> mClipItemAdapters; public ClipHistoryAdapter(Context context, int resource, int textViewResourceId, List<ClipItemAdapter> objects) { super(context, resource, textViewResourceId, objects); mClipItemAdapters = (ArrayList<ClipItemAdapter>) objects; } public void cancelAllClipRecords() { for (int index = 0; index < getCount(); index++) { ClipItemAdapter adapter = getItem(index); if (adapter.isClipExecuting()) { adapter.cancelFileClip(); } } } public void cancelClipRecord(int position) { ClipItemAdapter adapter = getItem(position); if (adapter == null) { return; } adapter.cancelFileClip(); } public void clearClipRecords() { boolean needPrompt = false; for (int index = 0; index < getCount(); index++) { ClipItemAdapter adapter = getItem(index); if (adapter.isClipExecuting()) { needPrompt = true; break; } } if (needPrompt) { confirmClearClipRecords(); } else { removeAllClipRecords(); } } public void confirmAppendClipRecord(final ClipSourceProvider provider, final boolean executeAtOnce) { if (provider == null) { return; } // TBD // Execute without confirm appendClipRecord(provider, executeAtOnce); if (true) { return; } HashSet<Object> sources = provider.getMutualSources(); if (sources != null && sources.size() > 0) { boolean directAppend = true; boolean directDiscard = false; int failedCount = 0; ArrayList<String> noFullPermission = new ArrayList<String>(); int actionType = ClipSourceProvider .getActionTypeFromClipType(provider.getClipType()); boolean doWrite = (actionType == MutualSourceProvider.ACTION_TYPE_WRITE_SOURCE); boolean fullPermission = true; for (Object source : sources) { if (source instanceof FileItem) { if (doWrite) { fullPermission = FileUtil .hasFullReadWritePermission( ((FileItem) source).getPath(), noFullPermission); if (!fullPermission) { directAppend = false; File file = new File(((FileItem) source).getPath()); failedCount += (file.canRead() && file.canWrite()) ? 0 : 1; } } else { fullPermission = FileUtil .hasFullReadPermission( ((FileItem) source).getPath(), noFullPermission); if (!fullPermission) { directAppend = false; File file = new File(((FileItem) source).getPath()); failedCount += file.canRead() ? 0 : 1; } } } } directDiscard = (failedCount == sources.size()); if (directAppend) { appendClipRecord(provider, executeAtOnce); } else if (directDiscard) { Toast.makeText(getContext(), R.string.permission_insufficient, Toast.LENGTH_SHORT).show(); } else { float clipType = provider.getClipType(); // Just show toast for single file clip if (clipType == ClipSourceProvider.CLIP_TYPE_RENAME_SINGLE_SOURCE) { Toast.makeText(getContext(), R.string.cannot_rename_single_prompt, Toast.LENGTH_SHORT).show(); return; } // Show dialog for multiple file clip Resources resources = getContext().getResources(); int size = noFullPermission.size(); String firstNoPermission = (size > 0) ? noFullPermission.get(0) : null; String noPermissions; String pronoun; if (size > 1) { noPermissions = String.format("%s, ...(%s %d %s)", firstNoPermission, resources.getString(R.string.total), size, resources.getString(R.string.items)); pronoun = resources.getString(R.string.theseFiles); } else { noPermissions = firstNoPermission; pronoun = resources.getString(R.string.theFile); } String clipTypeStr = null; String promptMessage = null; if (clipType == ClipSourceProvider.CLIP_TYPE_COPY_SOURCE) { clipTypeStr = resources.getString(R.string.copy); promptMessage = resources .getString(R.string.cannot_copy_prompt, noPermissions, pronoun); } else if (clipType == ClipSourceProvider.CLIP_TYPE_CUT_SOURCE) { clipTypeStr = resources.getString(R.string.cut); promptMessage = resources .getString(R.string.cannot_move_prompt, noPermissions, pronoun); } else if (clipType == ClipSourceProvider.CLIP_TYPE_DELETE_SOURCE) { clipTypeStr = resources.getString(R.string.delete); promptMessage = resources.getString( R.string.cannot_delete_prompt, noPermissions, pronoun); } AlertDialog dialog = new AlertDialog.Builder(getContext()) .setTitle(clipTypeStr) .setPositiveButton(R.string.continue_op, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { appendClipRecord(provider, executeAtOnce); } }).setNegativeButton(R.string.cancel, null) .setMessage(promptMessage).create(); dialog.show(); } } } @Override public boolean consumeChoice(HashSet<Object> choices, MultiChoiceConsumeOptions options) { if (choices == null || options == null) { return false; } float clipType; if (options.consumeDescription.equals(CLIP_CUT)) { clipType = ClipSourceProvider.CLIP_TYPE_CUT_SOURCE; } else if (options.consumeDescription.equals(CLIP_COPY)) { clipType = ClipSourceProvider.CLIP_TYPE_COPY_SOURCE; } else if (options.consumeDescription.equals(CLIP_DELETE)) { clipType = ClipSourceProvider.CLIP_TYPE_DELETE_SOURCE; } else if (options.consumeDescription.equals(CLIP_RENAME_SINGLE)) { clipType = ClipSourceProvider.CLIP_TYPE_RENAME_SINGLE_SOURCE; } else { clipType = ClipSourceProvider.CLIP_TYPE_UNKNOWN; } final ClipSourceProvider provider = new ClipSourceProvider(clipType); HashMap<Object, Float> conflictions = new HashMap<Object, Float>(); provider.setMutualSources(choices, conflictions); if (conflictions.size() == choices.size()) { Log.i(TAG, "No source is available."); Toast.makeText( getContext(), getContext().getResources() .getString(R.string.target_inUse), Toast.LENGTH_SHORT).show(); } else { if (conflictions.isEmpty()) { Log.i(TAG, "successfully to set all mutual source!"); if (clipType == ClipSourceProvider.CLIP_TYPE_DELETE_SOURCE) { String lastFileName = ""; int count = 0; for (Object object : choices) { if (object instanceof FileItem) { count += 1; lastFileName = FileUtil .getFileName(((FileItem) object).getPath()); } } Resources resources = getContext().getResources(); String target = (count > 1) ? String.format("%s...(%d %s)", lastFileName, count, resources.getString(R.string.items)) : lastFileName; String deleteMessage = resources.getString( R.string.delete_prompt, target); confirmDeleteOperation(provider, deleteMessage); } else if (clipType == ClipSourceProvider.CLIP_TYPE_RENAME_SINGLE_SOURCE) { int count = provider.getMutualSourceCount(); final String srcFilePath; if (count > 0) { Object[] sources = provider.getMutualSources() .toArray(); Object srcSource = sources[0]; if (srcSource instanceof FileItem) { srcFilePath = ((FileItem) srcSource).getPath(); Dialog renameDialog = createRenameSingleDialog(srcFilePath); renameDialog.show(); } } } else { confirmAppendClipRecord(provider, false); } } else { String failedFiles = ""; for (Object object : conflictions.keySet()) { if (object instanceof FileItem) { String fileName = FileUtil .getFileName(((FileItem) object).getPath()); failedFiles += (failedFiles.equals("")) ? fileName : (", " + fileName); } } String conflictionMessage = getContext().getResources() .getString(R.string.detect_file_inUse, failedFiles); confirmConfliction(provider, conflictionMessage); } } return true; } public void executeClipRecord(int position) { ClipItemAdapter adapter = getItem(position); if (adapter == null) { return; } HashSet<FileClip> toExecute = adapter.prepare(); adapter.executeFileClip(toExecute); } public ArrayList<ClipItemAdapter> getClipItemAdapters() { return mClipItemAdapters; } @Override public String getClipPasteDir() { if (mClipHistoryCallback != null) { return mClipHistoryCallback.getClipPasteDir(); } else { return null; } } @Override public DeleteDelegate getDeleteDelegate() { if (mClipHistoryCallback != null && (mClipHistoryCallback instanceof DeleteDelegate)) { return ((DeleteDelegate) mClipHistoryCallback); } else { return null; } } @Override public PasteDelegate getPasteDelegate() { if (mClipHistoryCallback != null && (mClipHistoryCallback instanceof PasteDelegate)) { return ((PasteDelegate) mClipHistoryCallback); } else { return null; } } @Override public View getView(int position, View convertView, ViewGroup parent) { final ClipGroupViewHolder viewHolder; if (convertView == null) { convertView = LayoutInflater.from(getContext()).inflate( R.layout.clip_group, null); viewHolder = new ClipGroupViewHolder(); viewHolder.indicatorImageView = (ImageView) convertView .findViewById(R.id.clip_indicator); viewHolder.titleTextView = (TextView) convertView .findViewById(R.id.clip_title); viewHolder.oneshotTaskView = convertView .findViewById(R.id.clip_oneshot_task); viewHolder.clipContentView = convertView .findViewById(R.id.clip_content); viewHolder.oneshotStatus = (TextView) convertView .findViewById(R.id.oneshot_status); viewHolder.oneshotProgress = (ProgressBar) convertView .findViewById(R.id.oneshot_progress); viewHolder.clipListView = (GridView) convertView .findViewById(R.id.clip_list); viewHolder.executeButton = (ImageButton) convertView .findViewById(R.id.executeClip); viewHolder.removeButton = (ImageButton) convertView .findViewById(R.id.removeClip); viewHolder.cancelButton = (ImageButton) convertView .findViewById(R.id.cancelTask); viewHolder.repeatableTasksList = (ListView) convertView .findViewById(R.id.repeatTasks); convertView.setTag(viewHolder); } else { viewHolder = (ClipGroupViewHolder) convertView.getTag(); } final ClipItemAdapter clipItemAdapter = getItem(position); clipItemAdapter.bindInterativeClipUi(viewHolder.clipContentView, viewHolder.oneshotTaskView, viewHolder.oneshotStatus, viewHolder.oneshotProgress, viewHolder.repeatableTasksList); viewHolder.clipListView.setAdapter(clipItemAdapter); int displayItemNum = clipItemAdapter.getDisplayItemNum(); viewHolder.clipListView .setNumColumns(displayItemNum > 0 ? displayItemNum : 6); viewHolder.clipListView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_MOVE) { return true; } return false; } }); final int pos = position; viewHolder.executeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { executeClipRecord(pos); } }); viewHolder.removeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { removeClipRecord(pos); } }); viewHolder.cancelButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { cancelClipRecord(pos); } }); Resources resouces = getContext().getResources(); float clipType = clipItemAdapter.getClipType(); String clipTypeStr = null; int clipTypeIcon = -1; if (clipType == ClipSourceProvider.CLIP_TYPE_COPY_SOURCE) { clipTypeStr = resouces.getString(R.string._copyItem); clipTypeIcon = R.drawable.copy_indicator; } else if (clipType == ClipSourceProvider.CLIP_TYPE_CUT_SOURCE) { clipTypeStr = resouces.getString(R.string._cutItem); clipTypeIcon = R.drawable.cut_indicator; } else if (clipType == ClipSourceProvider.CLIP_TYPE_DELETE_SOURCE) { clipTypeStr = resouces.getString(R.string._deleteItem); clipTypeIcon = R.drawable.delete_indicator; } viewHolder.indicatorImageView.setImageResource(clipTypeIcon); String clipTitle = String.format("%d%s %s", clipItemAdapter.getCount(), clipTypeStr, clipItemAdapter.getId()); viewHolder.titleTextView.setText(clipTitle); return convertView; } public void onClipHistoryChanged() { if (mCurrentClearPrompt != null && getCount() == 0) { mCurrentClearPrompt.dismiss(); } if (mClipHistoryCallback != null) { mClipHistoryCallback.onClipHistoryChanged(this); } } @Override public void onClipItemFinished(ClipItemAdapter adapter) { if (adapter != null) { this.remove(adapter); this.onClipHistoryChanged(); } } @Override public void onClipItemRemoved(ClipItemAdapter adapter) { if (adapter != null) { this.remove(adapter); this.onClipHistoryChanged(); } } @Override public void onClipSourceChanged(ClipSourceProvider provider) { if (provider == null) { return; } if (provider.getMutualSourceCount() == 0) { HashSet<MutualAdapter> adapters = provider.getBoundMutualAdapters(); if (adapters.size() > 0) { for (MutualAdapter adapter : adapters) { if (adapter instanceof ClipItemAdapter) { this.remove((ClipItemAdapter) adapter); this.onClipHistoryChanged(); } } } } } public void setClipHistoryCallback(ClipHistoryCallback callback) { mClipHistoryCallback = callback; } private void appendClipRecord(ClipSourceProvider provider, boolean executeAtOnce) { if (provider == null) { return; } Time time = new Time(); time.setToNow(); String clipItemId = String.format("%02d:%02d:%02d", time.hour, time.minute, time.second); ClipItemAdapter adapter = new ClipItemAdapter(getContext(), null, R.layout.clip_item, null, null); adapter.setId(clipItemId); adapter.setClipItemManager(this); adapter.setDisplayItemNum(6); adapter.setMutualSourceProcessor(mClipGenerator); // register this as the callback for the given source provider provider.registerClipSourceCallbacks(this); // bind adapter to the given source provider provider.bindMutualAdapter(adapter); this.add(adapter); this.onClipHistoryChanged(); if (executeAtOnce) { HashSet<FileClip> toExecute = adapter.prepare(); adapter.executeFileClip(toExecute); } } private void confirmClearClipRecords() { AlertDialog dialog = new AlertDialog.Builder(getContext()) .setTitle(R.string.clear) .setPositiveButton(R.string.execute_background, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ClipHistoryAdapter.this.removeAllClipRecords(); } }) .setNegativeButton(R.string.cancelAndClear, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ClipHistoryAdapter.this.cancelAllClipRecords(); ClipHistoryAdapter.this.removeAllClipRecords(); } }).setMessage(R.string.confirm_clear_clips).create(); mCurrentClearPrompt = dialog; dialog.show(); } private void confirmConfliction(final ClipSourceProvider provider, String conflictionMessage) { AlertDialog dialog = new AlertDialog.Builder(getContext()) .setTitle(R.string.conflict) .setPositiveButton(R.string.skipAndContinue, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { confirmAppendClipRecord(provider, false); } }).setNegativeButton(R.string.cancel, null) .setMessage(conflictionMessage).create(); dialog.show(); } private void confirmDeleteOperation(final ClipSourceProvider provider, String deleteMessage) { AlertDialog dialog = new AlertDialog.Builder(getContext()) .setTitle(R.string.delete) .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { confirmAppendClipRecord(provider, true); } }).setNegativeButton(R.string.cancel, null) .setMessage(deleteMessage).create(); dialog.show(); } private Dialog createRenameSingleDialog(final String srcFilePath) { View contentRootView = LayoutInflater.from(getContext()).inflate( R.layout.rename_dialog, null); final EditText nameEdit = (EditText) contentRootView .findViewById(R.id.name_edit); final String oldName = FileUtil.getFileName(srcFilePath); nameEdit.setText(oldName); nameEdit.selectAll(); AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(R.string.rename) .setView(contentRootView) .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Context context = getContext(); Resources resources = context.getResources(); String newName = nameEdit.getText().toString(); if (oldName.equals(newName)) { int ret = FileUtil.ERROR_FILE_EXISTS; String errStr = FileUtil .getDefaultFileOpErrStr(resources, ret); Toast.makeText( context, resources.getString( R.string.rename_failed, oldName, errStr), Toast.LENGTH_LONG).show(); dialog.dismiss(); return; } String targetFilePath; int pos = srcFilePath.lastIndexOf(oldName); if (pos > -1) { targetFilePath = srcFilePath.substring(0, pos) + newName + srcFilePath.substring(pos + oldName.length(), srcFilePath.length()); } else { targetFilePath = srcFilePath; } dialog.dismiss(); ClipItemAdapter.renameSingleFile(context, srcFilePath, targetFilePath); } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); return builder.create(); } private void removeAllClipRecords() { clear(); onClipHistoryChanged(); } private void removeClipRecord(int pos) { ClipItemAdapter adapter = getItem(pos); if (adapter != null) { remove(adapter); onClipHistoryChanged(); } } }