blob: 69315f7b84eb0961202c820f0c4fd6f528b9be1c [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;
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +090020import static com.android.documentsui.Shared.EXTRA_BENCHMARK;
Steve McKay3eb2d072016-01-25 19:00:22 -080021import static com.android.documentsui.State.MODE_GRID;
Steve McKayef3e2cf2015-04-20 17:18:15 -070022
Steve McKaye934ce62015-03-25 14:35:33 -070023import android.app.Activity;
24import android.app.Fragment;
Aga Wronska893390b2016-02-17 13:50:42 -080025import android.app.FragmentManager;
Steve McKayef3e2cf2015-04-20 17:18:15 -070026import android.content.Intent;
Ben Kwa77797402015-05-29 15:40:31 -070027import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.ProviderInfo;
Steve McKayef3e2cf2015-04-20 17:18:15 -070031import android.net.Uri;
32import android.os.AsyncTask;
33import android.os.Bundle;
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +090034import android.os.Handler;
35import android.os.MessageQueue;
36import android.os.MessageQueue.IdleHandler;
Steve McKayef3e2cf2015-04-20 17:18:15 -070037import android.provider.DocumentsContract;
38import android.provider.DocumentsContract.Root;
Steve McKay3ce95952016-02-02 11:41:03 -080039import android.support.annotation.CallSuper;
Steve McKay12055472015-08-20 16:48:49 -070040import android.support.annotation.LayoutRes;
Steve McKay58efce32015-08-20 16:19:38 +000041import android.support.annotation.Nullable;
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +090042import android.support.annotation.VisibleForTesting;
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 Wronska893390b2016-02-17 13:50:42 -080049import com.android.documentsui.SearchViewManager.SearchManagerListener;
Steve McKay3eb2d072016-01-25 19:00:22 -080050import com.android.documentsui.State.ViewMode;
Steve McKayce710822016-03-11 10:49:32 -080051import com.android.documentsui.dirlist.AnimationView;
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;
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;
Daichi Hirono7352c552016-03-25 19:04:39 +090061import java.util.Date;
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
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +090068 private static final String BENCHMARK_TESTING_PACKAGE = "com.android.documentsui.appperftests";
69
Steve McKay12055472015-08-20 16:48:49 -070070 State mState;
Steve McKayef3e2cf2015-04-20 17:18:15 -070071 RootsCache mRoots;
Aga Wronska893390b2016-02-17 13:50:42 -080072 SearchViewManager mSearchManager;
Steve McKay12055472015-08-20 16:48:49 -070073 DrawerController mDrawer;
Steve McKay1f264a82016-02-03 11:15:57 -080074 NavigationView mNavigator;
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +090075 List<EventListener> mEventListeners = new ArrayList<>();
Steve McKaya78a3692015-04-22 15:55:34 -070076
Steve McKay9f9d5b42015-09-23 15:44:24 -070077 private final String mTag;
Steve McKayf8737692016-02-04 19:40:45 -080078
Steve McKay12055472015-08-20 16:48:49 -070079 @LayoutRes
80 private int mLayoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -070081
Ben Kwa2036dad2016-02-10 07:46:35 -080082 private boolean mNavDrawerHasFocus;
Daichi Hirono7352c552016-03-25 19:04:39 +090083 private long mStartTime;
Ben Kwa2036dad2016-02-10 07:46:35 -080084
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090085 public abstract void onDocumentPicked(DocumentInfo doc, Model model);
Steve McKaye934ce62015-03-25 14:35:33 -070086 public abstract void onDocumentsPicked(List<DocumentInfo> docs);
Steve McKay6eaf38632015-08-04 10:11:01 -070087
Steve McKayef3e2cf2015-04-20 17:18:15 -070088 abstract void onTaskFinished(Uri... uris);
Aga Wronskaf6a31d32016-01-15 17:30:15 -080089 abstract void refreshDirectory(int anim);
Steve McKay95cd85a2016-02-04 12:15:22 -080090 /** Allows sub-classes to include information in a newly created State instance. */
91 abstract void includeState(State initialState);
Steve McKayef3e2cf2015-04-20 17:18:15 -070092
Steve McKay12055472015-08-20 16:48:49 -070093 public BaseActivity(@LayoutRes int layoutId, String tag) {
94 mLayoutId = layoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -070095 mTag = tag;
96 }
97
Steve McKay1f264a82016-02-03 11:15:57 -080098 @CallSuper
Steve McKayef3e2cf2015-04-20 17:18:15 -070099 @Override
100 public void onCreate(Bundle icicle) {
Daichi Hirono7352c552016-03-25 19:04:39 +0900101 // Record the time when onCreate is invoked for metric.
102 mStartTime = new Date().getTime();
103
Steve McKayef3e2cf2015-04-20 17:18:15 -0700104 super.onCreate(icicle);
Steve McKay12055472015-08-20 16:48:49 -0700105
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +0900106 final Intent intent = getIntent();
107
Daichi Hirono7352c552016-03-25 19:04:39 +0900108 addListenerForLaunchCompletion();
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +0900109
Steve McKay1f264a82016-02-03 11:15:57 -0800110 setContentView(mLayoutId);
111
112 mDrawer = DrawerController.create(this);
Steve McKay95cd85a2016-02-04 12:15:22 -0800113 mState = getState(icicle);
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +0900114 Metrics.logActivityLaunch(this, mState, intent);
Ben Kwa72379982016-01-26 11:50:03 -0800115
Steve McKayef3e2cf2015-04-20 17:18:15 -0700116 mRoots = DocumentsApplication.getRootsCache(this);
Steve McKay1f264a82016-02-03 11:15:57 -0800117
Daichi Hirono60e9a072015-12-25 11:08:42 +0900118 mRoots.setOnCacheUpdateListener(
119 new RootsCache.OnCacheUpdateListener() {
120 @Override
121 public void onCacheUpdate() {
Steve McKay95cd85a2016-02-04 12:15:22 -0800122 new HandleRootsChangedTask(BaseActivity.this)
123 .execute(getCurrentRoot());
Daichi Hirono60e9a072015-12-25 11:08:42 +0900124 }
125 });
Steve McKay1f264a82016-02-03 11:15:57 -0800126
Aga Wronska893390b2016-02-17 13:50:42 -0800127 mSearchManager = new SearchViewManager(this, icicle);
Steve McKay12055472015-08-20 16:48:49 -0700128
Steve McKay1f264a82016-02-03 11:15:57 -0800129 DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
Aga Wronska78efc9f2016-03-23 14:34:45 -0700130 Display.adjustToolbar(toolbar, this);
Steve McKay1f264a82016-02-03 11:15:57 -0800131 setActionBar(toolbar);
132 mNavigator = new NavigationView(
133 mDrawer,
134 toolbar,
135 (Spinner) findViewById(R.id.stack),
136 mState,
137 this);
138
Steve McKay12055472015-08-20 16:48:49 -0700139 // Base classes must update result in their onCreate.
140 setResult(Activity.RESULT_CANCELED);
Steve McKaya78a3692015-04-22 15:55:34 -0700141 }
142
143 @Override
144 public boolean onCreateOptionsMenu(Menu menu) {
145 boolean showMenu = super.onCreateOptionsMenu(menu);
146
147 getMenuInflater().inflate(R.menu.activity, menu);
Aga Wronska893390b2016-02-17 13:50:42 -0800148 mNavigator.update();
Aga Wronskab0998562016-03-24 18:22:09 -0700149 boolean fullBarSearch = getResources().getBoolean(R.bool.full_bar_search_view);
150 mSearchManager.install((DocumentsToolbar) findViewById(R.id.toolbar), fullBarSearch);
Steve McKaya78a3692015-04-22 15:55:34 -0700151
152 return showMenu;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700153 }
154
Steve McKay69aee092015-04-30 16:12:59 -0700155 @Override
Steve McKay3ce95952016-02-02 11:41:03 -0800156 @CallSuper
Steve McKay69aee092015-04-30 16:12:59 -0700157 public boolean onPrepareOptionsMenu(Menu menu) {
Steve McKay5bbae102015-10-01 11:39:24 -0700158 super.onPrepareOptionsMenu(menu);
Steve McKay69aee092015-04-30 16:12:59 -0700159
Steve McKay3ce95952016-02-02 11:41:03 -0800160 mSearchManager.showMenu(canSearchRoot());
161
Steve McKay5bbae102015-10-01 11:39:24 -0700162 final boolean inRecents = getCurrentDirectory() == null;
Steve McKay69aee092015-04-30 16:12:59 -0700163
164 final MenuItem sort = menu.findItem(R.id.menu_sort);
165 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
166 final MenuItem grid = menu.findItem(R.id.menu_grid);
167 final MenuItem list = menu.findItem(R.id.menu_list);
Steve McKay69aee092015-04-30 16:12:59 -0700168 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
169
Steve McKay3ce95952016-02-02 11:41:03 -0800170 // Search uses backend ranking; no sorting, recents doesn't support sort.
Steve McKayf8300172016-03-08 14:01:47 -0800171 sort.setEnabled(!inRecents && !mSearchManager.isSearching());
Steve McKay3ce95952016-02-02 11:41:03 -0800172 sortSize.setVisible(mState.showSize); // Only sort by size when file sizes are visible
173 fileSize.setVisible(!mState.forceSize);
Steve McKay5bbae102015-10-01 11:39:24 -0700174
175 // grid/list is effectively a toggle.
176 grid.setVisible(mState.derivedMode != State.MODE_GRID);
177 list.setVisible(mState.derivedMode != State.MODE_LIST);
178
Steve McKay69aee092015-04-30 16:12:59 -0700179 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
180 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
181
Steve McKay5bbae102015-10-01 11:39:24 -0700182 return true;
Steve McKay69aee092015-04-30 16:12:59 -0700183 }
184
Daichi Hironoda19ee02016-01-13 13:19:02 +0900185 @Override
186 protected void onDestroy() {
187 mRoots.setOnCacheUpdateListener(null);
188 super.onDestroy();
189 }
190
Steve McKay95cd85a2016-02-04 12:15:22 -0800191 private State getState(@Nullable Bundle icicle) {
192 if (icicle != null) {
Aga Wronska893390b2016-02-17 13:50:42 -0800193 State state = icicle.<State>getParcelable(Shared.EXTRA_STATE);
Steve McKay95cd85a2016-02-04 12:15:22 -0800194 if (DEBUG) Log.d(mTag, "Recovered existing state object: " + state);
195 return state;
196 }
197
198 State state = createSharedState();
199 includeState(state);
200 if (DEBUG) Log.d(mTag, "Created new state object: " + state);
201 return state;
202 }
203
204 private State createSharedState() {
Ben Kwa0f7078f02015-09-08 07:31:19 -0700205 State state = new State();
206
207 final Intent intent = getIntent();
Ben Kwa0f7078f02015-09-08 07:31:19 -0700208
209 state.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
Ben Kwa0f7078f02015-09-08 07:31:19 -0700210
Steve McKay83df8c02015-09-16 15:07:31 -0700211 state.forceSize = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, false);
212 state.showSize = state.forceSize || LocalPreferences.getDisplayFileSize(this);
213
Steve McKay83df8c02015-09-16 15:07:31 -0700214 state.initAcceptMimes(intent);
Ben Kwa0f7078f02015-09-08 07:31:19 -0700215 state.excludedAuthorities = getExcludedAuthorities();
216
217 return state;
218 }
219
Steve McKayaa15dae2016-02-09 16:17:24 -0800220 public void setRootsDrawerOpen(boolean open) {
221 mNavigator.revealRootsDrawer(open);
222 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700223
224 void onRootPicked(RootInfo root) {
Aga Wronska893390b2016-02-17 13:50:42 -0800225 // Clicking on the current root removes search
226 mSearchManager.cancelSearch();
227
Aga Wronskae436f942016-02-08 12:00:38 -0800228 // Skip refreshing if root nor directory didn't change
229 if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
Aga Wronska45f75e22016-02-02 18:01:58 -0800230 return;
231 }
232
Steve McKay3eb2d072016-01-25 19:00:22 -0800233 mState.derivedMode = LocalPreferences.getViewMode(this, root, MODE_GRID);
234
Steve McKayef3e2cf2015-04-20 17:18:15 -0700235 // Clear entire backstack and start in new root
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900236 mState.onRootChanged(root);
Steve McKaya78a3692015-04-22 15:55:34 -0700237
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)) {
Steve McKayce710822016-03-11 10:49:32 -0800242 refreshCurrentRootAndDirectory(AnimationView.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:
Steve McKayf8300172016-03-08 14:01:47 -0800261 // SearchViewManager listens for this directly.
Steve McKay8fd086a2015-12-04 11:19:09 -0800262 return false;
263
264 case R.id.menu_sort_name:
265 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
266 return true;
267
268 case R.id.menu_sort_date:
269 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
270 return true;
271 case R.id.menu_sort_size:
272 setUserSortOrder(State.SORT_ORDER_SIZE);
273 return true;
274
275 case R.id.menu_grid:
Steve McKay3eb2d072016-01-25 19:00:22 -0800276 setViewMode(State.MODE_GRID);
Steve McKay8fd086a2015-12-04 11:19:09 -0800277 return true;
278
279 case R.id.menu_list:
Steve McKay3eb2d072016-01-25 19:00:22 -0800280 setViewMode(State.MODE_LIST);
Steve McKay8fd086a2015-12-04 11:19:09 -0800281 return true;
282
283 case R.id.menu_paste_from_clipboard:
Steve McKay3ce95952016-02-02 11:41:03 -0800284 DirectoryFragment dir = getDirectoryFragment();
285 if (dir != null) {
286 dir.pasteFromClipboard();
287 }
Ben Kwa359bbeb2016-02-17 10:48:57 -0800288 return true;
Steve McKay8fd086a2015-12-04 11:19:09 -0800289
Steve McKay8fd086a2015-12-04 11:19:09 -0800290 case R.id.menu_file_size:
291 setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
292 return true;
293
294 case R.id.menu_settings:
295 final RootInfo root = getCurrentRoot();
296 final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
297 intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
298 startActivity(intent);
299 return true;
300
301 default:
302 return super.onOptionsItemSelected(item);
303 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700304 }
305
Steve McKay3ce95952016-02-02 11:41:03 -0800306 final @Nullable DirectoryFragment getDirectoryFragment() {
307 return DirectoryFragment.get(getFragmentManager());
308 }
309
Steve McKaya521d0d2015-05-19 16:10:25 -0700310 void showCreateDirectoryDialog() {
311 CreateDirectoryFragment.show(getFragmentManager());
312 }
313
Steve McKayb9a20d12016-02-19 12:57:05 -0800314 void onDirectoryCreated(DocumentInfo doc) {
315 // By default we do nothing, just let the new directory appear.
316 // DocumentsActivity auto-opens directories after creating them
317 // As that is more attuned to the "picker" use cases it supports.
318 }
319
Steve McKaya521d0d2015-05-19 16:10:25 -0700320 /**
321 * Returns true if a directory can be created in the current location.
322 * @return
323 */
324 boolean canCreateDirectory() {
325 final RootInfo root = getCurrentRoot();
326 final DocumentInfo cwd = getCurrentDirectory();
327 return cwd != null
328 && cwd.isCreateSupported()
329 && !mSearchManager.isSearching()
Steve McKay5bbae102015-10-01 11:39:24 -0700330 && !root.isRecents()
Steve McKaya521d0d2015-05-19 16:10:25 -0700331 && !root.isDownloads();
332 }
333
Tomasz Mikolajewski39acff52015-11-25 13:01:18 +0900334 void openContainerDocument(DocumentInfo doc) {
Steve McKaya1f76802016-02-25 13:34:03 -0800335 assert(doc.isContainer());
336
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +0900337 notifyDirectoryNavigated(doc.derivedUri);
338
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900339 mState.pushDocument(doc);
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900340 // Show an opening animation only if pressing "back" would get us back to the
341 // previous directory. Especially after opening a root document, pressing
342 // back, wouldn't go to the previous root, but close the activity.
343 final int anim = (mState.hasLocationChanged() && mState.stack.size() > 1)
Steve McKayce710822016-03-11 10:49:32 -0800344 ? AnimationView.ANIM_ENTER : AnimationView.ANIM_NONE;
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900345 refreshCurrentRootAndDirectory(anim);
Steve McKay6eaf38632015-08-04 10:11:01 -0700346 }
347
Steve McKayef3e2cf2015-04-20 17:18:15 -0700348 /**
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800349 * Refreshes the content of the director and the menu/action bar.
350 * The current directory name and selection will get updated.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700351 * @param anim
352 */
Steve McKay1f264a82016-02-03 11:15:57 -0800353 @Override
354 public final void refreshCurrentRootAndDirectory(int anim) {
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800355 mSearchManager.cancelSearch();
356
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800357 refreshDirectory(anim);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700358
359 final RootsFragment roots = RootsFragment.get(getFragmentManager());
360 if (roots != null) {
361 roots.onCurrentRootChanged();
362 }
363
Steve McKay1f264a82016-02-03 11:15:57 -0800364 mNavigator.update();
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800365 invalidateOptionsMenu();
366 }
367
Steve McKayc95d87c2016-02-23 14:34:50 -0800368 final void loadRoot(final Uri uri) {
369 new LoadRootTask(this, uri).executeOnExecutor(
370 ProviderExecutor.forAuthority(uri.getAuthority()));
371 }
372
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800373 /**
374 * Called when search results changed.
375 * Refreshes the content of the directory. It doesn't refresh elements on the action bar.
376 * e.g. The current directory name displayed on the action bar won't get updated.
377 */
378 @Override
Aga Wronska893390b2016-02-17 13:50:42 -0800379 public void onSearchChanged(@Nullable String query) {
380 // We should not get here if root is not searchable
Steve McKaya1f76802016-02-25 13:34:03 -0800381 assert(canSearchRoot());
Aga Wronska893390b2016-02-17 13:50:42 -0800382 reloadSearch(query);
Aga Wronskab0998562016-03-24 18:22:09 -0700383 }
384
385 @Override
386 public void onSearchFinished() {
387 // Restores menu icons state
Steve McKayf8300172016-03-08 14:01:47 -0800388 invalidateOptionsMenu();
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800389 }
390
Aga Wronska893390b2016-02-17 13:50:42 -0800391 private void reloadSearch(String query) {
392 FragmentManager fm = getFragmentManager();
393 RootInfo root = getCurrentRoot();
394 DocumentInfo cwd = getCurrentDirectory();
395
396 DirectoryFragment.reloadSearch(fm, root, cwd, query);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700397 }
398
Ben Kwa77797402015-05-29 15:40:31 -0700399 final List<String> getExcludedAuthorities() {
400 List<String> authorities = new ArrayList<>();
401 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
402 // Exclude roots provided by the calling package.
403 String packageName = getCallingPackageMaybeExtra();
404 try {
405 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
406 PackageManager.GET_PROVIDERS);
407 for (ProviderInfo provider: pkgInfo.providers) {
408 authorities.add(provider.authority);
409 }
410 } catch (PackageManager.NameNotFoundException e) {
411 Log.e(mTag, "Calling package name does not resolve: " + packageName);
412 }
413 }
414 return authorities;
415 }
416
Aga Wronska91c7fb32016-01-29 11:41:41 -0800417 boolean canSearchRoot() {
418 final RootInfo root = getCurrentRoot();
419 return (root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0;
420 }
421
Steve McKayef3e2cf2015-04-20 17:18:15 -0700422 final String getCallingPackageMaybeExtra() {
Ben Kwa77797402015-05-29 15:40:31 -0700423 String callingPackage = getCallingPackage();
424 // System apps can set the calling package name using an extra.
425 try {
426 ApplicationInfo info = getPackageManager().getApplicationInfo(callingPackage, 0);
427 if (info.isSystemApp() || info.isUpdatedSystemApp()) {
428 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
429 if (extra != null) {
430 callingPackage = extra;
431 }
432 }
433 } finally {
434 return callingPackage;
435 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700436 }
Steve McKaye934ce62015-03-25 14:35:33 -0700437
438 public static BaseActivity get(Fragment fragment) {
439 return (BaseActivity) fragment.getActivity();
440 }
441
Ben Kwa0f7078f02015-09-08 07:31:19 -0700442 public State getDisplayState() {
443 return mState;
444 }
445
Aga Wronska4e627162016-03-22 14:18:43 -0700446 /*
447 * Get the default directory to be presented after starting the activity.
448 * Method can be overridden if the change of the behavior of the the child activity is needed.
449 */
450 public Uri getDefaultRoot() {
451 return Shared.isHomeRootHidden(this)
452 ? DocumentsContract.buildRootUri("com.android.providers.downloads.documents",
453 "downloads")
454 : DocumentsContract.buildHomeUri();
455 }
456
Steve McKayef3e2cf2015-04-20 17:18:15 -0700457 void setDisplayFileSize(boolean display) {
458 LocalPreferences.setDisplayFileSize(this, display);
Steve McKay323ee3e2015-09-25 16:02:56 -0700459 mState.showSize = display;
Steve McKay3ce95952016-02-02 11:41:03 -0800460 DirectoryFragment dir = getDirectoryFragment();
461 if (dir != null) {
462 dir.onDisplayStateChanged();
463 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700464 invalidateOptionsMenu();
465 }
466
Steve McKayef3e2cf2015-04-20 17:18:15 -0700467 /**
468 * Set state sort order based on explicit user action.
469 */
470 void setUserSortOrder(int sortOrder) {
Steve McKay323ee3e2015-09-25 16:02:56 -0700471 mState.userSortOrder = sortOrder;
Steve McKay3ce95952016-02-02 11:41:03 -0800472 DirectoryFragment dir = getDirectoryFragment();
473 if (dir != null) {
474 dir.onSortOrderChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800475 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700476 }
477
478 /**
Steve McKay3eb2d072016-01-25 19:00:22 -0800479 * Set mode based on explicit user action.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700480 */
Steve McKay3eb2d072016-01-25 19:00:22 -0800481 void setViewMode(@ViewMode int mode) {
Steve McKayc2651172016-02-17 11:00:55 -0800482 LocalPreferences.setViewMode(this, getCurrentRoot(), mode);
Steve McKay3eb2d072016-01-25 19:00:22 -0800483 mState.derivedMode = mode;
484
485 // view icon needs to be updated, but we *could* do it
486 // in onOptionsItemSelected, and not do the full invalidation
487 // But! That's a larger refactoring we'll save for another day.
488 invalidateOptionsMenu();
Steve McKay3ce95952016-02-02 11:41:03 -0800489 DirectoryFragment dir = getDirectoryFragment();
490 if (dir != null) {
491 dir.onViewModeChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800492 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700493 }
494
Aga Wronska3b327ef2016-01-20 16:32:33 -0800495 public void setPending(boolean pending) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700496 final SaveFragment save = SaveFragment.get(getFragmentManager());
497 if (save != null) {
498 save.setPending(pending);
499 }
500 }
501
502 @Override
503 protected void onSaveInstanceState(Bundle state) {
504 super.onSaveInstanceState(state);
Aga Wronska893390b2016-02-17 13:50:42 -0800505 state.putParcelable(Shared.EXTRA_STATE, mState);
506 mSearchManager.onSaveInstanceState(state);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700507 }
508
509 @Override
510 protected void onRestoreInstanceState(Bundle state) {
511 super.onRestoreInstanceState(state);
512 }
513
Steve McKay1f264a82016-02-03 11:15:57 -0800514 @Override
515 public boolean isSearchExpanded() {
516 return mSearchManager.isExpanded();
517 }
518
519 @Override
Steve McKayf8621552015-11-03 15:23:16 -0800520 public RootInfo getCurrentRoot() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700521 if (mState.stack.root != null) {
522 return mState.stack.root;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700523 } else {
524 return mRoots.getRecentsRoot();
525 }
526 }
527
528 public DocumentInfo getCurrentDirectory() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700529 return mState.stack.peek();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700530 }
531
Steve McKay83df8c02015-09-16 15:07:31 -0700532 public Executor getExecutorForCurrentDirectory() {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700533 final DocumentInfo cwd = getCurrentDirectory();
534 if (cwd != null && cwd.authority != null) {
535 return ProviderExecutor.forAuthority(cwd.authority);
536 } else {
537 return AsyncTask.THREAD_POOL_EXECUTOR;
538 }
539 }
540
Steve McKay12055472015-08-20 16:48:49 -0700541 @Override
542 public void onBackPressed() {
543 // While action bar is expanded, the state stack UI is hidden.
544 if (mSearchManager.cancelSearch()) {
545 return;
546 }
547
Steve McKay3ce95952016-02-02 11:41:03 -0800548 DirectoryFragment dir = getDirectoryFragment();
549 if (dir != null && dir.onBackPressed()) {
Steve McKaycbee5442016-01-28 15:30:10 -0800550 return;
551 }
552
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900553 if (!mState.hasLocationChanged()) {
Steve McKay12055472015-08-20 16:48:49 -0700554 super.onBackPressed();
555 return;
556 }
557
Steve McKayc95d87c2016-02-23 14:34:50 -0800558 if (onBeforePopDir() || popDir()) {
Steve McKayf8737692016-02-04 19:40:45 -0800559 return;
Steve McKay12055472015-08-20 16:48:49 -0700560 }
Steve McKayf8737692016-02-04 19:40:45 -0800561
562 super.onBackPressed();
Steve McKay12055472015-08-20 16:48:49 -0700563 }
564
Steve McKayc95d87c2016-02-23 14:34:50 -0800565 boolean onBeforePopDir() {
566 // Files app overrides this with some fancy logic.
567 return false;
568 }
569
Steve McKayef3e2cf2015-04-20 17:18:15 -0700570 public void onStackPicked(DocumentStack stack) {
571 try {
572 // Update the restored stack to ensure we have freshest data
573 stack.updateDocuments(getContentResolver());
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900574 mState.setStack(stack);
Steve McKayce710822016-03-11 10:49:32 -0800575 refreshCurrentRootAndDirectory(AnimationView.ANIM_SIDE);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700576
577 } catch (FileNotFoundException e) {
578 Log.w(mTag, "Failed to restore stack: " + e);
579 }
580 }
581
Ben Kwa2036dad2016-02-10 07:46:35 -0800582 /**
583 * Declare a global key handler to route key events when there isn't a specific focus view. This
584 * covers the scenario where a user opens DocumentsUI and just starts typing.
585 *
586 * @param keyCode
587 * @param event
588 * @return
589 */
590 @CallSuper
591 @Override
592 public boolean onKeyDown(int keyCode, KeyEvent event) {
593 if (Events.isNavigationKeyCode(keyCode)) {
594 // Forward all unclaimed navigation keystrokes to the DirectoryFragment. This causes any
595 // stray navigation keystrokes focus the content pane, which is probably what the user
596 // is trying to do.
597 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
598 if (df != null) {
599 df.requestFocus();
600 return true;
601 }
602 } else if (keyCode == KeyEvent.KEYCODE_TAB) {
Ben Kwa359bbeb2016-02-17 10:48:57 -0800603 // Tab toggles focus on the navigation drawer.
Ben Kwa2036dad2016-02-10 07:46:35 -0800604 toggleNavDrawerFocus();
605 return true;
Ben Kwa359bbeb2016-02-17 10:48:57 -0800606 } else if (keyCode == KeyEvent.KEYCODE_DEL) {
607 popDir();
608 return true;
Ben Kwa2036dad2016-02-10 07:46:35 -0800609 }
610 return super.onKeyDown(keyCode, event);
611 }
612
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +0900613 public void addEventListener(EventListener listener) {
614 mEventListeners.add(listener);
615 }
616
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +0900617 public void removeEventListener(EventListener listener) {
618 mEventListeners.remove(listener);
619 }
620
621 public void notifyDirectoryLoaded(Uri uri) {
622 for (EventListener listener : mEventListeners) {
623 listener.onDirectoryLoaded(uri);
624 }
625 }
626
627 void notifyDirectoryNavigated(Uri uri) {
628 for (EventListener listener : mEventListeners) {
629 listener.onDirectoryNavigated(uri);
630 }
631 }
632
Ben Kwa2036dad2016-02-10 07:46:35 -0800633 /**
634 * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't
635 * locked, open/close it as appropriate.
636 */
637 void toggleNavDrawerFocus() {
638 if (mNavDrawerHasFocus) {
639 mDrawer.setOpen(false);
640 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
641 if (df != null) {
642 df.requestFocus();
643 }
644 } else {
645 mDrawer.setOpen(true);
646 RootsFragment rf = RootsFragment.get(getFragmentManager());
647 if (rf != null) {
648 rf.requestFocus();
649 }
650 }
651 mNavDrawerHasFocus = !mNavDrawerHasFocus;
652 }
653
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900654 DocumentInfo getRootDocumentBlocking(RootInfo root) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900655 try {
656 final Uri uri = DocumentsContract.buildDocumentUri(
657 root.authority, root.documentId);
658 return DocumentInfo.fromUri(getContentResolver(), uri);
659 } catch (FileNotFoundException e) {
660 Log.w(mTag, "Failed to find root", e);
661 return null;
662 }
663 }
664
Ben Kwa359bbeb2016-02-17 10:48:57 -0800665 /**
666 * Pops the top entry off the directory stack, and returns the user to the previous directory.
667 * If the directory stack only contains one item, this method does nothing.
668 *
669 * @return Whether the stack was popped.
670 */
671 private boolean popDir() {
672 if (mState.stack.size() > 1) {
673 mState.stack.pop();
Steve McKayce710822016-03-11 10:49:32 -0800674 refreshCurrentRootAndDirectory(AnimationView.ANIM_LEAVE);
Ben Kwa359bbeb2016-02-17 10:48:57 -0800675 return true;
676 }
677 return false;
678 }
679
Daichi Hirono7352c552016-03-25 19:04:39 +0900680 /**
681 * Closes the activity when it's idle.
682 */
683 private void addListenerForLaunchCompletion() {
684 addEventListener(new EventListener() {
685 @Override
686 public void onDirectoryNavigated(Uri uri) {
687 }
688
689 @Override
690 public void onDirectoryLoaded(Uri uri) {
691 removeEventListener(this);
692 getMainLooper().getQueue().addIdleHandler(new IdleHandler() {
693 @Override
694 public boolean queueIdle() {
695 // If startup benchmark is requested by a whitelisted testing package, then
696 // close the activity once idle, and notify the testing activity.
697 if (getIntent().getBooleanExtra(EXTRA_BENCHMARK, false) &&
698 BENCHMARK_TESTING_PACKAGE.equals(getCallingPackage())) {
699 setResult(RESULT_OK);
700 finish();
701 }
702
703 Metrics.logStartupMs(
704 BaseActivity.this, (int) (new Date().getTime() - mStartTime));
705
706 // Remove the idle handler.
707 return false;
708 }
709 });
710 new Handler().post(new Runnable() {
711 @Override public void run() {
712 }
713 });
714 }
715 });
716 }
717
Steve McKay95cd85a2016-02-04 12:15:22 -0800718 private static final class PickRootTask extends PairedTask<BaseActivity, Void, DocumentInfo> {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700719 private RootInfo mRoot;
720
Steve McKay95cd85a2016-02-04 12:15:22 -0800721 public PickRootTask(BaseActivity activity, RootInfo root) {
722 super(activity);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700723 mRoot = root;
724 }
725
726 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800727 protected DocumentInfo run(Void... params) {
728 return mOwner.getRootDocumentBlocking(mRoot);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700729 }
730
731 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800732 protected void finish(DocumentInfo result) {
733 if (result != null) {
734 mOwner.openContainerDocument(result);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700735 }
736 }
737 }
738
Steve McKay95cd85a2016-02-04 12:15:22 -0800739 private static final class HandleRootsChangedTask
740 extends PairedTask<BaseActivity, RootInfo, RootInfo> {
Daichi Hirono72843c12016-03-15 12:47:57 +0900741 DocumentInfo mDownloadsDocument;
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900742
Steve McKay95cd85a2016-02-04 12:15:22 -0800743 public HandleRootsChangedTask(BaseActivity activity) {
744 super(activity);
745 }
746
Daichi Hirono60e9a072015-12-25 11:08:42 +0900747 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800748 protected RootInfo run(RootInfo... roots) {
Steve McKaya1f76802016-02-25 13:34:03 -0800749 assert(roots.length == 1);
750
Daichi Hirono60e9a072015-12-25 11:08:42 +0900751 final RootInfo currentRoot = roots[0];
Steve McKay95cd85a2016-02-04 12:15:22 -0800752 final Collection<RootInfo> cachedRoots = mOwner.mRoots.getRootsBlocking();
Daichi Hirono72843c12016-03-15 12:47:57 +0900753 RootInfo downloadsRoot = null;
Daichi Hirono60e9a072015-12-25 11:08:42 +0900754 for (final RootInfo root : cachedRoots) {
Daichi Hirono72843c12016-03-15 12:47:57 +0900755 if (root.isDownloads()) {
756 downloadsRoot = root;
Daichi Hirono60e9a072015-12-25 11:08:42 +0900757 }
758 if (root.getUri().equals(currentRoot.getUri())) {
759 // We don't need to change the current root as the current root was not removed.
760 return null;
761 }
762 }
Daichi Hirono72843c12016-03-15 12:47:57 +0900763 assert(downloadsRoot != null);
764 mDownloadsDocument = mOwner.getRootDocumentBlocking(downloadsRoot);
765 return downloadsRoot;
Daichi Hirono60e9a072015-12-25 11:08:42 +0900766 }
767
768 @Override
Daichi Hirono72843c12016-03-15 12:47:57 +0900769 protected void finish(RootInfo downloadsRoot) {
770 if (downloadsRoot != null && mDownloadsDocument != null) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900771 // Clear entire backstack and start in new root
Daichi Hirono72843c12016-03-15 12:47:57 +0900772 mOwner.mState.onRootChanged(downloadsRoot);
773 mOwner.mSearchManager.update(downloadsRoot);
774 mOwner.openContainerDocument(mDownloadsDocument);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900775 }
776 }
777 }
Steve McKaye934ce62015-03-25 14:35:33 -0700778}