blob: 95c49ffe8a924df3dfb19275373beb19287aa363 [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;
Aga Wronska893390b2016-02-17 13:50:42 -080026import static com.google.common.base.Preconditions.checkState;
Steve McKayef3e2cf2015-04-20 17:18:15 -070027
Steve McKaye934ce62015-03-25 14:35:33 -070028import android.app.Activity;
29import android.app.Fragment;
Aga Wronska893390b2016-02-17 13:50:42 -080030import android.app.FragmentManager;
Steve McKayef3e2cf2015-04-20 17:18:15 -070031import android.content.Intent;
Ben Kwa77797402015-05-29 15:40:31 -070032import android.content.pm.ApplicationInfo;
33import android.content.pm.PackageInfo;
34import android.content.pm.PackageManager;
35import android.content.pm.ProviderInfo;
Steve McKayef3e2cf2015-04-20 17:18:15 -070036import android.net.Uri;
37import android.os.AsyncTask;
38import android.os.Bundle;
Steve McKayef3e2cf2015-04-20 17:18:15 -070039import android.provider.DocumentsContract;
40import android.provider.DocumentsContract.Root;
Steve McKay3ce95952016-02-02 11:41:03 -080041import android.support.annotation.CallSuper;
Steve McKay12055472015-08-20 16:48:49 -070042import android.support.annotation.LayoutRes;
Steve McKay58efce32015-08-20 16:19:38 +000043import android.support.annotation.Nullable;
Steve McKayef3e2cf2015-04-20 17:18:15 -070044import android.util.Log;
Ben Kwa2036dad2016-02-10 07:46:35 -080045import android.view.KeyEvent;
Steve McKayef3e2cf2015-04-20 17:18:15 -070046import android.view.Menu;
47import android.view.MenuItem;
Steve McKay1f264a82016-02-03 11:15:57 -080048import android.widget.Spinner;
Steve McKaye934ce62015-03-25 14:35:33 -070049
Aga Wronska893390b2016-02-17 13:50:42 -080050import com.android.documentsui.SearchViewManager.SearchManagerListener;
Steve McKay3eb2d072016-01-25 19:00:22 -080051import com.android.documentsui.State.ViewMode;
Steve McKayf8621552015-11-03 15:23:16 -080052import com.android.documentsui.dirlist.DirectoryFragment;
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090053import com.android.documentsui.dirlist.Model;
Steve McKaye934ce62015-03-25 14:35:33 -070054import com.android.documentsui.model.DocumentInfo;
55import com.android.documentsui.model.DocumentStack;
Steve McKaye934ce62015-03-25 14:35:33 -070056import com.android.documentsui.model.RootInfo;
Daichi Hirono60e9a072015-12-25 11:08:42 +090057import com.android.internal.util.Preconditions;
Steve McKay273103b2015-05-12 12:49:58 -070058
Steve McKay273103b2015-05-12 12:49:58 -070059import java.io.FileNotFoundException;
Steve McKay273103b2015-05-12 12:49:58 -070060import java.util.ArrayList;
61import java.util.Collection;
Steve McKay273103b2015-05-12 12:49:58 -070062import java.util.List;
63import java.util.concurrent.Executor;
64
Steve McKay1f264a82016-02-03 11:15:57 -080065public abstract class BaseActivity extends Activity
66 implements SearchManagerListener, NavigationView.Environment {
Steve McKayef3e2cf2015-04-20 17:18:15 -070067
Steve McKay12055472015-08-20 16:48:49 -070068 State mState;
Steve McKayef3e2cf2015-04-20 17:18:15 -070069 RootsCache mRoots;
Aga Wronska893390b2016-02-17 13:50:42 -080070 SearchViewManager mSearchManager;
Steve McKay12055472015-08-20 16:48:49 -070071 DrawerController mDrawer;
Steve McKay1f264a82016-02-03 11:15:57 -080072 NavigationView mNavigator;
Steve McKaya78a3692015-04-22 15:55:34 -070073
Steve McKay9f9d5b42015-09-23 15:44:24 -070074 private final String mTag;
Steve McKayf8737692016-02-04 19:40:45 -080075
Steve McKay12055472015-08-20 16:48:49 -070076 @LayoutRes
77 private int mLayoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -070078
Ben Kwa2036dad2016-02-10 07:46:35 -080079 private boolean mNavDrawerHasFocus;
80
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090081 public abstract void onDocumentPicked(DocumentInfo doc, Model model);
Steve McKaye934ce62015-03-25 14:35:33 -070082 public abstract void onDocumentsPicked(List<DocumentInfo> docs);
Steve McKay6eaf38632015-08-04 10:11:01 -070083
Steve McKayef3e2cf2015-04-20 17:18:15 -070084 abstract void onTaskFinished(Uri... uris);
Aga Wronskaf6a31d32016-01-15 17:30:15 -080085 abstract void refreshDirectory(int anim);
Steve McKay95cd85a2016-02-04 12:15:22 -080086 /** Allows sub-classes to include information in a newly created State instance. */
87 abstract void includeState(State initialState);
Steve McKayef3e2cf2015-04-20 17:18:15 -070088
Steve McKay12055472015-08-20 16:48:49 -070089 public BaseActivity(@LayoutRes int layoutId, String tag) {
90 mLayoutId = layoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -070091 mTag = tag;
92 }
93
Steve McKay1f264a82016-02-03 11:15:57 -080094 @CallSuper
Steve McKayef3e2cf2015-04-20 17:18:15 -070095 @Override
96 public void onCreate(Bundle icicle) {
97 super.onCreate(icicle);
Steve McKay12055472015-08-20 16:48:49 -070098
Steve McKay1f264a82016-02-03 11:15:57 -080099 setContentView(mLayoutId);
100
101 mDrawer = DrawerController.create(this);
Steve McKay95cd85a2016-02-04 12:15:22 -0800102 mState = getState(icicle);
Ben Kwa72379982016-01-26 11:50:03 -0800103 Metrics.logActivityLaunch(this, mState, getIntent());
104
Steve McKayef3e2cf2015-04-20 17:18:15 -0700105 mRoots = DocumentsApplication.getRootsCache(this);
Steve McKay1f264a82016-02-03 11:15:57 -0800106
Daichi Hirono60e9a072015-12-25 11:08:42 +0900107 mRoots.setOnCacheUpdateListener(
108 new RootsCache.OnCacheUpdateListener() {
109 @Override
110 public void onCacheUpdate() {
Steve McKay95cd85a2016-02-04 12:15:22 -0800111 new HandleRootsChangedTask(BaseActivity.this)
112 .execute(getCurrentRoot());
Daichi Hirono60e9a072015-12-25 11:08:42 +0900113 }
114 });
Steve McKay1f264a82016-02-03 11:15:57 -0800115
Aga Wronska893390b2016-02-17 13:50:42 -0800116 mSearchManager = new SearchViewManager(this, icicle);
Steve McKay12055472015-08-20 16:48:49 -0700117
Steve McKay1f264a82016-02-03 11:15:57 -0800118 DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
119 setActionBar(toolbar);
120 mNavigator = new NavigationView(
121 mDrawer,
122 toolbar,
123 (Spinner) findViewById(R.id.stack),
124 mState,
125 this);
126
Steve McKay12055472015-08-20 16:48:49 -0700127 // Base classes must update result in their onCreate.
128 setResult(Activity.RESULT_CANCELED);
Steve McKaya78a3692015-04-22 15:55:34 -0700129 }
130
131 @Override
132 public boolean onCreateOptionsMenu(Menu menu) {
133 boolean showMenu = super.onCreateOptionsMenu(menu);
134
135 getMenuInflater().inflate(R.menu.activity, menu);
Aga Wronska893390b2016-02-17 13:50:42 -0800136 mNavigator.update();
Steve McKay1f264a82016-02-03 11:15:57 -0800137 mSearchManager.install((DocumentsToolbar) findViewById(R.id.toolbar));
Steve McKaya78a3692015-04-22 15:55:34 -0700138
139 return showMenu;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700140 }
141
Steve McKay69aee092015-04-30 16:12:59 -0700142 @Override
Steve McKay3ce95952016-02-02 11:41:03 -0800143 @CallSuper
Steve McKay69aee092015-04-30 16:12:59 -0700144 public boolean onPrepareOptionsMenu(Menu menu) {
Steve McKay5bbae102015-10-01 11:39:24 -0700145 super.onPrepareOptionsMenu(menu);
Steve McKay69aee092015-04-30 16:12:59 -0700146
Steve McKay3ce95952016-02-02 11:41:03 -0800147 mSearchManager.showMenu(canSearchRoot());
148
Steve McKay5bbae102015-10-01 11:39:24 -0700149 final boolean inRecents = getCurrentDirectory() == null;
Steve McKay69aee092015-04-30 16:12:59 -0700150
151 final MenuItem sort = menu.findItem(R.id.menu_sort);
152 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
153 final MenuItem grid = menu.findItem(R.id.menu_grid);
154 final MenuItem list = menu.findItem(R.id.menu_list);
Steve McKay69aee092015-04-30 16:12:59 -0700155 final MenuItem advanced = menu.findItem(R.id.menu_advanced);
156 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
157
Steve McKay3ce95952016-02-02 11:41:03 -0800158 // Search uses backend ranking; no sorting, recents doesn't support sort.
Steve McKay5bbae102015-10-01 11:39:24 -0700159 sort.setVisible(!inRecents && !mSearchManager.isSearching());
Steve McKay3ce95952016-02-02 11:41:03 -0800160 sortSize.setVisible(mState.showSize); // Only sort by size when file sizes are visible
161 fileSize.setVisible(!mState.forceSize);
Steve McKay5bbae102015-10-01 11:39:24 -0700162
163 // grid/list is effectively a toggle.
164 grid.setVisible(mState.derivedMode != State.MODE_GRID);
165 list.setVisible(mState.derivedMode != State.MODE_LIST);
166
Steve McKay5bbae102015-10-01 11:39:24 -0700167 advanced.setVisible(!mState.forceAdvanced);
Steve McKay69aee092015-04-30 16:12:59 -0700168 advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
169 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
170 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
171 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
172
Steve McKay5bbae102015-10-01 11:39:24 -0700173 return true;
Steve McKay69aee092015-04-30 16:12:59 -0700174 }
175
Daichi Hironoda19ee02016-01-13 13:19:02 +0900176 @Override
177 protected void onDestroy() {
178 mRoots.setOnCacheUpdateListener(null);
179 super.onDestroy();
180 }
181
Steve McKay95cd85a2016-02-04 12:15:22 -0800182 private State getState(@Nullable Bundle icicle) {
183 if (icicle != null) {
Aga Wronska893390b2016-02-17 13:50:42 -0800184 State state = icicle.<State>getParcelable(Shared.EXTRA_STATE);
Steve McKay95cd85a2016-02-04 12:15:22 -0800185 if (DEBUG) Log.d(mTag, "Recovered existing state object: " + state);
186 return state;
187 }
188
189 State state = createSharedState();
190 includeState(state);
191 if (DEBUG) Log.d(mTag, "Created new state object: " + state);
192 return state;
193 }
194
195 private State createSharedState() {
Ben Kwa0f7078f02015-09-08 07:31:19 -0700196 State state = new State();
197
198 final Intent intent = getIntent();
Ben Kwa0f7078f02015-09-08 07:31:19 -0700199
200 state.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
Ben Kwa0f7078f02015-09-08 07:31:19 -0700201
Steve McKay83df8c02015-09-16 15:07:31 -0700202 state.forceSize = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, false);
203 state.showSize = state.forceSize || LocalPreferences.getDisplayFileSize(this);
204
205 state.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
206 state.showAdvanced = state.forceAdvanced
207 || LocalPreferences.getDisplayAdvancedDevices(this);
208
209 state.initAcceptMimes(intent);
Ben Kwa0f7078f02015-09-08 07:31:19 -0700210 state.excludedAuthorities = getExcludedAuthorities();
211
212 return state;
213 }
214
Steve McKayaa15dae2016-02-09 16:17:24 -0800215 public void setRootsDrawerOpen(boolean open) {
216 mNavigator.revealRootsDrawer(open);
217 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700218
219 void onRootPicked(RootInfo root) {
Aga Wronska893390b2016-02-17 13:50:42 -0800220 // Clicking on the current root removes search
221 mSearchManager.cancelSearch();
222
Aga Wronskae436f942016-02-08 12:00:38 -0800223 // Skip refreshing if root nor directory didn't change
224 if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
Aga Wronska45f75e22016-02-02 18:01:58 -0800225 return;
226 }
227
Steve McKay3eb2d072016-01-25 19:00:22 -0800228 mState.derivedMode = LocalPreferences.getViewMode(this, root, MODE_GRID);
229
Steve McKayef3e2cf2015-04-20 17:18:15 -0700230 // Clear entire backstack and start in new root
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900231 mState.onRootChanged(root);
Steve McKaya78a3692015-04-22 15:55:34 -0700232
233 // Recents is always in memory, so we just load it directly.
234 // Otherwise we delegate loading data from disk to a task
235 // to ensure a responsive ui.
236 if (mRoots.isRecentsRoot(root)) {
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900237 refreshCurrentRootAndDirectory(ANIM_NONE);
Steve McKaya78a3692015-04-22 15:55:34 -0700238 } else {
Steve McKay95cd85a2016-02-04 12:15:22 -0800239 new PickRootTask(this, root).executeOnExecutor(getExecutorForCurrentDirectory());
Daichi Hirono60e9a072015-12-25 11:08:42 +0900240 }
241 }
242
Steve McKayef3e2cf2015-04-20 17:18:15 -0700243 @Override
244 public boolean onOptionsItemSelected(MenuItem item) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700245
Steve McKay8fd086a2015-12-04 11:19:09 -0800246 switch (item.getItemId()) {
247 case android.R.id.home:
248 onBackPressed();
249 return true;
250
251 case R.id.menu_create_dir:
252 showCreateDirectoryDialog();
253 return true;
254
255 case R.id.menu_search:
256 return false;
257
258 case R.id.menu_sort_name:
259 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
260 return true;
261
262 case R.id.menu_sort_date:
263 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
264 return true;
265 case R.id.menu_sort_size:
266 setUserSortOrder(State.SORT_ORDER_SIZE);
267 return true;
268
269 case R.id.menu_grid:
Steve McKay3eb2d072016-01-25 19:00:22 -0800270 setViewMode(State.MODE_GRID);
Steve McKay8fd086a2015-12-04 11:19:09 -0800271 return true;
272
273 case R.id.menu_list:
Steve McKay3eb2d072016-01-25 19:00:22 -0800274 setViewMode(State.MODE_LIST);
Steve McKay8fd086a2015-12-04 11:19:09 -0800275 return true;
276
277 case R.id.menu_paste_from_clipboard:
Steve McKay3ce95952016-02-02 11:41:03 -0800278 DirectoryFragment dir = getDirectoryFragment();
279 if (dir != null) {
280 dir.pasteFromClipboard();
281 }
Ben Kwa359bbeb2016-02-17 10:48:57 -0800282 return true;
Steve McKay8fd086a2015-12-04 11:19:09 -0800283
284 case R.id.menu_advanced:
285 setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this));
286 return true;
287
288 case R.id.menu_file_size:
289 setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
290 return true;
291
292 case R.id.menu_settings:
293 final RootInfo root = getCurrentRoot();
294 final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
295 intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
296 startActivity(intent);
297 return true;
298
299 default:
300 return super.onOptionsItemSelected(item);
301 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700302 }
303
Steve McKay3ce95952016-02-02 11:41:03 -0800304 final @Nullable DirectoryFragment getDirectoryFragment() {
305 return DirectoryFragment.get(getFragmentManager());
306 }
307
Steve McKaya521d0d2015-05-19 16:10:25 -0700308 void showCreateDirectoryDialog() {
309 CreateDirectoryFragment.show(getFragmentManager());
310 }
311
Steve McKayb9a20d12016-02-19 12:57:05 -0800312 void onDirectoryCreated(DocumentInfo doc) {
313 // By default we do nothing, just let the new directory appear.
314 // DocumentsActivity auto-opens directories after creating them
315 // As that is more attuned to the "picker" use cases it supports.
316 }
317
Steve McKaya521d0d2015-05-19 16:10:25 -0700318 /**
319 * Returns true if a directory can be created in the current location.
320 * @return
321 */
322 boolean canCreateDirectory() {
323 final RootInfo root = getCurrentRoot();
324 final DocumentInfo cwd = getCurrentDirectory();
325 return cwd != null
326 && cwd.isCreateSupported()
327 && !mSearchManager.isSearching()
Steve McKay5bbae102015-10-01 11:39:24 -0700328 && !root.isRecents()
Steve McKaya521d0d2015-05-19 16:10:25 -0700329 && !root.isDownloads();
330 }
331
Tomasz Mikolajewski39acff52015-11-25 13:01:18 +0900332 void openContainerDocument(DocumentInfo doc) {
333 checkArgument(doc.isContainer());
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900334 mState.pushDocument(doc);
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900335 // Show an opening animation only if pressing "back" would get us back to the
336 // previous directory. Especially after opening a root document, pressing
337 // back, wouldn't go to the previous root, but close the activity.
338 final int anim = (mState.hasLocationChanged() && mState.stack.size() > 1)
Tomasz Mikolajewskie3fe9d72016-02-03 16:53:21 +0900339 ? ANIM_ENTER : ANIM_NONE;
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900340 refreshCurrentRootAndDirectory(anim);
Steve McKay6eaf38632015-08-04 10:11:01 -0700341 }
342
Steve McKayef3e2cf2015-04-20 17:18:15 -0700343 /**
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800344 * Refreshes the content of the director and the menu/action bar.
345 * The current directory name and selection will get updated.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700346 * @param anim
347 */
Steve McKay1f264a82016-02-03 11:15:57 -0800348 @Override
349 public final void refreshCurrentRootAndDirectory(int anim) {
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800350 mSearchManager.cancelSearch();
351
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800352 refreshDirectory(anim);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700353
354 final RootsFragment roots = RootsFragment.get(getFragmentManager());
355 if (roots != null) {
356 roots.onCurrentRootChanged();
357 }
358
Steve McKay1f264a82016-02-03 11:15:57 -0800359 mNavigator.update();
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800360 invalidateOptionsMenu();
361 }
362
Steve McKayc95d87c2016-02-23 14:34:50 -0800363 final void loadRoot(final Uri uri) {
364 new LoadRootTask(this, uri).executeOnExecutor(
365 ProviderExecutor.forAuthority(uri.getAuthority()));
366 }
367
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800368 /**
369 * Called when search results changed.
370 * Refreshes the content of the directory. It doesn't refresh elements on the action bar.
371 * e.g. The current directory name displayed on the action bar won't get updated.
372 */
373 @Override
Aga Wronska893390b2016-02-17 13:50:42 -0800374 public void onSearchChanged(@Nullable String query) {
375 // We should not get here if root is not searchable
376 checkState(canSearchRoot());
377 reloadSearch(query);
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800378 }
379
Aga Wronska893390b2016-02-17 13:50:42 -0800380 private void reloadSearch(String query) {
381 FragmentManager fm = getFragmentManager();
382 RootInfo root = getCurrentRoot();
383 DocumentInfo cwd = getCurrentDirectory();
384
385 DirectoryFragment.reloadSearch(fm, root, cwd, query);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700386 }
387
Ben Kwa77797402015-05-29 15:40:31 -0700388 final List<String> getExcludedAuthorities() {
389 List<String> authorities = new ArrayList<>();
390 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
391 // Exclude roots provided by the calling package.
392 String packageName = getCallingPackageMaybeExtra();
393 try {
394 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
395 PackageManager.GET_PROVIDERS);
396 for (ProviderInfo provider: pkgInfo.providers) {
397 authorities.add(provider.authority);
398 }
399 } catch (PackageManager.NameNotFoundException e) {
400 Log.e(mTag, "Calling package name does not resolve: " + packageName);
401 }
402 }
403 return authorities;
404 }
405
Aga Wronska91c7fb32016-01-29 11:41:41 -0800406 boolean canSearchRoot() {
407 final RootInfo root = getCurrentRoot();
408 return (root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0;
409 }
410
Steve McKayef3e2cf2015-04-20 17:18:15 -0700411 final String getCallingPackageMaybeExtra() {
Ben Kwa77797402015-05-29 15:40:31 -0700412 String callingPackage = getCallingPackage();
413 // System apps can set the calling package name using an extra.
414 try {
415 ApplicationInfo info = getPackageManager().getApplicationInfo(callingPackage, 0);
416 if (info.isSystemApp() || info.isUpdatedSystemApp()) {
417 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
418 if (extra != null) {
419 callingPackage = extra;
420 }
421 }
422 } finally {
423 return callingPackage;
424 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700425 }
Steve McKaye934ce62015-03-25 14:35:33 -0700426
427 public static BaseActivity get(Fragment fragment) {
428 return (BaseActivity) fragment.getActivity();
429 }
430
Ben Kwa0f7078f02015-09-08 07:31:19 -0700431 public State getDisplayState() {
432 return mState;
433 }
434
Steve McKayef3e2cf2015-04-20 17:18:15 -0700435 void setDisplayAdvancedDevices(boolean display) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700436 LocalPreferences.setDisplayAdvancedDevices(this, display);
Steve McKay323ee3e2015-09-25 16:02:56 -0700437 mState.showAdvanced = mState.forceAdvanced | display;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700438 RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
439 invalidateOptionsMenu();
440 }
441
442 void setDisplayFileSize(boolean display) {
443 LocalPreferences.setDisplayFileSize(this, display);
Steve McKay323ee3e2015-09-25 16:02:56 -0700444 mState.showSize = display;
Steve McKay3ce95952016-02-02 11:41:03 -0800445 DirectoryFragment dir = getDirectoryFragment();
446 if (dir != null) {
447 dir.onDisplayStateChanged();
448 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700449 invalidateOptionsMenu();
450 }
451
Steve McKayef3e2cf2015-04-20 17:18:15 -0700452 /**
453 * Set state sort order based on explicit user action.
454 */
455 void setUserSortOrder(int sortOrder) {
Steve McKay323ee3e2015-09-25 16:02:56 -0700456 mState.userSortOrder = sortOrder;
Steve McKay3ce95952016-02-02 11:41:03 -0800457 DirectoryFragment dir = getDirectoryFragment();
458 if (dir != null) {
459 dir.onSortOrderChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800460 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700461 }
462
463 /**
Steve McKay3eb2d072016-01-25 19:00:22 -0800464 * Set mode based on explicit user action.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700465 */
Steve McKay3eb2d072016-01-25 19:00:22 -0800466 void setViewMode(@ViewMode int mode) {
Steve McKayc2651172016-02-17 11:00:55 -0800467 LocalPreferences.setViewMode(this, getCurrentRoot(), mode);
Steve McKay3eb2d072016-01-25 19:00:22 -0800468 mState.derivedMode = mode;
469
470 // view icon needs to be updated, but we *could* do it
471 // in onOptionsItemSelected, and not do the full invalidation
472 // But! That's a larger refactoring we'll save for another day.
473 invalidateOptionsMenu();
Steve McKay3ce95952016-02-02 11:41:03 -0800474 DirectoryFragment dir = getDirectoryFragment();
475 if (dir != null) {
476 dir.onViewModeChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800477 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700478 }
479
Aga Wronska3b327ef2016-01-20 16:32:33 -0800480 public void setPending(boolean pending) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700481 final SaveFragment save = SaveFragment.get(getFragmentManager());
482 if (save != null) {
483 save.setPending(pending);
484 }
485 }
486
487 @Override
488 protected void onSaveInstanceState(Bundle state) {
489 super.onSaveInstanceState(state);
Aga Wronska893390b2016-02-17 13:50:42 -0800490 state.putParcelable(Shared.EXTRA_STATE, mState);
491 mSearchManager.onSaveInstanceState(state);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700492 }
493
494 @Override
495 protected void onRestoreInstanceState(Bundle state) {
496 super.onRestoreInstanceState(state);
497 }
498
Steve McKay1f264a82016-02-03 11:15:57 -0800499 @Override
500 public boolean isSearchExpanded() {
501 return mSearchManager.isExpanded();
502 }
503
504 @Override
Steve McKayf8621552015-11-03 15:23:16 -0800505 public RootInfo getCurrentRoot() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700506 if (mState.stack.root != null) {
507 return mState.stack.root;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700508 } else {
509 return mRoots.getRecentsRoot();
510 }
511 }
512
513 public DocumentInfo getCurrentDirectory() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700514 return mState.stack.peek();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700515 }
516
Steve McKay83df8c02015-09-16 15:07:31 -0700517 public Executor getExecutorForCurrentDirectory() {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700518 final DocumentInfo cwd = getCurrentDirectory();
519 if (cwd != null && cwd.authority != null) {
520 return ProviderExecutor.forAuthority(cwd.authority);
521 } else {
522 return AsyncTask.THREAD_POOL_EXECUTOR;
523 }
524 }
525
Steve McKay12055472015-08-20 16:48:49 -0700526 @Override
527 public void onBackPressed() {
528 // While action bar is expanded, the state stack UI is hidden.
529 if (mSearchManager.cancelSearch()) {
530 return;
531 }
532
Steve McKay3ce95952016-02-02 11:41:03 -0800533 DirectoryFragment dir = getDirectoryFragment();
534 if (dir != null && dir.onBackPressed()) {
Steve McKaycbee5442016-01-28 15:30:10 -0800535 return;
536 }
537
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900538 if (!mState.hasLocationChanged()) {
Steve McKay12055472015-08-20 16:48:49 -0700539 super.onBackPressed();
540 return;
541 }
542
Steve McKayc95d87c2016-02-23 14:34:50 -0800543 if (onBeforePopDir() || popDir()) {
Steve McKayf8737692016-02-04 19:40:45 -0800544 return;
Steve McKay12055472015-08-20 16:48:49 -0700545 }
Steve McKayf8737692016-02-04 19:40:45 -0800546
547 super.onBackPressed();
Steve McKay12055472015-08-20 16:48:49 -0700548 }
549
Steve McKayc95d87c2016-02-23 14:34:50 -0800550 boolean onBeforePopDir() {
551 // Files app overrides this with some fancy logic.
552 return false;
553 }
554
Steve McKayef3e2cf2015-04-20 17:18:15 -0700555 public void onStackPicked(DocumentStack stack) {
556 try {
557 // Update the restored stack to ensure we have freshest data
558 stack.updateDocuments(getContentResolver());
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900559 mState.setStack(stack);
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800560 refreshCurrentRootAndDirectory(ANIM_SIDE);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700561
562 } catch (FileNotFoundException e) {
563 Log.w(mTag, "Failed to restore stack: " + e);
564 }
565 }
566
Ben Kwa2036dad2016-02-10 07:46:35 -0800567 /**
568 * Declare a global key handler to route key events when there isn't a specific focus view. This
569 * covers the scenario where a user opens DocumentsUI and just starts typing.
570 *
571 * @param keyCode
572 * @param event
573 * @return
574 */
575 @CallSuper
576 @Override
577 public boolean onKeyDown(int keyCode, KeyEvent event) {
578 if (Events.isNavigationKeyCode(keyCode)) {
579 // Forward all unclaimed navigation keystrokes to the DirectoryFragment. This causes any
580 // stray navigation keystrokes focus the content pane, which is probably what the user
581 // is trying to do.
582 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
583 if (df != null) {
584 df.requestFocus();
585 return true;
586 }
587 } else if (keyCode == KeyEvent.KEYCODE_TAB) {
Ben Kwa359bbeb2016-02-17 10:48:57 -0800588 // Tab toggles focus on the navigation drawer.
Ben Kwa2036dad2016-02-10 07:46:35 -0800589 toggleNavDrawerFocus();
590 return true;
Ben Kwa359bbeb2016-02-17 10:48:57 -0800591 } else if (keyCode == KeyEvent.KEYCODE_DEL) {
592 popDir();
593 return true;
Ben Kwa2036dad2016-02-10 07:46:35 -0800594 }
595 return super.onKeyDown(keyCode, event);
596 }
597
598 /**
599 * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't
600 * locked, open/close it as appropriate.
601 */
602 void toggleNavDrawerFocus() {
603 if (mNavDrawerHasFocus) {
604 mDrawer.setOpen(false);
605 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
606 if (df != null) {
607 df.requestFocus();
608 }
609 } else {
610 mDrawer.setOpen(true);
611 RootsFragment rf = RootsFragment.get(getFragmentManager());
612 if (rf != null) {
613 rf.requestFocus();
614 }
615 }
616 mNavDrawerHasFocus = !mNavDrawerHasFocus;
617 }
618
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900619 DocumentInfo getRootDocumentBlocking(RootInfo root) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900620 try {
621 final Uri uri = DocumentsContract.buildDocumentUri(
622 root.authority, root.documentId);
623 return DocumentInfo.fromUri(getContentResolver(), uri);
624 } catch (FileNotFoundException e) {
625 Log.w(mTag, "Failed to find root", e);
626 return null;
627 }
628 }
629
Ben Kwa359bbeb2016-02-17 10:48:57 -0800630 /**
631 * Pops the top entry off the directory stack, and returns the user to the previous directory.
632 * If the directory stack only contains one item, this method does nothing.
633 *
634 * @return Whether the stack was popped.
635 */
636 private boolean popDir() {
637 if (mState.stack.size() > 1) {
638 mState.stack.pop();
639 refreshCurrentRootAndDirectory(ANIM_LEAVE);
640 return true;
641 }
642 return false;
643 }
644
Steve McKay95cd85a2016-02-04 12:15:22 -0800645 private static final class PickRootTask extends PairedTask<BaseActivity, Void, DocumentInfo> {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700646 private RootInfo mRoot;
647
Steve McKay95cd85a2016-02-04 12:15:22 -0800648 public PickRootTask(BaseActivity activity, RootInfo root) {
649 super(activity);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700650 mRoot = root;
651 }
652
653 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800654 protected DocumentInfo run(Void... params) {
655 return mOwner.getRootDocumentBlocking(mRoot);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700656 }
657
658 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800659 protected void finish(DocumentInfo result) {
660 if (result != null) {
661 mOwner.openContainerDocument(result);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700662 }
663 }
664 }
665
Steve McKay95cd85a2016-02-04 12:15:22 -0800666 private static final class HandleRootsChangedTask
667 extends PairedTask<BaseActivity, RootInfo, RootInfo> {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900668 DocumentInfo mHome;
669
Steve McKay95cd85a2016-02-04 12:15:22 -0800670 public HandleRootsChangedTask(BaseActivity activity) {
671 super(activity);
672 }
673
Daichi Hirono60e9a072015-12-25 11:08:42 +0900674 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800675 protected RootInfo run(RootInfo... roots) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900676 checkArgument(roots.length == 1);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900677 final RootInfo currentRoot = roots[0];
Steve McKay95cd85a2016-02-04 12:15:22 -0800678 final Collection<RootInfo> cachedRoots = mOwner.mRoots.getRootsBlocking();
Daichi Hirono60e9a072015-12-25 11:08:42 +0900679 RootInfo homeRoot = null;
680 for (final RootInfo root : cachedRoots) {
681 if (root.isHome()) {
682 homeRoot = root;
683 }
684 if (root.getUri().equals(currentRoot.getUri())) {
685 // We don't need to change the current root as the current root was not removed.
686 return null;
687 }
688 }
689 Preconditions.checkNotNull(homeRoot);
Steve McKay95cd85a2016-02-04 12:15:22 -0800690 mHome = mOwner.getRootDocumentBlocking(homeRoot);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900691 return homeRoot;
692 }
693
694 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800695 protected void finish(RootInfo homeRoot) {
696 if (homeRoot != null && mHome != null) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900697 // Clear entire backstack and start in new root
Steve McKay95cd85a2016-02-04 12:15:22 -0800698 mOwner.mState.onRootChanged(homeRoot);
699 mOwner.mSearchManager.update(homeRoot);
700 mOwner.openContainerDocument(mHome);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900701 }
702 }
703 }
Steve McKaye934ce62015-03-25 14:35:33 -0700704}