From 39cea3faa1dc25acca82c659aef9f344c33b412f Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Mon, 21 Jan 2013 11:42:33 +0100 Subject: [PATCH] Allow picking of directories. This is used e.g. by k-9 to select an attachment storage path. There are a number of intent filter conventions for picking directories. In k-9's code I found the following: - action org.openintents.action.PICK_DIRECTORY, scheme file - action com.estrongs.action.PICK_DIRECTORY, scheme file - action Intent.ACTION_PICK, scheme folder - action com.androidworkz.action.PICK_DIRECTORY, scheme file Implemented is the third variant, as it's the most generic way to describe the intention. Change-Id: I8752fe0db923a9ca169cc09eeee6a13bb5236626 --- AndroidManifest.xml | 8 ++ res/values-de/strings.xml | 2 + res/values/strings.xml | 2 + .../filemanager/activities/PickerActivity.java | 140 +++++++++++++++++++-- .../preferences/DisplayRestrictions.java | 4 + .../filemanager/ui/widgets/NavigationView.java | 29 ++++- .../cyanogenmod/filemanager/util/FileHelper.java | 9 ++ .../filemanager/util/MimeTypeHelper.java | 5 + 8 files changed, 186 insertions(+), 13 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b7c37b4..618b4a6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -115,6 +115,14 @@ + + + + + + + + Nein Alle Überschreiben + Auswählen ]]> @@ -209,6 +210,7 @@ Datei wählen + Verzeichnis wählen Editor diff --git a/res/values/strings.xml b/res/values/strings.xml index aa25f55..c14239b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -44,6 +44,7 @@ No All Overwrite + Select ]]> @@ -362,6 +363,7 @@ @string/app_name Pick a file + Pick a directory Editor diff --git a/src/com/cyanogenmod/filemanager/activities/PickerActivity.java b/src/com/cyanogenmod/filemanager/activities/PickerActivity.java index 298263f..3c8d179 100644 --- a/src/com/cyanogenmod/filemanager/activities/PickerActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/PickerActivity.java @@ -54,6 +54,7 @@ import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; import com.cyanogenmod.filemanager.ui.widgets.Breadcrumb; import com.cyanogenmod.filemanager.ui.widgets.ButtonItem; import com.cyanogenmod.filemanager.ui.widgets.NavigationView; +import com.cyanogenmod.filemanager.ui.widgets.NavigationView.OnDirectoryChangedListener; import com.cyanogenmod.filemanager.ui.widgets.NavigationView.OnFilePickedListener; import com.cyanogenmod.filemanager.util.DialogHelper; import com.cyanogenmod.filemanager.util.ExceptionUtil; @@ -72,7 +73,7 @@ import java.util.Map; * application. */ public class PickerActivity extends Activity - implements OnCancelListener, OnDismissListener, OnFilePickedListener { + implements OnCancelListener, OnDismissListener, OnFilePickedListener, OnDirectoryChangedListener { private static final String TAG = "PickerActivity"; //$NON-NLS-1$ @@ -105,7 +106,13 @@ public class PickerActivity extends Activity // Extra data for Gallery CROP action private static final String EXTRA_CROP = "crop"; //$NON-NLS-1$ - private FileSystemObject mFso; // The picked item + // Scheme for file and directory picking + private static final String FILE_URI_SCHEME = "file"; //$NON-NLS-1$ + private static final String FOLDER_URI_SCHEME = "folder"; //$NON-NLS-1$ + private static final String DIRECTORY_URI_SCHEME = "directory"; //$NON-NLS-1$ + + FileSystemObject mFso; // The picked item + FileSystemObject mCurrentDirectory; private AlertDialog mDialog; private Handler mHandler; /** @@ -169,11 +176,19 @@ public class PickerActivity extends Activity * proposed file */ private void init() { - // Check that call has a valid request (GET_CONTENT a and mime type) - String action = getIntent().getAction(); - - Log.d(TAG, "PickerActivity. action: " + String.valueOf(action)); //$NON-NLS-1$ - if (action.compareTo(Intent.ACTION_GET_CONTENT.toString()) != 0) { + final boolean pickingDirectory; + final Intent intent = getIntent(); + + if (isFilePickIntent(intent)) { + // ok + Log.d(TAG, "PickerActivity: got file pick intent: " + String.valueOf(intent)); //$NON-NLS-1$ + pickingDirectory = false; + } else if (isDirectoryPickIntent(getIntent())) { + // ok + Log.d(TAG, "PickerActivity: got folder pick intent: " + String.valueOf(intent)); //$NON-NLS-1$ + pickingDirectory = true; + } else { + Log.d(TAG, "PickerActivity got unrecognized intent: " + String.valueOf(intent)); //$NON-NLS-1$ setResult(Activity.RESULT_CANCELED); finish(); return; @@ -183,7 +198,6 @@ public class PickerActivity extends Activity Map restrictions = new HashMap(); //- Mime/Type restriction String mimeType = getIntent().getType(); - Log.d(TAG, "PickerActivity. type: " + String.valueOf(mimeType)); //$NON-NLS-1$ if (mimeType != null) { if (!MimeTypeHelper.isMimeTypeKnown(this, mimeType)) { Log.i(TAG, @@ -212,6 +226,9 @@ public class PickerActivity extends Activity Boolean.valueOf(localOnly)); } } + if (pickingDirectory) { + restrictions.put(DisplayRestrictions.DIRECTORY_ONLY_RESTRICTION, Boolean.TRUE); + } // Create or use the console if (!initializeConsole()) { @@ -243,6 +260,7 @@ public class PickerActivity extends Activity (NavigationView)this.mRootView.findViewById(R.id.navigation_view); this.mNavigationView.setRestrictions(restrictions); this.mNavigationView.setOnFilePickedListener(this); + this.mNavigationView.setOnDirectoryChangedListener(this); this.mNavigationView.setBreadcrumb(breadcrumb); // Apply the current theme @@ -250,9 +268,12 @@ public class PickerActivity extends Activity // Create the dialog this.mDialog = DialogHelper.createDialog( - this, R.drawable.ic_launcher, R.string.picker_title, this.mRootView); + this, R.drawable.ic_launcher, + pickingDirectory ? R.string.directory_picker_title : R.string.picker_title, + this.mRootView); + this.mDialog.setButton( - DialogInterface.BUTTON_NEUTRAL, + DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override @@ -260,6 +281,18 @@ public class PickerActivity extends Activity dlg.cancel(); } }); + if (pickingDirectory) { + this.mDialog.setButton( + DialogInterface.BUTTON_POSITIVE, + getString(R.string.select), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dlg, int which) { + PickerActivity.this.mFso = PickerActivity.this.mCurrentDirectory; + dlg.dismiss(); + } + }); + } this.mDialog.setCancelable(true); this.mDialog.setOnCancelListener(this); this.mDialog.setOnDismissListener(this); @@ -269,12 +302,21 @@ public class PickerActivity extends Activity ButtonItem fs = (ButtonItem)this.mRootView.findViewById(R.id.ab_filesystem_info); fs.setContentDescription(getString(R.string.actionbar_button_storage_cd)); + final File initialDir = getInitialDirectoryFromIntent(getIntent()); + final String rootDirectory; + + if (initialDir != null) { + rootDirectory = initialDir.getAbsolutePath(); + } else { + rootDirectory = FileHelper.ROOT_DIRECTORY; + } + this.mHandler = new Handler(); this.mHandler.post(new Runnable() { @Override public void run() { // Navigate to. The navigation view will redirect to the appropriate directory - PickerActivity.this.mNavigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY); + PickerActivity.this.mNavigationView.changeCurrentDir(rootDirectory); } }); @@ -374,7 +416,7 @@ public class PickerActivity extends Activity // Return the picked file, as expected (this activity should fill the intent data // and return RESULT_OK result) Intent result = new Intent(); - result.setData(Uri.fromFile(src)); + result.setData(getResultUriForFileFromIntent(src, getIntent())); setResult(Activity.RESULT_OK, result); finish(); @@ -383,6 +425,72 @@ public class PickerActivity extends Activity } } + private boolean isFilePickIntent(Intent intent) { + final String action = intent.getAction(); + + if (Intent.ACTION_GET_CONTENT.equals(action)) { + return true; + } + if (Intent.ACTION_PICK.equals(action)) { + final Uri data = intent.getData(); + if (data != null && FILE_URI_SCHEME.equals(data.getScheme())) { + return true; + } + } + + return false; + } + + private boolean isDirectoryPickIntent(Intent intent) { + if (Intent.ACTION_PICK.equals(intent.getAction()) && intent.getData() != null) { + String scheme = intent.getData().getScheme(); + if (FOLDER_URI_SCHEME.equals(scheme) || DIRECTORY_URI_SCHEME.equals(scheme)) { + return true; + } + } + + return false; + } + + private File getInitialDirectoryFromIntent(Intent intent) { + if (!Intent.ACTION_PICK.equals(intent.getAction())) { + return null; + } + + final Uri data = intent.getData(); + if (data == null) { + return null; + } + + final String path = data.getPath(); + if (path == null) { + return null; + } + + final File file = new File(path); + if (!file.exists() || !file.isAbsolute()) { + return null; + } + + if (file.isDirectory()) { + return file; + } + return file.getParentFile(); + } + + private Uri getResultUriForFileFromIntent(File src, Intent intent) { + Uri result = Uri.fromFile(src); + + if (Intent.ACTION_PICK.equals(intent.getAction()) && intent.getData() != null) { + String scheme = intent.getData().getScheme(); + if (scheme != null) { + result = result.buildUpon().scheme(scheme).build(); + } + } + + return result; + } + /** * {@inheritDoc} */ @@ -401,6 +509,14 @@ public class PickerActivity extends Activity } /** + * {@inheritDoc} + */ + @Override + public void onDirectoryChanged(FileSystemObject item) { + this.mCurrentDirectory = item; + } + + /** * Method invoked when an action item is clicked. * * @param view The button pushed diff --git a/src/com/cyanogenmod/filemanager/preferences/DisplayRestrictions.java b/src/com/cyanogenmod/filemanager/preferences/DisplayRestrictions.java index 2525ade..7b1e9cf 100644 --- a/src/com/cyanogenmod/filemanager/preferences/DisplayRestrictions.java +++ b/src/com/cyanogenmod/filemanager/preferences/DisplayRestrictions.java @@ -33,6 +33,10 @@ public enum DisplayRestrictions { */ SIZE_RESTRICTION, /** + * Restriction for display only directories + */ + DIRECTORY_ONLY_RESTRICTION, + /** * Restriction for display only files from the local file system. Avoid remote files. */ LOCAL_FILESYSTEM_ONLY_RESTRICTION diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java index 4a71e5e..9695760 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java +++ b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java @@ -67,6 +67,7 @@ import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult; import com.cyanogenmod.filemanager.util.FileHelper; import com.cyanogenmod.filemanager.util.StorageHelper; +import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -124,6 +125,18 @@ public class NavigationView extends RelativeLayout implements } /** + * An interface to communicate a change of the current directory + */ + public interface OnDirectoryChangedListener { + /** + * Method invoked when the current directory changes + * + * @param item The newly active directory + */ + void onDirectoryChanged(FileSystemObject item); + } + + /** * The navigation view mode * @hide */ @@ -205,6 +218,7 @@ public class NavigationView extends RelativeLayout implements private OnNavigationSelectionChangedListener mOnNavigationSelectionChangedListener; private OnNavigationRequestMenuListener mOnNavigationRequestMenuListener; private OnFilePickedListener mOnFilePickedListener; + private OnDirectoryChangedListener mOnDirectoryChangedListener; private boolean mChRooted; @@ -479,6 +493,16 @@ public class NavigationView extends RelativeLayout implements } /** + * Method that sets the listener for directory changes + * + * @param onDirectoryChangedListener The listener reference + */ + public void setOnDirectoryChangedListener( + OnDirectoryChangedListener onDirectoryChangedListener) { + this.mOnDirectoryChangedListener = onDirectoryChangedListener; + } + + /** * Method that sets if the view should use flinger gesture detection. * * @param useFlinger If the view should use flinger gesture detection @@ -991,7 +1015,10 @@ public class NavigationView extends RelativeLayout implements //The current directory is now the "newDir" this.mCurrentDir = newDir; - + if (this.mOnDirectoryChangedListener != null) { + FileSystemObject dir = FileHelper.createFileSystemObject(new File(newDir)); + this.mOnDirectoryChangedListener.onDirectoryChanged(dir); + } } finally { //If calling activity is search, then save the search history if (searchInfo != null) { diff --git a/src/com/cyanogenmod/filemanager/util/FileHelper.java b/src/com/cyanogenmod/filemanager/util/FileHelper.java index 4d4c8d5..c5fb16a 100644 --- a/src/com/cyanogenmod/filemanager/util/FileHelper.java +++ b/src/com/cyanogenmod/filemanager/util/FileHelper.java @@ -683,6 +683,15 @@ public final class FileHelper { } break; + case DIRECTORY_ONLY_RESTRICTION: + if (value instanceof Boolean) { + Boolean directoryOnly = (Boolean) value; + if (directoryOnly.booleanValue() && !FileHelper.isDirectory(fso)) { + return false; + } + } + break; + case LOCAL_FILESYSTEM_ONLY_RESTRICTION: if (value instanceof Boolean) { Boolean localOnly = (Boolean)value; diff --git a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java b/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java index 5a6f868..da37d3f 100644 --- a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java +++ b/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java @@ -292,6 +292,11 @@ public final class MimeTypeHelper { loadMimeTypes(context); } + //Directories don't have a mime type + if (FileHelper.isDirectory(fso)) { + return null; + } + //Get the extension and delivery String ext = FileHelper.getExtension(fso); if (ext != null) { -- 2.11.0