blob: 7b50b626941308d3468488933586d0e98a7aee28 [file] [log] [blame]
Steve McKaye934ce62015-03-25 14:35:33 -07001/*
2 * Copyright (C) 2015 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
Steve McKay6eaf38632015-08-04 10:11:01 -070019import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
Steve McKayef3e2cf2015-04-20 17:18:15 -070020import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
21import static com.android.documentsui.DirectoryFragment.ANIM_SIDE;
22import static com.android.documentsui.DirectoryFragment.ANIM_UP;
Steve McKay6eaf38632015-08-04 10:11:01 -070023import static com.android.internal.util.Preconditions.checkArgument;
Steve McKayef3e2cf2015-04-20 17:18:15 -070024
Steve McKaye934ce62015-03-25 14:35:33 -070025import android.app.Activity;
26import android.app.Fragment;
Steve McKayef3e2cf2015-04-20 17:18:15 -070027import android.content.Intent;
Ben Kwa77797402015-05-29 15:40:31 -070028import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.ProviderInfo;
Steve McKayef3e2cf2015-04-20 17:18:15 -070032import android.database.Cursor;
33import android.net.Uri;
34import android.os.AsyncTask;
35import android.os.Bundle;
Steve McKaye934ce62015-03-25 14:35:33 -070036import android.os.Parcel;
37import android.os.Parcelable;
Steve McKayef3e2cf2015-04-20 17:18:15 -070038import android.provider.DocumentsContract;
39import android.provider.DocumentsContract.Root;
Steve McKay12055472015-08-20 16:48:49 -070040import android.support.annotation.LayoutRes;
Steve McKay58efce32015-08-20 16:19:38 +000041import android.support.annotation.Nullable;
Steve McKayef3e2cf2015-04-20 17:18:15 -070042import android.util.Log;
Steve McKaye934ce62015-03-25 14:35:33 -070043import android.util.SparseArray;
Steve McKayef3e2cf2015-04-20 17:18:15 -070044import android.view.LayoutInflater;
45import android.view.Menu;
46import android.view.MenuItem;
47import android.view.MenuItem.OnActionExpandListener;
48import android.view.View;
49import android.view.ViewGroup;
Steve McKay12055472015-08-20 16:48:49 -070050import android.view.ViewStub;
Steve McKayef3e2cf2015-04-20 17:18:15 -070051import android.widget.AdapterView;
52import android.widget.AdapterView.OnItemSelectedListener;
53import android.widget.BaseAdapter;
54import android.widget.ImageView;
55import android.widget.SearchView;
56import android.widget.SearchView.OnQueryTextListener;
57import android.widget.TextView;
Steve McKaye934ce62015-03-25 14:35:33 -070058
Steve McKayef3e2cf2015-04-20 17:18:15 -070059import com.android.documentsui.RecentsProvider.ResumeColumns;
Steve McKaye934ce62015-03-25 14:35:33 -070060import com.android.documentsui.model.DocumentInfo;
61import com.android.documentsui.model.DocumentStack;
62import com.android.documentsui.model.DurableUtils;
63import com.android.documentsui.model.RootInfo;
Steve McKay273103b2015-05-12 12:49:58 -070064
Steve McKay273103b2015-05-12 12:49:58 -070065import libcore.io.IoUtils;
66
67import java.io.FileNotFoundException;
68import java.io.IOException;
69import java.util.ArrayList;
70import java.util.Collection;
71import java.util.HashMap;
72import java.util.List;
73import java.util.concurrent.Executor;
74
Steve McKaye934ce62015-03-25 14:35:33 -070075abstract class BaseActivity extends Activity {
Steve McKayef3e2cf2015-04-20 17:18:15 -070076
77 static final String EXTRA_STATE = "state";
78
Steve McKay12055472015-08-20 16:48:49 -070079 State mState;
Steve McKayef3e2cf2015-04-20 17:18:15 -070080 RootsCache mRoots;
Steve McKaya78a3692015-04-22 15:55:34 -070081 SearchManager mSearchManager;
Steve McKay12055472015-08-20 16:48:49 -070082 DrawerController mDrawer;
Steve McKaya78a3692015-04-22 15:55:34 -070083
Steve McKay12055472015-08-20 16:48:49 -070084 @LayoutRes
85 private int mLayoutId;
Steve McKaya78a3692015-04-22 15:55:34 -070086 private final String mTag;
Steve McKayef3e2cf2015-04-20 17:18:15 -070087
Steve McKaye934ce62015-03-25 14:35:33 -070088 public abstract State getDisplayState();
Steve McKay6eaf38632015-08-04 10:11:01 -070089 public abstract void onDocumentPicked(DocumentInfo doc, @Nullable DocumentContext siblings);
Steve McKaye934ce62015-03-25 14:35:33 -070090 public abstract void onDocumentsPicked(List<DocumentInfo> docs);
Steve McKay6eaf38632015-08-04 10:11:01 -070091
Steve McKayef3e2cf2015-04-20 17:18:15 -070092 abstract void onTaskFinished(Uri... uris);
93 abstract void onDirectoryChanged(int anim);
94 abstract void updateActionBar();
95 abstract void saveStackBlocking();
Steve McKay12055472015-08-20 16:48:49 -070096 abstract State buildDefaultState();
Steve McKayef3e2cf2015-04-20 17:18:15 -070097
Steve McKay12055472015-08-20 16:48:49 -070098 public BaseActivity(@LayoutRes int layoutId, String tag) {
99 mLayoutId = layoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700100 mTag = tag;
101 }
102
103 @Override
104 public void onCreate(Bundle icicle) {
105 super.onCreate(icicle);
Steve McKay12055472015-08-20 16:48:49 -0700106
107 mState = (icicle != null)
108 ? icicle.<State>getParcelable(EXTRA_STATE)
109 : buildDefaultState();
110
111 setContentView(R.layout.files_activity);
112 ViewStub stub = (ViewStub) findViewById(R.id.stub);
113 stub.inflate();
114
Steve McKayef3e2cf2015-04-20 17:18:15 -0700115 mRoots = DocumentsApplication.getRootsCache(this);
Steve McKaya78a3692015-04-22 15:55:34 -0700116 mSearchManager = new SearchManager();
Steve McKay12055472015-08-20 16:48:49 -0700117
118 // Base classes must update result in their onCreate.
119 setResult(Activity.RESULT_CANCELED);
Steve McKaya78a3692015-04-22 15:55:34 -0700120 }
121
122 @Override
Jeff Sharkey67f74e62015-06-16 20:54:11 -0700123 public void onResume() {
124 super.onResume();
125
126 final State state = getDisplayState();
127 final RootInfo root = getCurrentRoot();
128
129 // If we're browsing a specific root, and that root went away, then we
130 // have no reason to hang around
131 if (state.action == State.ACTION_BROWSE && root != null) {
132 if (mRoots.getRootBlocking(root.authority, root.rootId) == null) {
133 finish();
134 }
135 }
136 }
137
138 @Override
Steve McKaya78a3692015-04-22 15:55:34 -0700139 public boolean onCreateOptionsMenu(Menu menu) {
140 boolean showMenu = super.onCreateOptionsMenu(menu);
141
142 getMenuInflater().inflate(R.menu.activity, menu);
Daichi Hirono186efa32015-06-11 14:29:28 +0900143 mSearchManager.install((DocumentsToolBar) findViewById(R.id.toolbar));
Steve McKaya78a3692015-04-22 15:55:34 -0700144
145 return showMenu;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700146 }
147
Steve McKay69aee092015-04-30 16:12:59 -0700148 @Override
149 public boolean onPrepareOptionsMenu(Menu menu) {
150 boolean shown = super.onPrepareOptionsMenu(menu);
151
152 final RootInfo root = getCurrentRoot();
153 final DocumentInfo cwd = getCurrentDirectory();
154
155 final MenuItem sort = menu.findItem(R.id.menu_sort);
156 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
157 final MenuItem grid = menu.findItem(R.id.menu_grid);
158 final MenuItem list = menu.findItem(R.id.menu_list);
159
160 final MenuItem advanced = menu.findItem(R.id.menu_advanced);
161 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
162
163 mSearchManager.update(root);
164
165 // Search uses backend ranking; no sorting
166 sort.setVisible(cwd != null && !mSearchManager.isSearching());
167
168 State state = getDisplayState();
169 grid.setVisible(state.derivedMode != State.MODE_GRID);
170 list.setVisible(state.derivedMode != State.MODE_LIST);
171
172 // Only sort by size when visible
173 sortSize.setVisible(state.showSize);
174
175 advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
176 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
177 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
178 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
179
180 return shown;
181 }
182
Steve McKayef3e2cf2015-04-20 17:18:15 -0700183 void onStackRestored(boolean restored, boolean external) {}
184
185 void onRootPicked(RootInfo root) {
186 State state = getDisplayState();
187
188 // Clear entire backstack and start in new root
189 state.stack.root = root;
190 state.stack.clear();
191 state.stackTouched = true;
192
Steve McKaya78a3692015-04-22 15:55:34 -0700193 mSearchManager.update(root);
194
195 // Recents is always in memory, so we just load it directly.
196 // Otherwise we delegate loading data from disk to a task
197 // to ensure a responsive ui.
198 if (mRoots.isRecentsRoot(root)) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700199 onCurrentDirectoryChanged(ANIM_SIDE);
Steve McKaya78a3692015-04-22 15:55:34 -0700200 } else {
201 new PickRootTask(root).executeOnExecutor(getCurrentExecutor());
Steve McKayef3e2cf2015-04-20 17:18:15 -0700202 }
203 }
204
205 void expandMenus(Menu menu) {
206 for (int i = 0; i < menu.size(); i++) {
207 final MenuItem item = menu.getItem(i);
208 switch (item.getItemId()) {
209 case R.id.menu_advanced:
210 case R.id.menu_file_size:
211 break;
212 default:
213 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
214 }
215 }
216 }
217
218 @Override
219 public boolean onOptionsItemSelected(MenuItem item) {
220 final int id = item.getItemId();
221 if (id == android.R.id.home) {
222 onBackPressed();
223 return true;
224 } else if (id == R.id.menu_create_dir) {
Steve McKaya521d0d2015-05-19 16:10:25 -0700225 showCreateDirectoryDialog();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700226 return true;
227 } else if (id == R.id.menu_search) {
228 return false;
229 } else if (id == R.id.menu_sort_name) {
230 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
231 return true;
232 } else if (id == R.id.menu_sort_date) {
233 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
234 return true;
235 } else if (id == R.id.menu_sort_size) {
236 setUserSortOrder(State.SORT_ORDER_SIZE);
237 return true;
238 } else if (id == R.id.menu_grid) {
239 setUserMode(State.MODE_GRID);
240 return true;
241 } else if (id == R.id.menu_list) {
242 setUserMode(State.MODE_LIST);
243 return true;
Steve McKaybdbd0ff2015-05-20 15:58:42 -0700244 } else if (id == R.id.menu_paste_from_clipboard) {
245 DirectoryFragment.get(getFragmentManager())
246 .pasteFromClipboard();
247 return true;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700248 } else if (id == R.id.menu_advanced) {
249 setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this));
250 return true;
251 } else if (id == R.id.menu_file_size) {
252 setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
253 return true;
254 } else if (id == R.id.menu_settings) {
255 final RootInfo root = getCurrentRoot();
256 final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
257 intent.setDataAndType(DocumentsContract.buildRootUri(root.authority, root.rootId),
258 DocumentsContract.Root.MIME_TYPE_ITEM);
259 startActivity(intent);
260 return true;
261 }
262
263 return super.onOptionsItemSelected(item);
264 }
265
Steve McKaya521d0d2015-05-19 16:10:25 -0700266 void showCreateDirectoryDialog() {
267 CreateDirectoryFragment.show(getFragmentManager());
268 }
269
270 /**
271 * Returns true if a directory can be created in the current location.
272 * @return
273 */
274 boolean canCreateDirectory() {
275 final RootInfo root = getCurrentRoot();
276 final DocumentInfo cwd = getCurrentDirectory();
277 return cwd != null
278 && cwd.isCreateSupported()
279 && !mSearchManager.isSearching()
280 && !root.isDownloads();
281 }
282
Steve McKay6eaf38632015-08-04 10:11:01 -0700283 void onDirectoryCreated(DocumentInfo doc) {
284 checkArgument(doc.isDirectory());
285 openDirectory(doc);
286 }
287
288 void openDirectory(DocumentInfo doc) {
289 getDisplayState().stack.push(doc);
290 getDisplayState().stackTouched = true;
291 onCurrentDirectoryChanged(ANIM_DOWN);
292 }
293
Steve McKayef3e2cf2015-04-20 17:18:15 -0700294 /**
295 * Call this when directory changes. Prior to root fragment update
296 * the (abstract) directoryChanged method will be called.
297 * @param anim
298 */
299 final void onCurrentDirectoryChanged(int anim) {
300 onDirectoryChanged(anim);
301
302 final RootsFragment roots = RootsFragment.get(getFragmentManager());
303 if (roots != null) {
304 roots.onCurrentRootChanged();
305 }
306
307 updateActionBar();
308 invalidateOptionsMenu();
309 }
310
Ben Kwa77797402015-05-29 15:40:31 -0700311 final List<String> getExcludedAuthorities() {
312 List<String> authorities = new ArrayList<>();
313 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
314 // Exclude roots provided by the calling package.
315 String packageName = getCallingPackageMaybeExtra();
316 try {
317 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
318 PackageManager.GET_PROVIDERS);
319 for (ProviderInfo provider: pkgInfo.providers) {
320 authorities.add(provider.authority);
321 }
322 } catch (PackageManager.NameNotFoundException e) {
323 Log.e(mTag, "Calling package name does not resolve: " + packageName);
324 }
325 }
326 return authorities;
327 }
328
Steve McKayef3e2cf2015-04-20 17:18:15 -0700329 final String getCallingPackageMaybeExtra() {
Ben Kwa77797402015-05-29 15:40:31 -0700330 String callingPackage = getCallingPackage();
331 // System apps can set the calling package name using an extra.
332 try {
333 ApplicationInfo info = getPackageManager().getApplicationInfo(callingPackage, 0);
334 if (info.isSystemApp() || info.isUpdatedSystemApp()) {
335 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
336 if (extra != null) {
337 callingPackage = extra;
338 }
339 }
340 } finally {
341 return callingPackage;
342 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700343 }
Steve McKaye934ce62015-03-25 14:35:33 -0700344
345 public static BaseActivity get(Fragment fragment) {
346 return (BaseActivity) fragment.getActivity();
347 }
348
Daichi Hirono1a2fdb42015-04-15 13:41:18 +0900349 public static abstract class DocumentsIntent {
350 /** Intent action name to open copy destination. */
351 public static String ACTION_OPEN_COPY_DESTINATION =
352 "com.android.documentsui.OPEN_COPY_DESTINATION";
353
354 /**
355 * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which
356 * specifies if the destination directory needs to create new directory or not.
357 */
358 public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
359 }
360
Steve McKaye934ce62015-03-25 14:35:33 -0700361 public static class State implements android.os.Parcelable {
362 public int action;
363 public String[] acceptMimes;
364
365 /** Explicit user choice */
366 public int userMode = MODE_UNKNOWN;
367 /** Derived after loader */
368 public int derivedMode = MODE_LIST;
369
370 /** Explicit user choice */
371 public int userSortOrder = SORT_ORDER_UNKNOWN;
372 /** Derived after loader */
373 public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME;
374
Ben Kwa0b4a3c42015-05-05 11:50:11 -0700375 public boolean allowMultiple;
376 public boolean showSize;
377 public boolean localOnly ;
378 public boolean forceAdvanced ;
379 public boolean showAdvanced ;
380 public boolean stackTouched ;
381 public boolean restored ;
382 public boolean directoryCopy ;
383 /** Transfer mode for file copy/move operations. */
384 public int transferMode;
Steve McKaye934ce62015-03-25 14:35:33 -0700385
386 /** Current user navigation stack; empty implies recents. */
387 public DocumentStack stack = new DocumentStack();
388 /** Currently active search, overriding any stack. */
389 public String currentSearch;
390
391 /** Instance state for every shown directory */
Steve McKay58efce32015-08-20 16:19:38 +0000392 public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();
Steve McKaye934ce62015-03-25 14:35:33 -0700393
Daichi Hironocf0e9ac2015-04-17 16:19:15 +0900394 /** Currently copying file */
395 public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<DocumentInfo>();
396
Ben Kwa77797402015-05-29 15:40:31 -0700397 /** Name of the package that started DocsUI */
398 public List<String> excludedAuthorities = new ArrayList<>();
399
Steve McKaye934ce62015-03-25 14:35:33 -0700400 public static final int ACTION_OPEN = 1;
401 public static final int ACTION_CREATE = 2;
402 public static final int ACTION_GET_CONTENT = 3;
403 public static final int ACTION_OPEN_TREE = 4;
404 public static final int ACTION_MANAGE = 5;
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700405 public static final int ACTION_BROWSE = 6;
406 public static final int ACTION_BROWSE_ALL = 7;
Daichi Hironobbe22922015-04-10 15:50:38 +0900407 public static final int ACTION_OPEN_COPY_DESTINATION = 8;
Steve McKaye934ce62015-03-25 14:35:33 -0700408
409 public static final int MODE_UNKNOWN = 0;
410 public static final int MODE_LIST = 1;
411 public static final int MODE_GRID = 2;
412
413 public static final int SORT_ORDER_UNKNOWN = 0;
414 public static final int SORT_ORDER_DISPLAY_NAME = 1;
415 public static final int SORT_ORDER_LAST_MODIFIED = 2;
416 public static final int SORT_ORDER_SIZE = 3;
417
418 @Override
419 public int describeContents() {
420 return 0;
421 }
422
423 @Override
424 public void writeToParcel(Parcel out, int flags) {
425 out.writeInt(action);
426 out.writeInt(userMode);
427 out.writeStringArray(acceptMimes);
428 out.writeInt(userSortOrder);
429 out.writeInt(allowMultiple ? 1 : 0);
430 out.writeInt(showSize ? 1 : 0);
431 out.writeInt(localOnly ? 1 : 0);
432 out.writeInt(forceAdvanced ? 1 : 0);
433 out.writeInt(showAdvanced ? 1 : 0);
434 out.writeInt(stackTouched ? 1 : 0);
435 out.writeInt(restored ? 1 : 0);
436 DurableUtils.writeToParcel(out, stack);
437 out.writeString(currentSearch);
438 out.writeMap(dirState);
Daichi Hironocf0e9ac2015-04-17 16:19:15 +0900439 out.writeList(selectedDocumentsForCopy);
Ben Kwa77797402015-05-29 15:40:31 -0700440 out.writeList(excludedAuthorities);
Steve McKaye934ce62015-03-25 14:35:33 -0700441 }
442
443 public static final Creator<State> CREATOR = new Creator<State>() {
444 @Override
445 public State createFromParcel(Parcel in) {
446 final State state = new State();
447 state.action = in.readInt();
448 state.userMode = in.readInt();
449 state.acceptMimes = in.readStringArray();
450 state.userSortOrder = in.readInt();
451 state.allowMultiple = in.readInt() != 0;
452 state.showSize = in.readInt() != 0;
453 state.localOnly = in.readInt() != 0;
454 state.forceAdvanced = in.readInt() != 0;
455 state.showAdvanced = in.readInt() != 0;
456 state.stackTouched = in.readInt() != 0;
457 state.restored = in.readInt() != 0;
458 DurableUtils.readFromParcel(in, state.stack);
459 state.currentSearch = in.readString();
460 in.readMap(state.dirState, null);
Daichi Hironocf0e9ac2015-04-17 16:19:15 +0900461 in.readList(state.selectedDocumentsForCopy, null);
Ben Kwa77797402015-05-29 15:40:31 -0700462 in.readList(state.excludedAuthorities, null);
Steve McKaye934ce62015-03-25 14:35:33 -0700463 return state;
464 }
465
466 @Override
467 public State[] newArray(int size) {
468 return new State[size];
469 }
470 };
471 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700472
473 void setDisplayAdvancedDevices(boolean display) {
474 State state = getDisplayState();
475 LocalPreferences.setDisplayAdvancedDevices(this, display);
476 state.showAdvanced = state.forceAdvanced | display;
477 RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
478 invalidateOptionsMenu();
479 }
480
481 void setDisplayFileSize(boolean display) {
482 LocalPreferences.setDisplayFileSize(this, display);
483 getDisplayState().showSize = display;
484 DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
485 invalidateOptionsMenu();
486 }
487
488 void onStateChanged() {
489 invalidateOptionsMenu();
490 }
491
492 /**
493 * Set state sort order based on explicit user action.
494 */
495 void setUserSortOrder(int sortOrder) {
496 getDisplayState().userSortOrder = sortOrder;
497 DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
498 }
499
500 /**
501 * Set state mode based on explicit user action.
502 */
503 void setUserMode(int mode) {
504 getDisplayState().userMode = mode;
505 DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
506 }
507
508 void setPending(boolean pending) {
509 final SaveFragment save = SaveFragment.get(getFragmentManager());
510 if (save != null) {
511 save.setPending(pending);
512 }
513 }
514
515 @Override
516 protected void onSaveInstanceState(Bundle state) {
517 super.onSaveInstanceState(state);
518 state.putParcelable(EXTRA_STATE, getDisplayState());
519 }
520
521 @Override
522 protected void onRestoreInstanceState(Bundle state) {
523 super.onRestoreInstanceState(state);
524 }
525
526 RootInfo getCurrentRoot() {
527 State state = getDisplayState();
528 if (state.stack.root != null) {
529 return state.stack.root;
530 } else {
531 return mRoots.getRecentsRoot();
532 }
533 }
534
535 public DocumentInfo getCurrentDirectory() {
536 return getDisplayState().stack.peek();
537 }
538
539 public Executor getCurrentExecutor() {
540 final DocumentInfo cwd = getCurrentDirectory();
541 if (cwd != null && cwd.authority != null) {
542 return ProviderExecutor.forAuthority(cwd.authority);
543 } else {
544 return AsyncTask.THREAD_POOL_EXECUTOR;
545 }
546 }
547
Steve McKay12055472015-08-20 16:48:49 -0700548 @Override
549 public void onBackPressed() {
550 // While action bar is expanded, the state stack UI is hidden.
551 if (mSearchManager.cancelSearch()) {
552 return;
553 }
554
555 if (!mState.stackTouched) {
556 super.onBackPressed();
557 return;
558 }
559
560 final int size = mState.stack.size();
561
562 if (mDrawer.isOpen()) {
563 mDrawer.setOpen(false);
564 } else if (size > 1) {
565 mState.stack.pop();
566 onCurrentDirectoryChanged(ANIM_UP);
567 } else {
568 super.onBackPressed();
569 }
570 }
571
Steve McKayef3e2cf2015-04-20 17:18:15 -0700572 public void onStackPicked(DocumentStack stack) {
573 try {
574 // Update the restored stack to ensure we have freshest data
575 stack.updateDocuments(getContentResolver());
576
577 State state = getDisplayState();
578 state.stack = stack;
579 state.stackTouched = true;
580 onCurrentDirectoryChanged(ANIM_SIDE);
581
582 } catch (FileNotFoundException e) {
583 Log.w(mTag, "Failed to restore stack: " + e);
584 }
585 }
586
587 final class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
588 private RootInfo mRoot;
589
590 public PickRootTask(RootInfo root) {
591 mRoot = root;
592 }
593
594 @Override
595 protected DocumentInfo doInBackground(Void... params) {
596 try {
597 final Uri uri = DocumentsContract.buildDocumentUri(
598 mRoot.authority, mRoot.documentId);
599 return DocumentInfo.fromUri(getContentResolver(), uri);
600 } catch (FileNotFoundException e) {
601 Log.w(mTag, "Failed to find root", e);
602 return null;
603 }
604 }
605
606 @Override
607 protected void onPostExecute(DocumentInfo result) {
608 if (result != null) {
609 State state = getDisplayState();
610 state.stack.push(result);
611 state.stackTouched = true;
612 onCurrentDirectoryChanged(ANIM_SIDE);
613 }
614 }
615 }
616
617 final class RestoreStackTask extends AsyncTask<Void, Void, Void> {
618 private volatile boolean mRestoredStack;
619 private volatile boolean mExternal;
620
621 @Override
622 protected Void doInBackground(Void... params) {
623 State state = getDisplayState();
624 RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);
625
626 // Restore last stack for calling package
627 final String packageName = getCallingPackageMaybeExtra();
628 final Cursor cursor = getContentResolver()
629 .query(RecentsProvider.buildResume(packageName), null, null, null, null);
630 try {
631 if (cursor.moveToFirst()) {
632 mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
633 final byte[] rawStack = cursor.getBlob(
634 cursor.getColumnIndex(ResumeColumns.STACK));
635 DurableUtils.readFromArray(rawStack, state.stack);
636 mRestoredStack = true;
637 }
638 } catch (IOException e) {
639 Log.w(mTag, "Failed to resume: " + e);
640 } finally {
641 IoUtils.closeQuietly(cursor);
642 }
643
644 if (mRestoredStack) {
645 // Update the restored stack to ensure we have freshest data
646 final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(state);
647 try {
648 state.stack.updateRoot(matchingRoots);
649 state.stack.updateDocuments(getContentResolver());
650 } catch (FileNotFoundException e) {
651 Log.w(mTag, "Failed to restore stack: " + e);
652 state.stack.reset();
653 mRestoredStack = false;
654 }
655 }
656
657 return null;
658 }
659
660 @Override
661 protected void onPostExecute(Void result) {
662 if (isDestroyed()) return;
663 getDisplayState().restored = true;
664 onCurrentDirectoryChanged(ANIM_NONE);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700665 onStackRestored(mRestoredStack, mExternal);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700666 }
667 }
668
669 final class ItemSelectedListener implements OnItemSelectedListener {
670
671 boolean mIgnoreNextNavigation;
672
673 @Override
674 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
675 if (mIgnoreNextNavigation) {
676 mIgnoreNextNavigation = false;
677 return;
678 }
679
680 State state = getDisplayState();
681 while (state.stack.size() > position + 1) {
682 state.stackTouched = true;
683 state.stack.pop();
684 }
685 onCurrentDirectoryChanged(ANIM_UP);
686 }
687
688 @Override
689 public void onNothingSelected(AdapterView<?> parent) {
690 // Ignored
691 }
692 }
693
694 /**
695 * Class providing toolbar with runtime access to useful activity data.
696 */
697 final class StackAdapter extends BaseAdapter {
698 @Override
699 public int getCount() {
700 return getDisplayState().stack.size();
701 }
702
703 @Override
704 public DocumentInfo getItem(int position) {
705 State state = getDisplayState();
706 return state.stack.get(state.stack.size() - position - 1);
707 }
708
709 @Override
710 public long getItemId(int position) {
711 return position;
712 }
713
714 @Override
715 public View getView(int position, View convertView, ViewGroup parent) {
716 if (convertView == null) {
717 convertView = LayoutInflater.from(parent.getContext())
718 .inflate(R.layout.item_subdir_title, parent, false);
719 }
720
721 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
722 final DocumentInfo doc = getItem(position);
723
724 if (position == 0) {
725 final RootInfo root = getCurrentRoot();
726 title.setText(root.title);
727 } else {
728 title.setText(doc.displayName);
729 }
730
731 return convertView;
732 }
733
734 @Override
735 public View getDropDownView(int position, View convertView, ViewGroup parent) {
736 if (convertView == null) {
737 convertView = LayoutInflater.from(parent.getContext())
738 .inflate(R.layout.item_subdir, parent, false);
739 }
740
741 final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
742 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
743 final DocumentInfo doc = getItem(position);
744
745 if (position == 0) {
746 final RootInfo root = getCurrentRoot();
747 title.setText(root.title);
748 subdir.setVisibility(View.GONE);
749 } else {
750 title.setText(doc.displayName);
751 subdir.setVisibility(View.VISIBLE);
752 }
753
754 return convertView;
755 }
756 }
757
758 /**
759 * Facade over the various search parts in the menu.
760 */
761 final class SearchManager implements
Daichi Hirono186efa32015-06-11 14:29:28 +0900762 SearchView.OnCloseListener, OnActionExpandListener, OnQueryTextListener,
763 DocumentsToolBar.OnActionViewCollapsedListener {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700764
Steve McKaya78a3692015-04-22 15:55:34 -0700765 private boolean mSearchExpanded;
766 private boolean mIgnoreNextClose;
767 private boolean mIgnoreNextCollapse;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700768
Daichi Hirono186efa32015-06-11 14:29:28 +0900769 private DocumentsToolBar mActionBar;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700770 private MenuItem mMenu;
771 private SearchView mView;
772
Daichi Hirono186efa32015-06-11 14:29:28 +0900773 public void install(DocumentsToolBar actionBar) {
774 assert(mActionBar == null);
775 mActionBar = actionBar;
776 mMenu = actionBar.getSearchMenu();
777 mView = (SearchView) mMenu.getActionView();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700778
Daichi Hirono186efa32015-06-11 14:29:28 +0900779 mActionBar.setOnActionViewCollapsedListener(this);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700780 mMenu.setOnActionExpandListener(this);
781 mView.setOnQueryTextListener(this);
782 mView.setOnCloseListener(this);
783 }
784
785 /**
786 * @param root Info about the current directory.
787 */
788 void update(RootInfo root) {
789 if (mMenu == null) {
Steve McKaya78a3692015-04-22 15:55:34 -0700790 Log.d(mTag, "update called before Search MenuItem installed.");
Steve McKayef3e2cf2015-04-20 17:18:15 -0700791 return;
792 }
Steve McKaya78a3692015-04-22 15:55:34 -0700793
Steve McKayef3e2cf2015-04-20 17:18:15 -0700794 State state = getDisplayState();
795 if (state.currentSearch != null) {
796 mMenu.expandActionView();
797
798 mView.setIconified(false);
799 mView.clearFocus();
800 mView.setQuery(state.currentSearch, false);
801 } else {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700802 mView.clearFocus();
Steve McKaya78a3692015-04-22 15:55:34 -0700803 if (!mView.isIconified()) {
804 mIgnoreNextClose = true;
805 mView.setIconified(true);
806 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700807
Steve McKaya78a3692015-04-22 15:55:34 -0700808 if (mMenu.isActionViewExpanded()) {
809 mIgnoreNextCollapse = true;
810 mMenu.collapseActionView();
811 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700812 }
813
814 showMenu(root != null
815 && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0));
816 }
817
818 void showMenu(boolean visible) {
819 if (mMenu == null) {
820 Log.d(mTag, "showMenu called before Search MenuItem installed.");
821 return;
822 }
Steve McKaya78a3692015-04-22 15:55:34 -0700823
Steve McKayef3e2cf2015-04-20 17:18:15 -0700824 mMenu.setVisible(visible);
Steve McKaya78a3692015-04-22 15:55:34 -0700825 if (!visible) {
826 getDisplayState().currentSearch = null;
827 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700828 }
829
Daichi Hirono186efa32015-06-11 14:29:28 +0900830 /**
831 * Cancels current search operation.
832 * @return True if it cancels search. False if it does not operate
833 * search currently.
834 */
835 boolean cancelSearch() {
836 if (mActionBar.hasExpandedActionView()) {
837 mActionBar.collapseActionView();
838 return true;
839 }
840 return false;
841 }
842
Steve McKayef3e2cf2015-04-20 17:18:15 -0700843 boolean isSearching() {
844 return getDisplayState().currentSearch != null;
845 }
846
847 boolean isExpanded() {
848 return mSearchExpanded;
849 }
850
851 @Override
852 public boolean onClose() {
853 mSearchExpanded = false;
854 if (mIgnoreNextClose) {
855 mIgnoreNextClose = false;
856 return false;
857 }
858
859 getDisplayState().currentSearch = null;
860 onCurrentDirectoryChanged(ANIM_NONE);
861 return false;
862 }
Steve McKaya78a3692015-04-22 15:55:34 -0700863
Steve McKayef3e2cf2015-04-20 17:18:15 -0700864 @Override
865 public boolean onMenuItemActionExpand(MenuItem item) {
866 mSearchExpanded = true;
867 updateActionBar();
868 return true;
869 }
870
871 @Override
872 public boolean onMenuItemActionCollapse(MenuItem item) {
873 mSearchExpanded = false;
874 if (mIgnoreNextCollapse) {
875 mIgnoreNextCollapse = false;
876 return true;
877 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700878 getDisplayState().currentSearch = null;
879 onCurrentDirectoryChanged(ANIM_NONE);
880 return true;
881 }
Steve McKaya78a3692015-04-22 15:55:34 -0700882
Steve McKayef3e2cf2015-04-20 17:18:15 -0700883 @Override
884 public boolean onQueryTextSubmit(String query) {
885 mSearchExpanded = true;
886 getDisplayState().currentSearch = query;
887 mView.clearFocus();
888 onCurrentDirectoryChanged(ANIM_NONE);
889 return true;
890 }
891
892 @Override
893 public boolean onQueryTextChange(String newText) {
894 return false;
895 }
Daichi Hirono186efa32015-06-11 14:29:28 +0900896
897 @Override
898 public void onActionViewCollapsed() {
899 updateActionBar();
900 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700901 }
Steve McKay6eaf38632015-08-04 10:11:01 -0700902
903 /**
904 * Interface providing access to current view of documents
905 * even when all documents are not homed to the same parent.
906 */
907 interface DocumentContext {
908 /**
909 * Returns the cursor for the selected document. The cursor can be used to retrieve
910 * details about a document and its siblings.
911 * @return
912 */
913 Cursor getCursor();
914 }
Steve McKaye934ce62015-03-25 14:35:33 -0700915}