blob: b67a6915add907e0d0084fbb4347e56dcfad7e17 [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 McKay323ee3e2015-09-25 16:02:56 -070019import static com.android.documentsui.Shared.DEBUG;
Steve McKay3eb2d072016-01-25 19:00:22 -080020import static com.android.documentsui.State.MODE_GRID;
Tomasz Mikolajewskie3fe9d72016-02-03 16:53:21 +090021import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_ENTER;
22import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_LEAVE;
Steve McKayf8621552015-11-03 15:23:16 -080023import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
24import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_SIDE;
Steve McKay6eaf38632015-08-04 10:11:01 -070025import static com.android.internal.util.Preconditions.checkArgument;
Steve McKayef3e2cf2015-04-20 17:18:15 -070026
Steve McKaye934ce62015-03-25 14:35:33 -070027import android.app.Activity;
28import android.app.Fragment;
Steve McKayef3e2cf2015-04-20 17:18:15 -070029import android.content.Intent;
Ben Kwa77797402015-05-29 15:40:31 -070030import android.content.pm.ApplicationInfo;
31import android.content.pm.PackageInfo;
32import android.content.pm.PackageManager;
33import android.content.pm.ProviderInfo;
Steve McKayef3e2cf2015-04-20 17:18:15 -070034import android.database.Cursor;
35import android.net.Uri;
36import android.os.AsyncTask;
37import android.os.Bundle;
Steve McKayef3e2cf2015-04-20 17:18:15 -070038import android.provider.DocumentsContract;
39import android.provider.DocumentsContract.Root;
Steve McKay3ce95952016-02-02 11:41:03 -080040import android.support.annotation.CallSuper;
Steve McKay12055472015-08-20 16:48:49 -070041import android.support.annotation.LayoutRes;
Steve McKay58efce32015-08-20 16:19:38 +000042import android.support.annotation.Nullable;
Steve McKayef3e2cf2015-04-20 17:18:15 -070043import android.util.Log;
Ben Kwa2036dad2016-02-10 07:46:35 -080044import android.view.KeyEvent;
Steve McKayef3e2cf2015-04-20 17:18:15 -070045import android.view.Menu;
46import android.view.MenuItem;
Steve McKay1f264a82016-02-03 11:15:57 -080047import android.widget.Spinner;
Steve McKaye934ce62015-03-25 14:35:33 -070048
Aga Wronskaf6a31d32016-01-15 17:30:15 -080049import com.android.documentsui.SearchManager.SearchManagerListener;
Steve McKay3eb2d072016-01-25 19:00:22 -080050import com.android.documentsui.State.ViewMode;
Steve McKayf8621552015-11-03 15:23:16 -080051import com.android.documentsui.dirlist.DirectoryFragment;
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090052import com.android.documentsui.dirlist.Model;
Steve McKaye934ce62015-03-25 14:35:33 -070053import com.android.documentsui.model.DocumentInfo;
54import com.android.documentsui.model.DocumentStack;
Steve McKaye934ce62015-03-25 14:35:33 -070055import com.android.documentsui.model.RootInfo;
Daichi Hirono60e9a072015-12-25 11:08:42 +090056import com.android.internal.util.Preconditions;
Steve McKay273103b2015-05-12 12:49:58 -070057
Steve McKay273103b2015-05-12 12:49:58 -070058import java.io.FileNotFoundException;
Steve McKay273103b2015-05-12 12:49:58 -070059import java.util.ArrayList;
60import java.util.Collection;
Steve McKay273103b2015-05-12 12:49:58 -070061import java.util.List;
62import java.util.concurrent.Executor;
63
Steve McKay1f264a82016-02-03 11:15:57 -080064public abstract class BaseActivity extends Activity
65 implements SearchManagerListener, NavigationView.Environment {
Steve McKayef3e2cf2015-04-20 17:18:15 -070066
67 static final String EXTRA_STATE = "state";
68
Steve McKayf8737692016-02-04 19:40:45 -080069 // See comments where this const is referenced for details.
70 private static final int DRAWER_NO_FIDDLE_DELAY = 1500;
71
Steve McKay12055472015-08-20 16:48:49 -070072 State mState;
Steve McKayef3e2cf2015-04-20 17:18:15 -070073 RootsCache mRoots;
Steve McKaya78a3692015-04-22 15:55:34 -070074 SearchManager mSearchManager;
Steve McKay12055472015-08-20 16:48:49 -070075 DrawerController mDrawer;
Steve McKay1f264a82016-02-03 11:15:57 -080076 NavigationView mNavigator;
Steve McKaya78a3692015-04-22 15:55:34 -070077
Steve McKay9f9d5b42015-09-23 15:44:24 -070078 private final String mTag;
Steve McKayf8737692016-02-04 19:40:45 -080079
Steve McKay12055472015-08-20 16:48:49 -070080 @LayoutRes
81 private int mLayoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -070082
Steve McKayf8737692016-02-04 19:40:45 -080083 // Track the time we opened the drawer in response to back being pressed.
84 // We use the time gap to figure out whether to close app or reopen the drawer.
85 private long mDrawerLastFiddled;
86
Ben Kwa2036dad2016-02-10 07:46:35 -080087 private boolean mNavDrawerHasFocus;
88
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090089 public abstract void onDocumentPicked(DocumentInfo doc, Model model);
Steve McKaye934ce62015-03-25 14:35:33 -070090 public abstract void onDocumentsPicked(List<DocumentInfo> docs);
Steve McKay6eaf38632015-08-04 10:11:01 -070091
Steve McKayef3e2cf2015-04-20 17:18:15 -070092 abstract void onTaskFinished(Uri... uris);
Aga Wronskaf6a31d32016-01-15 17:30:15 -080093 abstract void refreshDirectory(int anim);
Steve McKay95cd85a2016-02-04 12:15:22 -080094 /** Allows sub-classes to include information in a newly created State instance. */
95 abstract void includeState(State initialState);
Steve McKayef3e2cf2015-04-20 17:18:15 -070096
Steve McKay12055472015-08-20 16:48:49 -070097 public BaseActivity(@LayoutRes int layoutId, String tag) {
98 mLayoutId = layoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -070099 mTag = tag;
100 }
101
Steve McKay1f264a82016-02-03 11:15:57 -0800102 @CallSuper
Steve McKayef3e2cf2015-04-20 17:18:15 -0700103 @Override
104 public void onCreate(Bundle icicle) {
105 super.onCreate(icicle);
Steve McKay12055472015-08-20 16:48:49 -0700106
Steve McKay1f264a82016-02-03 11:15:57 -0800107 setContentView(mLayoutId);
108
109 mDrawer = DrawerController.create(this);
Steve McKay95cd85a2016-02-04 12:15:22 -0800110 mState = getState(icicle);
Ben Kwa72379982016-01-26 11:50:03 -0800111 Metrics.logActivityLaunch(this, mState, getIntent());
112
Steve McKayef3e2cf2015-04-20 17:18:15 -0700113 mRoots = DocumentsApplication.getRootsCache(this);
Steve McKay1f264a82016-02-03 11:15:57 -0800114
Daichi Hirono60e9a072015-12-25 11:08:42 +0900115 mRoots.setOnCacheUpdateListener(
116 new RootsCache.OnCacheUpdateListener() {
117 @Override
118 public void onCacheUpdate() {
Steve McKay95cd85a2016-02-04 12:15:22 -0800119 new HandleRootsChangedTask(BaseActivity.this)
120 .execute(getCurrentRoot());
Daichi Hirono60e9a072015-12-25 11:08:42 +0900121 }
122 });
Steve McKay1f264a82016-02-03 11:15:57 -0800123
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800124 mSearchManager = new SearchManager(this);
Steve McKay12055472015-08-20 16:48:49 -0700125
Steve McKay1f264a82016-02-03 11:15:57 -0800126 DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
127 setActionBar(toolbar);
128 mNavigator = new NavigationView(
129 mDrawer,
130 toolbar,
131 (Spinner) findViewById(R.id.stack),
132 mState,
133 this);
134
Steve McKay12055472015-08-20 16:48:49 -0700135 // Base classes must update result in their onCreate.
136 setResult(Activity.RESULT_CANCELED);
Steve McKaya78a3692015-04-22 15:55:34 -0700137 }
138
139 @Override
140 public boolean onCreateOptionsMenu(Menu menu) {
141 boolean showMenu = super.onCreateOptionsMenu(menu);
142
143 getMenuInflater().inflate(R.menu.activity, menu);
Steve McKay1f264a82016-02-03 11:15:57 -0800144 mSearchManager.install((DocumentsToolbar) findViewById(R.id.toolbar));
Steve McKaya78a3692015-04-22 15:55:34 -0700145
146 return showMenu;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700147 }
148
Steve McKay69aee092015-04-30 16:12:59 -0700149 @Override
Steve McKay3ce95952016-02-02 11:41:03 -0800150 @CallSuper
Steve McKay69aee092015-04-30 16:12:59 -0700151 public boolean onPrepareOptionsMenu(Menu menu) {
Steve McKay5bbae102015-10-01 11:39:24 -0700152 super.onPrepareOptionsMenu(menu);
Steve McKay69aee092015-04-30 16:12:59 -0700153
Steve McKay3ce95952016-02-02 11:41:03 -0800154 mSearchManager.showMenu(canSearchRoot());
155
Steve McKay5bbae102015-10-01 11:39:24 -0700156 final boolean inRecents = getCurrentDirectory() == null;
Steve McKay69aee092015-04-30 16:12:59 -0700157
158 final MenuItem sort = menu.findItem(R.id.menu_sort);
159 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
160 final MenuItem grid = menu.findItem(R.id.menu_grid);
161 final MenuItem list = menu.findItem(R.id.menu_list);
Steve McKay69aee092015-04-30 16:12:59 -0700162 final MenuItem advanced = menu.findItem(R.id.menu_advanced);
163 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
164
Steve McKay3ce95952016-02-02 11:41:03 -0800165 // Search uses backend ranking; no sorting, recents doesn't support sort.
Steve McKay5bbae102015-10-01 11:39:24 -0700166 sort.setVisible(!inRecents && !mSearchManager.isSearching());
Steve McKay3ce95952016-02-02 11:41:03 -0800167 sortSize.setVisible(mState.showSize); // Only sort by size when file sizes are visible
168 fileSize.setVisible(!mState.forceSize);
Steve McKay5bbae102015-10-01 11:39:24 -0700169
170 // grid/list is effectively a toggle.
171 grid.setVisible(mState.derivedMode != State.MODE_GRID);
172 list.setVisible(mState.derivedMode != State.MODE_LIST);
173
Steve McKay5bbae102015-10-01 11:39:24 -0700174 advanced.setVisible(!mState.forceAdvanced);
Steve McKay69aee092015-04-30 16:12:59 -0700175 advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
176 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
177 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
178 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
179
Steve McKay5bbae102015-10-01 11:39:24 -0700180 return true;
Steve McKay69aee092015-04-30 16:12:59 -0700181 }
182
Daichi Hironoda19ee02016-01-13 13:19:02 +0900183 @Override
184 protected void onDestroy() {
185 mRoots.setOnCacheUpdateListener(null);
186 super.onDestroy();
187 }
188
Steve McKay95cd85a2016-02-04 12:15:22 -0800189 private State getState(@Nullable Bundle icicle) {
190 if (icicle != null) {
191 State state = icicle.<State>getParcelable(EXTRA_STATE);
192 if (DEBUG) Log.d(mTag, "Recovered existing state object: " + state);
193 return state;
194 }
195
196 State state = createSharedState();
197 includeState(state);
198 if (DEBUG) Log.d(mTag, "Created new state object: " + state);
199 return state;
200 }
201
202 private State createSharedState() {
Ben Kwa0f7078f02015-09-08 07:31:19 -0700203 State state = new State();
204
205 final Intent intent = getIntent();
Ben Kwa0f7078f02015-09-08 07:31:19 -0700206
207 state.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
Ben Kwa0f7078f02015-09-08 07:31:19 -0700208
Steve McKay83df8c02015-09-16 15:07:31 -0700209 state.forceSize = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, false);
210 state.showSize = state.forceSize || LocalPreferences.getDisplayFileSize(this);
211
212 state.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
213 state.showAdvanced = state.forceAdvanced
214 || LocalPreferences.getDisplayAdvancedDevices(this);
215
216 state.initAcceptMimes(intent);
Ben Kwa0f7078f02015-09-08 07:31:19 -0700217 state.excludedAuthorities = getExcludedAuthorities();
218
219 return state;
220 }
221
Steve McKayaa15dae2016-02-09 16:17:24 -0800222 public void setRootsDrawerOpen(boolean open) {
223 mNavigator.revealRootsDrawer(open);
224 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700225
226 void onRootPicked(RootInfo root) {
Aga Wronskae436f942016-02-08 12:00:38 -0800227 // Skip refreshing if root nor directory didn't change
228 if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
Aga Wronska45f75e22016-02-02 18:01:58 -0800229 return;
230 }
231
Steve McKay3eb2d072016-01-25 19:00:22 -0800232 mState.derivedMode = LocalPreferences.getViewMode(this, root, MODE_GRID);
233
Steve McKayef3e2cf2015-04-20 17:18:15 -0700234 // Clear entire backstack and start in new root
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900235 mState.onRootChanged(root);
Steve McKaya78a3692015-04-22 15:55:34 -0700236 mSearchManager.update(root);
237
238 // Recents is always in memory, so we just load it directly.
239 // Otherwise we delegate loading data from disk to a task
240 // to ensure a responsive ui.
241 if (mRoots.isRecentsRoot(root)) {
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900242 refreshCurrentRootAndDirectory(ANIM_NONE);
Steve McKaya78a3692015-04-22 15:55:34 -0700243 } else {
Steve McKay95cd85a2016-02-04 12:15:22 -0800244 new PickRootTask(this, root).executeOnExecutor(getExecutorForCurrentDirectory());
Daichi Hirono60e9a072015-12-25 11:08:42 +0900245 }
246 }
247
Steve McKayef3e2cf2015-04-20 17:18:15 -0700248 @Override
249 public boolean onOptionsItemSelected(MenuItem item) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700250
Steve McKay8fd086a2015-12-04 11:19:09 -0800251 switch (item.getItemId()) {
252 case android.R.id.home:
253 onBackPressed();
254 return true;
255
256 case R.id.menu_create_dir:
257 showCreateDirectoryDialog();
258 return true;
259
260 case R.id.menu_search:
261 return false;
262
263 case R.id.menu_sort_name:
264 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
265 return true;
266
267 case R.id.menu_sort_date:
268 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
269 return true;
270 case R.id.menu_sort_size:
271 setUserSortOrder(State.SORT_ORDER_SIZE);
272 return true;
273
274 case R.id.menu_grid:
Steve McKay3eb2d072016-01-25 19:00:22 -0800275 setViewMode(State.MODE_GRID);
Steve McKay8fd086a2015-12-04 11:19:09 -0800276 return true;
277
278 case R.id.menu_list:
Steve McKay3eb2d072016-01-25 19:00:22 -0800279 setViewMode(State.MODE_LIST);
Steve McKay8fd086a2015-12-04 11:19:09 -0800280 return true;
281
282 case R.id.menu_paste_from_clipboard:
Steve McKay3ce95952016-02-02 11:41:03 -0800283 DirectoryFragment dir = getDirectoryFragment();
284 if (dir != null) {
285 dir.pasteFromClipboard();
286 }
Ben Kwa359bbeb2016-02-17 10:48:57 -0800287 return true;
Steve McKay8fd086a2015-12-04 11:19:09 -0800288
289 case R.id.menu_advanced:
290 setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this));
291 return true;
292
293 case R.id.menu_file_size:
294 setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
295 return true;
296
297 case R.id.menu_settings:
298 final RootInfo root = getCurrentRoot();
299 final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
300 intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
301 startActivity(intent);
302 return true;
303
304 default:
305 return super.onOptionsItemSelected(item);
306 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700307 }
308
Steve McKay3ce95952016-02-02 11:41:03 -0800309 final @Nullable DirectoryFragment getDirectoryFragment() {
310 return DirectoryFragment.get(getFragmentManager());
311 }
312
Steve McKaya521d0d2015-05-19 16:10:25 -0700313 void showCreateDirectoryDialog() {
314 CreateDirectoryFragment.show(getFragmentManager());
315 }
316
317 /**
318 * Returns true if a directory can be created in the current location.
319 * @return
320 */
321 boolean canCreateDirectory() {
322 final RootInfo root = getCurrentRoot();
323 final DocumentInfo cwd = getCurrentDirectory();
324 return cwd != null
325 && cwd.isCreateSupported()
326 && !mSearchManager.isSearching()
Steve McKay5bbae102015-10-01 11:39:24 -0700327 && !root.isRecents()
Steve McKaya521d0d2015-05-19 16:10:25 -0700328 && !root.isDownloads();
329 }
330
Steve McKay6eaf38632015-08-04 10:11:01 -0700331 void onDirectoryCreated(DocumentInfo doc) {
332 checkArgument(doc.isDirectory());
Tomasz Mikolajewski39acff52015-11-25 13:01:18 +0900333 openContainerDocument(doc);
Steve McKay6eaf38632015-08-04 10:11:01 -0700334 }
335
Tomasz Mikolajewski39acff52015-11-25 13:01:18 +0900336 void openContainerDocument(DocumentInfo doc) {
337 checkArgument(doc.isContainer());
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900338 mState.pushDocument(doc);
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900339 // Show an opening animation only if pressing "back" would get us back to the
340 // previous directory. Especially after opening a root document, pressing
341 // back, wouldn't go to the previous root, but close the activity.
342 final int anim = (mState.hasLocationChanged() && mState.stack.size() > 1)
Tomasz Mikolajewskie3fe9d72016-02-03 16:53:21 +0900343 ? ANIM_ENTER : ANIM_NONE;
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900344 refreshCurrentRootAndDirectory(anim);
Steve McKay6eaf38632015-08-04 10:11:01 -0700345 }
346
Steve McKayef3e2cf2015-04-20 17:18:15 -0700347 /**
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800348 * Refreshes the content of the director and the menu/action bar.
349 * The current directory name and selection will get updated.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700350 * @param anim
351 */
Steve McKay1f264a82016-02-03 11:15:57 -0800352 @Override
353 public final void refreshCurrentRootAndDirectory(int anim) {
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800354 mSearchManager.cancelSearch();
355
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800356 refreshDirectory(anim);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700357
358 final RootsFragment roots = RootsFragment.get(getFragmentManager());
359 if (roots != null) {
360 roots.onCurrentRootChanged();
361 }
362
Steve McKay1f264a82016-02-03 11:15:57 -0800363 mNavigator.update();
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800364 invalidateOptionsMenu();
365 }
366
367 /**
368 * Called when search results changed.
369 * Refreshes the content of the directory. It doesn't refresh elements on the action bar.
370 * e.g. The current directory name displayed on the action bar won't get updated.
371 */
372 @Override
373 public void onSearchChanged() {
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800374 refreshDirectory(ANIM_NONE);
375 }
376
377 /**
378 * Called when search query changed.
379 * Updates the state object.
380 * @param query - New query
381 */
382 @Override
383 public void onSearchQueryChanged(String query) {
384 mState.currentSearch = query;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700385 }
386
Ben Kwa77797402015-05-29 15:40:31 -0700387 final List<String> getExcludedAuthorities() {
388 List<String> authorities = new ArrayList<>();
389 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
390 // Exclude roots provided by the calling package.
391 String packageName = getCallingPackageMaybeExtra();
392 try {
393 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
394 PackageManager.GET_PROVIDERS);
395 for (ProviderInfo provider: pkgInfo.providers) {
396 authorities.add(provider.authority);
397 }
398 } catch (PackageManager.NameNotFoundException e) {
399 Log.e(mTag, "Calling package name does not resolve: " + packageName);
400 }
401 }
402 return authorities;
403 }
404
Aga Wronska91c7fb32016-01-29 11:41:41 -0800405 boolean canSearchRoot() {
406 final RootInfo root = getCurrentRoot();
407 return (root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0;
408 }
409
Steve McKayef3e2cf2015-04-20 17:18:15 -0700410 final String getCallingPackageMaybeExtra() {
Ben Kwa77797402015-05-29 15:40:31 -0700411 String callingPackage = getCallingPackage();
412 // System apps can set the calling package name using an extra.
413 try {
414 ApplicationInfo info = getPackageManager().getApplicationInfo(callingPackage, 0);
415 if (info.isSystemApp() || info.isUpdatedSystemApp()) {
416 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
417 if (extra != null) {
418 callingPackage = extra;
419 }
420 }
421 } finally {
422 return callingPackage;
423 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700424 }
Steve McKaye934ce62015-03-25 14:35:33 -0700425
426 public static BaseActivity get(Fragment fragment) {
427 return (BaseActivity) fragment.getActivity();
428 }
429
Ben Kwa0f7078f02015-09-08 07:31:19 -0700430 public State getDisplayState() {
431 return mState;
432 }
433
Steve McKayef3e2cf2015-04-20 17:18:15 -0700434 void setDisplayAdvancedDevices(boolean display) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700435 LocalPreferences.setDisplayAdvancedDevices(this, display);
Steve McKay323ee3e2015-09-25 16:02:56 -0700436 mState.showAdvanced = mState.forceAdvanced | display;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700437 RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
438 invalidateOptionsMenu();
439 }
440
441 void setDisplayFileSize(boolean display) {
442 LocalPreferences.setDisplayFileSize(this, display);
Steve McKay323ee3e2015-09-25 16:02:56 -0700443 mState.showSize = display;
Steve McKay3ce95952016-02-02 11:41:03 -0800444 DirectoryFragment dir = getDirectoryFragment();
445 if (dir != null) {
446 dir.onDisplayStateChanged();
447 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700448 invalidateOptionsMenu();
449 }
450
Steve McKayef3e2cf2015-04-20 17:18:15 -0700451 /**
452 * Set state sort order based on explicit user action.
453 */
454 void setUserSortOrder(int sortOrder) {
Steve McKay323ee3e2015-09-25 16:02:56 -0700455 mState.userSortOrder = sortOrder;
Steve McKay3ce95952016-02-02 11:41:03 -0800456 DirectoryFragment dir = getDirectoryFragment();
457 if (dir != null) {
458 dir.onSortOrderChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800459 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700460 }
461
462 /**
Steve McKay3eb2d072016-01-25 19:00:22 -0800463 * Set mode based on explicit user action.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700464 */
Steve McKay3eb2d072016-01-25 19:00:22 -0800465 void setViewMode(@ViewMode int mode) {
Steve McKayc2651172016-02-17 11:00:55 -0800466 LocalPreferences.setViewMode(this, getCurrentRoot(), mode);
Steve McKay3eb2d072016-01-25 19:00:22 -0800467 mState.derivedMode = mode;
468
469 // view icon needs to be updated, but we *could* do it
470 // in onOptionsItemSelected, and not do the full invalidation
471 // But! That's a larger refactoring we'll save for another day.
472 invalidateOptionsMenu();
Steve McKay3ce95952016-02-02 11:41:03 -0800473 DirectoryFragment dir = getDirectoryFragment();
474 if (dir != null) {
475 dir.onViewModeChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800476 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700477 }
478
Aga Wronska3b327ef2016-01-20 16:32:33 -0800479 public void setPending(boolean pending) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700480 final SaveFragment save = SaveFragment.get(getFragmentManager());
481 if (save != null) {
482 save.setPending(pending);
483 }
484 }
485
486 @Override
487 protected void onSaveInstanceState(Bundle state) {
488 super.onSaveInstanceState(state);
Steve McKay323ee3e2015-09-25 16:02:56 -0700489 state.putParcelable(EXTRA_STATE, mState);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700490 }
491
492 @Override
493 protected void onRestoreInstanceState(Bundle state) {
494 super.onRestoreInstanceState(state);
495 }
496
Steve McKay1f264a82016-02-03 11:15:57 -0800497 @Override
498 public boolean isSearchExpanded() {
499 return mSearchManager.isExpanded();
500 }
501
502 @Override
Steve McKayf8621552015-11-03 15:23:16 -0800503 public RootInfo getCurrentRoot() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700504 if (mState.stack.root != null) {
505 return mState.stack.root;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700506 } else {
507 return mRoots.getRecentsRoot();
508 }
509 }
510
511 public DocumentInfo getCurrentDirectory() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700512 return mState.stack.peek();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700513 }
514
Steve McKay83df8c02015-09-16 15:07:31 -0700515 public Executor getExecutorForCurrentDirectory() {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700516 final DocumentInfo cwd = getCurrentDirectory();
517 if (cwd != null && cwd.authority != null) {
518 return ProviderExecutor.forAuthority(cwd.authority);
519 } else {
520 return AsyncTask.THREAD_POOL_EXECUTOR;
521 }
522 }
523
Steve McKay12055472015-08-20 16:48:49 -0700524 @Override
525 public void onBackPressed() {
526 // While action bar is expanded, the state stack UI is hidden.
527 if (mSearchManager.cancelSearch()) {
528 return;
529 }
530
Steve McKay3ce95952016-02-02 11:41:03 -0800531 DirectoryFragment dir = getDirectoryFragment();
532 if (dir != null && dir.onBackPressed()) {
Steve McKaycbee5442016-01-28 15:30:10 -0800533 return;
534 }
535
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900536 if (!mState.hasLocationChanged()) {
Steve McKay12055472015-08-20 16:48:49 -0700537 super.onBackPressed();
538 return;
539 }
540
Steve McKayf8737692016-02-04 19:40:45 -0800541 int size = mState.stack.size();
Steve McKay12055472015-08-20 16:48:49 -0700542
Steve McKayf8737692016-02-04 19:40:45 -0800543 // Do some "do what a I want" drawer fiddling, but don't
544 // do it if user already hit back recently and we recently
545 // did some fiddling.
Steve McKay9146ac62016-02-09 12:40:07 -0800546 if (mDrawer.isPresent()
547 && (System.currentTimeMillis() - mDrawerLastFiddled) > DRAWER_NO_FIDDLE_DELAY) {
Steve McKayf8737692016-02-04 19:40:45 -0800548 // Close drawer if it is open.
549 if (mDrawer.isOpen()) {
550 mDrawer.setOpen(false);
551 mDrawerLastFiddled = System.currentTimeMillis();
552 return;
553 }
554
555 // Open the Close drawer if it is closed and we're at the top of a root.
556 if (size == 1) {
557 mDrawer.setOpen(true);
558 // Remember so we don't just close it again if back is pressed again.
559 mDrawerLastFiddled = System.currentTimeMillis();
560 return;
561 }
562 }
563
Ben Kwa359bbeb2016-02-17 10:48:57 -0800564 if (popDir()) {
Steve McKayf8737692016-02-04 19:40:45 -0800565 return;
Steve McKay12055472015-08-20 16:48:49 -0700566 }
Steve McKayf8737692016-02-04 19:40:45 -0800567
568 super.onBackPressed();
Steve McKay12055472015-08-20 16:48:49 -0700569 }
570
Steve McKayef3e2cf2015-04-20 17:18:15 -0700571 public void onStackPicked(DocumentStack stack) {
572 try {
573 // Update the restored stack to ensure we have freshest data
574 stack.updateDocuments(getContentResolver());
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900575 mState.setStack(stack);
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800576 refreshCurrentRootAndDirectory(ANIM_SIDE);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700577
578 } catch (FileNotFoundException e) {
579 Log.w(mTag, "Failed to restore stack: " + e);
580 }
581 }
582
Ben Kwa2036dad2016-02-10 07:46:35 -0800583 /**
584 * Declare a global key handler to route key events when there isn't a specific focus view. This
585 * covers the scenario where a user opens DocumentsUI and just starts typing.
586 *
587 * @param keyCode
588 * @param event
589 * @return
590 */
591 @CallSuper
592 @Override
593 public boolean onKeyDown(int keyCode, KeyEvent event) {
594 if (Events.isNavigationKeyCode(keyCode)) {
595 // Forward all unclaimed navigation keystrokes to the DirectoryFragment. This causes any
596 // stray navigation keystrokes focus the content pane, which is probably what the user
597 // is trying to do.
598 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
599 if (df != null) {
600 df.requestFocus();
601 return true;
602 }
603 } else if (keyCode == KeyEvent.KEYCODE_TAB) {
Ben Kwa359bbeb2016-02-17 10:48:57 -0800604 // Tab toggles focus on the navigation drawer.
Ben Kwa2036dad2016-02-10 07:46:35 -0800605 toggleNavDrawerFocus();
606 return true;
Ben Kwa359bbeb2016-02-17 10:48:57 -0800607 } else if (keyCode == KeyEvent.KEYCODE_DEL) {
608 popDir();
609 return true;
Ben Kwa2036dad2016-02-10 07:46:35 -0800610 }
611 return super.onKeyDown(keyCode, event);
612 }
613
614 /**
615 * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't
616 * locked, open/close it as appropriate.
617 */
618 void toggleNavDrawerFocus() {
619 if (mNavDrawerHasFocus) {
620 mDrawer.setOpen(false);
621 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
622 if (df != null) {
623 df.requestFocus();
624 }
625 } else {
626 mDrawer.setOpen(true);
627 RootsFragment rf = RootsFragment.get(getFragmentManager());
628 if (rf != null) {
629 rf.requestFocus();
630 }
631 }
632 mNavDrawerHasFocus = !mNavDrawerHasFocus;
633 }
634
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900635 DocumentInfo getRootDocumentBlocking(RootInfo root) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900636 try {
637 final Uri uri = DocumentsContract.buildDocumentUri(
638 root.authority, root.documentId);
639 return DocumentInfo.fromUri(getContentResolver(), uri);
640 } catch (FileNotFoundException e) {
641 Log.w(mTag, "Failed to find root", e);
642 return null;
643 }
644 }
645
Ben Kwa359bbeb2016-02-17 10:48:57 -0800646 /**
647 * Pops the top entry off the directory stack, and returns the user to the previous directory.
648 * If the directory stack only contains one item, this method does nothing.
649 *
650 * @return Whether the stack was popped.
651 */
652 private boolean popDir() {
653 if (mState.stack.size() > 1) {
654 mState.stack.pop();
655 refreshCurrentRootAndDirectory(ANIM_LEAVE);
656 return true;
657 }
658 return false;
659 }
660
Steve McKay95cd85a2016-02-04 12:15:22 -0800661 private static final class PickRootTask extends PairedTask<BaseActivity, Void, DocumentInfo> {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700662 private RootInfo mRoot;
663
Steve McKay95cd85a2016-02-04 12:15:22 -0800664 public PickRootTask(BaseActivity activity, RootInfo root) {
665 super(activity);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700666 mRoot = root;
667 }
668
669 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800670 protected DocumentInfo run(Void... params) {
671 return mOwner.getRootDocumentBlocking(mRoot);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700672 }
673
674 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800675 protected void finish(DocumentInfo result) {
676 if (result != null) {
677 mOwner.openContainerDocument(result);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700678 }
679 }
680 }
681
Steve McKay95cd85a2016-02-04 12:15:22 -0800682 private static final class HandleRootsChangedTask
683 extends PairedTask<BaseActivity, RootInfo, RootInfo> {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900684 DocumentInfo mHome;
685
Steve McKay95cd85a2016-02-04 12:15:22 -0800686 public HandleRootsChangedTask(BaseActivity activity) {
687 super(activity);
688 }
689
Daichi Hirono60e9a072015-12-25 11:08:42 +0900690 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800691 protected RootInfo run(RootInfo... roots) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900692 checkArgument(roots.length == 1);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900693 final RootInfo currentRoot = roots[0];
Steve McKay95cd85a2016-02-04 12:15:22 -0800694 final Collection<RootInfo> cachedRoots = mOwner.mRoots.getRootsBlocking();
Daichi Hirono60e9a072015-12-25 11:08:42 +0900695 RootInfo homeRoot = null;
696 for (final RootInfo root : cachedRoots) {
697 if (root.isHome()) {
698 homeRoot = root;
699 }
700 if (root.getUri().equals(currentRoot.getUri())) {
701 // We don't need to change the current root as the current root was not removed.
702 return null;
703 }
704 }
705 Preconditions.checkNotNull(homeRoot);
Steve McKay95cd85a2016-02-04 12:15:22 -0800706 mHome = mOwner.getRootDocumentBlocking(homeRoot);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900707 return homeRoot;
708 }
709
710 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800711 protected void finish(RootInfo homeRoot) {
712 if (homeRoot != null && mHome != null) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900713 // Clear entire backstack and start in new root
Steve McKay95cd85a2016-02-04 12:15:22 -0800714 mOwner.mState.onRootChanged(homeRoot);
715 mOwner.mSearchManager.update(homeRoot);
716 mOwner.openContainerDocument(mHome);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900717 }
718 }
719 }
Steve McKaye934ce62015-03-25 14:35:33 -0700720}