blob: c9d18b3791e68404632a449ca5ff8f62c5084f32 [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 McKayef3e2cf2015-04-20 17:18:15 -070025
Steve McKaye934ce62015-03-25 14:35:33 -070026import android.app.Activity;
27import android.app.Fragment;
Aga Wronska893390b2016-02-17 13:50:42 -080028import android.app.FragmentManager;
Steve McKayef3e2cf2015-04-20 17:18:15 -070029import android.content.Intent;
Ben Kwa77797402015-05-29 15:40:31 -070030import android.content.pm.ApplicationInfo;
31import android.content.pm.PackageInfo;
32import android.content.pm.PackageManager;
33import android.content.pm.ProviderInfo;
Steve McKayef3e2cf2015-04-20 17:18:15 -070034import android.net.Uri;
35import android.os.AsyncTask;
36import android.os.Bundle;
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 McKayf8621552015-11-03 15:23:16 -080051import com.android.documentsui.dirlist.DirectoryFragment;
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090052import com.android.documentsui.dirlist.Model;
Steve McKaye934ce62015-03-25 14:35:33 -070053import com.android.documentsui.model.DocumentInfo;
54import com.android.documentsui.model.DocumentStack;
Steve McKaye934ce62015-03-25 14:35:33 -070055import com.android.documentsui.model.RootInfo;
Steve McKay273103b2015-05-12 12:49:58 -070056
Steve McKay273103b2015-05-12 12:49:58 -070057import java.io.FileNotFoundException;
Steve McKay273103b2015-05-12 12:49:58 -070058import java.util.ArrayList;
59import java.util.Collection;
Steve McKay273103b2015-05-12 12:49:58 -070060import java.util.List;
61import java.util.concurrent.Executor;
62
Steve McKay1f264a82016-02-03 11:15:57 -080063public abstract class BaseActivity extends Activity
64 implements SearchManagerListener, NavigationView.Environment {
Steve McKayef3e2cf2015-04-20 17:18:15 -070065
Steve McKay12055472015-08-20 16:48:49 -070066 State mState;
Steve McKayef3e2cf2015-04-20 17:18:15 -070067 RootsCache mRoots;
Aga Wronska893390b2016-02-17 13:50:42 -080068 SearchViewManager mSearchManager;
Steve McKay12055472015-08-20 16:48:49 -070069 DrawerController mDrawer;
Steve McKay1f264a82016-02-03 11:15:57 -080070 NavigationView mNavigator;
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +090071 List<EventListener> mEventListeners = new ArrayList<>();
Steve McKaya78a3692015-04-22 15:55:34 -070072
Steve McKay9f9d5b42015-09-23 15:44:24 -070073 private final String mTag;
Steve McKayf8737692016-02-04 19:40:45 -080074
Steve McKay12055472015-08-20 16:48:49 -070075 @LayoutRes
76 private int mLayoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -070077
Ben Kwa2036dad2016-02-10 07:46:35 -080078 private boolean mNavDrawerHasFocus;
79
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090080 public abstract void onDocumentPicked(DocumentInfo doc, Model model);
Steve McKaye934ce62015-03-25 14:35:33 -070081 public abstract void onDocumentsPicked(List<DocumentInfo> docs);
Steve McKay6eaf38632015-08-04 10:11:01 -070082
Steve McKayef3e2cf2015-04-20 17:18:15 -070083 abstract void onTaskFinished(Uri... uris);
Aga Wronskaf6a31d32016-01-15 17:30:15 -080084 abstract void refreshDirectory(int anim);
Steve McKay95cd85a2016-02-04 12:15:22 -080085 /** Allows sub-classes to include information in a newly created State instance. */
86 abstract void includeState(State initialState);
Steve McKayef3e2cf2015-04-20 17:18:15 -070087
Steve McKay12055472015-08-20 16:48:49 -070088 public BaseActivity(@LayoutRes int layoutId, String tag) {
89 mLayoutId = layoutId;
Steve McKayef3e2cf2015-04-20 17:18:15 -070090 mTag = tag;
91 }
92
Steve McKay1f264a82016-02-03 11:15:57 -080093 @CallSuper
Steve McKayef3e2cf2015-04-20 17:18:15 -070094 @Override
95 public void onCreate(Bundle icicle) {
96 super.onCreate(icicle);
Steve McKay12055472015-08-20 16:48:49 -070097
Steve McKay1f264a82016-02-03 11:15:57 -080098 setContentView(mLayoutId);
99
100 mDrawer = DrawerController.create(this);
Steve McKay95cd85a2016-02-04 12:15:22 -0800101 mState = getState(icicle);
Ben Kwa72379982016-01-26 11:50:03 -0800102 Metrics.logActivityLaunch(this, mState, getIntent());
103
Steve McKayef3e2cf2015-04-20 17:18:15 -0700104 mRoots = DocumentsApplication.getRootsCache(this);
Steve McKay1f264a82016-02-03 11:15:57 -0800105
Daichi Hirono60e9a072015-12-25 11:08:42 +0900106 mRoots.setOnCacheUpdateListener(
107 new RootsCache.OnCacheUpdateListener() {
108 @Override
109 public void onCacheUpdate() {
Steve McKay95cd85a2016-02-04 12:15:22 -0800110 new HandleRootsChangedTask(BaseActivity.this)
111 .execute(getCurrentRoot());
Daichi Hirono60e9a072015-12-25 11:08:42 +0900112 }
113 });
Steve McKay1f264a82016-02-03 11:15:57 -0800114
Aga Wronska893390b2016-02-17 13:50:42 -0800115 mSearchManager = new SearchViewManager(this, icicle);
Steve McKay12055472015-08-20 16:48:49 -0700116
Steve McKay1f264a82016-02-03 11:15:57 -0800117 DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
118 setActionBar(toolbar);
119 mNavigator = new NavigationView(
120 mDrawer,
121 toolbar,
122 (Spinner) findViewById(R.id.stack),
123 mState,
124 this);
125
Steve McKay12055472015-08-20 16:48:49 -0700126 // Base classes must update result in their onCreate.
127 setResult(Activity.RESULT_CANCELED);
Steve McKaya78a3692015-04-22 15:55:34 -0700128 }
129
130 @Override
131 public boolean onCreateOptionsMenu(Menu menu) {
132 boolean showMenu = super.onCreateOptionsMenu(menu);
133
134 getMenuInflater().inflate(R.menu.activity, menu);
Aga Wronska893390b2016-02-17 13:50:42 -0800135 mNavigator.update();
Steve McKay1f264a82016-02-03 11:15:57 -0800136 mSearchManager.install((DocumentsToolbar) findViewById(R.id.toolbar));
Steve McKaya78a3692015-04-22 15:55:34 -0700137
138 return showMenu;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700139 }
140
Steve McKay69aee092015-04-30 16:12:59 -0700141 @Override
Steve McKay3ce95952016-02-02 11:41:03 -0800142 @CallSuper
Steve McKay69aee092015-04-30 16:12:59 -0700143 public boolean onPrepareOptionsMenu(Menu menu) {
Steve McKay5bbae102015-10-01 11:39:24 -0700144 super.onPrepareOptionsMenu(menu);
Steve McKay69aee092015-04-30 16:12:59 -0700145
Steve McKay3ce95952016-02-02 11:41:03 -0800146 mSearchManager.showMenu(canSearchRoot());
147
Steve McKay5bbae102015-10-01 11:39:24 -0700148 final boolean inRecents = getCurrentDirectory() == null;
Steve McKay69aee092015-04-30 16:12:59 -0700149
150 final MenuItem sort = menu.findItem(R.id.menu_sort);
151 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
152 final MenuItem grid = menu.findItem(R.id.menu_grid);
153 final MenuItem list = menu.findItem(R.id.menu_list);
Steve McKay69aee092015-04-30 16:12:59 -0700154 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
155
Steve McKay3ce95952016-02-02 11:41:03 -0800156 // Search uses backend ranking; no sorting, recents doesn't support sort.
Steve McKayf8300172016-03-08 14:01:47 -0800157 sort.setEnabled(!inRecents && !mSearchManager.isSearching());
Steve McKay3ce95952016-02-02 11:41:03 -0800158 sortSize.setVisible(mState.showSize); // Only sort by size when file sizes are visible
159 fileSize.setVisible(!mState.forceSize);
Steve McKay5bbae102015-10-01 11:39:24 -0700160
161 // grid/list is effectively a toggle.
162 grid.setVisible(mState.derivedMode != State.MODE_GRID);
163 list.setVisible(mState.derivedMode != State.MODE_LIST);
164
Steve McKay69aee092015-04-30 16:12:59 -0700165 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
166 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
167
Steve McKay5bbae102015-10-01 11:39:24 -0700168 return true;
Steve McKay69aee092015-04-30 16:12:59 -0700169 }
170
Daichi Hironoda19ee02016-01-13 13:19:02 +0900171 @Override
172 protected void onDestroy() {
173 mRoots.setOnCacheUpdateListener(null);
174 super.onDestroy();
175 }
176
Steve McKay95cd85a2016-02-04 12:15:22 -0800177 private State getState(@Nullable Bundle icicle) {
178 if (icicle != null) {
Aga Wronska893390b2016-02-17 13:50:42 -0800179 State state = icicle.<State>getParcelable(Shared.EXTRA_STATE);
Steve McKay95cd85a2016-02-04 12:15:22 -0800180 if (DEBUG) Log.d(mTag, "Recovered existing state object: " + state);
181 return state;
182 }
183
184 State state = createSharedState();
185 includeState(state);
186 if (DEBUG) Log.d(mTag, "Created new state object: " + state);
187 return state;
188 }
189
190 private State createSharedState() {
Ben Kwa0f7078f02015-09-08 07:31:19 -0700191 State state = new State();
192
193 final Intent intent = getIntent();
Ben Kwa0f7078f02015-09-08 07:31:19 -0700194
195 state.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
Ben Kwa0f7078f02015-09-08 07:31:19 -0700196
Steve McKay83df8c02015-09-16 15:07:31 -0700197 state.forceSize = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, false);
198 state.showSize = state.forceSize || LocalPreferences.getDisplayFileSize(this);
199
Steve McKay83df8c02015-09-16 15:07:31 -0700200 state.initAcceptMimes(intent);
Ben Kwa0f7078f02015-09-08 07:31:19 -0700201 state.excludedAuthorities = getExcludedAuthorities();
202
203 return state;
204 }
205
Steve McKayaa15dae2016-02-09 16:17:24 -0800206 public void setRootsDrawerOpen(boolean open) {
207 mNavigator.revealRootsDrawer(open);
208 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700209
210 void onRootPicked(RootInfo root) {
Aga Wronska893390b2016-02-17 13:50:42 -0800211 // Clicking on the current root removes search
212 mSearchManager.cancelSearch();
213
Aga Wronskae436f942016-02-08 12:00:38 -0800214 // Skip refreshing if root nor directory didn't change
215 if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
Aga Wronska45f75e22016-02-02 18:01:58 -0800216 return;
217 }
218
Steve McKay3eb2d072016-01-25 19:00:22 -0800219 mState.derivedMode = LocalPreferences.getViewMode(this, root, MODE_GRID);
220
Steve McKayef3e2cf2015-04-20 17:18:15 -0700221 // Clear entire backstack and start in new root
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900222 mState.onRootChanged(root);
Steve McKaya78a3692015-04-22 15:55:34 -0700223
224 // Recents is always in memory, so we just load it directly.
225 // Otherwise we delegate loading data from disk to a task
226 // to ensure a responsive ui.
227 if (mRoots.isRecentsRoot(root)) {
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900228 refreshCurrentRootAndDirectory(ANIM_NONE);
Steve McKaya78a3692015-04-22 15:55:34 -0700229 } else {
Steve McKay95cd85a2016-02-04 12:15:22 -0800230 new PickRootTask(this, root).executeOnExecutor(getExecutorForCurrentDirectory());
Daichi Hirono60e9a072015-12-25 11:08:42 +0900231 }
232 }
233
Steve McKayef3e2cf2015-04-20 17:18:15 -0700234 @Override
235 public boolean onOptionsItemSelected(MenuItem item) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700236
Steve McKay8fd086a2015-12-04 11:19:09 -0800237 switch (item.getItemId()) {
238 case android.R.id.home:
239 onBackPressed();
240 return true;
241
242 case R.id.menu_create_dir:
243 showCreateDirectoryDialog();
244 return true;
245
246 case R.id.menu_search:
Steve McKayf8300172016-03-08 14:01:47 -0800247 // SearchViewManager listens for this directly.
Steve McKay8fd086a2015-12-04 11:19:09 -0800248 return false;
249
250 case R.id.menu_sort_name:
251 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
252 return true;
253
254 case R.id.menu_sort_date:
255 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
256 return true;
257 case R.id.menu_sort_size:
258 setUserSortOrder(State.SORT_ORDER_SIZE);
259 return true;
260
261 case R.id.menu_grid:
Steve McKay3eb2d072016-01-25 19:00:22 -0800262 setViewMode(State.MODE_GRID);
Steve McKay8fd086a2015-12-04 11:19:09 -0800263 return true;
264
265 case R.id.menu_list:
Steve McKay3eb2d072016-01-25 19:00:22 -0800266 setViewMode(State.MODE_LIST);
Steve McKay8fd086a2015-12-04 11:19:09 -0800267 return true;
268
269 case R.id.menu_paste_from_clipboard:
Steve McKay3ce95952016-02-02 11:41:03 -0800270 DirectoryFragment dir = getDirectoryFragment();
271 if (dir != null) {
272 dir.pasteFromClipboard();
273 }
Ben Kwa359bbeb2016-02-17 10:48:57 -0800274 return true;
Steve McKay8fd086a2015-12-04 11:19:09 -0800275
Steve McKay8fd086a2015-12-04 11:19:09 -0800276 case R.id.menu_file_size:
277 setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
278 return true;
279
280 case R.id.menu_settings:
281 final RootInfo root = getCurrentRoot();
282 final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
283 intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
284 startActivity(intent);
285 return true;
286
287 default:
288 return super.onOptionsItemSelected(item);
289 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700290 }
291
Steve McKay3ce95952016-02-02 11:41:03 -0800292 final @Nullable DirectoryFragment getDirectoryFragment() {
293 return DirectoryFragment.get(getFragmentManager());
294 }
295
Steve McKaya521d0d2015-05-19 16:10:25 -0700296 void showCreateDirectoryDialog() {
297 CreateDirectoryFragment.show(getFragmentManager());
298 }
299
Steve McKayb9a20d12016-02-19 12:57:05 -0800300 void onDirectoryCreated(DocumentInfo doc) {
301 // By default we do nothing, just let the new directory appear.
302 // DocumentsActivity auto-opens directories after creating them
303 // As that is more attuned to the "picker" use cases it supports.
304 }
305
Steve McKaya521d0d2015-05-19 16:10:25 -0700306 /**
307 * Returns true if a directory can be created in the current location.
308 * @return
309 */
310 boolean canCreateDirectory() {
311 final RootInfo root = getCurrentRoot();
312 final DocumentInfo cwd = getCurrentDirectory();
313 return cwd != null
314 && cwd.isCreateSupported()
315 && !mSearchManager.isSearching()
Steve McKay5bbae102015-10-01 11:39:24 -0700316 && !root.isRecents()
Steve McKaya521d0d2015-05-19 16:10:25 -0700317 && !root.isDownloads();
318 }
319
Tomasz Mikolajewski39acff52015-11-25 13:01:18 +0900320 void openContainerDocument(DocumentInfo doc) {
Steve McKaya1f76802016-02-25 13:34:03 -0800321 assert(doc.isContainer());
322
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +0900323 notifyDirectoryNavigated(doc.derivedUri);
324
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900325 mState.pushDocument(doc);
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900326 // Show an opening animation only if pressing "back" would get us back to the
327 // previous directory. Especially after opening a root document, pressing
328 // back, wouldn't go to the previous root, but close the activity.
329 final int anim = (mState.hasLocationChanged() && mState.stack.size() > 1)
Tomasz Mikolajewskie3fe9d72016-02-03 16:53:21 +0900330 ? ANIM_ENTER : ANIM_NONE;
Tomasz Mikolajewski42bd98f42016-02-03 15:49:58 +0900331 refreshCurrentRootAndDirectory(anim);
Steve McKay6eaf38632015-08-04 10:11:01 -0700332 }
333
Steve McKayef3e2cf2015-04-20 17:18:15 -0700334 /**
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800335 * Refreshes the content of the director and the menu/action bar.
336 * The current directory name and selection will get updated.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700337 * @param anim
338 */
Steve McKay1f264a82016-02-03 11:15:57 -0800339 @Override
340 public final void refreshCurrentRootAndDirectory(int anim) {
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800341 mSearchManager.cancelSearch();
342
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800343 refreshDirectory(anim);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700344
345 final RootsFragment roots = RootsFragment.get(getFragmentManager());
346 if (roots != null) {
347 roots.onCurrentRootChanged();
348 }
349
Steve McKay1f264a82016-02-03 11:15:57 -0800350 mNavigator.update();
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800351 invalidateOptionsMenu();
352 }
353
Steve McKayc95d87c2016-02-23 14:34:50 -0800354 final void loadRoot(final Uri uri) {
355 new LoadRootTask(this, uri).executeOnExecutor(
356 ProviderExecutor.forAuthority(uri.getAuthority()));
357 }
358
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800359 /**
360 * Called when search results changed.
361 * Refreshes the content of the directory. It doesn't refresh elements on the action bar.
362 * e.g. The current directory name displayed on the action bar won't get updated.
363 */
364 @Override
Aga Wronska893390b2016-02-17 13:50:42 -0800365 public void onSearchChanged(@Nullable String query) {
366 // We should not get here if root is not searchable
Steve McKaya1f76802016-02-25 13:34:03 -0800367 assert(canSearchRoot());
368
Aga Wronska893390b2016-02-17 13:50:42 -0800369 reloadSearch(query);
Steve McKayf8300172016-03-08 14:01:47 -0800370 invalidateOptionsMenu();
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800371 }
372
Aga Wronska893390b2016-02-17 13:50:42 -0800373 private void reloadSearch(String query) {
374 FragmentManager fm = getFragmentManager();
375 RootInfo root = getCurrentRoot();
376 DocumentInfo cwd = getCurrentDirectory();
377
378 DirectoryFragment.reloadSearch(fm, root, cwd, query);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700379 }
380
Ben Kwa77797402015-05-29 15:40:31 -0700381 final List<String> getExcludedAuthorities() {
382 List<String> authorities = new ArrayList<>();
383 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
384 // Exclude roots provided by the calling package.
385 String packageName = getCallingPackageMaybeExtra();
386 try {
387 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
388 PackageManager.GET_PROVIDERS);
389 for (ProviderInfo provider: pkgInfo.providers) {
390 authorities.add(provider.authority);
391 }
392 } catch (PackageManager.NameNotFoundException e) {
393 Log.e(mTag, "Calling package name does not resolve: " + packageName);
394 }
395 }
396 return authorities;
397 }
398
Aga Wronska91c7fb32016-01-29 11:41:41 -0800399 boolean canSearchRoot() {
400 final RootInfo root = getCurrentRoot();
401 return (root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0;
402 }
403
Steve McKayef3e2cf2015-04-20 17:18:15 -0700404 final String getCallingPackageMaybeExtra() {
Ben Kwa77797402015-05-29 15:40:31 -0700405 String callingPackage = getCallingPackage();
406 // System apps can set the calling package name using an extra.
407 try {
408 ApplicationInfo info = getPackageManager().getApplicationInfo(callingPackage, 0);
409 if (info.isSystemApp() || info.isUpdatedSystemApp()) {
410 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
411 if (extra != null) {
412 callingPackage = extra;
413 }
414 }
415 } finally {
416 return callingPackage;
417 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700418 }
Steve McKaye934ce62015-03-25 14:35:33 -0700419
420 public static BaseActivity get(Fragment fragment) {
421 return (BaseActivity) fragment.getActivity();
422 }
423
Ben Kwa0f7078f02015-09-08 07:31:19 -0700424 public State getDisplayState() {
425 return mState;
426 }
427
Steve McKayef3e2cf2015-04-20 17:18:15 -0700428 void setDisplayFileSize(boolean display) {
429 LocalPreferences.setDisplayFileSize(this, display);
Steve McKay323ee3e2015-09-25 16:02:56 -0700430 mState.showSize = display;
Steve McKay3ce95952016-02-02 11:41:03 -0800431 DirectoryFragment dir = getDirectoryFragment();
432 if (dir != null) {
433 dir.onDisplayStateChanged();
434 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700435 invalidateOptionsMenu();
436 }
437
Steve McKayef3e2cf2015-04-20 17:18:15 -0700438 /**
439 * Set state sort order based on explicit user action.
440 */
441 void setUserSortOrder(int sortOrder) {
Steve McKay323ee3e2015-09-25 16:02:56 -0700442 mState.userSortOrder = sortOrder;
Steve McKay3ce95952016-02-02 11:41:03 -0800443 DirectoryFragment dir = getDirectoryFragment();
444 if (dir != null) {
445 dir.onSortOrderChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800446 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700447 }
448
449 /**
Steve McKay3eb2d072016-01-25 19:00:22 -0800450 * Set mode based on explicit user action.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700451 */
Steve McKay3eb2d072016-01-25 19:00:22 -0800452 void setViewMode(@ViewMode int mode) {
Steve McKayc2651172016-02-17 11:00:55 -0800453 LocalPreferences.setViewMode(this, getCurrentRoot(), mode);
Steve McKay3eb2d072016-01-25 19:00:22 -0800454 mState.derivedMode = mode;
455
456 // view icon needs to be updated, but we *could* do it
457 // in onOptionsItemSelected, and not do the full invalidation
458 // But! That's a larger refactoring we'll save for another day.
459 invalidateOptionsMenu();
Steve McKay3ce95952016-02-02 11:41:03 -0800460 DirectoryFragment dir = getDirectoryFragment();
461 if (dir != null) {
462 dir.onViewModeChanged();
Ben Kwa359bbeb2016-02-17 10:48:57 -0800463 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700464 }
465
Aga Wronska3b327ef2016-01-20 16:32:33 -0800466 public void setPending(boolean pending) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700467 final SaveFragment save = SaveFragment.get(getFragmentManager());
468 if (save != null) {
469 save.setPending(pending);
470 }
471 }
472
473 @Override
474 protected void onSaveInstanceState(Bundle state) {
475 super.onSaveInstanceState(state);
Aga Wronska893390b2016-02-17 13:50:42 -0800476 state.putParcelable(Shared.EXTRA_STATE, mState);
477 mSearchManager.onSaveInstanceState(state);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700478 }
479
480 @Override
481 protected void onRestoreInstanceState(Bundle state) {
482 super.onRestoreInstanceState(state);
483 }
484
Steve McKay1f264a82016-02-03 11:15:57 -0800485 @Override
486 public boolean isSearchExpanded() {
487 return mSearchManager.isExpanded();
488 }
489
490 @Override
Steve McKayf8621552015-11-03 15:23:16 -0800491 public RootInfo getCurrentRoot() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700492 if (mState.stack.root != null) {
493 return mState.stack.root;
Steve McKayef3e2cf2015-04-20 17:18:15 -0700494 } else {
495 return mRoots.getRecentsRoot();
496 }
497 }
498
499 public DocumentInfo getCurrentDirectory() {
Steve McKay323ee3e2015-09-25 16:02:56 -0700500 return mState.stack.peek();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700501 }
502
Steve McKay83df8c02015-09-16 15:07:31 -0700503 public Executor getExecutorForCurrentDirectory() {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700504 final DocumentInfo cwd = getCurrentDirectory();
505 if (cwd != null && cwd.authority != null) {
506 return ProviderExecutor.forAuthority(cwd.authority);
507 } else {
508 return AsyncTask.THREAD_POOL_EXECUTOR;
509 }
510 }
511
Steve McKay12055472015-08-20 16:48:49 -0700512 @Override
513 public void onBackPressed() {
514 // While action bar is expanded, the state stack UI is hidden.
515 if (mSearchManager.cancelSearch()) {
516 return;
517 }
518
Steve McKay3ce95952016-02-02 11:41:03 -0800519 DirectoryFragment dir = getDirectoryFragment();
520 if (dir != null && dir.onBackPressed()) {
Steve McKaycbee5442016-01-28 15:30:10 -0800521 return;
522 }
523
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900524 if (!mState.hasLocationChanged()) {
Steve McKay12055472015-08-20 16:48:49 -0700525 super.onBackPressed();
526 return;
527 }
528
Steve McKayc95d87c2016-02-23 14:34:50 -0800529 if (onBeforePopDir() || popDir()) {
Steve McKayf8737692016-02-04 19:40:45 -0800530 return;
Steve McKay12055472015-08-20 16:48:49 -0700531 }
Steve McKayf8737692016-02-04 19:40:45 -0800532
533 super.onBackPressed();
Steve McKay12055472015-08-20 16:48:49 -0700534 }
535
Steve McKayc95d87c2016-02-23 14:34:50 -0800536 boolean onBeforePopDir() {
537 // Files app overrides this with some fancy logic.
538 return false;
539 }
540
Steve McKayef3e2cf2015-04-20 17:18:15 -0700541 public void onStackPicked(DocumentStack stack) {
542 try {
543 // Update the restored stack to ensure we have freshest data
544 stack.updateDocuments(getContentResolver());
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900545 mState.setStack(stack);
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800546 refreshCurrentRootAndDirectory(ANIM_SIDE);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700547
548 } catch (FileNotFoundException e) {
549 Log.w(mTag, "Failed to restore stack: " + e);
550 }
551 }
552
Ben Kwa2036dad2016-02-10 07:46:35 -0800553 /**
554 * Declare a global key handler to route key events when there isn't a specific focus view. This
555 * covers the scenario where a user opens DocumentsUI and just starts typing.
556 *
557 * @param keyCode
558 * @param event
559 * @return
560 */
561 @CallSuper
562 @Override
563 public boolean onKeyDown(int keyCode, KeyEvent event) {
564 if (Events.isNavigationKeyCode(keyCode)) {
565 // Forward all unclaimed navigation keystrokes to the DirectoryFragment. This causes any
566 // stray navigation keystrokes focus the content pane, which is probably what the user
567 // is trying to do.
568 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
569 if (df != null) {
570 df.requestFocus();
571 return true;
572 }
573 } else if (keyCode == KeyEvent.KEYCODE_TAB) {
Ben Kwa359bbeb2016-02-17 10:48:57 -0800574 // Tab toggles focus on the navigation drawer.
Ben Kwa2036dad2016-02-10 07:46:35 -0800575 toggleNavDrawerFocus();
576 return true;
Ben Kwa359bbeb2016-02-17 10:48:57 -0800577 } else if (keyCode == KeyEvent.KEYCODE_DEL) {
578 popDir();
579 return true;
Ben Kwa2036dad2016-02-10 07:46:35 -0800580 }
581 return super.onKeyDown(keyCode, event);
582 }
583
Tomasz Mikolajewskiae6d6b42016-02-24 12:53:44 +0900584 @VisibleForTesting
585 public void addEventListener(EventListener listener) {
586 mEventListeners.add(listener);
587 }
588
589 @VisibleForTesting
590 public void removeEventListener(EventListener listener) {
591 mEventListeners.remove(listener);
592 }
593
594 public void notifyDirectoryLoaded(Uri uri) {
595 for (EventListener listener : mEventListeners) {
596 listener.onDirectoryLoaded(uri);
597 }
598 }
599
600 void notifyDirectoryNavigated(Uri uri) {
601 for (EventListener listener : mEventListeners) {
602 listener.onDirectoryNavigated(uri);
603 }
604 }
605
Ben Kwa2036dad2016-02-10 07:46:35 -0800606 /**
607 * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't
608 * locked, open/close it as appropriate.
609 */
610 void toggleNavDrawerFocus() {
611 if (mNavDrawerHasFocus) {
612 mDrawer.setOpen(false);
613 DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
614 if (df != null) {
615 df.requestFocus();
616 }
617 } else {
618 mDrawer.setOpen(true);
619 RootsFragment rf = RootsFragment.get(getFragmentManager());
620 if (rf != null) {
621 rf.requestFocus();
622 }
623 }
624 mNavDrawerHasFocus = !mNavDrawerHasFocus;
625 }
626
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900627 DocumentInfo getRootDocumentBlocking(RootInfo root) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900628 try {
629 final Uri uri = DocumentsContract.buildDocumentUri(
630 root.authority, root.documentId);
631 return DocumentInfo.fromUri(getContentResolver(), uri);
632 } catch (FileNotFoundException e) {
633 Log.w(mTag, "Failed to find root", e);
634 return null;
635 }
636 }
637
Ben Kwa359bbeb2016-02-17 10:48:57 -0800638 /**
639 * Pops the top entry off the directory stack, and returns the user to the previous directory.
640 * If the directory stack only contains one item, this method does nothing.
641 *
642 * @return Whether the stack was popped.
643 */
644 private boolean popDir() {
645 if (mState.stack.size() > 1) {
646 mState.stack.pop();
647 refreshCurrentRootAndDirectory(ANIM_LEAVE);
648 return true;
649 }
650 return false;
651 }
652
Steve McKay95cd85a2016-02-04 12:15:22 -0800653 private static final class PickRootTask extends PairedTask<BaseActivity, Void, DocumentInfo> {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700654 private RootInfo mRoot;
655
Steve McKay95cd85a2016-02-04 12:15:22 -0800656 public PickRootTask(BaseActivity activity, RootInfo root) {
657 super(activity);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700658 mRoot = root;
659 }
660
661 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800662 protected DocumentInfo run(Void... params) {
663 return mOwner.getRootDocumentBlocking(mRoot);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700664 }
665
666 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800667 protected void finish(DocumentInfo result) {
668 if (result != null) {
669 mOwner.openContainerDocument(result);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700670 }
671 }
672 }
673
Steve McKay95cd85a2016-02-04 12:15:22 -0800674 private static final class HandleRootsChangedTask
675 extends PairedTask<BaseActivity, RootInfo, RootInfo> {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900676 DocumentInfo mHome;
677
Steve McKay95cd85a2016-02-04 12:15:22 -0800678 public HandleRootsChangedTask(BaseActivity activity) {
679 super(activity);
680 }
681
Daichi Hirono60e9a072015-12-25 11:08:42 +0900682 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800683 protected RootInfo run(RootInfo... roots) {
Steve McKaya1f76802016-02-25 13:34:03 -0800684 assert(roots.length == 1);
685
Daichi Hirono60e9a072015-12-25 11:08:42 +0900686 final RootInfo currentRoot = roots[0];
Steve McKay95cd85a2016-02-04 12:15:22 -0800687 final Collection<RootInfo> cachedRoots = mOwner.mRoots.getRootsBlocking();
Daichi Hirono60e9a072015-12-25 11:08:42 +0900688 RootInfo homeRoot = null;
689 for (final RootInfo root : cachedRoots) {
690 if (root.isHome()) {
691 homeRoot = root;
692 }
693 if (root.getUri().equals(currentRoot.getUri())) {
694 // We don't need to change the current root as the current root was not removed.
695 return null;
696 }
697 }
Steve McKaya1f76802016-02-25 13:34:03 -0800698 assert(homeRoot != null);
Steve McKay95cd85a2016-02-04 12:15:22 -0800699 mHome = mOwner.getRootDocumentBlocking(homeRoot);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900700 return homeRoot;
701 }
702
703 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800704 protected void finish(RootInfo homeRoot) {
705 if (homeRoot != null && mHome != null) {
Daichi Hirono3b36c5a2016-01-07 15:29:12 +0900706 // Clear entire backstack and start in new root
Steve McKay95cd85a2016-02-04 12:15:22 -0800707 mOwner.mState.onRootChanged(homeRoot);
708 mOwner.mSearchManager.update(homeRoot);
709 mOwner.openContainerDocument(mHome);
Daichi Hirono60e9a072015-12-25 11:08:42 +0900710 }
711 }
712 }
Steve McKaye934ce62015-03-25 14:35:33 -0700713}