blob: 87136ef35bc9795e89dcaf9417dad82a4ab9ba7f [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;
Aga Wronska774cc932016-03-30 18:07:59 -070021import static com.android.documentsui.State.ACTION_CREATE;
22import static com.android.documentsui.State.ACTION_OPEN;
23import static com.android.documentsui.State.ACTION_OPEN_TREE;
24import static com.android.documentsui.State.ACTION_GET_CONTENT;
25import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
Steve McKay3eb2d072016-01-25 19:00:22 -080026import static com.android.documentsui.State.MODE_GRID;
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;
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +090039import android.os.Handler;
40import android.os.MessageQueue;
41import android.os.MessageQueue.IdleHandler;
Steve McKayef3e2cf2015-04-20 17:18:15 -070042import android.provider.DocumentsContract;
43import android.provider.DocumentsContract.Root;
Steve McKay3ce95952016-02-02 11:41:03 -080044import android.support.annotation.CallSuper;
Steve McKay12055472015-08-20 16:48:49 -070045import android.support.annotation.LayoutRes;
Steve McKay58efce32015-08-20 16:19:38 +000046import android.support.annotation.Nullable;
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +090047import android.support.annotation.VisibleForTesting;
Steve McKayef3e2cf2015-04-20 17:18:15 -070048import android.util.Log;
Ben Kwa2036dad2016-02-10 07:46:35 -080049import android.view.KeyEvent;
Steve McKayef3e2cf2015-04-20 17:18:15 -070050import android.view.Menu;
51import android.view.MenuItem;
Aga Wronska198cbf62016-03-29 12:56:56 -070052import android.view.WindowManager;
Steve McKay1f264a82016-02-03 11:15:57 -080053import android.widget.Spinner;
Steve McKaye934ce62015-03-25 14:35:33 -070054
Aga Wronska893390b2016-02-17 13:50:42 -080055import com.android.documentsui.SearchViewManager.SearchManagerListener;
Steve McKay3eb2d072016-01-25 19:00:22 -080056import com.android.documentsui.State.ViewMode;
Steve McKayce710822016-03-11 10:49:32 -080057import com.android.documentsui.dirlist.AnimationView;
Steve McKayf8621552015-11-03 15:23:16 -080058import com.android.documentsui.dirlist.DirectoryFragment;
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090059import com.android.documentsui.dirlist.Model;
Steve McKaye934ce62015-03-25 14:35:33 -070060import com.android.documentsui.model.DocumentInfo;
61import com.android.documentsui.model.DocumentStack;
Steve McKaye934ce62015-03-25 14:35:33 -070062import com.android.documentsui.model.RootInfo;
Steve McKay273103b2015-05-12 12:49:58 -070063
Steve McKay273103b2015-05-12 12:49:58 -070064import java.io.FileNotFoundException;
Steve McKay273103b2015-05-12 12:49:58 -070065import java.util.ArrayList;
66import java.util.Collection;
Daichi Hirono7352c552016-03-25 19:04:39 +090067import java.util.Date;
Steve McKay273103b2015-05-12 12:49:58 -070068import java.util.List;
69import java.util.concurrent.Executor;
70
Steve McKay1f264a82016-02-03 11:15:57 -080071public abstract class BaseActivity extends Activity
72 implements SearchManagerListener, NavigationView.Environment {
Steve McKayef3e2cf2015-04-20 17:18:15 -070073
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +090074 private static final String BENCHMARK_TESTING_PACKAGE = "com.android.documentsui.appperftests";
75
Steve McKay12055472015-08-20 16:48:49 -070076 State mState;
Steve McKayef3e2cf2015-04-20 17:18:15 -070077 RootsCache mRoots;
Aga Wronska893390b2016-02-17 13:50:42 -080078 SearchViewManager mSearchManager;
Steve McKay12055472015-08-20 16:48:49 -070079 DrawerController mDrawer;
Steve McKay1f264a82016-02-03 11:15:57 -080080 NavigationView mNavigator;
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +090081 List<EventListener> mEventListeners = new ArrayList<>();
Steve McKaya78a3692015-04-22 15:55:34 -070082
Steve McKay9f9d5b42015-09-23 15:44:24 -070083 private final String mTag;
Steve McKayf8737692016-02-04 19:40:45 -080084
Steve McKay12055472015-08-20 16:48:49 -070085 @LayoutRes
86 private int mLayoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -070087
Ben Kwa2036dad2016-02-10 07:46:35 -080088 private boolean mNavDrawerHasFocus;
Daichi Hirono7352c552016-03-25 19:04:39 +090089 private long mStartTime;
Ben Kwa2036dad2016-02-10 07:46:35 -080090
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090091 public abstract void onDocumentPicked(DocumentInfo doc, Model model);
Steve McKaye934ce62015-03-25 14:35:33 -070092 public abstract void onDocumentsPicked(List<DocumentInfo> docs);
Steve McKay6eaf38632015-08-04 10:11:01 -070093
Steve McKayef3e2cf2015-04-20 17:18:15 -070094 abstract void onTaskFinished(Uri... uris);
Aga Wronskaf6a31d32016-01-15 17:30:15 -080095 abstract void refreshDirectory(int anim);
Steve McKay95cd85a2016-02-04 12:15:22 -080096 /** Allows sub-classes to include information in a newly created State instance. */
97 abstract void includeState(State initialState);
Steve McKayef3e2cf2015-04-20 17:18:15 -070098
Steve McKay12055472015-08-20 16:48:49 -070099 public BaseActivity(@LayoutRes int layoutId, String tag) {
100 mLayoutId = layoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700101 mTag = tag;
102 }
103
Steve McKay1f264a82016-02-03 11:15:57 -0800104 @CallSuper
Steve McKayef3e2cf2015-04-20 17:18:15 -0700105 @Override
106 public void onCreate(Bundle icicle) {
Aga Wronskacf59d322016-03-31 17:54:27 -0700107 // This flag is being set here as a result of the bug. When the flag was set in the
108 // styles.xml keyboard was messing the layout of dialogs (create dir, rename).
109 // Attempts were made to keep the flag in the main theme and to override it in the dialog
110 // layout xml or to create separate style for dialog and assign it in styles.xml.
111 // None of this brought successful results.
112 // Setting the flag works here most probably because of the timing when it is set. Also the
113 // setting might not affect the dialogs that are created in new windows or it affects them
114 // in the different way that having this in the style.
Aga Wronska198cbf62016-03-29 12:56:56 -0700115 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
116
Daichi Hirono7352c552016-03-25 19:04:39 +0900117 // Record the time when onCreate is invoked for metric.
118 mStartTime = new Date().getTime();
119
Steve McKayef3e2cf2015-04-20 17:18:15 -0700120 super.onCreate(icicle);
Steve McKay12055472015-08-20 16:48:49 -0700121
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +0900122 final Intent intent = getIntent();
123
Daichi Hirono7352c552016-03-25 19:04:39 +0900124 addListenerForLaunchCompletion();
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +0900125
Steve McKay1f264a82016-02-03 11:15:57 -0800126 setContentView(mLayoutId);
127
128 mDrawer = DrawerController.create(this);
Steve McKay95cd85a2016-02-04 12:15:22 -0800129 mState = getState(icicle);
Tomasz Mikolajewskie46d7f92016-03-15 17:41:31 +0900130 Metrics.logActivityLaunch(this, mState, intent);
Ben Kwa72379982016-01-26 11:50:03 -0800131
Steve McKayef3e2cf2015-04-20 17:18:15 -0700132 mRoots = DocumentsApplication.getRootsCache(this);
Steve McKay1f264a82016-02-03 11:15:57 -0800133
Daichi Hirono60e9a072015-12-25 11:08:42 +0900134 mRoots.setOnCacheUpdateListener(
135 new RootsCache.OnCacheUpdateListener() {
136 @Override
137 public void onCacheUpdate() {
Steve McKay95cd85a2016-02-04 12:15:22 -0800138 new HandleRootsChangedTask(BaseActivity.this)
139 .execute(getCurrentRoot());
Daichi Hirono60e9a072015-12-25 11:08:42 +0900140 }
141 });
Steve McKay1f264a82016-02-03 11:15:57 -0800142
Aga Wronska893390b2016-02-17 13:50:42 -0800143 mSearchManager = new SearchViewManager(this, icicle);
Steve McKay12055472015-08-20 16:48:49 -0700144
Steve McKay1f264a82016-02-03 11:15:57 -0800145 DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
Aga Wronska78efc9f2016-03-23 14:34:45 -0700146 Display.adjustToolbar(toolbar, this);
Steve McKay1f264a82016-02-03 11:15:57 -0800147 setActionBar(toolbar);
148 mNavigator = new NavigationView(
149 mDrawer,
150 toolbar,
151 (Spinner) findViewById(R.id.stack),
152 mState,
153 this);
154
Steve McKay12055472015-08-20 16:48:49 -0700155 // Base classes must update result in their onCreate.
156 setResult(Activity.RESULT_CANCELED);
Steve McKaya78a3692015-04-22 15:55:34 -0700157 }
158
159 @Override
160 public boolean onCreateOptionsMenu(Menu menu) {
161 boolean showMenu = super.onCreateOptionsMenu(menu);
162
163 getMenuInflater().inflate(R.menu.activity, menu);
Aga Wronska893390b2016-02-17 13:50:42 -0800164 mNavigator.update();
Aga Wronskab0998562016-03-24 18:22:09 -0700165 boolean fullBarSearch = getResources().getBoolean(R.bool.full_bar_search_view);
166 mSearchManager.install((DocumentsToolbar) findViewById(R.id.toolbar), fullBarSearch);
Steve McKaya78a3692015-04-22 15:55:34 -0700167
168 return showMenu;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700169 }
170
Steve McKay69aee092015-04-30 16:12:59 -0700171 @Override
Steve McKay3ce95952016-02-02 11:41:03 -0800172 @CallSuper
Steve McKay69aee092015-04-30 16:12:59 -0700173 public boolean onPrepareOptionsMenu(Menu menu) {
Steve McKay5bbae102015-10-01 11:39:24 -0700174 super.onPrepareOptionsMenu(menu);
Steve McKay69aee092015-04-30 16:12:59 -0700175
Steve McKay3ce95952016-02-02 11:41:03 -0800176 mSearchManager.showMenu(canSearchRoot());
177
Steve McKay5bbae102015-10-01 11:39:24 -0700178 final boolean inRecents = getCurrentDirectory() == null;
Steve McKay69aee092015-04-30 16:12:59 -0700179
180 final MenuItem sort = menu.findItem(R.id.menu_sort);
181 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
182 final MenuItem grid = menu.findItem(R.id.menu_grid);
183 final MenuItem list = menu.findItem(R.id.menu_list);
Aga Wronska774cc932016-03-30 18:07:59 -0700184 final MenuItem advanced = menu.findItem(R.id.menu_advanced);
Steve McKay69aee092015-04-30 16:12:59 -0700185 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
186
Steve McKay3ce95952016-02-02 11:41:03 -0800187 // Search uses backend ranking; no sorting, recents doesn't support sort.
Steve McKayf8300172016-03-08 14:01:47 -0800188 sort.setEnabled(!inRecents && !mSearchManager.isSearching());
Steve McKay3ce95952016-02-02 11:41:03 -0800189 sortSize.setVisible(mState.showSize); // Only sort by size when file sizes are visible
190 fileSize.setVisible(!mState.forceSize);
Steve McKay5bbae102015-10-01 11:39:24 -0700191
192 // grid/list is effectively a toggle.
193 grid.setVisible(mState.derivedMode != State.MODE_GRID);
194 list.setVisible(mState.derivedMode != State.MODE_LIST);
195
Aga Wronska774cc932016-03-30 18:07:59 -0700196 advanced.setVisible(mState.showAdvancedOption);
197 advanced.setTitle(mState.showAdvancedOption && mState.showAdvanced
198 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
Steve McKay69aee092015-04-30 16:12:59 -0700199 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
200 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
201
Steve McKay5bbae102015-10-01 11:39:24 -0700202 return true;
Steve McKay69aee092015-04-30 16:12:59 -0700203 }
204
Daichi Hironoda19ee02016-01-13 13:19:02 +0900205 @Override
206 protected void onDestroy() {
207 mRoots.setOnCacheUpdateListener(null);
208 super.onDestroy();
209 }
210
Steve McKay95cd85a2016-02-04 12:15:22 -0800211 private State getState(@Nullable Bundle icicle) {
212 if (icicle != null) {
Aga Wronska893390b2016-02-17 13:50:42 -0800213 State state = icicle.<State>getParcelable(Shared.EXTRA_STATE);
Steve McKay95cd85a2016-02-04 12:15:22 -0800214 if (DEBUG) Log.d(mTag, "Recovered existing state object: " + state);
215 return state;
216 }
217
Ben Kwa0f7078f02015-09-08 07:31:19 -0700218 State state = new State();
219
220 final Intent intent = getIntent();
Ben Kwa0f7078f02015-09-08 07:31:19 -0700221
222 state.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
Steve McKay83df8c02015-09-16 15:07:31 -0700223 state.forceSize = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, false);
224 state.showSize = state.forceSize || LocalPreferences.getDisplayFileSize(this);
Steve McKay83df8c02015-09-16 15:07:31 -0700225 state.initAcceptMimes(intent);
Ben Kwa0f7078f02015-09-08 07:31:19 -0700226 state.excludedAuthorities = getExcludedAuthorities();
227
Aga Wronska774cc932016-03-30 18:07:59 -0700228 includeState(state);
229
230 // Advanced roots are shown by deafult without menu option if forced by config or intent.
231 state.showAdvanced = getResources().getBoolean(R.bool.advanced_roots_shown)
232 || intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
233 // Menu option is shown for whitelisted intents if advanced roots are not shown by default.
234 state.showAdvancedOption = !state.showAdvanced &&
235 (state.action == ACTION_OPEN ||
236 state.action == ACTION_CREATE ||
237 state.action == ACTION_OPEN_TREE ||
238 state.action == ACTION_GET_CONTENT);
239
240 if (DEBUG) Log.d(mTag, "Created new state object: " + state);
241
Ben Kwa0f7078f02015-09-08 07:31:19 -0700242 return state;
243 }
244
Steve McKayaa15dae2016-02-09 16:17:24 -0800245 public void setRootsDrawerOpen(boolean open) {
246 mNavigator.revealRootsDrawer(open);
247 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700248
249 void onRootPicked(RootInfo root) {
Aga Wronska893390b2016-02-17 13:50:42 -0800250 // Clicking on the current root removes search
251 mSearchManager.cancelSearch();
252
Aga Wronskae436f942016-02-08 12:00:38 -0800253 // Skip refreshing if root nor directory didn't change
254 if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
Aga Wronska45f75e22016-02-02 18:01:58 -0800255 return;
256 }
257
Steve McKay3eb2d072016-01-25 19:00:22 -0800258 mState.derivedMode = LocalPreferences.getViewMode(this, root, MODE_GRID);
259
Steve McKayef3e2cf2015-04-20 17:18:15 -0700260 // Clear entire backstack and start in new root
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900261 mState.onRootChanged(root);
Steve McKaya78a3692015-04-22 15:55:34 -0700262
263 // Recents is always in memory, so we just load it directly.
264 // Otherwise we delegate loading data from disk to a task
265 // to ensure a responsive ui.
266 if (mRoots.isRecentsRoot(root)) {
Steve McKayce710822016-03-11 10:49:32 -0800267 refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
Steve McKaya78a3692015-04-22 15:55:34 -0700268 } else {
Steve McKay95cd85a2016-02-04 12:15:22 -0800269 new PickRootTask(this, root).executeOnExecutor(getExecutorForCurrentDirectory());
Daichi Hirono60e9a072015-12-25 11:08:42 +0900270 }
271 }
272
Steve McKayef3e2cf2015-04-20 17:18:15 -0700273 @Override
274 public boolean onOptionsItemSelected(MenuItem item) {
Aga Wronskacf966ae2016-03-30 13:55:19 -0700275 Metrics.logMenuAction(this, item.getItemId());
Steve McKayef3e2cf2015-04-20 17:18:15 -0700276
Steve McKay8fd086a2015-12-04 11:19:09 -0800277 switch (item.getItemId()) {
278 case android.R.id.home:
279 onBackPressed();
280 return true;
281
282 case R.id.menu_create_dir:
283 showCreateDirectoryDialog();
284 return true;
285
286 case R.id.menu_search:
Steve McKayf8300172016-03-08 14:01:47 -0800287 // SearchViewManager listens for this directly.
Steve McKay8fd086a2015-12-04 11:19:09 -0800288 return false;
289
290 case R.id.menu_sort_name:
291 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
292 return true;
293
294 case R.id.menu_sort_date:
295 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
296 return true;
297 case R.id.menu_sort_size:
298 setUserSortOrder(State.SORT_ORDER_SIZE);
299 return true;
300
301 case R.id.menu_grid:
Steve McKay3eb2d072016-01-25 19:00:22 -0800302 setViewMode(State.MODE_GRID);
Steve McKay8fd086a2015-12-04 11:19:09 -0800303 return true;
304
305 case R.id.menu_list:
Steve McKay3eb2d072016-01-25 19:00:22 -0800306 setViewMode(State.MODE_LIST);
Steve McKay8fd086a2015-12-04 11:19:09 -0800307 return true;
308
309 case R.id.menu_paste_from_clipboard:
Steve McKay3ce95952016-02-02 11:41:03 -0800310 DirectoryFragment dir = getDirectoryFragment();
311 if (dir != null) {
312 dir.pasteFromClipboard();
313 }
Ben Kwa359bbeb2016-02-17 10:48:57 -0800314 return true;
Steve McKay8fd086a2015-12-04 11:19:09 -0800315
Aga Wronska774cc932016-03-30 18:07:59 -0700316 case R.id.menu_advanced:
317 setDisplayAdvancedDevices(!mState.showAdvanced);
318 return true;
319
Steve McKay8fd086a2015-12-04 11:19:09 -0800320 case R.id.menu_file_size:
321 setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
322 return true;
323
324 case R.id.menu_settings:
325 final RootInfo root = getCurrentRoot();
326 final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
327 intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
328 startActivity(intent);
329 return true;
330
331 default:
332 return super.onOptionsItemSelected(item);
333 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700334 }
335
Steve McKay3ce95952016-02-02 11:41:03 -0800336 final @Nullable DirectoryFragment getDirectoryFragment() {
337 return DirectoryFragment.get(getFragmentManager());
338 }
339
Steve McKaya521d0d2015-05-19 16:10:25 -0700340 void showCreateDirectoryDialog() {
341 CreateDirectoryFragment.show(getFragmentManager());
342 }
343
Steve McKayb9a20d12016-02-19 12:57:05 -0800344 void onDirectoryCreated(DocumentInfo doc) {
345 // By default we do nothing, just let the new directory appear.
346 // DocumentsActivity auto-opens directories after creating them
347 // As that is more attuned to the "picker" use cases it supports.
348 }
349
Steve McKaya521d0d2015-05-19 16:10:25 -0700350 /**
351 * Returns true if a directory can be created in the current location.
352 * @return
353 */
354 boolean canCreateDirectory() {
355 final RootInfo root = getCurrentRoot();
356 final DocumentInfo cwd = getCurrentDirectory();
357 return cwd != null
358 && cwd.isCreateSupported()
359 && !mSearchManager.isSearching()
Steve McKay5bbae102015-10-01 11:39:24 -0700360 && !root.isRecents()
Steve McKaya521d0d2015-05-19 16:10:25 -0700361 && !root.isDownloads();
362 }
363
Tomasz Mikolajewski39acff52015-11-25 13:01:18 +0900364 void openContainerDocument(DocumentInfo doc) {
Steve McKaya1f76802016-02-25 13:34:03 -0800365 assert(doc.isContainer());
366
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +0900367 notifyDirectoryNavigated(doc.derivedUri);
368
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900369 mState.pushDocument(doc);
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900370 // Show an opening animation only if pressing "back" would get us back to the
371 // previous directory. Especially after opening a root document, pressing
372 // back, wouldn't go to the previous root, but close the activity.
373 final int anim = (mState.hasLocationChanged() && mState.stack.size() > 1)
Steve McKayce710822016-03-11 10:49:32 -0800374 ? AnimationView.ANIM_ENTER : AnimationView.ANIM_NONE;
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900375 refreshCurrentRootAndDirectory(anim);
Steve McKay6eaf38632015-08-04 10:11:01 -0700376 }
377
Steve McKayef3e2cf2015-04-20 17:18:15 -0700378 /**
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800379 * Refreshes the content of the director and the menu/action bar.
380 * The current directory name and selection will get updated.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700381 * @param anim
382 */
Steve McKay1f264a82016-02-03 11:15:57 -0800383 @Override
384 public final void refreshCurrentRootAndDirectory(int anim) {
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800385 mSearchManager.cancelSearch();
386
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800387 refreshDirectory(anim);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700388
389 final RootsFragment roots = RootsFragment.get(getFragmentManager());
390 if (roots != null) {
391 roots.onCurrentRootChanged();
392 }
393
Steve McKay1f264a82016-02-03 11:15:57 -0800394 mNavigator.update();
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800395 invalidateOptionsMenu();
396 }
397
Steve McKayc95d87c2016-02-23 14:34:50 -0800398 final void loadRoot(final Uri uri) {
399 new LoadRootTask(this, uri).executeOnExecutor(
400 ProviderExecutor.forAuthority(uri.getAuthority()));
401 }
402
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800403 /**
404 * Called when search results changed.
405 * Refreshes the content of the directory. It doesn't refresh elements on the action bar.
406 * e.g. The current directory name displayed on the action bar won't get updated.
407 */
408 @Override
Aga Wronska893390b2016-02-17 13:50:42 -0800409 public void onSearchChanged(@Nullable String query) {
410 // We should not get here if root is not searchable
Steve McKaya1f76802016-02-25 13:34:03 -0800411 assert(canSearchRoot());
Aga Wronska893390b2016-02-17 13:50:42 -0800412 reloadSearch(query);
Aga Wronskab0998562016-03-24 18:22:09 -0700413 }
414
415 @Override
416 public void onSearchFinished() {
417 // Restores menu icons state
Steve McKayf8300172016-03-08 14:01:47 -0800418 invalidateOptionsMenu();
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800419 }
420
Aga Wronska893390b2016-02-17 13:50:42 -0800421 private void reloadSearch(String query) {
422 FragmentManager fm = getFragmentManager();
423 RootInfo root = getCurrentRoot();
424 DocumentInfo cwd = getCurrentDirectory();
425
426 DirectoryFragment.reloadSearch(fm, root, cwd, query);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700427 }
428
Ben Kwa77797402015-05-29 15:40:31 -0700429 final List<String> getExcludedAuthorities() {
430 List<String> authorities = new ArrayList<>();
431 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
432 // Exclude roots provided by the calling package.
433 String packageName = getCallingPackageMaybeExtra();
434 try {
435 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
436 PackageManager.GET_PROVIDERS);
437 for (ProviderInfo provider: pkgInfo.providers) {
438 authorities.add(provider.authority);
439 }
440 } catch (PackageManager.NameNotFoundException e) {
441 Log.e(mTag, "Calling package name does not resolve: " + packageName);
442 }
443 }
444 return authorities;
445 }
446
Aga Wronska91c7fb32016-01-29 11:41:41 -0800447 boolean canSearchRoot() {
448 final RootInfo root = getCurrentRoot();
449 return (root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0;
450 }
451
Steve McKayef3e2cf2015-04-20 17:18:15 -0700452 final String getCallingPackageMaybeExtra() {
Ben Kwa77797402015-05-29 15:40:31 -0700453 String callingPackage = getCallingPackage();
454 // System apps can set the calling package name using an extra.
455 try {
456 ApplicationInfo info = getPackageManager().getApplicationInfo(callingPackage, 0);
457 if (info.isSystemApp() || info.isUpdatedSystemApp()) {
458 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
459 if (extra != null) {
460 callingPackage = extra;
461 }
462 }
463 } finally {
464 return callingPackage;
465 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700466 }
Steve McKaye934ce62015-03-25 14:35:33 -0700467
468 public static BaseActivity get(Fragment fragment) {
469 return (BaseActivity) fragment.getActivity();
470 }
471
Ben Kwa0f7078f02015-09-08 07:31:19 -0700472 public State getDisplayState() {
473 return mState;
474 }
475
Aga Wronska4e627162016-03-22 14:18:43 -0700476 /*
477 * Get the default directory to be presented after starting the activity.
478 * Method can be overridden if the change of the behavior of the the child activity is needed.
479 */
480 public Uri getDefaultRoot() {
481 return Shared.isHomeRootHidden(this)
482 ? DocumentsContract.buildRootUri("com.android.providers.downloads.documents",
483 "downloads")
484 : DocumentsContract.buildHomeUri();
Aga Wronska774cc932016-03-30 18:07:59 -0700485 }
486
487 void setDisplayAdvancedDevices(boolean display) {
488 mState.showAdvanced = display;
489 RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
490 invalidateOptionsMenu();
Aga Wronska4e627162016-03-22 14:18:43 -0700491 }
492
Steve McKayef3e2cf2015-04-20 17:18:15 -0700493 void setDisplayFileSize(boolean display) {
494 LocalPreferences.setDisplayFileSize(this, display);
Steve McKay323ee3e2015-09-25 16:02:56 -0700495 mState.showSize = display;
Steve McKay3ce95952016-02-02 11:41:03 -0800496 DirectoryFragment dir = getDirectoryFragment();
497 if (dir != null) {
498 dir.onDisplayStateChanged();
499 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700500 invalidateOptionsMenu();
501 }
502
Steve McKayef3e2cf2015-04-20 17:18:15 -0700503 /**
504 * Set state sort order based on explicit user action.
505 */
506 void setUserSortOrder(int sortOrder) {
Steve McKay323ee3e2015-09-25 16:02:56 -0700507 mState.userSortOrder = sortOrder;
Steve McKay3ce95952016-02-02 11:41:03 -0800508 DirectoryFragment dir = getDirectoryFragment();
509 if (dir != null) {
510 dir.onSortOrderChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800511 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700512 }
513
514 /**
Steve McKay3eb2d072016-01-25 19:00:22 -0800515 * Set mode based on explicit user action.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700516 */
Steve McKay3eb2d072016-01-25 19:00:22 -0800517 void setViewMode(@ViewMode int mode) {
Steve McKayc2651172016-02-17 11:00:55 -0800518 LocalPreferences.setViewMode(this, getCurrentRoot(), mode);
Steve McKay3eb2d072016-01-25 19:00:22 -0800519 mState.derivedMode = mode;
520
521 // view icon needs to be updated, but we *could* do it
522 // in onOptionsItemSelected, and not do the full invalidation
523 // But! That's a larger refactoring we'll save for another day.
524 invalidateOptionsMenu();
Steve McKay3ce95952016-02-02 11:41:03 -0800525 DirectoryFragment dir = getDirectoryFragment();
526 if (dir != null) {
527 dir.onViewModeChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800528 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700529 }
530
Aga Wronska3b327ef2016-01-20 16:32:33 -0800531 public void setPending(boolean pending) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700532 final SaveFragment save = SaveFragment.get(getFragmentManager());
533 if (save != null) {
534 save.setPending(pending);
535 }
536 }
537
538 @Override
539 protected void onSaveInstanceState(Bundle state) {
540 super.onSaveInstanceState(state);
Aga Wronska893390b2016-02-17 13:50:42 -0800541 state.putParcelable(Shared.EXTRA_STATE, mState);
542 mSearchManager.onSaveInstanceState(state);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700543 }
544
545 @Override
546 protected void onRestoreInstanceState(Bundle state) {
547 super.onRestoreInstanceState(state);
548 }
549
Steve McKay1f264a82016-02-03 11:15:57 -0800550 @Override
551 public boolean isSearchExpanded() {
552 return mSearchManager.isExpanded();
553 }
554
555 @Override
Steve McKayf8621552015-11-03 15:23:16 -0800556 public RootInfo getCurrentRoot() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700557 if (mState.stack.root != null) {
558 return mState.stack.root;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700559 } else {
560 return mRoots.getRecentsRoot();
561 }
562 }
563
564 public DocumentInfo getCurrentDirectory() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700565 return mState.stack.peek();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700566 }
567
Steve McKay83df8c02015-09-16 15:07:31 -0700568 public Executor getExecutorForCurrentDirectory() {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700569 final DocumentInfo cwd = getCurrentDirectory();
570 if (cwd != null && cwd.authority != null) {
571 return ProviderExecutor.forAuthority(cwd.authority);
572 } else {
573 return AsyncTask.THREAD_POOL_EXECUTOR;
574 }
575 }
576
Steve McKay12055472015-08-20 16:48:49 -0700577 @Override
578 public void onBackPressed() {
579 // While action bar is expanded, the state stack UI is hidden.
580 if (mSearchManager.cancelSearch()) {
581 return;
582 }
583
Steve McKay3ce95952016-02-02 11:41:03 -0800584 DirectoryFragment dir = getDirectoryFragment();
585 if (dir != null && dir.onBackPressed()) {
Steve McKaycbee5442016-01-28 15:30:10 -0800586 return;
587 }
588
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900589 if (!mState.hasLocationChanged()) {
Steve McKay12055472015-08-20 16:48:49 -0700590 super.onBackPressed();
591 return;
592 }
593
Steve McKayc95d87c2016-02-23 14:34:50 -0800594 if (onBeforePopDir() || popDir()) {
Steve McKayf8737692016-02-04 19:40:45 -0800595 return;
Steve McKay12055472015-08-20 16:48:49 -0700596 }
Steve McKayf8737692016-02-04 19:40:45 -0800597
598 super.onBackPressed();
Steve McKay12055472015-08-20 16:48:49 -0700599 }
600
Steve McKayc95d87c2016-02-23 14:34:50 -0800601 boolean onBeforePopDir() {
602 // Files app overrides this with some fancy logic.
603 return false;
604 }
605
Steve McKayef3e2cf2015-04-20 17:18:15 -0700606 public void onStackPicked(DocumentStack stack) {
607 try {
608 // Update the restored stack to ensure we have freshest data
609 stack.updateDocuments(getContentResolver());
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900610 mState.setStack(stack);
Steve McKayce710822016-03-11 10:49:32 -0800611 refreshCurrentRootAndDirectory(AnimationView.ANIM_SIDE);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700612
613 } catch (FileNotFoundException e) {
614 Log.w(mTag, "Failed to restore stack: " + e);
615 }
616 }
617
Ben Kwa2036dad2016-02-10 07:46:35 -0800618 /**
619 * Declare a global key handler to route key events when there isn't a specific focus view. This
620 * covers the scenario where a user opens DocumentsUI and just starts typing.
621 *
622 * @param keyCode
623 * @param event
624 * @return
625 */
626 @CallSuper
627 @Override
628 public boolean onKeyDown(int keyCode, KeyEvent event) {
629 if (Events.isNavigationKeyCode(keyCode)) {
630 // Forward all unclaimed navigation keystrokes to the DirectoryFragment. This causes any
631 // stray navigation keystrokes focus the content pane, which is probably what the user
632 // is trying to do.
633 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
634 if (df != null) {
635 df.requestFocus();
636 return true;
637 }
638 } else if (keyCode == KeyEvent.KEYCODE_TAB) {
Aga Wronska0614c162016-03-31 15:08:43 -0700639 Metrics.logKeyboardAction(this, keyCode);
Ben Kwa359bbeb2016-02-17 10:48:57 -0800640 // Tab toggles focus on the navigation drawer.
Ben Kwa2036dad2016-02-10 07:46:35 -0800641 toggleNavDrawerFocus();
642 return true;
Ben Kwa359bbeb2016-02-17 10:48:57 -0800643 } else if (keyCode == KeyEvent.KEYCODE_DEL) {
Aga Wronska0614c162016-03-31 15:08:43 -0700644 Metrics.logKeyboardAction(this, keyCode);
Ben Kwa359bbeb2016-02-17 10:48:57 -0800645 popDir();
646 return true;
Ben Kwa2036dad2016-02-10 07:46:35 -0800647 }
648 return super.onKeyDown(keyCode, event);
649 }
650
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +0900651 public void addEventListener(EventListener listener) {
652 mEventListeners.add(listener);
653 }
654
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +0900655 public void removeEventListener(EventListener listener) {
656 mEventListeners.remove(listener);
657 }
658
659 public void notifyDirectoryLoaded(Uri uri) {
660 for (EventListener listener : mEventListeners) {
661 listener.onDirectoryLoaded(uri);
662 }
663 }
664
665 void notifyDirectoryNavigated(Uri uri) {
666 for (EventListener listener : mEventListeners) {
667 listener.onDirectoryNavigated(uri);
668 }
669 }
670
Ben Kwa2036dad2016-02-10 07:46:35 -0800671 /**
672 * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't
673 * locked, open/close it as appropriate.
674 */
675 void toggleNavDrawerFocus() {
676 if (mNavDrawerHasFocus) {
677 mDrawer.setOpen(false);
678 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
679 if (df != null) {
680 df.requestFocus();
681 }
682 } else {
683 mDrawer.setOpen(true);
684 RootsFragment rf = RootsFragment.get(getFragmentManager());
685 if (rf != null) {
686 rf.requestFocus();
687 }
688 }
689 mNavDrawerHasFocus = !mNavDrawerHasFocus;
690 }
691
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900692 DocumentInfo getRootDocumentBlocking(RootInfo root) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900693 try {
694 final Uri uri = DocumentsContract.buildDocumentUri(
695 root.authority, root.documentId);
696 return DocumentInfo.fromUri(getContentResolver(), uri);
697 } catch (FileNotFoundException e) {
698 Log.w(mTag, "Failed to find root", e);
699 return null;
700 }
701 }
702
Ben Kwa359bbeb2016-02-17 10:48:57 -0800703 /**
704 * Pops the top entry off the directory stack, and returns the user to the previous directory.
705 * If the directory stack only contains one item, this method does nothing.
706 *
707 * @return Whether the stack was popped.
708 */
709 private boolean popDir() {
710 if (mState.stack.size() > 1) {
711 mState.stack.pop();
Steve McKayce710822016-03-11 10:49:32 -0800712 refreshCurrentRootAndDirectory(AnimationView.ANIM_LEAVE);
Ben Kwa359bbeb2016-02-17 10:48:57 -0800713 return true;
714 }
715 return false;
716 }
717
Daichi Hirono7352c552016-03-25 19:04:39 +0900718 /**
719 * Closes the activity when it's idle.
720 */
721 private void addListenerForLaunchCompletion() {
722 addEventListener(new EventListener() {
723 @Override
724 public void onDirectoryNavigated(Uri uri) {
725 }
726
727 @Override
728 public void onDirectoryLoaded(Uri uri) {
729 removeEventListener(this);
730 getMainLooper().getQueue().addIdleHandler(new IdleHandler() {
731 @Override
732 public boolean queueIdle() {
733 // If startup benchmark is requested by a whitelisted testing package, then
734 // close the activity once idle, and notify the testing activity.
735 if (getIntent().getBooleanExtra(EXTRA_BENCHMARK, false) &&
736 BENCHMARK_TESTING_PACKAGE.equals(getCallingPackage())) {
737 setResult(RESULT_OK);
738 finish();
739 }
740
741 Metrics.logStartupMs(
742 BaseActivity.this, (int) (new Date().getTime() - mStartTime));
743
744 // Remove the idle handler.
745 return false;
746 }
747 });
748 new Handler().post(new Runnable() {
749 @Override public void run() {
750 }
751 });
752 }
753 });
754 }
755
Steve McKay95cd85a2016-02-04 12:15:22 -0800756 private static final class PickRootTask extends PairedTask<BaseActivity, Void, DocumentInfo> {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700757 private RootInfo mRoot;
758
Steve McKay95cd85a2016-02-04 12:15:22 -0800759 public PickRootTask(BaseActivity activity, RootInfo root) {
760 super(activity);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700761 mRoot = root;
762 }
763
764 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800765 protected DocumentInfo run(Void... params) {
766 return mOwner.getRootDocumentBlocking(mRoot);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700767 }
768
769 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800770 protected void finish(DocumentInfo result) {
771 if (result != null) {
772 mOwner.openContainerDocument(result);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700773 }
774 }
775 }
776
Steve McKay95cd85a2016-02-04 12:15:22 -0800777 private static final class HandleRootsChangedTask
778 extends PairedTask<BaseActivity, RootInfo, RootInfo> {
Daichi Hirono72843c12016-03-15 12:47:57 +0900779 DocumentInfo mDownloadsDocument;
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900780
Steve McKay95cd85a2016-02-04 12:15:22 -0800781 public HandleRootsChangedTask(BaseActivity activity) {
782 super(activity);
783 }
784
Daichi Hirono60e9a072015-12-25 11:08:42 +0900785 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800786 protected RootInfo run(RootInfo... roots) {
Steve McKaya1f76802016-02-25 13:34:03 -0800787 assert(roots.length == 1);
788
Daichi Hirono60e9a072015-12-25 11:08:42 +0900789 final RootInfo currentRoot = roots[0];
Steve McKay95cd85a2016-02-04 12:15:22 -0800790 final Collection<RootInfo> cachedRoots = mOwner.mRoots.getRootsBlocking();
Daichi Hirono72843c12016-03-15 12:47:57 +0900791 RootInfo downloadsRoot = null;
Daichi Hirono60e9a072015-12-25 11:08:42 +0900792 for (final RootInfo root : cachedRoots) {
Daichi Hirono72843c12016-03-15 12:47:57 +0900793 if (root.isDownloads()) {
794 downloadsRoot = root;
Daichi Hirono60e9a072015-12-25 11:08:42 +0900795 }
796 if (root.getUri().equals(currentRoot.getUri())) {
797 // We don't need to change the current root as the current root was not removed.
798 return null;
799 }
800 }
Daichi Hirono72843c12016-03-15 12:47:57 +0900801 assert(downloadsRoot != null);
802 mDownloadsDocument = mOwner.getRootDocumentBlocking(downloadsRoot);
803 return downloadsRoot;
Daichi Hirono60e9a072015-12-25 11:08:42 +0900804 }
805
806 @Override
Daichi Hirono72843c12016-03-15 12:47:57 +0900807 protected void finish(RootInfo downloadsRoot) {
808 if (downloadsRoot != null && mDownloadsDocument != null) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900809 // Clear entire backstack and start in new root
Daichi Hirono72843c12016-03-15 12:47:57 +0900810 mOwner.mState.onRootChanged(downloadsRoot);
811 mOwner.mSearchManager.update(downloadsRoot);
812 mOwner.openContainerDocument(mDownloadsDocument);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900813 }
814 }
815 }
Steve McKaye934ce62015-03-25 14:35:33 -0700816}