blob: bba33be68d41c17ee6d3c6b86bd7c8157b465394 [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 McKayef3e2cf2015-04-20 17:18:15 -070019import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
20import static com.android.documentsui.DirectoryFragment.ANIM_SIDE;
21import static com.android.documentsui.DirectoryFragment.ANIM_UP;
22
23import java.io.FileNotFoundException;
24import java.io.IOException;
Daichi Hironocf0e9ac2015-04-17 16:19:15 +090025import java.util.ArrayList;
Steve McKayef3e2cf2015-04-20 17:18:15 -070026import java.util.Collection;
Steve McKaye934ce62015-03-25 14:35:33 -070027import java.util.HashMap;
Ben Kwa77797402015-05-29 15:40:31 -070028import java.util.HashSet;
Steve McKaye934ce62015-03-25 14:35:33 -070029import java.util.List;
Steve McKayef3e2cf2015-04-20 17:18:15 -070030import java.util.concurrent.Executor;
Steve McKaye934ce62015-03-25 14:35:33 -070031
Steve McKayef3e2cf2015-04-20 17:18:15 -070032import libcore.io.IoUtils;
Steve McKaye934ce62015-03-25 14:35:33 -070033import android.app.Activity;
34import android.app.Fragment;
Steve McKayef3e2cf2015-04-20 17:18:15 -070035import android.content.Intent;
Ben Kwa77797402015-05-29 15:40:31 -070036import android.content.pm.ApplicationInfo;
37import android.content.pm.PackageInfo;
38import android.content.pm.PackageManager;
39import android.content.pm.ProviderInfo;
Steve McKayef3e2cf2015-04-20 17:18:15 -070040import android.database.Cursor;
41import android.net.Uri;
42import android.os.AsyncTask;
43import android.os.Bundle;
Steve McKaye934ce62015-03-25 14:35:33 -070044import android.os.Parcel;
45import android.os.Parcelable;
Steve McKayef3e2cf2015-04-20 17:18:15 -070046import android.provider.DocumentsContract;
47import android.provider.DocumentsContract.Root;
48import android.util.Log;
Steve McKaye934ce62015-03-25 14:35:33 -070049import android.util.SparseArray;
Steve McKayef3e2cf2015-04-20 17:18:15 -070050import android.view.LayoutInflater;
51import android.view.Menu;
52import android.view.MenuItem;
53import android.view.MenuItem.OnActionExpandListener;
54import android.view.View;
55import android.view.ViewGroup;
56import android.widget.AdapterView;
57import android.widget.AdapterView.OnItemSelectedListener;
58import android.widget.BaseAdapter;
59import android.widget.ImageView;
60import android.widget.SearchView;
61import android.widget.SearchView.OnQueryTextListener;
62import android.widget.TextView;
Steve McKaye934ce62015-03-25 14:35:33 -070063
Steve McKayef3e2cf2015-04-20 17:18:15 -070064import com.android.documentsui.RecentsProvider.ResumeColumns;
Steve McKaye934ce62015-03-25 14:35:33 -070065import com.android.documentsui.model.DocumentInfo;
66import com.android.documentsui.model.DocumentStack;
67import com.android.documentsui.model.DurableUtils;
68import com.android.documentsui.model.RootInfo;
69import com.google.common.collect.Maps;
70
71abstract class BaseActivity extends Activity {
Steve McKayef3e2cf2015-04-20 17:18:15 -070072
73 static final String EXTRA_STATE = "state";
74
Steve McKayef3e2cf2015-04-20 17:18:15 -070075 RootsCache mRoots;
Steve McKaya78a3692015-04-22 15:55:34 -070076 SearchManager mSearchManager;
77
78 private final String mTag;
Steve McKayef3e2cf2015-04-20 17:18:15 -070079
Steve McKaye934ce62015-03-25 14:35:33 -070080 public abstract State getDisplayState();
Steve McKaye934ce62015-03-25 14:35:33 -070081 public abstract void onDocumentPicked(DocumentInfo doc);
82 public abstract void onDocumentsPicked(List<DocumentInfo> docs);
Steve McKayef3e2cf2015-04-20 17:18:15 -070083 abstract void onTaskFinished(Uri... uris);
84 abstract void onDirectoryChanged(int anim);
85 abstract void updateActionBar();
86 abstract void saveStackBlocking();
87
88 public BaseActivity(String tag) {
89 mTag = tag;
90 }
91
92 @Override
93 public void onCreate(Bundle icicle) {
94 super.onCreate(icicle);
95 mRoots = DocumentsApplication.getRootsCache(this);
Steve McKaya78a3692015-04-22 15:55:34 -070096 mSearchManager = new SearchManager();
97 }
98
99 @Override
100 public boolean onCreateOptionsMenu(Menu menu) {
101 boolean showMenu = super.onCreateOptionsMenu(menu);
102
103 getMenuInflater().inflate(R.menu.activity, menu);
Daichi Hirono186efa32015-06-11 14:29:28 +0900104 mSearchManager.install((DocumentsToolBar) findViewById(R.id.toolbar));
Steve McKaya78a3692015-04-22 15:55:34 -0700105
106 return showMenu;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700107 }
108
Steve McKay69aee092015-04-30 16:12:59 -0700109 @Override
110 public boolean onPrepareOptionsMenu(Menu menu) {
111 boolean shown = super.onPrepareOptionsMenu(menu);
112
113 final RootInfo root = getCurrentRoot();
114 final DocumentInfo cwd = getCurrentDirectory();
115
116 final MenuItem sort = menu.findItem(R.id.menu_sort);
117 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
118 final MenuItem grid = menu.findItem(R.id.menu_grid);
119 final MenuItem list = menu.findItem(R.id.menu_list);
120
121 final MenuItem advanced = menu.findItem(R.id.menu_advanced);
122 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
123
124 mSearchManager.update(root);
125
126 // Search uses backend ranking; no sorting
127 sort.setVisible(cwd != null && !mSearchManager.isSearching());
128
129 State state = getDisplayState();
130 grid.setVisible(state.derivedMode != State.MODE_GRID);
131 list.setVisible(state.derivedMode != State.MODE_LIST);
132
133 // Only sort by size when visible
134 sortSize.setVisible(state.showSize);
135
136 advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
137 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
138 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
139 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
140
141 return shown;
142 }
143
Steve McKayef3e2cf2015-04-20 17:18:15 -0700144 void onStackRestored(boolean restored, boolean external) {}
145
146 void onRootPicked(RootInfo root) {
147 State state = getDisplayState();
148
149 // Clear entire backstack and start in new root
150 state.stack.root = root;
151 state.stack.clear();
152 state.stackTouched = true;
153
Steve McKaya78a3692015-04-22 15:55:34 -0700154 mSearchManager.update(root);
155
156 // Recents is always in memory, so we just load it directly.
157 // Otherwise we delegate loading data from disk to a task
158 // to ensure a responsive ui.
159 if (mRoots.isRecentsRoot(root)) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700160 onCurrentDirectoryChanged(ANIM_SIDE);
Steve McKaya78a3692015-04-22 15:55:34 -0700161 } else {
162 new PickRootTask(root).executeOnExecutor(getCurrentExecutor());
Steve McKayef3e2cf2015-04-20 17:18:15 -0700163 }
164 }
165
166 void expandMenus(Menu menu) {
167 for (int i = 0; i < menu.size(); i++) {
168 final MenuItem item = menu.getItem(i);
169 switch (item.getItemId()) {
170 case R.id.menu_advanced:
171 case R.id.menu_file_size:
172 break;
173 default:
174 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
175 }
176 }
177 }
178
179 @Override
180 public boolean onOptionsItemSelected(MenuItem item) {
181 final int id = item.getItemId();
182 if (id == android.R.id.home) {
183 onBackPressed();
184 return true;
185 } else if (id == R.id.menu_create_dir) {
186 CreateDirectoryFragment.show(getFragmentManager());
187 return true;
188 } else if (id == R.id.menu_search) {
189 return false;
190 } else if (id == R.id.menu_sort_name) {
191 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
192 return true;
193 } else if (id == R.id.menu_sort_date) {
194 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
195 return true;
196 } else if (id == R.id.menu_sort_size) {
197 setUserSortOrder(State.SORT_ORDER_SIZE);
198 return true;
199 } else if (id == R.id.menu_grid) {
200 setUserMode(State.MODE_GRID);
201 return true;
202 } else if (id == R.id.menu_list) {
203 setUserMode(State.MODE_LIST);
204 return true;
205 } else if (id == R.id.menu_advanced) {
206 setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this));
207 return true;
208 } else if (id == R.id.menu_file_size) {
209 setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
210 return true;
211 } else if (id == R.id.menu_settings) {
212 final RootInfo root = getCurrentRoot();
213 final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
214 intent.setDataAndType(DocumentsContract.buildRootUri(root.authority, root.rootId),
215 DocumentsContract.Root.MIME_TYPE_ITEM);
216 startActivity(intent);
217 return true;
218 }
219
220 return super.onOptionsItemSelected(item);
221 }
222
223 /**
224 * Call this when directory changes. Prior to root fragment update
225 * the (abstract) directoryChanged method will be called.
226 * @param anim
227 */
228 final void onCurrentDirectoryChanged(int anim) {
229 onDirectoryChanged(anim);
230
231 final RootsFragment roots = RootsFragment.get(getFragmentManager());
232 if (roots != null) {
233 roots.onCurrentRootChanged();
234 }
235
236 updateActionBar();
237 invalidateOptionsMenu();
238 }
239
Ben Kwa77797402015-05-29 15:40:31 -0700240 final List<String> getExcludedAuthorities() {
241 List<String> authorities = new ArrayList<>();
242 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
243 // Exclude roots provided by the calling package.
244 String packageName = getCallingPackageMaybeExtra();
245 try {
246 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
247 PackageManager.GET_PROVIDERS);
248 for (ProviderInfo provider: pkgInfo.providers) {
249 authorities.add(provider.authority);
250 }
251 } catch (PackageManager.NameNotFoundException e) {
252 Log.e(mTag, "Calling package name does not resolve: " + packageName);
253 }
254 }
255 return authorities;
256 }
257
Steve McKayef3e2cf2015-04-20 17:18:15 -0700258 final String getCallingPackageMaybeExtra() {
Ben Kwa77797402015-05-29 15:40:31 -0700259 String callingPackage = getCallingPackage();
260 // System apps can set the calling package name using an extra.
261 try {
262 ApplicationInfo info = getPackageManager().getApplicationInfo(callingPackage, 0);
263 if (info.isSystemApp() || info.isUpdatedSystemApp()) {
264 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
265 if (extra != null) {
266 callingPackage = extra;
267 }
268 }
269 } finally {
270 return callingPackage;
271 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700272 }
Steve McKaye934ce62015-03-25 14:35:33 -0700273
274 public static BaseActivity get(Fragment fragment) {
275 return (BaseActivity) fragment.getActivity();
276 }
277
Daichi Hirono1a2fdb42015-04-15 13:41:18 +0900278 public static abstract class DocumentsIntent {
279 /** Intent action name to open copy destination. */
280 public static String ACTION_OPEN_COPY_DESTINATION =
281 "com.android.documentsui.OPEN_COPY_DESTINATION";
282
283 /**
284 * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which
285 * specifies if the destination directory needs to create new directory or not.
286 */
287 public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
288 }
289
Steve McKaye934ce62015-03-25 14:35:33 -0700290 public static class State implements android.os.Parcelable {
291 public int action;
292 public String[] acceptMimes;
293
294 /** Explicit user choice */
295 public int userMode = MODE_UNKNOWN;
296 /** Derived after loader */
297 public int derivedMode = MODE_LIST;
298
299 /** Explicit user choice */
300 public int userSortOrder = SORT_ORDER_UNKNOWN;
301 /** Derived after loader */
302 public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME;
303
304 public boolean allowMultiple = false;
305 public boolean showSize = false;
306 public boolean localOnly = false;
307 public boolean forceAdvanced = false;
308 public boolean showAdvanced = false;
309 public boolean stackTouched = false;
310 public boolean restored = false;
Daichi Hirono9be34292015-04-14 17:12:54 +0900311 public boolean directoryCopy = false;
Steve McKaye934ce62015-03-25 14:35:33 -0700312
313 /** Current user navigation stack; empty implies recents. */
314 public DocumentStack stack = new DocumentStack();
315 /** Currently active search, overriding any stack. */
316 public String currentSearch;
317
318 /** Instance state for every shown directory */
319 public HashMap<String, SparseArray<Parcelable>> dirState = Maps.newHashMap();
320
Daichi Hironocf0e9ac2015-04-17 16:19:15 +0900321 /** Currently copying file */
322 public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<DocumentInfo>();
323
Ben Kwa77797402015-05-29 15:40:31 -0700324 /** Name of the package that started DocsUI */
325 public List<String> excludedAuthorities = new ArrayList<>();
326
Steve McKaye934ce62015-03-25 14:35:33 -0700327 public static final int ACTION_OPEN = 1;
328 public static final int ACTION_CREATE = 2;
329 public static final int ACTION_GET_CONTENT = 3;
330 public static final int ACTION_OPEN_TREE = 4;
331 public static final int ACTION_MANAGE = 5;
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700332 public static final int ACTION_BROWSE = 6;
333 public static final int ACTION_BROWSE_ALL = 7;
Daichi Hironobbe22922015-04-10 15:50:38 +0900334 public static final int ACTION_OPEN_COPY_DESTINATION = 8;
Steve McKaye934ce62015-03-25 14:35:33 -0700335
336 public static final int MODE_UNKNOWN = 0;
337 public static final int MODE_LIST = 1;
338 public static final int MODE_GRID = 2;
339
340 public static final int SORT_ORDER_UNKNOWN = 0;
341 public static final int SORT_ORDER_DISPLAY_NAME = 1;
342 public static final int SORT_ORDER_LAST_MODIFIED = 2;
343 public static final int SORT_ORDER_SIZE = 3;
344
345 @Override
346 public int describeContents() {
347 return 0;
348 }
349
350 @Override
351 public void writeToParcel(Parcel out, int flags) {
352 out.writeInt(action);
353 out.writeInt(userMode);
354 out.writeStringArray(acceptMimes);
355 out.writeInt(userSortOrder);
356 out.writeInt(allowMultiple ? 1 : 0);
357 out.writeInt(showSize ? 1 : 0);
358 out.writeInt(localOnly ? 1 : 0);
359 out.writeInt(forceAdvanced ? 1 : 0);
360 out.writeInt(showAdvanced ? 1 : 0);
361 out.writeInt(stackTouched ? 1 : 0);
362 out.writeInt(restored ? 1 : 0);
363 DurableUtils.writeToParcel(out, stack);
364 out.writeString(currentSearch);
365 out.writeMap(dirState);
Daichi Hironocf0e9ac2015-04-17 16:19:15 +0900366 out.writeList(selectedDocumentsForCopy);
Ben Kwa77797402015-05-29 15:40:31 -0700367 out.writeList(excludedAuthorities);
Steve McKaye934ce62015-03-25 14:35:33 -0700368 }
369
370 public static final Creator<State> CREATOR = new Creator<State>() {
371 @Override
372 public State createFromParcel(Parcel in) {
373 final State state = new State();
374 state.action = in.readInt();
375 state.userMode = in.readInt();
376 state.acceptMimes = in.readStringArray();
377 state.userSortOrder = in.readInt();
378 state.allowMultiple = in.readInt() != 0;
379 state.showSize = in.readInt() != 0;
380 state.localOnly = in.readInt() != 0;
381 state.forceAdvanced = in.readInt() != 0;
382 state.showAdvanced = in.readInt() != 0;
383 state.stackTouched = in.readInt() != 0;
384 state.restored = in.readInt() != 0;
385 DurableUtils.readFromParcel(in, state.stack);
386 state.currentSearch = in.readString();
387 in.readMap(state.dirState, null);
Daichi Hironocf0e9ac2015-04-17 16:19:15 +0900388 in.readList(state.selectedDocumentsForCopy, null);
Ben Kwa77797402015-05-29 15:40:31 -0700389 in.readList(state.excludedAuthorities, null);
Steve McKaye934ce62015-03-25 14:35:33 -0700390 return state;
391 }
392
393 @Override
394 public State[] newArray(int size) {
395 return new State[size];
396 }
397 };
398 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700399
400 void setDisplayAdvancedDevices(boolean display) {
401 State state = getDisplayState();
402 LocalPreferences.setDisplayAdvancedDevices(this, display);
403 state.showAdvanced = state.forceAdvanced | display;
404 RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
405 invalidateOptionsMenu();
406 }
407
408 void setDisplayFileSize(boolean display) {
409 LocalPreferences.setDisplayFileSize(this, display);
410 getDisplayState().showSize = display;
411 DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
412 invalidateOptionsMenu();
413 }
414
415 void onStateChanged() {
416 invalidateOptionsMenu();
417 }
418
419 /**
420 * Set state sort order based on explicit user action.
421 */
422 void setUserSortOrder(int sortOrder) {
423 getDisplayState().userSortOrder = sortOrder;
424 DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
425 }
426
427 /**
428 * Set state mode based on explicit user action.
429 */
430 void setUserMode(int mode) {
431 getDisplayState().userMode = mode;
432 DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
433 }
434
435 void setPending(boolean pending) {
436 final SaveFragment save = SaveFragment.get(getFragmentManager());
437 if (save != null) {
438 save.setPending(pending);
439 }
440 }
441
442 @Override
443 protected void onSaveInstanceState(Bundle state) {
444 super.onSaveInstanceState(state);
445 state.putParcelable(EXTRA_STATE, getDisplayState());
446 }
447
448 @Override
449 protected void onRestoreInstanceState(Bundle state) {
450 super.onRestoreInstanceState(state);
451 }
452
453 RootInfo getCurrentRoot() {
454 State state = getDisplayState();
455 if (state.stack.root != null) {
456 return state.stack.root;
457 } else {
458 return mRoots.getRecentsRoot();
459 }
460 }
461
462 public DocumentInfo getCurrentDirectory() {
463 return getDisplayState().stack.peek();
464 }
465
466 public Executor getCurrentExecutor() {
467 final DocumentInfo cwd = getCurrentDirectory();
468 if (cwd != null && cwd.authority != null) {
469 return ProviderExecutor.forAuthority(cwd.authority);
470 } else {
471 return AsyncTask.THREAD_POOL_EXECUTOR;
472 }
473 }
474
475 public void onStackPicked(DocumentStack stack) {
476 try {
477 // Update the restored stack to ensure we have freshest data
478 stack.updateDocuments(getContentResolver());
479
480 State state = getDisplayState();
481 state.stack = stack;
482 state.stackTouched = true;
483 onCurrentDirectoryChanged(ANIM_SIDE);
484
485 } catch (FileNotFoundException e) {
486 Log.w(mTag, "Failed to restore stack: " + e);
487 }
488 }
489
490 final class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
491 private RootInfo mRoot;
492
493 public PickRootTask(RootInfo root) {
494 mRoot = root;
495 }
496
497 @Override
498 protected DocumentInfo doInBackground(Void... params) {
499 try {
500 final Uri uri = DocumentsContract.buildDocumentUri(
501 mRoot.authority, mRoot.documentId);
502 return DocumentInfo.fromUri(getContentResolver(), uri);
503 } catch (FileNotFoundException e) {
504 Log.w(mTag, "Failed to find root", e);
505 return null;
506 }
507 }
508
509 @Override
510 protected void onPostExecute(DocumentInfo result) {
511 if (result != null) {
512 State state = getDisplayState();
513 state.stack.push(result);
514 state.stackTouched = true;
515 onCurrentDirectoryChanged(ANIM_SIDE);
516 }
517 }
518 }
519
520 final class RestoreStackTask extends AsyncTask<Void, Void, Void> {
521 private volatile boolean mRestoredStack;
522 private volatile boolean mExternal;
523
524 @Override
525 protected Void doInBackground(Void... params) {
526 State state = getDisplayState();
527 RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);
528
529 // Restore last stack for calling package
530 final String packageName = getCallingPackageMaybeExtra();
531 final Cursor cursor = getContentResolver()
532 .query(RecentsProvider.buildResume(packageName), null, null, null, null);
533 try {
534 if (cursor.moveToFirst()) {
535 mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
536 final byte[] rawStack = cursor.getBlob(
537 cursor.getColumnIndex(ResumeColumns.STACK));
538 DurableUtils.readFromArray(rawStack, state.stack);
539 mRestoredStack = true;
540 }
541 } catch (IOException e) {
542 Log.w(mTag, "Failed to resume: " + e);
543 } finally {
544 IoUtils.closeQuietly(cursor);
545 }
546
547 if (mRestoredStack) {
548 // Update the restored stack to ensure we have freshest data
549 final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(state);
550 try {
551 state.stack.updateRoot(matchingRoots);
552 state.stack.updateDocuments(getContentResolver());
553 } catch (FileNotFoundException e) {
554 Log.w(mTag, "Failed to restore stack: " + e);
555 state.stack.reset();
556 mRestoredStack = false;
557 }
558 }
559
560 return null;
561 }
562
563 @Override
564 protected void onPostExecute(Void result) {
565 if (isDestroyed()) return;
566 getDisplayState().restored = true;
567 onCurrentDirectoryChanged(ANIM_NONE);
568
569 onStackRestored(mRestoredStack, mExternal);
570
571 getDisplayState().restored = true;
572 onCurrentDirectoryChanged(ANIM_NONE);
573 }
574 }
575
576 final class ItemSelectedListener implements OnItemSelectedListener {
577
578 boolean mIgnoreNextNavigation;
579
580 @Override
581 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
582 if (mIgnoreNextNavigation) {
583 mIgnoreNextNavigation = false;
584 return;
585 }
586
587 State state = getDisplayState();
588 while (state.stack.size() > position + 1) {
589 state.stackTouched = true;
590 state.stack.pop();
591 }
592 onCurrentDirectoryChanged(ANIM_UP);
593 }
594
595 @Override
596 public void onNothingSelected(AdapterView<?> parent) {
597 // Ignored
598 }
599 }
600
601 /**
602 * Class providing toolbar with runtime access to useful activity data.
603 */
604 final class StackAdapter extends BaseAdapter {
605 @Override
606 public int getCount() {
607 return getDisplayState().stack.size();
608 }
609
610 @Override
611 public DocumentInfo getItem(int position) {
612 State state = getDisplayState();
613 return state.stack.get(state.stack.size() - position - 1);
614 }
615
616 @Override
617 public long getItemId(int position) {
618 return position;
619 }
620
621 @Override
622 public View getView(int position, View convertView, ViewGroup parent) {
623 if (convertView == null) {
624 convertView = LayoutInflater.from(parent.getContext())
625 .inflate(R.layout.item_subdir_title, parent, false);
626 }
627
628 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
629 final DocumentInfo doc = getItem(position);
630
631 if (position == 0) {
632 final RootInfo root = getCurrentRoot();
633 title.setText(root.title);
634 } else {
635 title.setText(doc.displayName);
636 }
637
638 return convertView;
639 }
640
641 @Override
642 public View getDropDownView(int position, View convertView, ViewGroup parent) {
643 if (convertView == null) {
644 convertView = LayoutInflater.from(parent.getContext())
645 .inflate(R.layout.item_subdir, parent, false);
646 }
647
648 final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
649 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
650 final DocumentInfo doc = getItem(position);
651
652 if (position == 0) {
653 final RootInfo root = getCurrentRoot();
654 title.setText(root.title);
655 subdir.setVisibility(View.GONE);
656 } else {
657 title.setText(doc.displayName);
658 subdir.setVisibility(View.VISIBLE);
659 }
660
661 return convertView;
662 }
663 }
664
665 /**
666 * Facade over the various search parts in the menu.
667 */
668 final class SearchManager implements
Daichi Hirono186efa32015-06-11 14:29:28 +0900669 SearchView.OnCloseListener, OnActionExpandListener, OnQueryTextListener,
670 DocumentsToolBar.OnActionViewCollapsedListener {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700671
Steve McKaya78a3692015-04-22 15:55:34 -0700672 private boolean mSearchExpanded;
673 private boolean mIgnoreNextClose;
674 private boolean mIgnoreNextCollapse;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700675
Daichi Hirono186efa32015-06-11 14:29:28 +0900676 private DocumentsToolBar mActionBar;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700677 private MenuItem mMenu;
678 private SearchView mView;
679
Daichi Hirono186efa32015-06-11 14:29:28 +0900680 public void install(DocumentsToolBar actionBar) {
681 assert(mActionBar == null);
682 mActionBar = actionBar;
683 mMenu = actionBar.getSearchMenu();
684 mView = (SearchView) mMenu.getActionView();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700685
Daichi Hirono186efa32015-06-11 14:29:28 +0900686 mActionBar.setOnActionViewCollapsedListener(this);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700687 mMenu.setOnActionExpandListener(this);
688 mView.setOnQueryTextListener(this);
689 mView.setOnCloseListener(this);
690 }
691
692 /**
693 * @param root Info about the current directory.
694 */
695 void update(RootInfo root) {
696 if (mMenu == null) {
Steve McKaya78a3692015-04-22 15:55:34 -0700697 Log.d(mTag, "update called before Search MenuItem installed.");
Steve McKayef3e2cf2015-04-20 17:18:15 -0700698 return;
699 }
Steve McKaya78a3692015-04-22 15:55:34 -0700700
Steve McKayef3e2cf2015-04-20 17:18:15 -0700701 State state = getDisplayState();
702 if (state.currentSearch != null) {
703 mMenu.expandActionView();
704
705 mView.setIconified(false);
706 mView.clearFocus();
707 mView.setQuery(state.currentSearch, false);
708 } else {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700709 mView.clearFocus();
Steve McKaya78a3692015-04-22 15:55:34 -0700710 if (!mView.isIconified()) {
711 mIgnoreNextClose = true;
712 mView.setIconified(true);
713 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700714
Steve McKaya78a3692015-04-22 15:55:34 -0700715 if (mMenu.isActionViewExpanded()) {
716 mIgnoreNextCollapse = true;
717 mMenu.collapseActionView();
718 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700719 }
720
721 showMenu(root != null
722 && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0));
723 }
724
725 void showMenu(boolean visible) {
726 if (mMenu == null) {
727 Log.d(mTag, "showMenu called before Search MenuItem installed.");
728 return;
729 }
Steve McKaya78a3692015-04-22 15:55:34 -0700730
Steve McKayef3e2cf2015-04-20 17:18:15 -0700731 mMenu.setVisible(visible);
Steve McKaya78a3692015-04-22 15:55:34 -0700732 if (!visible) {
733 getDisplayState().currentSearch = null;
734 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700735 }
736
Daichi Hirono186efa32015-06-11 14:29:28 +0900737 /**
738 * Cancels current search operation.
739 * @return True if it cancels search. False if it does not operate
740 * search currently.
741 */
742 boolean cancelSearch() {
743 if (mActionBar.hasExpandedActionView()) {
744 mActionBar.collapseActionView();
745 return true;
746 }
747 return false;
748 }
749
Steve McKayef3e2cf2015-04-20 17:18:15 -0700750 boolean isSearching() {
751 return getDisplayState().currentSearch != null;
752 }
753
754 boolean isExpanded() {
755 return mSearchExpanded;
756 }
757
758 @Override
759 public boolean onClose() {
760 mSearchExpanded = false;
761 if (mIgnoreNextClose) {
762 mIgnoreNextClose = false;
763 return false;
764 }
765
766 getDisplayState().currentSearch = null;
767 onCurrentDirectoryChanged(ANIM_NONE);
768 return false;
769 }
Steve McKaya78a3692015-04-22 15:55:34 -0700770
Steve McKayef3e2cf2015-04-20 17:18:15 -0700771 @Override
772 public boolean onMenuItemActionExpand(MenuItem item) {
773 mSearchExpanded = true;
774 updateActionBar();
775 return true;
776 }
777
778 @Override
779 public boolean onMenuItemActionCollapse(MenuItem item) {
780 mSearchExpanded = false;
781 if (mIgnoreNextCollapse) {
782 mIgnoreNextCollapse = false;
783 return true;
784 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700785 getDisplayState().currentSearch = null;
786 onCurrentDirectoryChanged(ANIM_NONE);
787 return true;
788 }
Steve McKaya78a3692015-04-22 15:55:34 -0700789
Steve McKayef3e2cf2015-04-20 17:18:15 -0700790 @Override
791 public boolean onQueryTextSubmit(String query) {
792 mSearchExpanded = true;
793 getDisplayState().currentSearch = query;
794 mView.clearFocus();
795 onCurrentDirectoryChanged(ANIM_NONE);
796 return true;
797 }
798
799 @Override
800 public boolean onQueryTextChange(String newText) {
801 return false;
802 }
Daichi Hirono186efa32015-06-11 14:29:28 +0900803
804 @Override
805 public void onActionViewCollapsed() {
806 updateActionBar();
807 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700808 }
Steve McKaye934ce62015-03-25 14:35:33 -0700809}