DocumentsActivity.java revision 1482789374fb8da3abea8f6f4f272a67205b95db
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui;
18
19import static com.android.documentsui.DirectoryFragment.getCursorString;
20
21import android.app.ActionBar;
22import android.app.ActionBar.OnNavigationListener;
23import android.app.Activity;
24import android.app.FragmentManager;
25import android.app.FragmentManager.BackStackEntry;
26import android.app.FragmentManager.OnBackStackChangedListener;
27import android.content.ClipData;
28import android.content.ContentResolver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.pm.PackageManager;
32import android.content.pm.ProviderInfo;
33import android.content.pm.ResolveInfo;
34import android.database.Cursor;
35import android.graphics.drawable.Drawable;
36import android.net.Uri;
37import android.os.Bundle;
38import android.provider.DocumentsContract;
39import android.provider.DocumentsContract.DocumentColumns;
40import android.util.Log;
41import android.view.LayoutInflater;
42import android.view.Menu;
43import android.view.MenuItem;
44import android.view.View;
45import android.view.ViewGroup;
46import android.widget.BaseAdapter;
47import android.widget.TextView;
48
49import java.util.Arrays;
50import java.util.List;
51
52public class DocumentsActivity extends Activity {
53    private static final String TAG = "Documents";
54
55    // TODO: fragment to show recently opened documents
56    // TODO: pull actionbar icon from current backend
57
58    private static final int ACTION_OPEN = 1;
59    private static final int ACTION_CREATE = 2;
60
61    private int mAction;
62    private String[] mAcceptMimes;
63
64    private final DisplayState mDisplayState = new DisplayState();
65
66    private boolean mIgnoreNextNavigation;
67
68    private Uri mCurrentDir;
69    private boolean mCurrentSupportsCreate;
70
71    @Override
72    public void onCreate(Bundle icicle) {
73        super.onCreate(icicle);
74
75        final Intent intent = getIntent();
76        final String action = intent.getAction();
77        if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
78            mAction = ACTION_OPEN;
79            mDisplayState.allowMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
80        } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
81            mAction = ACTION_CREATE;
82            mDisplayState.allowMultiple = false;
83        }
84
85        if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
86            mAcceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
87        } else {
88            mAcceptMimes = new String[] { intent.getType() };
89        }
90
91        if (mimeMatches("image/*", mAcceptMimes)) {
92            mDisplayState.mode = DisplayState.MODE_GRID;
93        } else {
94            mDisplayState.mode = DisplayState.MODE_LIST;
95        }
96
97        setResult(Activity.RESULT_CANCELED);
98        setContentView(R.layout.activity);
99
100        getFragmentManager().addOnBackStackChangedListener(mStackListener);
101        BackendFragment.show(getFragmentManager());
102
103        updateActionBar();
104
105        if (mAction == ACTION_CREATE) {
106            final String mimeType = getIntent().getType();
107            final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
108            SaveFragment.show(getFragmentManager(), mimeType, title);
109        }
110    }
111
112    public void updateActionBar() {
113        final FragmentManager fm = getFragmentManager();
114        final ActionBar actionBar = getActionBar();
115
116        if (fm.getBackStackEntryCount() > 0) {
117            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
118            actionBar.setDisplayShowHomeEnabled(true);
119            actionBar.setDisplayHomeAsUpEnabled(true);
120            actionBar.setTitle(null);
121            actionBar.setListNavigationCallbacks(mStackAdapter, mNavigationListener);
122            actionBar.setSelectedNavigationItem(mStackAdapter.getCount() - 1);
123            mIgnoreNextNavigation = true;
124
125        } else {
126            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
127            actionBar.setDisplayShowHomeEnabled(false);
128            actionBar.setDisplayHomeAsUpEnabled(false);
129
130            if (mAction == ACTION_OPEN) {
131                actionBar.setTitle(R.string.title_open);
132            } else if (mAction == ACTION_CREATE) {
133                actionBar.setTitle(R.string.title_save);
134            }
135        }
136    }
137
138    @Override
139    public boolean onCreateOptionsMenu(Menu menu) {
140        super.onCreateOptionsMenu(menu);
141        getMenuInflater().inflate(R.menu.activity, menu);
142        return true;
143    }
144
145    @Override
146    public boolean onPrepareOptionsMenu(Menu menu) {
147        super.onPrepareOptionsMenu(menu);
148
149        final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
150        createDir.setVisible(mAction == ACTION_CREATE);
151        createDir.setEnabled(mCurrentSupportsCreate);
152
153        return true;
154    }
155
156    @Override
157    public boolean onOptionsItemSelected(MenuItem item) {
158        final int id = item.getItemId();
159        if (id == android.R.id.home) {
160            getFragmentManager().popBackStack();
161            updateActionBar();
162        } else if (id == R.id.menu_create_dir) {
163            // TODO: show dialog to create directory
164        }
165        return super.onOptionsItemSelected(item);
166    }
167
168    private OnBackStackChangedListener mStackListener = new OnBackStackChangedListener() {
169        @Override
170        public void onBackStackChanged() {
171            updateActionBar();
172        }
173    };
174
175    private BaseAdapter mStackAdapter = new BaseAdapter() {
176        @Override
177        public int getCount() {
178            return getFragmentManager().getBackStackEntryCount();
179        }
180
181        @Override
182        public Object getItem(int position) {
183            return getFragmentManager().getBackStackEntryAt(position);
184        }
185
186        @Override
187        public long getItemId(int position) {
188            return getFragmentManager().getBackStackEntryAt(position).getId();
189        }
190
191        @Override
192        public View getView(int position, View convertView, ViewGroup parent) {
193            if (convertView == null) {
194                convertView = LayoutInflater.from(parent.getContext())
195                        .inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
196            }
197
198            final BackStackEntry entry = getFragmentManager().getBackStackEntryAt(position);
199            final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
200            text1.setText(entry.getBreadCrumbTitle());
201
202            return convertView;
203        }
204    };
205
206    private OnNavigationListener mNavigationListener = new OnNavigationListener() {
207        @Override
208        public boolean onNavigationItemSelected(int itemPosition, long itemId) {
209            if (mIgnoreNextNavigation) {
210                mIgnoreNextNavigation = false;
211                return false;
212            }
213
214            getFragmentManager().popBackStack((int) itemId, 0);
215            return true;
216        }
217    };
218
219    public DisplayState getDisplayState() {
220        return mDisplayState;
221    }
222
223    public void onDirectoryChanged(Uri uri, int flags) {
224        mCurrentDir = uri;
225        mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
226
227        if (mAction == ACTION_CREATE) {
228            final FragmentManager fm = getFragmentManager();
229            SaveFragment.get(fm).setSaveEnabled(mCurrentSupportsCreate);
230        }
231
232        invalidateOptionsMenu();
233    }
234
235    public void onBackendPicked(ProviderInfo info) {
236        final Uri uri = DocumentsContract.buildDocumentUri(
237                info.authority, DocumentsContract.ROOT_GUID);
238        final CharSequence displayName = info.loadLabel(getPackageManager());
239        DirectoryFragment.show(getFragmentManager(), uri, displayName.toString());
240    }
241
242    public void onDocumentPicked(Document doc) {
243        final FragmentManager fm = getFragmentManager();
244        if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
245            // Nested directory picked, recurse using new fragment
246            DirectoryFragment.show(fm, doc.uri, doc.displayName);
247        } else if (mAction == ACTION_OPEN) {
248            // Explicit file picked, return
249            onFinished(doc.uri);
250        } else if (mAction == ACTION_CREATE) {
251            // Overwrite current filename
252            SaveFragment.get(fm).setDisplayName(doc.displayName);
253        }
254    }
255
256    public void onDocumentsPicked(List<Document> docs) {
257        final int size = docs.size();
258        final Uri[] uris = new Uri[size];
259        for (int i = 0; i < size; i++) {
260            uris[i] = docs.get(i).uri;
261        }
262        onFinished(uris);
263    }
264
265    public void onSaveRequested(String mimeType, String displayName) {
266        // TODO: create file, confirming before overwriting
267        final Uri uri = null;
268        onFinished(uri);
269    }
270
271    private void onFinished(Uri... uris) {
272        Log.d(TAG, "onFinished() " + Arrays.toString(uris));
273
274        final Intent intent = new Intent();
275        if (uris.length == 1) {
276            intent.setData(uris[0]);
277        } else if (uris.length > 1) {
278            final ContentResolver resolver = getContentResolver();
279            final ClipData clipData = new ClipData(null, mAcceptMimes, new ClipData.Item(uris[0]));
280            for (int i = 1; i < uris.length; i++) {
281                clipData.addItem(new ClipData.Item(uris[i]));
282            }
283            intent.setClipData(clipData);
284        }
285
286        intent.addFlags(
287                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
288        if (mAction == ACTION_CREATE) {
289            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
290        }
291
292        setResult(Activity.RESULT_OK, intent);
293        finish();
294    }
295
296    public static class DisplayState {
297        public int mode;
298        public int sortBy;
299        public boolean allowMultiple;
300
301        public static final int MODE_LIST = 0;
302        public static final int MODE_GRID = 1;
303
304        public static final int SORT_BY_NAME = 0;
305        public static final int SORT_BY_DATE = 1;
306    }
307
308    public static class Document {
309        public Uri uri;
310        public String mimeType;
311        public String displayName;
312
313        public static Document fromCursor(String authority, Cursor cursor) {
314            final Document doc = new Document();
315            final String guid = getCursorString(cursor, DocumentColumns.GUID);
316            doc.uri = DocumentsContract.buildDocumentUri(authority, guid);
317            doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
318            doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
319            return doc;
320        }
321    }
322
323    public static boolean mimeMatches(String filter, String[] tests) {
324        for (String test : tests) {
325            if (mimeMatches(filter, test)) {
326                return true;
327            }
328        }
329        return false;
330    }
331
332    public static boolean mimeMatches(String filter, String test) {
333        if (filter.equals(test)) {
334            return true;
335        } else if ("*/*".equals(filter)) {
336            return true;
337        } else if (filter.endsWith("/*")) {
338            return filter.regionMatches(0, test, 0, filter.indexOf('/'));
339        } else {
340            return false;
341        }
342    }
343
344    public static Drawable resolveDocumentIcon(Context context, String mimeType) {
345        // TODO: allow backends to provide custom MIME icons
346        if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
347            return context.getResources().getDrawable(R.drawable.ic_dir);
348        } else {
349            final PackageManager pm = context.getPackageManager();
350            final Intent intent = new Intent(Intent.ACTION_VIEW);
351            intent.setType(mimeType);
352
353            final ResolveInfo info = pm.resolveActivity(
354                    intent, PackageManager.MATCH_DEFAULT_ONLY);
355            if (info != null) {
356                return info.loadIcon(pm);
357            } else {
358                return null;
359            }
360        }
361    }
362}
363