OSDN Git Service

Allow picking of directories.
authorDanny Baumann <dannybaumann@web.de>
Mon, 21 Jan 2013 10:42:33 +0000 (11:42 +0100)
committerJorge Ruesga <jorge@ruesga.com>
Wed, 23 Jan 2013 21:46:17 +0000 (22:46 +0100)
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
res/values-de/strings.xml
res/values/strings.xml
src/com/cyanogenmod/filemanager/activities/PickerActivity.java
src/com/cyanogenmod/filemanager/preferences/DisplayRestrictions.java
src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java
src/com/cyanogenmod/filemanager/util/FileHelper.java
src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java

index b7c37b4..618b4a6 100644 (file)
         <category android:name="android.intent.category.DEFAULT" />
         <data android:mimeType="*/*" />
       </intent-filter>
+      <intent-filter>
+        <action android:name="android.intent.action.PICK" />
+        <category android:name="android.intent.category.DEFAULT" />
+        <category android:name="android.intent.category.BROWSABLE" />
+        <data android:scheme="file" />
+        <data android:scheme="folder" />
+        <data android:scheme="directory" />
+      </intent-filter>
     </activity>
 
     <activity
index adc7055..5d320e2 100644 (file)
@@ -36,6 +36,7 @@
     <string name="no">Nein</string>
     <string name="all">Alle</string>
     <string name="overwrite">Überschreiben</string>
+    <string name="select">Auswählen</string>
 
     <!-- Root directory name -->
     <string name="root_directory_name"><![CDATA[<Wurzelverzeichnis>]]></string>
 
     <!-- Picker activity -->
     <string name="picker_title">Datei wählen</string>
+    <string name="directory_picker_title">Verzeichnis wählen</string>
 
     <!-- Editor -->
     <string name="editor">Editor</string>
index aa25f55..c14239b 100644 (file)
@@ -44,6 +44,7 @@
   <string name="no">No</string>
   <string name="all">All</string>
   <string name="overwrite">Overwrite</string>
+  <string name="select">Select</string>
 
   <!-- The root directory name -->
   <string name="root_directory_name"><![CDATA[<Root folder>]]></string>
   <string name="picker" translatable="false">@string/app_name</string>
   <!-- Picker Activity * Dialog title -->
   <string name="picker_title">Pick a file</string>
+  <string name="directory_picker_title">Pick a directory</string>
 
   <!-- Editor * Editor activity title -->
   <string name="editor">Editor</string>
index 298263f..3c8d179 100644 (file)
@@ -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<DisplayRestrictions, Object> restrictions = new HashMap<DisplayRestrictions, Object>();
         //- 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
index 2525ade..7b1e9cf 100644 (file)
@@ -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
index 4a71e5e..9695760 100644 (file)
@@ -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) {
index 4d4c8d5..c5fb16a 100644 (file)
@@ -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;
index 5a6f868..da37d3f 100644 (file)
@@ -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) {