package org.openintents.filemanager.util; import android.app.AlertDialog; import android.content.*; import android.content.DialogInterface.OnClickListener; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.app.DialogFragment; import android.view.*; import android.widget.CheckBox; import android.widget.Toast; import java.io.File; import java.util.ArrayList; import java.util.List; import org.openintents.filemanager.*; import org.openintents.filemanager.bookmarks.BookmarksProvider; import org.openintents.filemanager.compatibility.ActionbarRefreshHelper; import org.openintents.filemanager.dialogs.*; import org.openintents.filemanager.files.FileHolder; import org.openintents.filemanager.lists.FileListFragment; import org.openintents.filemanager.lists.SimpleFileListFragment; import org.openintents.intents.FileManagerIntents; /** * Utility class that helps centralize multiple and single selection actions for all API levels. * @author George Venios */ public abstract class MenuUtils { /** * Fill <code>m</code> with multiselection actions, using <code>mi</code>. * @param m The {@link Menu} to fill. * @param menuResource The resource id of the menu to load. * @param mi The {@link MenuInflater} to use. This is a parameter since the ActionMode provides a context-based inflater and we could possibly lose functionality with a common MenuInflater. */ static public void fillMultiselectionMenu(Menu m, int menuResource, MenuInflater mi) { mi.inflate(menuResource, m); } /** * Fill the passed Menu attribute with the proper single selection actions for the passed {@link FileHolder} object. * @param m The {@link Menu} to fill. * @param menuResource The resource id of the menu to load. * @param mi The {@link MenuInflater} to use. This is a parameter since the ActionMode provides a context-based inflater and we could possibly lose functionality with a common MenuInflater. */ static public void fillContextMenu(FileHolder item, Menu m, int menuResource, MenuInflater mi, Context context){ // Inflate all actions mi.inflate(menuResource, m); if(m instanceof ContextMenu){ ((ContextMenu) m).setHeaderTitle(item.getName()); ((ContextMenu) m).setHeaderIcon(item.getIcon()); } // Get the selected file File file = item.getFile(); // If selected item is a directory if (file.isDirectory()) { m.removeItem(R.id.menu_send); } // If selected item is a zip archive if (!FileUtils.checkIfZipArchive(file)) { m.removeItem(R.id.menu_extract); } else { m.removeItem(R.id.menu_compress); } // Add CATEGORY_SELECTED_ALTERNATIVE intent options Uri data = Uri.fromFile(file); Intent intent = new Intent(null, data); intent.setDataAndType(data, item.getMimeType()); intent.addCategory(Intent.CATEGORY_SELECTED_ALTERNATIVE); if (item.getMimeType() != null) { // Add additional options for the MIME type of the selected file. m.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, new ComponentName(context, FileManagerActivity.class), null, intent, 0, null); } } /** * Central point where we handle actions for multiple selection, for every API level. * @param mItem The selected menu option/action. * @param fItems The data to act upon. */ public static boolean handleMultipleSelectionAction(final FileListFragment navigator, MenuItem mItem, List<FileHolder> fItems, Context context) { DialogFragment dialog; Bundle args; switch (mItem.getItemId()) { case R.id.menu_send: Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); ArrayList<Uri> uris = new ArrayList<>(); intent.setType("text/plain"); for(FileHolder fh : fItems) uris.add(FileUtils.getUri(fh.getFile())); intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); context.startActivity(Intent.createChooser(intent, context.getString(R.string.send_chooser_title))); break; case R.id.menu_delete: dialog = new MultiDeleteDialog(); dialog.setTargetFragment(navigator, 0); args = new Bundle(); args.putParcelableArrayList(FileManagerIntents.EXTRA_DIALOG_FILE_HOLDER, new ArrayList<Parcelable>(fItems)); dialog.setArguments(args); dialog.show(navigator.getFragmentManager(), MultiDeleteDialog.class.getName()); break; case R.id.menu_move: ((FileManagerApplication) navigator.getActivity().getApplication()).getCopyHelper().cut(fItems); navigator.updateClipboardInfo(); // Refresh options menu if(VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) ActionbarRefreshHelper.activity_invalidateOptionsMenu(navigator.getActivity()); break; case R.id.menu_copy: ((FileManagerApplication) navigator.getActivity().getApplication()).getCopyHelper().copy(fItems); navigator.updateClipboardInfo(); // Refresh options menu if(VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) ActionbarRefreshHelper.activity_invalidateOptionsMenu(navigator.getActivity()); break; case R.id.menu_compress: dialog = new MultiCompressDialog(); dialog.setTargetFragment(navigator, 0); args = new Bundle(); args.putParcelableArrayList(FileManagerIntents.EXTRA_DIALOG_FILE_HOLDER, new ArrayList<Parcelable>(fItems)); dialog.setArguments(args); dialog.show(navigator.getFragmentManager(), MultiCompressDialog.class.getName()); break; default: return false; } return true; } /** * Central point where we handle actions for single selection, for every API level. * @param mItem The selected menu option/action. * @param fItem The data to act upon. */ public static boolean handleSingleSelectionAction(final SimpleFileListFragment navigator, MenuItem mItem, FileHolder fItem, Context context){ DialogFragment dialog; Bundle args; switch (mItem.getItemId()) { case R.id.menu_open: navigator.openInformingPathBar(fItem); return true; case R.id.menu_create_shortcut: createShortcut(fItem, context); return true; case R.id.menu_move: ((FileManagerApplication) navigator.getActivity().getApplication()).getCopyHelper().cut(fItem); navigator.updateClipboardInfo(); // Refresh options menu if(VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) ActionbarRefreshHelper.activity_invalidateOptionsMenu(navigator.getActivity()); return true; case R.id.menu_copy: ((FileManagerApplication) navigator.getActivity().getApplication()).getCopyHelper().copy(fItem); navigator.updateClipboardInfo(); // Refresh options menu if(VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) ActionbarRefreshHelper.activity_invalidateOptionsMenu(navigator.getActivity()); return true; case R.id.menu_delete: dialog = new SingleDeleteDialog(); dialog.setTargetFragment(navigator, 0); args = new Bundle(); args.putParcelable(FileManagerIntents.EXTRA_DIALOG_FILE_HOLDER, fItem); dialog.setArguments(args); dialog.show(navigator.getFragmentManager(), SingleDeleteDialog.class.getName()); return true; case R.id.menu_rename: dialog = new RenameDialog(); dialog.setTargetFragment(navigator, 0); args = new Bundle(); args.putParcelable(FileManagerIntents.EXTRA_DIALOG_FILE_HOLDER, fItem); dialog.setArguments(args); dialog.show(navigator.getFragmentManager(), RenameDialog.class.getName()); return true; case R.id.menu_send: sendFile(fItem, context); return true; case R.id.menu_details: dialog = new DetailsDialog(); dialog.setTargetFragment(navigator, 0); args = new Bundle(); args.putParcelable(FileManagerIntents.EXTRA_DIALOG_FILE_HOLDER, fItem); dialog.setArguments(args); dialog.show(navigator.getFragmentManager(), DetailsDialog.class.getName()); return true; case R.id.menu_compress: dialog = new SingleCompressDialog(); dialog.setTargetFragment(navigator, 0); args = new Bundle(); args.putParcelable(FileManagerIntents.EXTRA_DIALOG_FILE_HOLDER, fItem); dialog.setArguments(args); dialog.show(navigator.getFragmentManager(), SingleCompressDialog.class.getName()); return true; case R.id.menu_extract: File dest = new File(fItem.getFile().getParentFile(), FileUtils.getNameWithoutExtension(fItem.getFile())); dest.mkdirs(); // Changed from the previous behavior. // We just extract on the current directory. If the user needs to put it in another dir, // he/she can copy/cut the file with the new, equally easy to use way. new ExtractManager(context) .setOnExtractFinishedListener(new ExtractManager.OnExtractFinishedListener() { @Override public void extractFinished() { navigator.refresh(); } }) .extract(fItem.getFile(), dest.getAbsolutePath()); return true; case R.id.menu_bookmark: String path = fItem.getFile().getAbsolutePath(); Cursor query = context.getContentResolver().query(BookmarksProvider.CONTENT_URI, new String[]{BookmarksProvider._ID}, BookmarksProvider.PATH + "=?", new String[]{path}, null); if(!query.moveToFirst()){ ContentValues values = new ContentValues(); values.put(BookmarksProvider.NAME, fItem.getName()); values.put(BookmarksProvider.PATH, path); context.getContentResolver().insert(BookmarksProvider.CONTENT_URI, values); Toast.makeText(context, R.string.bookmark_added, Toast.LENGTH_SHORT).show(); } else{ Toast.makeText(context, R.string.bookmark_already_exists, Toast.LENGTH_SHORT).show(); } query.close(); return true; case R.id.menu_more: if (!PreferenceActivity.getShowAllWarning(context)){ showMoreCommandsDialog(fItem, context); return true; } showWarningDialog(fItem, context); return true; } return false; } /** * Creates a home screen shortcut. * @param fileholder The {@link File} to create the shortcut to. */ static private void createShortcut(FileHolder fileholder, Context context) { Intent shortcutintent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); shortcutintent.putExtra("duplicate", false); shortcutintent.putExtra(Intent.EXTRA_SHORTCUT_NAME, fileholder.getName()); Parcelable icon = Intent.ShortcutIconResource.fromContext(context.getApplicationContext(), R.drawable.ic_launcher_shortcut); shortcutintent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon); // Intent to load Intent itl = new Intent(Intent.ACTION_VIEW); if(fileholder.getFile().isDirectory()) itl.setData(Uri.fromFile(fileholder.getFile())); else itl.setDataAndType(Uri.fromFile(fileholder.getFile()), fileholder.getMimeType()); itl.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); shortcutintent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, itl); context.sendBroadcast(shortcutintent); } /** * Creates an activity picker to send a file. * @param fHolder A {@link FileHolder} containing the {@link File} to send. * @param context {@link Context} in which to create the picker. */ private static void sendFile(FileHolder fHolder, Context context) { String filename = fHolder.getName(); Intent i = new Intent(); i.setAction(Intent.ACTION_SEND); i.setType(fHolder.getMimeType()); i.putExtra(Intent.EXTRA_SUBJECT, filename); i.putExtra(Intent.EXTRA_STREAM, FileUtils.getUri(fHolder.getFile())); i.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + FileManagerProvider.AUTHORITY + fHolder.getFile().getAbsolutePath())); i = Intent.createChooser(i, context.getString(R.string.menu_send)); try { context.startActivity(i); } catch (ActivityNotFoundException e) { Toast.makeText(context, R.string.send_not_available, Toast.LENGTH_SHORT).show(); } } /** * Call this to show the dialog that informs the user about possibly broken options in the "More" dialog. * @param context The context that will be used for this dialog. * @param holder A {@link FileHolder} containing the file to act upon. */ private static void showWarningDialog(final FileHolder holder, final Context context) { LayoutInflater li = LayoutInflater.from(context); View warningView = li.inflate(R.layout.dialog_warning, null); final CheckBox showWarningAgain = (CheckBox)warningView.findViewById(R.id.showagaincheckbox); showWarningAgain.setChecked(PreferenceActivity.getShowAllWarning(context)); new AlertDialog.Builder(context).setView(warningView).setTitle(context.getString(R.string.title_warning_some_may_not_work)) .setMessage(context.getString(R.string.warning_some_may_not_work)) .setIcon(android.R.drawable.ic_dialog_alert).setPositiveButton( android.R.string.ok, new OnClickListener() { public void onClick(DialogInterface dialog, int which) { PreferenceActivity.setShowAllWarning(context, showWarningAgain.isChecked()); showMoreCommandsDialog(holder, context); } }).create() .show(); } /** * Call this to show the "More" dialog for the passed {@link FileHolder}. * @param context Always useful, isn't it? */ private static void showMoreCommandsDialog(FileHolder holder, final Context context) { final Uri data = Uri.fromFile(holder.getFile()); final Intent intent = new Intent(); intent.setDataAndType(data, holder.getMimeType()); if (holder.getMimeType() != null) { // Add additional options for the MIME type of the selected file. PackageManager pm = context.getPackageManager(); final List<ResolveInfo> lri = pm.queryIntentActivityOptions( new ComponentName(context, FileManagerActivity.class), null, intent, 0); final int N = lri != null ? lri.size() : 0; // Create name list for menu item. final List<CharSequence> items = new ArrayList<>(); /* Some of the options don't go to the list hence we have to remove them * to keep the lri correspond with the menu items. In the addition, we have * to remove them after the first iteration, otherwise the iteration breaks. */ List<ResolveInfo> toRemove = new ArrayList<>(); for (int i = 0; i < N; i++) { final ResolveInfo ri = lri.get(i); Intent rintent = new Intent(intent); rintent.setComponent( new ComponentName( ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name)); ActivityInfo info = rintent.resolveActivityInfo(pm, 0); String permission = info.permission; if(info.exported && (permission == null || context.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED)) items.add(ri.loadLabel(pm)); else toRemove.add(ri); } for(ResolveInfo ri : toRemove){ lri.remove(ri); } new AlertDialog.Builder(context) .setTitle(holder.getName()) .setIcon(holder.getIcon()) .setItems(items.toArray(new CharSequence[0]), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { final ResolveInfo ri = lri.get(item); Intent rintent = new Intent(intent) .setComponent(new ComponentName( ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name)); context.startActivity(rintent); } }).create() .show(); } } }