blob: c45d2b4990ff26ae1b2887ba89629eb271839553 [file] [log] [blame]
Jeff Sharkey9e0036e2013-04-26 16:54:55 -07001/*
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
Jeff Sharkey54e55b72013-06-30 20:02:59 -070019import static com.android.documentsui.DirectoryFragment.getCursorString;
20
21import android.app.ActionBar;
22import android.app.ActionBar.OnNavigationListener;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070023import android.app.Activity;
24import android.app.FragmentManager;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070025import android.app.FragmentManager.BackStackEntry;
26import android.app.FragmentManager.OnBackStackChangedListener;
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070027import android.content.ClipData;
28import android.content.ContentResolver;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070029import android.content.Context;
30import android.content.Intent;
31import android.content.pm.PackageManager;
32import android.content.pm.ProviderInfo;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070033import android.content.pm.ResolveInfo;
34import android.database.Cursor;
35import android.graphics.drawable.Drawable;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070036import android.net.Uri;
37import android.os.Bundle;
38import android.provider.DocumentsContract;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070039import android.provider.DocumentsContract.DocumentColumns;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070040import android.util.Log;
41import android.view.LayoutInflater;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070042import android.view.Menu;
43import android.view.MenuItem;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070044import android.view.View;
45import android.view.ViewGroup;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070046import android.widget.BaseAdapter;
47import android.widget.TextView;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070048
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070049import java.util.Arrays;
50import java.util.List;
51
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070052public class DocumentsActivity extends Activity {
53 private static final String TAG = "Documents";
54
Jeff Sharkey54e55b72013-06-30 20:02:59 -070055 // TODO: fragment to show recently opened documents
56 // TODO: pull actionbar icon from current backend
57
Jeff Sharkey14827892013-07-01 17:22:02 -070058 private static final int ACTION_OPEN = 1;
59 private static final int ACTION_CREATE = 2;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070060
Jeff Sharkey14827892013-07-01 17:22:02 -070061 private int mAction;
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070062 private String[] mAcceptMimes;
63
Jeff Sharkey14827892013-07-01 17:22:02 -070064 private final DisplayState mDisplayState = new DisplayState();
65
Jeff Sharkey54e55b72013-06-30 20:02:59 -070066 private boolean mIgnoreNextNavigation;
67
68 private Uri mCurrentDir;
69 private boolean mCurrentSupportsCreate;
70
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070071 @Override
72 public void onCreate(Bundle icicle) {
73 super.onCreate(icicle);
74
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070075 final Intent intent = getIntent();
76 final String action = intent.getAction();
Jeff Sharkey54e55b72013-06-30 20:02:59 -070077 if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
Jeff Sharkey14827892013-07-01 17:22:02 -070078 mAction = ACTION_OPEN;
79 mDisplayState.allowMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
Jeff Sharkey54e55b72013-06-30 20:02:59 -070080 } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
Jeff Sharkey14827892013-07-01 17:22:02 -070081 mAction = ACTION_CREATE;
82 mDisplayState.allowMultiple = false;
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070083 }
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() };
Jeff Sharkey54e55b72013-06-30 20:02:59 -070089 }
90
Jeff Sharkey14827892013-07-01 17:22:02 -070091 if (mimeMatches("image/*", mAcceptMimes)) {
92 mDisplayState.mode = DisplayState.MODE_GRID;
93 } else {
94 mDisplayState.mode = DisplayState.MODE_LIST;
95 }
96
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070097 setResult(Activity.RESULT_CANCELED);
Jeff Sharkey54e55b72013-06-30 20:02:59 -070098 setContentView(R.layout.activity);
99
100 getFragmentManager().addOnBackStackChangedListener(mStackListener);
101 BackendFragment.show(getFragmentManager());
102
103 updateActionBar();
104
Jeff Sharkey14827892013-07-01 17:22:02 -0700105 if (mAction == ACTION_CREATE) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700106 final String mimeType = getIntent().getType();
107 final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
108 SaveFragment.show(getFragmentManager(), mimeType, title);
109 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700110 }
111
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700112 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
Jeff Sharkey14827892013-07-01 17:22:02 -0700130 if (mAction == ACTION_OPEN) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700131 actionBar.setTitle(R.string.title_open);
Jeff Sharkey14827892013-07-01 17:22:02 -0700132 } else if (mAction == ACTION_CREATE) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700133 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);
Jeff Sharkey14827892013-07-01 17:22:02 -0700150 createDir.setVisible(mAction == ACTION_CREATE);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700151 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
Jeff Sharkey14827892013-07-01 17:22:02 -0700219 public DisplayState getDisplayState() {
220 return mDisplayState;
221 }
222
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700223 public void onDirectoryChanged(Uri uri, int flags) {
224 mCurrentDir = uri;
225 mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
226
Jeff Sharkey14827892013-07-01 17:22:02 -0700227 if (mAction == ACTION_CREATE) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700228 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());
Jeff Sharkey14827892013-07-01 17:22:02 -0700239 DirectoryFragment.show(getFragmentManager(), uri, displayName.toString());
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700240 }
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
Jeff Sharkey14827892013-07-01 17:22:02 -0700246 DirectoryFragment.show(fm, doc.uri, doc.displayName);
247 } else if (mAction == ACTION_OPEN) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700248 // Explicit file picked, return
249 onFinished(doc.uri);
Jeff Sharkey14827892013-07-01 17:22:02 -0700250 } else if (mAction == ACTION_CREATE) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700251 // Overwrite current filename
252 SaveFragment.get(fm).setDisplayName(doc.displayName);
253 }
254 }
255
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700256 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);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700263 }
264
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700265 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));
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700273
274 final Intent intent = new Intent();
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700275 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 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700285
286 intent.addFlags(
287 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
Jeff Sharkey14827892013-07-01 17:22:02 -0700288 if (mAction == ACTION_CREATE) {
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700289 intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
290 }
291
292 setResult(Activity.RESULT_OK, intent);
293 finish();
294 }
295
Jeff Sharkey14827892013-07-01 17:22:02 -0700296 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
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700308 public static class Document {
309 public Uri uri;
310 public String mimeType;
311 public String displayName;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700312
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700313 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;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700320 }
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700321 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700322
Jeff Sharkey14827892013-07-01 17:22:02 -0700323 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
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700344 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);
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700352
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700353 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;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700359 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700360 }
361 }
362}