blob: ab0f666ef5d3cb7ba8ec1b64466c6f746023d3f8 [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 McKay351a7492015-08-04 10:11:01 -070019import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
Steve McKayb68dd222015-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 McKay4d0255f2015-09-25 16:02:56 -070023import static com.android.documentsui.Shared.DEBUG;
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;
Steve McKayb68dd222015-04-20 17:18:15 -070042import android.view.LayoutInflater;
43import android.view.Menu;
44import android.view.MenuItem;
45import android.view.MenuItem.OnActionExpandListener;
46import android.view.View;
47import android.view.ViewGroup;
48import android.widget.AdapterView;
49import android.widget.AdapterView.OnItemSelectedListener;
50import android.widget.BaseAdapter;
51import android.widget.ImageView;
52import android.widget.SearchView;
53import android.widget.SearchView.OnQueryTextListener;
54import android.widget.TextView;
Steve McKayd0a2a2c2015-03-25 14:35:33 -070055
Steve McKayb68dd222015-04-20 17:18:15 -070056import com.android.documentsui.RecentsProvider.ResumeColumns;
Steve McKayd0a2a2c2015-03-25 14:35:33 -070057import com.android.documentsui.model.DocumentInfo;
58import com.android.documentsui.model.DocumentStack;
59import com.android.documentsui.model.DurableUtils;
60import com.android.documentsui.model.RootInfo;
Steve McKay64ac2512015-05-12 12:49:58 -070061
Steve McKay64ac2512015-05-12 12:49:58 -070062import libcore.io.IoUtils;
63
64import java.io.FileNotFoundException;
65import java.io.IOException;
66import java.util.ArrayList;
67import java.util.Collection;
Steve McKay64ac2512015-05-12 12:49:58 -070068import java.util.List;
69import java.util.concurrent.Executor;
70
Steve McKayd0a2a2c2015-03-25 14:35:33 -070071abstract class BaseActivity extends Activity {
Steve McKayb68dd222015-04-20 17:18:15 -070072
73 static final String EXTRA_STATE = "state";
74
Steve McKay0fbfc652015-08-20 16:48:49 -070075 State mState;
Steve McKayb68dd222015-04-20 17:18:15 -070076 RootsCache mRoots;
Steve McKay0269fb62015-04-22 15:55:34 -070077 SearchManager mSearchManager;
Steve McKay0fbfc652015-08-20 16:48:49 -070078 DrawerController mDrawer;
Steve McKayf2c8b0d2015-09-23 15:44:24 -070079 boolean mProductivityDevice;
Steve McKay0269fb62015-04-22 15:55:34 -070080
Steve McKayf2c8b0d2015-09-23 15:44:24 -070081 private final String mTag;
Steve McKay0fbfc652015-08-20 16:48:49 -070082 @LayoutRes
83 private int mLayoutId;
Steve McKayfad3d4a2015-09-22 15:09:21 -070084 private DirectoryContainerView mDirectoryContainer;
Steve McKayb68dd222015-04-20 17:18:15 -070085
Steve McKay351a7492015-08-04 10:11:01 -070086 public abstract void onDocumentPicked(DocumentInfo doc, @Nullable DocumentContext siblings);
Steve McKayd0a2a2c2015-03-25 14:35:33 -070087 public abstract void onDocumentsPicked(List<DocumentInfo> docs);
Steve McKay351a7492015-08-04 10:11:01 -070088
Steve McKayb68dd222015-04-20 17:18:15 -070089 abstract void onTaskFinished(Uri... uris);
90 abstract void onDirectoryChanged(int anim);
91 abstract void updateActionBar();
92 abstract void saveStackBlocking();
Ben Kwa0574b182015-09-08 07:31:19 -070093 abstract State buildState();
Steve McKayb68dd222015-04-20 17:18:15 -070094
Steve McKay0fbfc652015-08-20 16:48:49 -070095 public BaseActivity(@LayoutRes int layoutId, String tag) {
96 mLayoutId = layoutId;
Steve McKayb68dd222015-04-20 17:18:15 -070097 mTag = tag;
98 }
99
100 @Override
101 public void onCreate(Bundle icicle) {
102 super.onCreate(icicle);
Steve McKay0fbfc652015-08-20 16:48:49 -0700103
Steve McKayf2c8b0d2015-09-23 15:44:24 -0700104 mProductivityDevice = getResources().getBoolean(R.bool.productivity_device);
Steve McKay0fbfc652015-08-20 16:48:49 -0700105 mState = (icicle != null)
106 ? icicle.<State>getParcelable(EXTRA_STATE)
Ben Kwa0574b182015-09-08 07:31:19 -0700107 : buildState();
Steve McKay0fbfc652015-08-20 16:48:49 -0700108
Steve McKay7f395012015-08-24 10:34:49 -0700109 setContentView(mLayoutId);
Steve McKay0fbfc652015-08-20 16:48:49 -0700110
Steve McKayb68dd222015-04-20 17:18:15 -0700111 mRoots = DocumentsApplication.getRootsCache(this);
Steve McKayfad3d4a2015-09-22 15:09:21 -0700112 mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory);
Steve McKay0269fb62015-04-22 15:55:34 -0700113 mSearchManager = new SearchManager();
Steve McKay0fbfc652015-08-20 16:48:49 -0700114
115 // Base classes must update result in their onCreate.
116 setResult(Activity.RESULT_CANCELED);
Steve McKay0269fb62015-04-22 15:55:34 -0700117 }
118
119 @Override
120 public boolean onCreateOptionsMenu(Menu menu) {
121 boolean showMenu = super.onCreateOptionsMenu(menu);
122
123 getMenuInflater().inflate(R.menu.activity, menu);
Daichi Hironoe577ef02015-06-11 14:29:28 +0900124 mSearchManager.install((DocumentsToolBar) findViewById(R.id.toolbar));
Steve McKay0269fb62015-04-22 15:55:34 -0700125
126 return showMenu;
Steve McKayb68dd222015-04-20 17:18:15 -0700127 }
128
Steve McKay7bd32e12015-04-30 16:12:59 -0700129 @Override
130 public boolean onPrepareOptionsMenu(Menu menu) {
131 boolean shown = super.onPrepareOptionsMenu(menu);
132
133 final RootInfo root = getCurrentRoot();
134 final DocumentInfo cwd = getCurrentDirectory();
135
136 final MenuItem sort = menu.findItem(R.id.menu_sort);
137 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
138 final MenuItem grid = menu.findItem(R.id.menu_grid);
139 final MenuItem list = menu.findItem(R.id.menu_list);
Steve McKay7bd32e12015-04-30 16:12:59 -0700140 final MenuItem advanced = menu.findItem(R.id.menu_advanced);
141 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
Ben Kwa0574b182015-09-08 07:31:19 -0700142 final MenuItem settings = menu.findItem(R.id.menu_settings);
Steve McKay7bd32e12015-04-30 16:12:59 -0700143
144 mSearchManager.update(root);
145
146 // Search uses backend ranking; no sorting
147 sort.setVisible(cwd != null && !mSearchManager.isSearching());
148
Steve McKay7bd32e12015-04-30 16:12:59 -0700149 advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
150 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
151 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
152 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
153
Steve McKay4d0255f2015-09-25 16:02:56 -0700154 sortSize.setVisible(mState.showSize); // Only sort by size when visible
155 fileSize.setVisible(!mState.showSize);
156 grid.setVisible(mState.derivedMode != State.MODE_GRID);
157 list.setVisible(mState.derivedMode != State.MODE_LIST);
Steve McKay459bc2b2015-09-16 15:07:31 -0700158 advanced.setVisible(!mState.showAdvanced);
Ben Kwa0574b182015-09-08 07:31:19 -0700159 settings.setVisible((root.flags & Root.FLAG_HAS_SETTINGS) != 0);
160
Steve McKay7bd32e12015-04-30 16:12:59 -0700161 return shown;
162 }
163
Ben Kwa0574b182015-09-08 07:31:19 -0700164 State buildDefaultState() {
165 State state = new State();
166
167 final Intent intent = getIntent();
Ben Kwa0574b182015-09-08 07:31:19 -0700168
169 state.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
Ben Kwa0574b182015-09-08 07:31:19 -0700170
Steve McKay459bc2b2015-09-16 15:07:31 -0700171 state.forceSize = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, false);
172 state.showSize = state.forceSize || LocalPreferences.getDisplayFileSize(this);
173
174 state.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
175 state.showAdvanced = state.forceAdvanced
176 || LocalPreferences.getDisplayAdvancedDevices(this);
177
178 state.initAcceptMimes(intent);
Ben Kwa0574b182015-09-08 07:31:19 -0700179 state.excludedAuthorities = getExcludedAuthorities();
180
181 return state;
182 }
183
Steve McKayb68dd222015-04-20 17:18:15 -0700184 void onStackRestored(boolean restored, boolean external) {}
185
186 void onRootPicked(RootInfo root) {
Steve McKayb68dd222015-04-20 17:18:15 -0700187 // Clear entire backstack and start in new root
Steve McKay4d0255f2015-09-25 16:02:56 -0700188 mState.stack.root = root;
189 mState.stack.clear();
190 mState.stackTouched = true;
Steve McKayb68dd222015-04-20 17:18:15 -0700191
Steve McKay0269fb62015-04-22 15:55:34 -0700192 mSearchManager.update(root);
193
194 // Recents is always in memory, so we just load it directly.
195 // Otherwise we delegate loading data from disk to a task
196 // to ensure a responsive ui.
197 if (mRoots.isRecentsRoot(root)) {
Steve McKayb68dd222015-04-20 17:18:15 -0700198 onCurrentDirectoryChanged(ANIM_SIDE);
Steve McKay0269fb62015-04-22 15:55:34 -0700199 } else {
Steve McKay459bc2b2015-09-16 15:07:31 -0700200 new PickRootTask(root).executeOnExecutor(getExecutorForCurrentDirectory());
Steve McKayb68dd222015-04-20 17:18:15 -0700201 }
202 }
203
204 void expandMenus(Menu menu) {
205 for (int i = 0; i < menu.size(); i++) {
206 final MenuItem item = menu.getItem(i);
207 switch (item.getItemId()) {
208 case R.id.menu_advanced:
209 case R.id.menu_file_size:
Steve McKayf2c8b0d2015-09-23 15:44:24 -0700210 case R.id.menu_new_window:
Steve McKayb68dd222015-04-20 17:18:15 -0700211 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 McKayceeb3f72015-05-19 16:10:25 -0700225 showCreateDirectoryDialog();
Steve McKayb68dd222015-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 McKay1f199482015-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 McKayb68dd222015-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 McKayceeb3f72015-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 McKay351a7492015-08-04 10:11:01 -0700283 void onDirectoryCreated(DocumentInfo doc) {
284 checkArgument(doc.isDirectory());
285 openDirectory(doc);
286 }
287
288 void openDirectory(DocumentInfo doc) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700289 mState.stack.push(doc);
290 mState.stackTouched = true;
Steve McKay351a7492015-08-04 10:11:01 -0700291 onCurrentDirectoryChanged(ANIM_DOWN);
292 }
293
Steve McKayb68dd222015-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) {
Steve McKayfad3d4a2015-09-22 15:09:21 -0700300 mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN);
Steve McKayb68dd222015-04-20 17:18:15 -0700301 onDirectoryChanged(anim);
302
303 final RootsFragment roots = RootsFragment.get(getFragmentManager());
304 if (roots != null) {
305 roots.onCurrentRootChanged();
306 }
307
308 updateActionBar();
309 invalidateOptionsMenu();
310 }
311
Ben Kwa0bcdec32015-05-29 15:40:31 -0700312 final List<String> getExcludedAuthorities() {
313 List<String> authorities = new ArrayList<>();
314 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
315 // Exclude roots provided by the calling package.
316 String packageName = getCallingPackageMaybeExtra();
317 try {
318 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
319 PackageManager.GET_PROVIDERS);
320 for (ProviderInfo provider: pkgInfo.providers) {
321 authorities.add(provider.authority);
322 }
323 } catch (PackageManager.NameNotFoundException e) {
324 Log.e(mTag, "Calling package name does not resolve: " + packageName);
325 }
326 }
327 return authorities;
328 }
329
Steve McKayb68dd222015-04-20 17:18:15 -0700330 final String getCallingPackageMaybeExtra() {
Ben Kwa0bcdec32015-05-29 15:40:31 -0700331 String callingPackage = getCallingPackage();
332 // System apps can set the calling package name using an extra.
333 try {
334 ApplicationInfo info = getPackageManager().getApplicationInfo(callingPackage, 0);
335 if (info.isSystemApp() || info.isUpdatedSystemApp()) {
336 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
337 if (extra != null) {
338 callingPackage = extra;
339 }
340 }
341 } finally {
342 return callingPackage;
343 }
Steve McKayb68dd222015-04-20 17:18:15 -0700344 }
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700345
346 public static BaseActivity get(Fragment fragment) {
347 return (BaseActivity) fragment.getActivity();
348 }
349
Ben Kwa0574b182015-09-08 07:31:19 -0700350 public State getDisplayState() {
351 return mState;
352 }
353
Daichi Hirono22574ed2015-04-15 13:41:18 +0900354 public static abstract class DocumentsIntent {
355 /** Intent action name to open copy destination. */
356 public static String ACTION_OPEN_COPY_DESTINATION =
357 "com.android.documentsui.OPEN_COPY_DESTINATION";
358
359 /**
360 * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which
361 * specifies if the destination directory needs to create new directory or not.
362 */
363 public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
364 }
365
Steve McKayb68dd222015-04-20 17:18:15 -0700366 void setDisplayAdvancedDevices(boolean display) {
Steve McKayb68dd222015-04-20 17:18:15 -0700367 LocalPreferences.setDisplayAdvancedDevices(this, display);
Steve McKay4d0255f2015-09-25 16:02:56 -0700368 mState.showAdvanced = mState.forceAdvanced | display;
Steve McKayb68dd222015-04-20 17:18:15 -0700369 RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
370 invalidateOptionsMenu();
371 }
372
373 void setDisplayFileSize(boolean display) {
374 LocalPreferences.setDisplayFileSize(this, display);
Steve McKay4d0255f2015-09-25 16:02:56 -0700375 mState.showSize = display;
Steve McKayb68dd222015-04-20 17:18:15 -0700376 DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
377 invalidateOptionsMenu();
378 }
379
380 void onStateChanged() {
381 invalidateOptionsMenu();
382 }
383
384 /**
385 * Set state sort order based on explicit user action.
386 */
387 void setUserSortOrder(int sortOrder) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700388 mState.userSortOrder = sortOrder;
Steve McKayb68dd222015-04-20 17:18:15 -0700389 DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
390 }
391
392 /**
393 * Set state mode based on explicit user action.
394 */
395 void setUserMode(int mode) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700396 mState.userMode = mode;
Steve McKayb68dd222015-04-20 17:18:15 -0700397 DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
398 }
399
400 void setPending(boolean pending) {
401 final SaveFragment save = SaveFragment.get(getFragmentManager());
402 if (save != null) {
403 save.setPending(pending);
404 }
405 }
406
407 @Override
408 protected void onSaveInstanceState(Bundle state) {
409 super.onSaveInstanceState(state);
Steve McKay4d0255f2015-09-25 16:02:56 -0700410 state.putParcelable(EXTRA_STATE, mState);
Steve McKayb68dd222015-04-20 17:18:15 -0700411 }
412
413 @Override
414 protected void onRestoreInstanceState(Bundle state) {
415 super.onRestoreInstanceState(state);
416 }
417
418 RootInfo getCurrentRoot() {
Steve McKay4d0255f2015-09-25 16:02:56 -0700419 if (mState.stack.root != null) {
420 return mState.stack.root;
Steve McKayb68dd222015-04-20 17:18:15 -0700421 } else {
422 return mRoots.getRecentsRoot();
423 }
424 }
425
426 public DocumentInfo getCurrentDirectory() {
Steve McKay4d0255f2015-09-25 16:02:56 -0700427 return mState.stack.peek();
Steve McKayb68dd222015-04-20 17:18:15 -0700428 }
429
Steve McKay459bc2b2015-09-16 15:07:31 -0700430 public Executor getExecutorForCurrentDirectory() {
Steve McKayb68dd222015-04-20 17:18:15 -0700431 final DocumentInfo cwd = getCurrentDirectory();
432 if (cwd != null && cwd.authority != null) {
433 return ProviderExecutor.forAuthority(cwd.authority);
434 } else {
435 return AsyncTask.THREAD_POOL_EXECUTOR;
436 }
437 }
438
Steve McKay0fbfc652015-08-20 16:48:49 -0700439 @Override
440 public void onBackPressed() {
441 // While action bar is expanded, the state stack UI is hidden.
442 if (mSearchManager.cancelSearch()) {
443 return;
444 }
445
446 if (!mState.stackTouched) {
447 super.onBackPressed();
448 return;
449 }
450
451 final int size = mState.stack.size();
452
453 if (mDrawer.isOpen()) {
454 mDrawer.setOpen(false);
455 } else if (size > 1) {
456 mState.stack.pop();
457 onCurrentDirectoryChanged(ANIM_UP);
458 } else {
459 super.onBackPressed();
460 }
461 }
462
Steve McKayb68dd222015-04-20 17:18:15 -0700463 public void onStackPicked(DocumentStack stack) {
464 try {
465 // Update the restored stack to ensure we have freshest data
466 stack.updateDocuments(getContentResolver());
467
Steve McKay4d0255f2015-09-25 16:02:56 -0700468 mState.stack = stack;
469 mState.stackTouched = true;
Steve McKayb68dd222015-04-20 17:18:15 -0700470 onCurrentDirectoryChanged(ANIM_SIDE);
471
472 } catch (FileNotFoundException e) {
473 Log.w(mTag, "Failed to restore stack: " + e);
474 }
475 }
476
477 final class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
478 private RootInfo mRoot;
479
480 public PickRootTask(RootInfo root) {
481 mRoot = root;
482 }
483
484 @Override
485 protected DocumentInfo doInBackground(Void... params) {
486 try {
487 final Uri uri = DocumentsContract.buildDocumentUri(
488 mRoot.authority, mRoot.documentId);
489 return DocumentInfo.fromUri(getContentResolver(), uri);
490 } catch (FileNotFoundException e) {
491 Log.w(mTag, "Failed to find root", e);
492 return null;
493 }
494 }
495
496 @Override
497 protected void onPostExecute(DocumentInfo result) {
498 if (result != null) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700499 mState.stack.push(result);
500 mState.stackTouched = true;
Steve McKayb68dd222015-04-20 17:18:15 -0700501 onCurrentDirectoryChanged(ANIM_SIDE);
502 }
503 }
504 }
505
506 final class RestoreStackTask extends AsyncTask<Void, Void, Void> {
507 private volatile boolean mRestoredStack;
508 private volatile boolean mExternal;
509
510 @Override
511 protected Void doInBackground(Void... params) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700512 if (DEBUG && !mState.stack.isEmpty()) {
513 Log.w(mTag, "Overwriting existing stack.");
514 }
Steve McKayb68dd222015-04-20 17:18:15 -0700515 RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);
516
517 // Restore last stack for calling package
518 final String packageName = getCallingPackageMaybeExtra();
519 final Cursor cursor = getContentResolver()
520 .query(RecentsProvider.buildResume(packageName), null, null, null, null);
521 try {
522 if (cursor.moveToFirst()) {
523 mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
524 final byte[] rawStack = cursor.getBlob(
525 cursor.getColumnIndex(ResumeColumns.STACK));
Steve McKay4d0255f2015-09-25 16:02:56 -0700526 DurableUtils.readFromArray(rawStack, mState.stack);
Steve McKayb68dd222015-04-20 17:18:15 -0700527 mRestoredStack = true;
528 }
529 } catch (IOException e) {
530 Log.w(mTag, "Failed to resume: " + e);
531 } finally {
532 IoUtils.closeQuietly(cursor);
533 }
534
535 if (mRestoredStack) {
536 // Update the restored stack to ensure we have freshest data
Steve McKay4d0255f2015-09-25 16:02:56 -0700537 final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
Steve McKayb68dd222015-04-20 17:18:15 -0700538 try {
Steve McKay4d0255f2015-09-25 16:02:56 -0700539 mState.stack.updateRoot(matchingRoots);
540 mState.stack.updateDocuments(getContentResolver());
Steve McKayb68dd222015-04-20 17:18:15 -0700541 } catch (FileNotFoundException e) {
542 Log.w(mTag, "Failed to restore stack: " + e);
Steve McKay4d0255f2015-09-25 16:02:56 -0700543 mState.stack.reset();
Steve McKayb68dd222015-04-20 17:18:15 -0700544 mRestoredStack = false;
545 }
546 }
547
548 return null;
549 }
550
551 @Override
552 protected void onPostExecute(Void result) {
553 if (isDestroyed()) return;
Steve McKay4d0255f2015-09-25 16:02:56 -0700554 mState.restored = true;
Steve McKayb68dd222015-04-20 17:18:15 -0700555 onCurrentDirectoryChanged(ANIM_NONE);
Steve McKayb68dd222015-04-20 17:18:15 -0700556 onStackRestored(mRestoredStack, mExternal);
Steve McKayb68dd222015-04-20 17:18:15 -0700557 }
558 }
559
Ben Kwa0574b182015-09-08 07:31:19 -0700560 final class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> {
561 private Uri mRootUri;
562
563 public RestoreRootTask(Uri rootUri) {
564 mRootUri = rootUri;
565 }
566
567 @Override
568 protected RootInfo doInBackground(Void... params) {
569 final String rootId = DocumentsContract.getRootId(mRootUri);
570 return mRoots.getRootOneshot(mRootUri.getAuthority(), rootId);
571 }
572
573 @Override
574 protected void onPostExecute(RootInfo root) {
575 if (isDestroyed()) return;
576 mState.restored = true;
577
578 if (root != null) {
579 onRootPicked(root);
580 } else {
581 Log.w(mTag, "Failed to find root: " + mRootUri);
582 finish();
583 }
584 }
585 }
586
Steve McKayb68dd222015-04-20 17:18:15 -0700587 final class ItemSelectedListener implements OnItemSelectedListener {
588
589 boolean mIgnoreNextNavigation;
590
591 @Override
592 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
593 if (mIgnoreNextNavigation) {
594 mIgnoreNextNavigation = false;
595 return;
596 }
597
Steve McKay4d0255f2015-09-25 16:02:56 -0700598 while (mState.stack.size() > position + 1) {
599 mState.stackTouched = true;
600 mState.stack.pop();
Steve McKayb68dd222015-04-20 17:18:15 -0700601 }
602 onCurrentDirectoryChanged(ANIM_UP);
603 }
604
605 @Override
606 public void onNothingSelected(AdapterView<?> parent) {
607 // Ignored
608 }
609 }
610
611 /**
612 * Class providing toolbar with runtime access to useful activity data.
613 */
614 final class StackAdapter extends BaseAdapter {
615 @Override
616 public int getCount() {
Steve McKay4d0255f2015-09-25 16:02:56 -0700617 return mState.stack.size();
Steve McKayb68dd222015-04-20 17:18:15 -0700618 }
619
620 @Override
621 public DocumentInfo getItem(int position) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700622 return mState.stack.get(mState.stack.size() - position - 1);
Steve McKayb68dd222015-04-20 17:18:15 -0700623 }
624
625 @Override
626 public long getItemId(int position) {
627 return position;
628 }
629
630 @Override
631 public View getView(int position, View convertView, ViewGroup parent) {
632 if (convertView == null) {
633 convertView = LayoutInflater.from(parent.getContext())
634 .inflate(R.layout.item_subdir_title, parent, false);
635 }
636
637 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
638 final DocumentInfo doc = getItem(position);
639
640 if (position == 0) {
641 final RootInfo root = getCurrentRoot();
642 title.setText(root.title);
643 } else {
644 title.setText(doc.displayName);
645 }
646
647 return convertView;
648 }
649
650 @Override
651 public View getDropDownView(int position, View convertView, ViewGroup parent) {
652 if (convertView == null) {
653 convertView = LayoutInflater.from(parent.getContext())
654 .inflate(R.layout.item_subdir, parent, false);
655 }
656
657 final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
658 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
659 final DocumentInfo doc = getItem(position);
660
661 if (position == 0) {
662 final RootInfo root = getCurrentRoot();
663 title.setText(root.title);
664 subdir.setVisibility(View.GONE);
665 } else {
666 title.setText(doc.displayName);
667 subdir.setVisibility(View.VISIBLE);
668 }
669
670 return convertView;
671 }
672 }
673
674 /**
675 * Facade over the various search parts in the menu.
676 */
677 final class SearchManager implements
Daichi Hironoe577ef02015-06-11 14:29:28 +0900678 SearchView.OnCloseListener, OnActionExpandListener, OnQueryTextListener,
679 DocumentsToolBar.OnActionViewCollapsedListener {
Steve McKayb68dd222015-04-20 17:18:15 -0700680
Steve McKay0269fb62015-04-22 15:55:34 -0700681 private boolean mSearchExpanded;
682 private boolean mIgnoreNextClose;
683 private boolean mIgnoreNextCollapse;
Steve McKayb68dd222015-04-20 17:18:15 -0700684
Daichi Hironoe577ef02015-06-11 14:29:28 +0900685 private DocumentsToolBar mActionBar;
Steve McKayb68dd222015-04-20 17:18:15 -0700686 private MenuItem mMenu;
687 private SearchView mView;
688
Daichi Hironoe577ef02015-06-11 14:29:28 +0900689 public void install(DocumentsToolBar actionBar) {
690 assert(mActionBar == null);
691 mActionBar = actionBar;
692 mMenu = actionBar.getSearchMenu();
693 mView = (SearchView) mMenu.getActionView();
Steve McKayb68dd222015-04-20 17:18:15 -0700694
Daichi Hironoe577ef02015-06-11 14:29:28 +0900695 mActionBar.setOnActionViewCollapsedListener(this);
Steve McKayb68dd222015-04-20 17:18:15 -0700696 mMenu.setOnActionExpandListener(this);
697 mView.setOnQueryTextListener(this);
698 mView.setOnCloseListener(this);
699 }
700
701 /**
702 * @param root Info about the current directory.
703 */
704 void update(RootInfo root) {
705 if (mMenu == null) {
Steve McKay0269fb62015-04-22 15:55:34 -0700706 Log.d(mTag, "update called before Search MenuItem installed.");
Steve McKayb68dd222015-04-20 17:18:15 -0700707 return;
708 }
Steve McKay0269fb62015-04-22 15:55:34 -0700709
Steve McKay4d0255f2015-09-25 16:02:56 -0700710 if (mState.currentSearch != null) {
Steve McKayb68dd222015-04-20 17:18:15 -0700711 mMenu.expandActionView();
712
713 mView.setIconified(false);
714 mView.clearFocus();
Steve McKay4d0255f2015-09-25 16:02:56 -0700715 mView.setQuery(mState.currentSearch, false);
Steve McKayb68dd222015-04-20 17:18:15 -0700716 } else {
Steve McKayb68dd222015-04-20 17:18:15 -0700717 mView.clearFocus();
Steve McKay0269fb62015-04-22 15:55:34 -0700718 if (!mView.isIconified()) {
719 mIgnoreNextClose = true;
720 mView.setIconified(true);
721 }
Steve McKayb68dd222015-04-20 17:18:15 -0700722
Steve McKay0269fb62015-04-22 15:55:34 -0700723 if (mMenu.isActionViewExpanded()) {
724 mIgnoreNextCollapse = true;
725 mMenu.collapseActionView();
726 }
Steve McKayb68dd222015-04-20 17:18:15 -0700727 }
728
729 showMenu(root != null
730 && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0));
731 }
732
733 void showMenu(boolean visible) {
734 if (mMenu == null) {
735 Log.d(mTag, "showMenu called before Search MenuItem installed.");
736 return;
737 }
Steve McKay0269fb62015-04-22 15:55:34 -0700738
Steve McKayb68dd222015-04-20 17:18:15 -0700739 mMenu.setVisible(visible);
Steve McKay0269fb62015-04-22 15:55:34 -0700740 if (!visible) {
Steve McKay4d0255f2015-09-25 16:02:56 -0700741 mState.currentSearch = null;
Steve McKay0269fb62015-04-22 15:55:34 -0700742 }
Steve McKayb68dd222015-04-20 17:18:15 -0700743 }
744
Daichi Hironoe577ef02015-06-11 14:29:28 +0900745 /**
746 * Cancels current search operation.
747 * @return True if it cancels search. False if it does not operate
748 * search currently.
749 */
750 boolean cancelSearch() {
751 if (mActionBar.hasExpandedActionView()) {
752 mActionBar.collapseActionView();
753 return true;
754 }
755 return false;
756 }
757
Steve McKayb68dd222015-04-20 17:18:15 -0700758 boolean isSearching() {
Steve McKay4d0255f2015-09-25 16:02:56 -0700759 return mState.currentSearch != null;
Steve McKayb68dd222015-04-20 17:18:15 -0700760 }
761
762 boolean isExpanded() {
763 return mSearchExpanded;
764 }
765
766 @Override
767 public boolean onClose() {
768 mSearchExpanded = false;
769 if (mIgnoreNextClose) {
770 mIgnoreNextClose = false;
771 return false;
772 }
773
Steve McKay4d0255f2015-09-25 16:02:56 -0700774 mState.currentSearch = null;
Steve McKayb68dd222015-04-20 17:18:15 -0700775 onCurrentDirectoryChanged(ANIM_NONE);
776 return false;
777 }
Steve McKay0269fb62015-04-22 15:55:34 -0700778
Steve McKayb68dd222015-04-20 17:18:15 -0700779 @Override
780 public boolean onMenuItemActionExpand(MenuItem item) {
781 mSearchExpanded = true;
782 updateActionBar();
783 return true;
784 }
785
786 @Override
787 public boolean onMenuItemActionCollapse(MenuItem item) {
788 mSearchExpanded = false;
789 if (mIgnoreNextCollapse) {
790 mIgnoreNextCollapse = false;
791 return true;
792 }
Steve McKay4d0255f2015-09-25 16:02:56 -0700793 mState.currentSearch = null;
Steve McKayb68dd222015-04-20 17:18:15 -0700794 onCurrentDirectoryChanged(ANIM_NONE);
795 return true;
796 }
Steve McKay0269fb62015-04-22 15:55:34 -0700797
Steve McKayb68dd222015-04-20 17:18:15 -0700798 @Override
799 public boolean onQueryTextSubmit(String query) {
800 mSearchExpanded = true;
Steve McKay4d0255f2015-09-25 16:02:56 -0700801 mState.currentSearch = query;
Steve McKayb68dd222015-04-20 17:18:15 -0700802 mView.clearFocus();
803 onCurrentDirectoryChanged(ANIM_NONE);
804 return true;
805 }
806
807 @Override
808 public boolean onQueryTextChange(String newText) {
809 return false;
810 }
Daichi Hironoe577ef02015-06-11 14:29:28 +0900811
812 @Override
813 public void onActionViewCollapsed() {
814 updateActionBar();
815 }
Steve McKayb68dd222015-04-20 17:18:15 -0700816 }
Steve McKay351a7492015-08-04 10:11:01 -0700817
818 /**
819 * Interface providing access to current view of documents
820 * even when all documents are not homed to the same parent.
821 */
822 interface DocumentContext {
823 /**
824 * Returns the cursor for the selected document. The cursor can be used to retrieve
825 * details about a document and its siblings.
826 * @return
827 */
828 Cursor getCursor();
829 }
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700830}