blob: 5cb3b0d5545fdf7caf21102a95464a75bc548252 [file] [log] [blame]
Steve McKayd0a2a2c2015-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 McKay4d0255f2015-09-25 16:02:56 -070019import static com.android.documentsui.Shared.DEBUG;
Steve McKayf68210e2015-11-03 15:23:16 -080020import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_DOWN;
21import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
22import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_SIDE;
23import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_UP;
Steve McKay351a7492015-08-04 10:11:01 -070024import static com.android.internal.util.Preconditions.checkArgument;
Steve McKayb68dd222015-04-20 17:18:15 -070025
Steve McKayd0a2a2c2015-03-25 14:35:33 -070026import android.app.Activity;
27import android.app.Fragment;
Steve McKayb68dd222015-04-20 17:18:15 -070028import android.content.Intent;
Ben Kwa0bcdec32015-05-29 15:40:31 -070029import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageInfo;
31import android.content.pm.PackageManager;
32import android.content.pm.ProviderInfo;
Steve McKayb68dd222015-04-20 17:18:15 -070033import android.database.Cursor;
34import android.net.Uri;
35import android.os.AsyncTask;
36import android.os.Bundle;
Steve McKayb68dd222015-04-20 17:18:15 -070037import android.provider.DocumentsContract;
38import android.provider.DocumentsContract.Root;
Steve McKay0fbfc652015-08-20 16:48:49 -070039import android.support.annotation.LayoutRes;
Steve McKayfefcd702015-08-20 16:19:38 +000040import android.support.annotation.Nullable;
Steve McKayb68dd222015-04-20 17:18:15 -070041import android.util.Log;
Ben Kwae1fffca2015-10-05 18:10:05 -070042import android.view.KeyEvent;
Steve McKayb68dd222015-04-20 17:18:15 -070043import android.view.LayoutInflater;
44import android.view.Menu;
45import android.view.MenuItem;
46import android.view.MenuItem.OnActionExpandListener;
47import android.view.View;
48import android.view.ViewGroup;
49import android.widget.AdapterView;
50import android.widget.AdapterView.OnItemSelectedListener;
51import android.widget.BaseAdapter;
52import android.widget.ImageView;
53import android.widget.SearchView;
54import android.widget.SearchView.OnQueryTextListener;
55import android.widget.TextView;
Steve McKayd0a2a2c2015-03-25 14:35:33 -070056
Steve McKayb68dd222015-04-20 17:18:15 -070057import com.android.documentsui.RecentsProvider.ResumeColumns;
Steve McKayf68210e2015-11-03 15:23:16 -080058import com.android.documentsui.dirlist.DirectoryFragment;
Steve McKayd0a2a2c2015-03-25 14:35:33 -070059import com.android.documentsui.model.DocumentInfo;
60import com.android.documentsui.model.DocumentStack;
61import com.android.documentsui.model.DurableUtils;
62import com.android.documentsui.model.RootInfo;
Steve McKay64ac2512015-05-12 12:49:58 -070063
Steve McKay64ac2512015-05-12 12:49:58 -070064import libcore.io.IoUtils;
65
66import java.io.FileNotFoundException;
67import java.io.IOException;
68import java.util.ArrayList;
69import java.util.Collection;
Steve McKay64ac2512015-05-12 12:49:58 -070070import java.util.List;
71import java.util.concurrent.Executor;
72
Steve McKayf68210e2015-11-03 15:23:16 -080073public abstract class BaseActivity extends Activity {
Steve McKayb68dd222015-04-20 17:18:15 -070074
75 static final String EXTRA_STATE = "state";
76
Steve McKay0fbfc652015-08-20 16:48:49 -070077 State mState;
Steve McKayb68dd222015-04-20 17:18:15 -070078 RootsCache mRoots;
Steve McKay0269fb62015-04-22 15:55:34 -070079 SearchManager mSearchManager;
Steve McKay0fbfc652015-08-20 16:48:49 -070080 DrawerController mDrawer;
Steve McKayf2c8b0d2015-09-23 15:44:24 -070081 boolean mProductivityDevice;
Steve McKay0269fb62015-04-22 15:55:34 -070082
Steve McKayf2c8b0d2015-09-23 15:44:24 -070083 private final String mTag;
Steve McKay0fbfc652015-08-20 16:48:49 -070084 @LayoutRes
85 private int mLayoutId;
Steve McKayfad3d4a2015-09-22 15:09:21 -070086 private DirectoryContainerView mDirectoryContainer;
Steve McKayb68dd222015-04-20 17:18:15 -070087
Ben Kwab8a5e082015-12-07 13:25:27 -080088 public abstract void onDocumentPicked(DocumentInfo doc, @Nullable SiblingProvider siblings);
Steve McKayd0a2a2c2015-03-25 14:35:33 -070089 public abstract void onDocumentsPicked(List<DocumentInfo> docs);
Steve McKay351a7492015-08-04 10:11:01 -070090
Steve McKayb68dd222015-04-20 17:18:15 -070091 abstract void onTaskFinished(Uri... uris);
92 abstract void onDirectoryChanged(int anim);
93 abstract void updateActionBar();
94 abstract void saveStackBlocking();
Ben Kwa0574b182015-09-08 07:31:19 -070095 abstract State buildState();
Steve McKayb68dd222015-04-20 17:18:15 -070096
Steve McKay0fbfc652015-08-20 16:48:49 -070097 public BaseActivity(@LayoutRes int layoutId, String tag) {
98 mLayoutId = layoutId;
Steve McKayb68dd222015-04-20 17:18:15 -070099 mTag = tag;
100 }
101
102 @Override
103 public void onCreate(Bundle icicle) {
104 super.onCreate(icicle);
Steve McKay0fbfc652015-08-20 16:48:49 -0700105
Steve McKayf2c8b0d2015-09-23 15:44:24 -0700106 mProductivityDevice = getResources().getBoolean(R.bool.productivity_device);
Steve McKay0fbfc652015-08-20 16:48:49 -0700107 mState = (icicle != null)
108 ? icicle.<State>getParcelable(EXTRA_STATE)
Ben Kwa0574b182015-09-08 07:31:19 -0700109 : buildState();
Steve McKay0fbfc652015-08-20 16:48:49 -0700110
Steve McKay7f395012015-08-24 10:34:49 -0700111 setContentView(mLayoutId);
Steve McKay0fbfc652015-08-20 16:48:49 -0700112
Steve McKayb68dd222015-04-20 17:18:15 -0700113 mRoots = DocumentsApplication.getRootsCache(this);
Steve McKayfad3d4a2015-09-22 15:09:21 -0700114 mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory);
Steve McKay0269fb62015-04-22 15:55:34 -0700115 mSearchManager = new SearchManager();
Steve McKay0fbfc652015-08-20 16:48:49 -0700116
117 // Base classes must update result in their onCreate.
118 setResult(Activity.RESULT_CANCELED);
Steve McKay0269fb62015-04-22 15:55:34 -0700119 }
120
121 @Override
122 public boolean onCreateOptionsMenu(Menu menu) {
123 boolean showMenu = super.onCreateOptionsMenu(menu);
124
125 getMenuInflater().inflate(R.menu.activity, menu);
Daichi Hironoe577ef02015-06-11 14:29:28 +0900126 mSearchManager.install((DocumentsToolBar) findViewById(R.id.toolbar));
Steve McKay0269fb62015-04-22 15:55:34 -0700127
128 return showMenu;
Steve McKayb68dd222015-04-20 17:18:15 -0700129 }
130
Steve McKay7bd32e12015-04-30 16:12:59 -0700131 @Override
132 public boolean onPrepareOptionsMenu(Menu menu) {
Steve McKaye9809272015-10-01 11:39:24 -0700133 super.onPrepareOptionsMenu(menu);
Steve McKay7bd32e12015-04-30 16:12:59 -0700134
135 final RootInfo root = getCurrentRoot();
Steve McKaye9809272015-10-01 11:39:24 -0700136 final boolean inRecents = getCurrentDirectory() == null;
Steve McKay7bd32e12015-04-30 16:12:59 -0700137
138 final MenuItem sort = menu.findItem(R.id.menu_sort);
139 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
140 final MenuItem grid = menu.findItem(R.id.menu_grid);
141 final MenuItem list = menu.findItem(R.id.menu_list);
Steve McKay7bd32e12015-04-30 16:12:59 -0700142 final MenuItem advanced = menu.findItem(R.id.menu_advanced);
143 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
Ben Kwa0574b182015-09-08 07:31:19 -0700144 final MenuItem settings = menu.findItem(R.id.menu_settings);
Steve McKay7bd32e12015-04-30 16:12:59 -0700145
Steve McKaye9809272015-10-01 11:39:24 -0700146 // I'm thinkin' this isn't necesary here. If it is...'cuz of a bug....
147 // then uncomment the linke and let's get a proper bug reference here.
148 // mSearchManager.update(root);
Steve McKay7bd32e12015-04-30 16:12:59 -0700149
150 // Search uses backend ranking; no sorting
Steve McKaye9809272015-10-01 11:39:24 -0700151 sort.setVisible(!inRecents && !mSearchManager.isSearching());
152
153 // grid/list is effectively a toggle.
154 grid.setVisible(mState.derivedMode != State.MODE_GRID);
155 list.setVisible(mState.derivedMode != State.MODE_LIST);
156
157 sortSize.setVisible(mState.showSize); // Only sort by size when visible
158 fileSize.setVisible(!mState.forceSize);
159 advanced.setVisible(!mState.forceAdvanced);
160 settings.setVisible((root.flags & Root.FLAG_HAS_SETTINGS) != 0);
Steve McKay7bd32e12015-04-30 16:12:59 -0700161
Steve McKay7bd32e12015-04-30 16:12:59 -0700162 advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
163 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
164 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
165 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
166
Steve McKaye9809272015-10-01 11:39:24 -0700167 return true;
Steve McKay7bd32e12015-04-30 16:12:59 -0700168 }
169
Ben Kwa0574b182015-09-08 07:31:19 -0700170 State buildDefaultState() {
171 State state = new State();
172
173 final Intent intent = getIntent();
Ben Kwa0574b182015-09-08 07:31:19 -0700174
175 state.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
Ben Kwa0574b182015-09-08 07:31:19 -0700176
Steve McKay459bc2b2015-09-16 15:07:31 -0700177 state.forceSize = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, false);
178 state.showSize = state.forceSize || LocalPreferences.getDisplayFileSize(this);
179
180 state.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
181 state.showAdvanced = state.forceAdvanced
182 || LocalPreferences.getDisplayAdvancedDevices(this);
183
184 state.initAcceptMimes(intent);
Ben Kwa0574b182015-09-08 07:31:19 -0700185 state.excludedAuthorities = getExcludedAuthorities();
186
187 return state;
188 }
189
Steve McKayb68dd222015-04-20 17:18:15 -0700190 void onStackRestored(boolean restored, boolean external) {}
191
192 void onRootPicked(RootInfo root) {
Steve McKayb68dd222015-04-20 17:18:15 -0700193 // Clear entire backstack and start in new root
Steve McKay4d0255f2015-09-25 16:02:56 -0700194 mState.stack.root = root;
195 mState.stack.clear();
196 mState.stackTouched = true;
Steve McKayb68dd222015-04-20 17:18:15 -0700197
Steve McKay0269fb62015-04-22 15:55:34 -0700198 mSearchManager.update(root);
199
200 // Recents is always in memory, so we just load it directly.
201 // Otherwise we delegate loading data from disk to a task
202 // to ensure a responsive ui.
203 if (mRoots.isRecentsRoot(root)) {
Steve McKayb68dd222015-04-20 17:18:15 -0700204 onCurrentDirectoryChanged(ANIM_SIDE);
Steve McKay0269fb62015-04-22 15:55:34 -0700205 } else {
Steve McKay459bc2b2015-09-16 15:07:31 -0700206 new PickRootTask(root).executeOnExecutor(getExecutorForCurrentDirectory());
Steve McKayb68dd222015-04-20 17:18:15 -0700207 }
208 }
209
210 void expandMenus(Menu menu) {
211 for (int i = 0; i < menu.size(); i++) {
212 final MenuItem item = menu.getItem(i);
213 switch (item.getItemId()) {
214 case R.id.menu_advanced:
215 case R.id.menu_file_size:
Steve McKayf2c8b0d2015-09-23 15:44:24 -0700216 case R.id.menu_new_window:
Steve McKayb68dd222015-04-20 17:18:15 -0700217 break;
218 default:
219 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
220 }
221 }
222 }
223
224 @Override
225 public boolean onOptionsItemSelected(MenuItem item) {
Steve McKayb68dd222015-04-20 17:18:15 -0700226
Steve McKay6035b3c2015-12-04 11:19:09 -0800227 switch (item.getItemId()) {
228 case android.R.id.home:
229 onBackPressed();
230 return true;
231
232 case R.id.menu_create_dir:
233 showCreateDirectoryDialog();
234 return true;
235
236 case R.id.menu_search:
237 return false;
238
239 case R.id.menu_sort_name:
240 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
241 return true;
242
243 case R.id.menu_sort_date:
244 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
245 return true;
246 case R.id.menu_sort_size:
247 setUserSortOrder(State.SORT_ORDER_SIZE);
248 return true;
249
250 case R.id.menu_grid:
251 setUserMode(State.MODE_GRID);
252 return true;
253
254 case R.id.menu_list:
255 setUserMode(State.MODE_LIST);
256 return true;
257
258 case R.id.menu_paste_from_clipboard:
259 DirectoryFragment.get(getFragmentManager())
260 .pasteFromClipboard();
261 return true;
262
263 case R.id.menu_advanced:
264 setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this));
265 return true;
266
267 case R.id.menu_file_size:
268 setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
269 return true;
270
271 case R.id.menu_settings:
272 final RootInfo root = getCurrentRoot();
273 final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
274 intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
275 startActivity(intent);
276 return true;
277
278 default:
279 return super.onOptionsItemSelected(item);
280 }
Steve McKayb68dd222015-04-20 17:18:15 -0700281 }
282
Steve McKayceeb3f72015-05-19 16:10:25 -0700283 void showCreateDirectoryDialog() {
284 CreateDirectoryFragment.show(getFragmentManager());
285 }
286
287 /**
288 * Returns true if a directory can be created in the current location.
289 * @return
290 */
291 boolean canCreateDirectory() {
292 final RootInfo root = getCurrentRoot();
293 final DocumentInfo cwd = getCurrentDirectory();
294 return cwd != null
295 && cwd.isCreateSupported()
296 && !mSearchManager.isSearching()
Steve McKaye9809272015-10-01 11:39:24 -0700297 && !root.isRecents()
Steve McKayceeb3f72015-05-19 16:10:25 -0700298 && !root.isDownloads();
299 }
300
Steve McKay351a7492015-08-04 10:11:01 -0700301 void onDirectoryCreated(DocumentInfo doc) {
302 checkArgument(doc.isDirectory());
Tomasz Mikolajewski734936a2015-11-25 13:01:18 +0900303 openContainerDocument(doc);
Steve McKay351a7492015-08-04 10:11:01 -0700304 }
305
Tomasz Mikolajewski734936a2015-11-25 13:01:18 +0900306 void openContainerDocument(DocumentInfo doc) {
307 checkArgument(doc.isContainer());
Steve McKay4d0255f2015-09-25 16:02:56 -0700308 mState.stack.push(doc);
309 mState.stackTouched = true;
Steve McKay351a7492015-08-04 10:11:01 -0700310 onCurrentDirectoryChanged(ANIM_DOWN);
311 }
312
Steve McKayb68dd222015-04-20 17:18:15 -0700313 /**
314 * Call this when directory changes. Prior to root fragment update
315 * the (abstract) directoryChanged method will be called.
316 * @param anim
317 */
318 final void onCurrentDirectoryChanged(int anim) {
Steve McKayfad3d4a2015-09-22 15:09:21 -0700319 mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN);
Steve McKayb68dd222015-04-20 17:18:15 -0700320 onDirectoryChanged(anim);
321
322 final RootsFragment roots = RootsFragment.get(getFragmentManager());
323 if (roots != null) {
324 roots.onCurrentRootChanged();
325 }
326
327 updateActionBar();
328 invalidateOptionsMenu();
329 }
330
Ben Kwa0bcdec32015-05-29 15:40:31 -0700331 final List<String> getExcludedAuthorities() {
332 List<String> authorities = new ArrayList<>();
333 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
334 // Exclude roots provided by the calling package.
335 String packageName = getCallingPackageMaybeExtra();
336 try {
337 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
338 PackageManager.GET_PROVIDERS);
339 for (ProviderInfo provider: pkgInfo.providers) {
340 authorities.add(provider.authority);
341 }
342 } catch (PackageManager.NameNotFoundException e) {
343 Log.e(mTag, "Calling package name does not resolve: " + packageName);
344 }
345 }
346 return authorities;
347 }
348
Steve McKayb68dd222015-04-20 17:18:15 -0700349 final String getCallingPackageMaybeExtra() {
Ben Kwa0bcdec32015-05-29 15:40:31 -0700350 String callingPackage = getCallingPackage();
351 // System apps can set the calling package name using an extra.
352 try {
353 ApplicationInfo info = getPackageManager().getApplicationInfo(callingPackage, 0);
354 if (info.isSystemApp() || info.isUpdatedSystemApp()) {
355 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
356 if (extra != null) {
357 callingPackage = extra;
358 }
359 }
360 } finally {
361 return callingPackage;
362 }
Steve McKayb68dd222015-04-20 17:18:15 -0700363 }
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700364
365 public static BaseActivity get(Fragment fragment) {
366 return (BaseActivity) fragment.getActivity();
367 }
368
Ben Kwa0574b182015-09-08 07:31:19 -0700369 public State getDisplayState() {
370 return mState;
371 }
372
Steve McKayb68dd222015-04-20 17:18:15 -0700373 void setDisplayAdvancedDevices(boolean display) {
Steve McKayb68dd222015-04-20 17:18:15 -0700374 LocalPreferences.setDisplayAdvancedDevices(this, display);
Steve McKay4d0255f2015-09-25 16:02:56 -0700375 mState.showAdvanced = mState.forceAdvanced | display;
Steve McKayb68dd222015-04-20 17:18:15 -0700376 RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
377 invalidateOptionsMenu();
378 }
379
380 void setDisplayFileSize(boolean display) {
381 LocalPreferences.setDisplayFileSize(this, display);
Steve McKay4d0255f2015-09-25 16:02:56 -0700382 mState.showSize = display;
Steve McKayb68dd222015-04-20 17:18:15 -0700383 DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
384 invalidateOptionsMenu();
385 }
386
Steve McKayf68210e2015-11-03 15:23:16 -0800387 public void onStateChanged() {
Steve McKayb68dd222015-04-20 17:18:15 -0700388 invalidateOptionsMenu();
389 }
390
391 /**
392 * Set state sort order based on explicit user action.
393 */
394 void setUserSortOrder(int sortOrder) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700395 mState.userSortOrder = sortOrder;
Steve McKayb68dd222015-04-20 17:18:15 -0700396 DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
397 }
398
399 /**
400 * Set state mode based on explicit user action.
401 */
402 void setUserMode(int mode) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700403 mState.userMode = mode;
Steve McKayb68dd222015-04-20 17:18:15 -0700404 DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
405 }
406
407 void setPending(boolean pending) {
408 final SaveFragment save = SaveFragment.get(getFragmentManager());
409 if (save != null) {
410 save.setPending(pending);
411 }
412 }
413
414 @Override
415 protected void onSaveInstanceState(Bundle state) {
416 super.onSaveInstanceState(state);
Steve McKay4d0255f2015-09-25 16:02:56 -0700417 state.putParcelable(EXTRA_STATE, mState);
Steve McKayb68dd222015-04-20 17:18:15 -0700418 }
419
420 @Override
421 protected void onRestoreInstanceState(Bundle state) {
422 super.onRestoreInstanceState(state);
423 }
424
Steve McKayf68210e2015-11-03 15:23:16 -0800425 public RootInfo getCurrentRoot() {
Steve McKay4d0255f2015-09-25 16:02:56 -0700426 if (mState.stack.root != null) {
427 return mState.stack.root;
Steve McKayb68dd222015-04-20 17:18:15 -0700428 } else {
429 return mRoots.getRecentsRoot();
430 }
431 }
432
433 public DocumentInfo getCurrentDirectory() {
Steve McKay4d0255f2015-09-25 16:02:56 -0700434 return mState.stack.peek();
Steve McKayb68dd222015-04-20 17:18:15 -0700435 }
436
Steve McKay459bc2b2015-09-16 15:07:31 -0700437 public Executor getExecutorForCurrentDirectory() {
Steve McKayb68dd222015-04-20 17:18:15 -0700438 final DocumentInfo cwd = getCurrentDirectory();
439 if (cwd != null && cwd.authority != null) {
440 return ProviderExecutor.forAuthority(cwd.authority);
441 } else {
442 return AsyncTask.THREAD_POOL_EXECUTOR;
443 }
444 }
445
Steve McKay0fbfc652015-08-20 16:48:49 -0700446 @Override
447 public void onBackPressed() {
448 // While action bar is expanded, the state stack UI is hidden.
449 if (mSearchManager.cancelSearch()) {
450 return;
451 }
452
453 if (!mState.stackTouched) {
454 super.onBackPressed();
455 return;
456 }
457
458 final int size = mState.stack.size();
459
460 if (mDrawer.isOpen()) {
461 mDrawer.setOpen(false);
462 } else if (size > 1) {
463 mState.stack.pop();
464 onCurrentDirectoryChanged(ANIM_UP);
465 } else {
466 super.onBackPressed();
467 }
468 }
469
Steve McKayb68dd222015-04-20 17:18:15 -0700470 public void onStackPicked(DocumentStack stack) {
471 try {
472 // Update the restored stack to ensure we have freshest data
473 stack.updateDocuments(getContentResolver());
474
Steve McKay4d0255f2015-09-25 16:02:56 -0700475 mState.stack = stack;
476 mState.stackTouched = true;
Steve McKayb68dd222015-04-20 17:18:15 -0700477 onCurrentDirectoryChanged(ANIM_SIDE);
478
479 } catch (FileNotFoundException e) {
480 Log.w(mTag, "Failed to restore stack: " + e);
481 }
482 }
483
484 final class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
485 private RootInfo mRoot;
486
487 public PickRootTask(RootInfo root) {
488 mRoot = root;
489 }
490
491 @Override
492 protected DocumentInfo doInBackground(Void... params) {
493 try {
494 final Uri uri = DocumentsContract.buildDocumentUri(
495 mRoot.authority, mRoot.documentId);
496 return DocumentInfo.fromUri(getContentResolver(), uri);
497 } catch (FileNotFoundException e) {
498 Log.w(mTag, "Failed to find root", e);
499 return null;
500 }
501 }
502
503 @Override
504 protected void onPostExecute(DocumentInfo result) {
505 if (result != null) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700506 mState.stack.push(result);
507 mState.stackTouched = true;
Steve McKayb68dd222015-04-20 17:18:15 -0700508 onCurrentDirectoryChanged(ANIM_SIDE);
509 }
510 }
511 }
512
513 final class RestoreStackTask extends AsyncTask<Void, Void, Void> {
514 private volatile boolean mRestoredStack;
515 private volatile boolean mExternal;
516
517 @Override
518 protected Void doInBackground(Void... params) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700519 if (DEBUG && !mState.stack.isEmpty()) {
520 Log.w(mTag, "Overwriting existing stack.");
521 }
Steve McKayb68dd222015-04-20 17:18:15 -0700522 RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);
523
524 // Restore last stack for calling package
525 final String packageName = getCallingPackageMaybeExtra();
526 final Cursor cursor = getContentResolver()
527 .query(RecentsProvider.buildResume(packageName), null, null, null, null);
528 try {
529 if (cursor.moveToFirst()) {
530 mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
531 final byte[] rawStack = cursor.getBlob(
532 cursor.getColumnIndex(ResumeColumns.STACK));
Steve McKay4d0255f2015-09-25 16:02:56 -0700533 DurableUtils.readFromArray(rawStack, mState.stack);
Steve McKayb68dd222015-04-20 17:18:15 -0700534 mRestoredStack = true;
535 }
536 } catch (IOException e) {
537 Log.w(mTag, "Failed to resume: " + e);
538 } finally {
539 IoUtils.closeQuietly(cursor);
540 }
541
542 if (mRestoredStack) {
543 // Update the restored stack to ensure we have freshest data
Steve McKay4d0255f2015-09-25 16:02:56 -0700544 final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
Steve McKayb68dd222015-04-20 17:18:15 -0700545 try {
Steve McKay4d0255f2015-09-25 16:02:56 -0700546 mState.stack.updateRoot(matchingRoots);
547 mState.stack.updateDocuments(getContentResolver());
Steve McKayb68dd222015-04-20 17:18:15 -0700548 } catch (FileNotFoundException e) {
549 Log.w(mTag, "Failed to restore stack: " + e);
Steve McKay4d0255f2015-09-25 16:02:56 -0700550 mState.stack.reset();
Steve McKayb68dd222015-04-20 17:18:15 -0700551 mRestoredStack = false;
552 }
553 }
554
555 return null;
556 }
557
558 @Override
559 protected void onPostExecute(Void result) {
560 if (isDestroyed()) return;
Steve McKay4d0255f2015-09-25 16:02:56 -0700561 mState.restored = true;
Steve McKayb68dd222015-04-20 17:18:15 -0700562 onCurrentDirectoryChanged(ANIM_NONE);
Steve McKayb68dd222015-04-20 17:18:15 -0700563 onStackRestored(mRestoredStack, mExternal);
Steve McKayb68dd222015-04-20 17:18:15 -0700564 }
565 }
566
Ben Kwa0574b182015-09-08 07:31:19 -0700567 final class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> {
568 private Uri mRootUri;
569
570 public RestoreRootTask(Uri rootUri) {
571 mRootUri = rootUri;
572 }
573
574 @Override
575 protected RootInfo doInBackground(Void... params) {
576 final String rootId = DocumentsContract.getRootId(mRootUri);
577 return mRoots.getRootOneshot(mRootUri.getAuthority(), rootId);
578 }
579
580 @Override
581 protected void onPostExecute(RootInfo root) {
582 if (isDestroyed()) return;
583 mState.restored = true;
584
585 if (root != null) {
586 onRootPicked(root);
587 } else {
588 Log.w(mTag, "Failed to find root: " + mRootUri);
589 finish();
590 }
591 }
592 }
593
Steve McKayb68dd222015-04-20 17:18:15 -0700594 final class ItemSelectedListener implements OnItemSelectedListener {
595
596 boolean mIgnoreNextNavigation;
597
598 @Override
599 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
600 if (mIgnoreNextNavigation) {
601 mIgnoreNextNavigation = false;
602 return;
603 }
604
Steve McKay4d0255f2015-09-25 16:02:56 -0700605 while (mState.stack.size() > position + 1) {
606 mState.stackTouched = true;
607 mState.stack.pop();
Steve McKayb68dd222015-04-20 17:18:15 -0700608 }
609 onCurrentDirectoryChanged(ANIM_UP);
610 }
611
612 @Override
613 public void onNothingSelected(AdapterView<?> parent) {
614 // Ignored
615 }
616 }
617
618 /**
619 * Class providing toolbar with runtime access to useful activity data.
620 */
621 final class StackAdapter extends BaseAdapter {
622 @Override
623 public int getCount() {
Steve McKay4d0255f2015-09-25 16:02:56 -0700624 return mState.stack.size();
Steve McKayb68dd222015-04-20 17:18:15 -0700625 }
626
627 @Override
628 public DocumentInfo getItem(int position) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700629 return mState.stack.get(mState.stack.size() - position - 1);
Steve McKayb68dd222015-04-20 17:18:15 -0700630 }
631
632 @Override
633 public long getItemId(int position) {
634 return position;
635 }
636
637 @Override
638 public View getView(int position, View convertView, ViewGroup parent) {
639 if (convertView == null) {
640 convertView = LayoutInflater.from(parent.getContext())
641 .inflate(R.layout.item_subdir_title, parent, false);
642 }
643
644 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
645 final DocumentInfo doc = getItem(position);
646
647 if (position == 0) {
648 final RootInfo root = getCurrentRoot();
649 title.setText(root.title);
650 } else {
651 title.setText(doc.displayName);
652 }
653
654 return convertView;
655 }
656
657 @Override
658 public View getDropDownView(int position, View convertView, ViewGroup parent) {
659 if (convertView == null) {
660 convertView = LayoutInflater.from(parent.getContext())
661 .inflate(R.layout.item_subdir, parent, false);
662 }
663
664 final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
665 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
666 final DocumentInfo doc = getItem(position);
667
668 if (position == 0) {
669 final RootInfo root = getCurrentRoot();
670 title.setText(root.title);
671 subdir.setVisibility(View.GONE);
672 } else {
673 title.setText(doc.displayName);
674 subdir.setVisibility(View.VISIBLE);
675 }
676
677 return convertView;
678 }
679 }
680
681 /**
682 * Facade over the various search parts in the menu.
683 */
684 final class SearchManager implements
Daichi Hironoe577ef02015-06-11 14:29:28 +0900685 SearchView.OnCloseListener, OnActionExpandListener, OnQueryTextListener,
686 DocumentsToolBar.OnActionViewCollapsedListener {
Steve McKayb68dd222015-04-20 17:18:15 -0700687
Steve McKay0269fb62015-04-22 15:55:34 -0700688 private boolean mSearchExpanded;
689 private boolean mIgnoreNextClose;
690 private boolean mIgnoreNextCollapse;
Steve McKayb68dd222015-04-20 17:18:15 -0700691
Daichi Hironoe577ef02015-06-11 14:29:28 +0900692 private DocumentsToolBar mActionBar;
Steve McKayb68dd222015-04-20 17:18:15 -0700693 private MenuItem mMenu;
694 private SearchView mView;
695
Daichi Hironoe577ef02015-06-11 14:29:28 +0900696 public void install(DocumentsToolBar actionBar) {
697 assert(mActionBar == null);
698 mActionBar = actionBar;
699 mMenu = actionBar.getSearchMenu();
700 mView = (SearchView) mMenu.getActionView();
Steve McKayb68dd222015-04-20 17:18:15 -0700701
Daichi Hironoe577ef02015-06-11 14:29:28 +0900702 mActionBar.setOnActionViewCollapsedListener(this);
Steve McKayb68dd222015-04-20 17:18:15 -0700703 mMenu.setOnActionExpandListener(this);
704 mView.setOnQueryTextListener(this);
705 mView.setOnCloseListener(this);
706 }
707
708 /**
709 * @param root Info about the current directory.
710 */
711 void update(RootInfo root) {
712 if (mMenu == null) {
Steve McKay0269fb62015-04-22 15:55:34 -0700713 Log.d(mTag, "update called before Search MenuItem installed.");
Steve McKayb68dd222015-04-20 17:18:15 -0700714 return;
715 }
Steve McKay0269fb62015-04-22 15:55:34 -0700716
Steve McKay4d0255f2015-09-25 16:02:56 -0700717 if (mState.currentSearch != null) {
Steve McKayb68dd222015-04-20 17:18:15 -0700718 mMenu.expandActionView();
719
720 mView.setIconified(false);
721 mView.clearFocus();
Steve McKay4d0255f2015-09-25 16:02:56 -0700722 mView.setQuery(mState.currentSearch, false);
Steve McKayb68dd222015-04-20 17:18:15 -0700723 } else {
Steve McKayb68dd222015-04-20 17:18:15 -0700724 mView.clearFocus();
Steve McKay0269fb62015-04-22 15:55:34 -0700725 if (!mView.isIconified()) {
726 mIgnoreNextClose = true;
727 mView.setIconified(true);
728 }
Steve McKayb68dd222015-04-20 17:18:15 -0700729
Steve McKay0269fb62015-04-22 15:55:34 -0700730 if (mMenu.isActionViewExpanded()) {
731 mIgnoreNextCollapse = true;
732 mMenu.collapseActionView();
733 }
Steve McKayb68dd222015-04-20 17:18:15 -0700734 }
735
736 showMenu(root != null
737 && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0));
738 }
739
740 void showMenu(boolean visible) {
741 if (mMenu == null) {
742 Log.d(mTag, "showMenu called before Search MenuItem installed.");
743 return;
744 }
Steve McKay0269fb62015-04-22 15:55:34 -0700745
Steve McKayb68dd222015-04-20 17:18:15 -0700746 mMenu.setVisible(visible);
Steve McKay0269fb62015-04-22 15:55:34 -0700747 if (!visible) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700748 mState.currentSearch = null;
Steve McKay0269fb62015-04-22 15:55:34 -0700749 }
Steve McKayb68dd222015-04-20 17:18:15 -0700750 }
751
Daichi Hironoe577ef02015-06-11 14:29:28 +0900752 /**
753 * Cancels current search operation.
754 * @return True if it cancels search. False if it does not operate
755 * search currently.
756 */
757 boolean cancelSearch() {
758 if (mActionBar.hasExpandedActionView()) {
759 mActionBar.collapseActionView();
760 return true;
761 }
762 return false;
763 }
764
Steve McKayb68dd222015-04-20 17:18:15 -0700765 boolean isSearching() {
Steve McKay4d0255f2015-09-25 16:02:56 -0700766 return mState.currentSearch != null;
Steve McKayb68dd222015-04-20 17:18:15 -0700767 }
768
769 boolean isExpanded() {
770 return mSearchExpanded;
771 }
772
773 @Override
774 public boolean onClose() {
775 mSearchExpanded = false;
776 if (mIgnoreNextClose) {
777 mIgnoreNextClose = false;
778 return false;
779 }
780
Steve McKay4d0255f2015-09-25 16:02:56 -0700781 mState.currentSearch = null;
Steve McKayb68dd222015-04-20 17:18:15 -0700782 onCurrentDirectoryChanged(ANIM_NONE);
783 return false;
784 }
Steve McKay0269fb62015-04-22 15:55:34 -0700785
Steve McKayb68dd222015-04-20 17:18:15 -0700786 @Override
787 public boolean onMenuItemActionExpand(MenuItem item) {
788 mSearchExpanded = true;
789 updateActionBar();
790 return true;
791 }
792
793 @Override
794 public boolean onMenuItemActionCollapse(MenuItem item) {
795 mSearchExpanded = false;
796 if (mIgnoreNextCollapse) {
797 mIgnoreNextCollapse = false;
798 return true;
799 }
Steve McKay4d0255f2015-09-25 16:02:56 -0700800 mState.currentSearch = null;
Steve McKayb68dd222015-04-20 17:18:15 -0700801 onCurrentDirectoryChanged(ANIM_NONE);
802 return true;
803 }
Steve McKay0269fb62015-04-22 15:55:34 -0700804
Steve McKayb68dd222015-04-20 17:18:15 -0700805 @Override
806 public boolean onQueryTextSubmit(String query) {
807 mSearchExpanded = true;
Steve McKay4d0255f2015-09-25 16:02:56 -0700808 mState.currentSearch = query;
Steve McKayb68dd222015-04-20 17:18:15 -0700809 mView.clearFocus();
810 onCurrentDirectoryChanged(ANIM_NONE);
811 return true;
812 }
813
814 @Override
815 public boolean onQueryTextChange(String newText) {
816 return false;
817 }
Daichi Hironoe577ef02015-06-11 14:29:28 +0900818
819 @Override
820 public void onActionViewCollapsed() {
821 updateActionBar();
822 }
Steve McKayb68dd222015-04-20 17:18:15 -0700823 }
Steve McKay351a7492015-08-04 10:11:01 -0700824
825 /**
826 * Interface providing access to current view of documents
827 * even when all documents are not homed to the same parent.
828 */
Ben Kwab8a5e082015-12-07 13:25:27 -0800829 public interface SiblingProvider {
Steve McKay351a7492015-08-04 10:11:01 -0700830 /**
831 * Returns the cursor for the selected document. The cursor can be used to retrieve
832 * details about a document and its siblings.
833 * @return
834 */
835 Cursor getCursor();
836 }
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700837}