Merge "Support sorting in storage UI."
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 69ee466..ae11a8c 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -12,11 +12,21 @@
             <intent-filter android:priority="100">
                 <action android:name="android.intent.action.OPEN_DOCUMENT" />
                 <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
             </intent-filter>
             <intent-filter android:priority="100">
                 <action android:name="android.intent.action.CREATE_DOCUMENT" />
-                <data android:mimeType="*/*"/>
                 <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+        </activity>
+
+        <!-- TODO: remove when we have real clients -->
+        <activity android:name=".TestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
     </application>
diff --git a/packages/DocumentsUI/res/menu/directory.xml b/packages/DocumentsUI/res/menu/directory.xml
index 52c8f5c..c1fa228 100644
--- a/packages/DocumentsUI/res/menu/directory.xml
+++ b/packages/DocumentsUI/res/menu/directory.xml
@@ -18,13 +18,16 @@
     <item
         android:id="@+id/menu_grid"
         android:title="@string/menu_grid"
-        android:icon="@drawable/ic_menu_grid" />
+        android:icon="@drawable/ic_menu_grid"
+        android:showAsAction="ifRoom" />
     <item
         android:id="@+id/menu_list"
         android:title="@string/menu_list"
-        android:icon="@drawable/ic_menu_list" />
+        android:icon="@drawable/ic_menu_list"
+        android:showAsAction="ifRoom" />
     <item
         android:id="@+id/menu_sort"
         android:title="@string/menu_sort"
-        android:icon="@drawable/ic_menu_sort" />
+        android:icon="@drawable/ic_menu_sort"
+        android:showAsAction="ifRoom" />
 </menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 141ba80..6ae2d12 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -30,4 +30,7 @@
 
     <string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string>
 
+    <string name="sort_name">Name</string>
+    <string name="sort_date">Date modified</string>
+
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index bae42d5..531eaf3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -16,12 +16,17 @@
 
 package com.android.documentsui;
 
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.app.LoaderManager.LoaderCallbacks;
 import android.content.Context;
 import android.content.CursorLoader;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
 import android.content.Loader;
 import android.database.Cursor;
 import android.net.Uri;
@@ -47,6 +52,7 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
+import com.android.documentsui.DocumentsActivity.DisplayState;
 import com.android.documentsui.DocumentsActivity.Document;
 import com.google.android.collect.Lists;
 
@@ -58,7 +64,8 @@
 public class DirectoryFragment extends Fragment {
 
     // TODO: show storage backend in item views when requested
-    // TODO: implement sorting dialog
+
+    private static final String TAG_SORT = "sort";
 
     private ListView mListView;
     private GridView mGridView;
@@ -71,20 +78,12 @@
     private int mFlags;
 
     private static final String EXTRA_URI = "uri";
-    private static final String EXTRA_MODE = "display_mode";
-    private static final String EXTRA_ALLOW_MULTIPLE = "allow_multiple";
-
-    private static final int MODE_LIST = 1;
-    private static final int MODE_GRID = 2;
 
     private static final int LOADER_DOCUMENTS = 2;
 
-    public static void show(
-            FragmentManager fm, Uri uri, String displayName, boolean allowMultiple) {
+    public static void show(FragmentManager fm, Uri uri, String displayName) {
         final Bundle args = new Bundle();
         args.putParcelable(EXTRA_URI, uri);
-        args.putInt(EXTRA_MODE, MODE_LIST);
-        args.putBoolean(EXTRA_ALLOW_MULTIPLE, allowMultiple);
 
         final DirectoryFragment fragment = new DirectoryFragment();
         fragment.setArguments(args);
@@ -127,8 +126,18 @@
         mCallbacks = new LoaderCallbacks<Cursor>() {
             @Override
             public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+                final DisplayState state = getDisplayState(DirectoryFragment.this);
+                final String sortOrder;
+                if (state.sortBy == DisplayState.SORT_BY_NAME) {
+                    sortOrder = DocumentColumns.DISPLAY_NAME + " ASC";
+                } else if (state.sortBy == DisplayState.SORT_BY_DATE) {
+                    sortOrder = DocumentColumns.LAST_MODIFIED + " DESC";
+                } else {
+                    sortOrder = null;
+                }
+
                 final Uri contentsUri = DocumentsContract.buildContentsUri(uri);
-                return new CursorLoader(context, contentsUri, null, null, null, null);
+                return new CursorLoader(context, contentsUri, null, null, null, sortOrder);
             }
 
             @Override
@@ -170,21 +179,27 @@
     @Override
     public void onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
-        final int mode = getMode();
-        menu.findItem(R.id.menu_grid).setVisible(mode != MODE_GRID);
-        menu.findItem(R.id.menu_list).setVisible(mode != MODE_LIST);
+        final DisplayState state = getDisplayState(this);
+        menu.findItem(R.id.menu_grid).setVisible(state.mode != DisplayState.MODE_GRID);
+        menu.findItem(R.id.menu_list).setVisible(state.mode != DisplayState.MODE_LIST);
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
+        final DisplayState state = getDisplayState(this);
         final int id = item.getItemId();
         if (id == R.id.menu_grid) {
-            getArguments().putInt(EXTRA_MODE, MODE_GRID);
+            state.mode = DisplayState.MODE_GRID;
             updateMode();
+            getFragmentManager().invalidateOptionsMenu();
             return true;
         } else if (id == R.id.menu_list) {
-            getArguments().putInt(EXTRA_MODE, MODE_LIST);
+            state.mode = DisplayState.MODE_LIST;
             updateMode();
+            getFragmentManager().invalidateOptionsMenu();
+            return true;
+        } else if (id == R.id.menu_sort) {
+            SortFragment.show(this);
             return true;
         } else {
             return super.onOptionsItemSelected(item);
@@ -192,19 +207,19 @@
     }
 
     private void updateMode() {
-        final int mode = getMode();
+        final DisplayState state = getDisplayState(this);
 
-        mListView.setVisibility(mode == MODE_LIST ? View.VISIBLE : View.GONE);
-        mGridView.setVisibility(mode == MODE_GRID ? View.VISIBLE : View.GONE);
+        mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE);
+        mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE);
 
         final int choiceMode;
-        if (getArguments().getBoolean(EXTRA_ALLOW_MULTIPLE)) {
+        if (state.allowMultiple) {
             choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
         } else {
             choiceMode = ListView.CHOICE_MODE_NONE;
         }
 
-        if (mode == MODE_GRID) {
+        if (state.mode == DisplayState.MODE_GRID) {
             mListView.setAdapter(null);
             mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
             mGridView.setAdapter(mAdapter);
@@ -212,15 +227,21 @@
             mGridView.setNumColumns(GridView.AUTO_FIT);
             mGridView.setChoiceMode(choiceMode);
             mCurrentView = mGridView;
-        } else {
+        } else if (state.mode == DisplayState.MODE_LIST) {
             mGridView.setAdapter(null);
             mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
             mListView.setAdapter(mAdapter);
             mListView.setChoiceMode(choiceMode);
             mCurrentView = mListView;
+        } else {
+            throw new IllegalStateException();
         }
     }
 
+    private void updateSortBy() {
+        getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
+    }
+
     private OnItemClickListener mItemListener = new OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@@ -288,12 +309,8 @@
         }
     };
 
-    private boolean getSupportsCreate() {
-        return (mFlags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
-    }
-
-    private int getMode() {
-        return getArguments().getInt(EXTRA_MODE, MODE_LIST);
+    private static DisplayState getDisplayState(Fragment fragment) {
+        return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
     }
 
     private class DocumentsAdapter extends CursorAdapter {
@@ -304,10 +321,10 @@
         @Override
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
             final LayoutInflater inflater = LayoutInflater.from(context);
-            final int mode = getMode();
-            if (mode == MODE_LIST) {
+            final DisplayState state = getDisplayState(DirectoryFragment.this);
+            if (state.mode == DisplayState.MODE_LIST) {
                 return inflater.inflate(R.layout.item_doc_list, parent, false);
-            } else if (mode == MODE_GRID) {
+            } else if (state.mode == DisplayState.MODE_GRID) {
                 return inflater.inflate(R.layout.item_doc_grid, parent, false);
             } else {
                 throw new IllegalStateException();
@@ -341,6 +358,38 @@
         }
     }
 
+    public static class SortFragment extends DialogFragment {
+        public static void show(DirectoryFragment parent) {
+            if (!parent.isAdded()) return;
+
+            final SortFragment dialog = new SortFragment();
+            dialog.setTargetFragment(parent, 0);
+            dialog.show(parent.getFragmentManager(), TAG_SORT);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Context context = getActivity();
+            final DisplayState state = getDisplayState(this);
+
+            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+            builder.setTitle(R.string.menu_sort);
+            builder.setSingleChoiceItems(new CharSequence[] {
+                    getText(R.string.sort_name),
+                    getText(R.string.sort_date),
+            }, state.sortBy, new OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int which) {
+                    state.sortBy = which;
+                    ((DirectoryFragment) getTargetFragment()).updateSortBy();
+                    dismiss();
+                }
+            });
+
+            return builder.create();
+        }
+    }
+
     private static int getDocumentFlags(Context context, Uri uri) {
         final Cursor cursor = context.getContentResolver().query(uri, new String[] {
                 DocumentColumns.FLAGS }, null, null, null);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index ca4ea82..c45d2b4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -55,13 +55,14 @@
     // TODO: fragment to show recently opened documents
     // TODO: pull actionbar icon from current backend
 
-    private static final int MODE_OPEN = 1;
-    private static final int MODE_CREATE = 2;
+    private static final int ACTION_OPEN = 1;
+    private static final int ACTION_CREATE = 2;
 
-    private int mMode;
-    private boolean mAllowMultiple;
+    private int mAction;
     private String[] mAcceptMimes;
 
+    private final DisplayState mDisplayState = new DisplayState();
+
     private boolean mIgnoreNextNavigation;
 
     private Uri mCurrentDir;
@@ -74,11 +75,11 @@
         final Intent intent = getIntent();
         final String action = intent.getAction();
         if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
-            mMode = MODE_OPEN;
-            mAllowMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
+            mAction = ACTION_OPEN;
+            mDisplayState.allowMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
         } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
-            mMode = MODE_CREATE;
-            mAllowMultiple = false;
+            mAction = ACTION_CREATE;
+            mDisplayState.allowMultiple = false;
         }
 
         if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
@@ -87,6 +88,12 @@
             mAcceptMimes = new String[] { intent.getType() };
         }
 
+        if (mimeMatches("image/*", mAcceptMimes)) {
+            mDisplayState.mode = DisplayState.MODE_GRID;
+        } else {
+            mDisplayState.mode = DisplayState.MODE_LIST;
+        }
+
         setResult(Activity.RESULT_CANCELED);
         setContentView(R.layout.activity);
 
@@ -95,7 +102,7 @@
 
         updateActionBar();
 
-        if (mMode == MODE_CREATE) {
+        if (mAction == ACTION_CREATE) {
             final String mimeType = getIntent().getType();
             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
             SaveFragment.show(getFragmentManager(), mimeType, title);
@@ -120,9 +127,9 @@
             actionBar.setDisplayShowHomeEnabled(false);
             actionBar.setDisplayHomeAsUpEnabled(false);
 
-            if (mMode == MODE_OPEN) {
+            if (mAction == ACTION_OPEN) {
                 actionBar.setTitle(R.string.title_open);
-            } else if (mMode == MODE_CREATE) {
+            } else if (mAction == ACTION_CREATE) {
                 actionBar.setTitle(R.string.title_save);
             }
         }
@@ -140,7 +147,7 @@
         super.onPrepareOptionsMenu(menu);
 
         final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
-        createDir.setVisible(mMode == MODE_CREATE);
+        createDir.setVisible(mAction == ACTION_CREATE);
         createDir.setEnabled(mCurrentSupportsCreate);
 
         return true;
@@ -209,11 +216,15 @@
         }
     };
 
+    public DisplayState getDisplayState() {
+        return mDisplayState;
+    }
+
     public void onDirectoryChanged(Uri uri, int flags) {
         mCurrentDir = uri;
         mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
 
-        if (mMode == MODE_CREATE) {
+        if (mAction == ACTION_CREATE) {
             final FragmentManager fm = getFragmentManager();
             SaveFragment.get(fm).setSaveEnabled(mCurrentSupportsCreate);
         }
@@ -225,18 +236,18 @@
         final Uri uri = DocumentsContract.buildDocumentUri(
                 info.authority, DocumentsContract.ROOT_GUID);
         final CharSequence displayName = info.loadLabel(getPackageManager());
-        DirectoryFragment.show(getFragmentManager(), uri, displayName.toString(), mAllowMultiple);
+        DirectoryFragment.show(getFragmentManager(), uri, displayName.toString());
     }
 
     public void onDocumentPicked(Document doc) {
         final FragmentManager fm = getFragmentManager();
         if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
             // Nested directory picked, recurse using new fragment
-            DirectoryFragment.show(fm, doc.uri, doc.displayName, mAllowMultiple);
-        } else if (mMode == MODE_OPEN) {
+            DirectoryFragment.show(fm, doc.uri, doc.displayName);
+        } else if (mAction == ACTION_OPEN) {
             // Explicit file picked, return
             onFinished(doc.uri);
-        } else if (mMode == MODE_CREATE) {
+        } else if (mAction == ACTION_CREATE) {
             // Overwrite current filename
             SaveFragment.get(fm).setDisplayName(doc.displayName);
         }
@@ -274,7 +285,7 @@
 
         intent.addFlags(
                 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
-        if (mMode == MODE_CREATE) {
+        if (mAction == ACTION_CREATE) {
             intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
         }
 
@@ -282,6 +293,18 @@
         finish();
     }
 
+    public static class DisplayState {
+        public int mode;
+        public int sortBy;
+        public boolean allowMultiple;
+
+        public static final int MODE_LIST = 0;
+        public static final int MODE_GRID = 1;
+
+        public static final int SORT_BY_NAME = 0;
+        public static final int SORT_BY_DATE = 1;
+    }
+
     public static class Document {
         public Uri uri;
         public String mimeType;
@@ -297,6 +320,27 @@
         }
     }
 
+    public static boolean mimeMatches(String filter, String[] tests) {
+        for (String test : tests) {
+            if (mimeMatches(filter, test)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean mimeMatches(String filter, String test) {
+        if (filter.equals(test)) {
+            return true;
+        } else if ("*/*".equals(filter)) {
+            return true;
+        } else if (filter.endsWith("/*")) {
+            return filter.regionMatches(0, test, 0, filter.indexOf('/'));
+        } else {
+            return false;
+        }
+    }
+
     public static Drawable resolveDocumentIcon(Context context, String mimeType) {
         // TODO: allow backends to provide custom MIME icons
         if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
new file mode 100644
index 0000000..b15d123
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class TestActivity extends Activity {
+    private TextView mResult;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        final Context context = this;
+
+        final LinearLayout view = new LinearLayout(context);
+        view.setOrientation(LinearLayout.VERTICAL);
+
+        final CheckBox multiple = new CheckBox(context);
+        multiple.setText("ALLOW_MULTIPLE");
+        view.addView(multiple);
+
+        Button button;
+        button = new Button(context);
+        button.setText("OPEN_DOC */*");
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+                intent.setType("*/*");
+                if (multiple.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+                }
+                startActivityForResult(intent, 42);
+            }
+        });
+        view.addView(button);
+
+        button = new Button(context);
+        button.setText("OPEN_DOC image/*");
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+                intent.setType("image/*");
+                if (multiple.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+                }
+                startActivityForResult(intent, 42);
+            }
+        });
+        view.addView(button);
+
+        button = new Button(context);
+        button.setText("OPEN_DOC text/plain, application/msword");
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+                intent.setType("*/*");
+                intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {
+                        "text/plain", "application/msword" });
+                if (multiple.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+                }
+                startActivityForResult(intent, 42);
+            }
+        });
+        view.addView(button);
+
+        button = new Button(context);
+        button.setText("CREATE_DOC text/plain");
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+                intent.setType("text/plain");
+                intent.putExtra(Intent.EXTRA_TITLE, "foobar.txt");
+                startActivityForResult(intent, 42);
+            }
+        });
+        view.addView(button);
+
+        mResult = new TextView(context);
+        view.addView(mResult);
+
+        setContentView(view);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        mResult.setText("resultCode=" + resultCode + ", data=" + String.valueOf(data));
+    }
+}