blob: 9954e9d0ee831d429856d0ca6b8ab0a465884285 [file] [log] [blame]
Steve McKayd0a2a2c2015-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 McKayd9caa6a2016-09-15 16:36:45 -070019import static com.android.documentsui.base.Shared.EXTRA_BENCHMARK;
Riddle Hsu0c375982018-06-21 22:06:43 +080020import static com.android.documentsui.base.SharedMinimal.DEBUG;
Steve McKayd9caa6a2016-09-15 16:36:45 -070021import static com.android.documentsui.base.State.MODE_GRID;
Steve McKayb68dd222015-04-20 17:18:15 -070022
Steve McKayb68dd222015-04-20 17:18:15 -070023import android.content.Intent;
Ben Kwa0bcdec32015-05-29 15:40:31 -070024import android.content.pm.PackageInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.ProviderInfo;
Tony Huangf7b05c42019-05-02 16:51:01 +080027import android.graphics.Color;
Steve McKayb68dd222015-04-20 17:18:15 -070028import android.net.Uri;
Tony Huangf7b05c42019-05-02 16:51:01 +080029import android.os.Build;
Steve McKayb68dd222015-04-20 17:18:15 -070030import android.os.Bundle;
Tomasz Mikolajewskib8373c22016-03-15 17:41:31 +090031import android.os.MessageQueue.IdleHandler;
Steve McKay67375772017-03-06 15:44:13 -080032import android.preference.PreferenceManager;
Steve McKayb68dd222015-04-20 17:18:15 -070033import android.provider.DocumentsContract;
Tony Huang74ded6e2018-11-06 17:16:47 +080034import android.text.TextUtils;
Steve McKayb68dd222015-04-20 17:18:15 -070035import android.util.Log;
Jon Manneb1d11b2017-04-01 15:36:59 -070036import android.view.KeyEvent;
Steve McKayb68dd222015-04-20 17:18:15 -070037import android.view.Menu;
38import android.view.MenuItem;
Garfield, Tan171e6f52016-07-29 14:44:58 -070039import android.view.View;
Ivan Chiang329131e2019-01-08 15:58:19 +080040import android.view.ViewGroup;
shawnline0ba46d2019-01-25 15:21:26 +080041import android.widget.Checkable;
Tony Huangd74f3702018-12-05 17:45:15 +080042import android.widget.TextView;
Steve McKayd0a2a2c2015-03-25 14:35:33 -070043
Riddle Hsu0c375982018-06-21 22:06:43 +080044import androidx.annotation.CallSuper;
45import androidx.annotation.LayoutRes;
46import androidx.annotation.VisibleForTesting;
Tony Huang8d8d92f2018-09-13 14:41:16 +080047import androidx.appcompat.app.AppCompatActivity;
Tony Huangadce72b2019-01-31 15:21:55 +080048import androidx.appcompat.widget.ActionMenuView;
Tony Huang8d8d92f2018-09-13 14:41:16 +080049import androidx.appcompat.widget.Toolbar;
50import androidx.fragment.app.Fragment;
Riddle Hsu0c375982018-06-21 22:06:43 +080051
Steve McKayc8889af2016-09-23 11:22:41 -070052import com.android.documentsui.AbstractActionHandler.CommonAddons;
Steve McKay92ae43d2016-11-08 12:06:58 -080053import com.android.documentsui.Injector.Injected;
Ben Linb8c54e72016-06-10 12:13:27 -070054import com.android.documentsui.NavigationViewManager.Breadcrumb;
Ivan Chiangf0ea0ed2018-12-12 11:10:01 +080055import com.android.documentsui.R;
Steve McKayd0805062016-09-15 14:30:38 -070056import com.android.documentsui.base.DocumentInfo;
Steve McKay1d49d9c2017-05-24 14:27:43 -070057import com.android.documentsui.base.EventHandler;
Steve McKayd0805062016-09-15 14:30:38 -070058import com.android.documentsui.base.RootInfo;
Steve McKayd9caa6a2016-09-15 16:36:45 -070059import com.android.documentsui.base.Shared;
60import com.android.documentsui.base.State;
61import com.android.documentsui.base.State.ViewMode;
Steve McKayfb4fd2f2016-03-11 10:49:32 -080062import com.android.documentsui.dirlist.AnimationView;
Tony Huange092d2d2018-12-26 19:02:51 +080063import com.android.documentsui.dirlist.AppsRowManager;
Steve McKayf68210e2015-11-03 15:23:16 -080064import com.android.documentsui.dirlist.DirectoryFragment;
Steve McKaye8b547f2016-12-19 14:42:55 -080065import com.android.documentsui.prefs.LocalPreferences;
Steve McKay67375772017-03-06 15:44:13 -080066import com.android.documentsui.prefs.Preferences;
Chen Su7b6831b2016-12-22 16:21:32 +000067import com.android.documentsui.prefs.PreferencesMonitor;
Steve McKay67375772017-03-06 15:44:13 -080068import com.android.documentsui.prefs.ScopedPreferences;
Steve McKayaad78832017-03-09 10:11:12 -080069import com.android.documentsui.queries.CommandInterceptor;
shawnline0ba46d2019-01-25 15:21:26 +080070import com.android.documentsui.queries.SearchChipData;
Tony Huang2a022ba2019-01-10 20:02:52 +080071import com.android.documentsui.queries.SearchFragment;
Steve McKay3a268232016-10-19 11:15:47 -070072import com.android.documentsui.queries.SearchViewManager;
73import com.android.documentsui.queries.SearchViewManager.SearchManagerListener;
Jon Mann9bd40992017-03-24 12:34:34 -070074import com.android.documentsui.roots.ProvidersCache;
Steve McKayd0805062016-09-15 14:30:38 -070075import com.android.documentsui.sidebar.RootsFragment;
Garfield, Tan171e6f52016-07-29 14:44:58 -070076import com.android.documentsui.sorting.SortController;
77import com.android.documentsui.sorting.SortModel;
Steve McKay64ac2512015-05-12 12:49:58 -070078
Tony Huang0f7e8362018-11-23 12:14:34 +080079import com.google.android.material.appbar.AppBarLayout;
80
Steve McKay64ac2512015-05-12 12:49:58 -070081import java.util.ArrayList;
Daichi Hirono320a08f2016-03-25 19:04:39 +090082import java.util.Date;
Steve McKay64ac2512015-05-12 12:49:58 -070083import java.util.List;
Steve McKay64ac2512015-05-12 12:49:58 -070084
Steve McKay67375772017-03-06 15:44:13 -080085import javax.annotation.Nullable;
86
Steve McKay92ae43d2016-11-08 12:06:58 -080087public abstract class BaseActivity
Tony Huang8d8d92f2018-09-13 14:41:16 +080088 extends AppCompatActivity implements CommonAddons, NavigationViewManager.Environment {
Garfield, Tana5588b62016-07-13 09:23:04 -070089
Tomasz Mikolajewskib8373c22016-03-15 17:41:31 +090090 private static final String BENCHMARK_TESTING_PACKAGE = "com.android.documentsui.appperftests";
91
Steve McKay16e0c1f2016-09-15 12:41:13 -070092 protected SearchViewManager mSearchManager;
Tony Huange092d2d2018-12-26 19:02:51 +080093 protected AppsRowManager mAppsRowManager;
Steve McKayd9caa6a2016-09-15 16:36:45 -070094 protected State mState;
Steve McKay16e0c1f2016-09-15 12:41:13 -070095
Steve McKay92ae43d2016-11-08 12:06:58 -080096 @Injected
97 protected Injector<?> mInjector;
98
Jon Mann9bd40992017-03-24 12:34:34 -070099 protected ProvidersCache mProviders;
Garfield Tan63bf8132016-10-11 11:00:49 -0700100 protected DocumentsAccess mDocs;
Steve McKay16e0c1f2016-09-15 12:41:13 -0700101 protected DrawerController mDrawer;
Steve McKay92ae43d2016-11-08 12:06:58 -0800102
Steve McKay16e0c1f2016-09-15 12:41:13 -0700103 protected NavigationViewManager mNavigator;
Steve McKay16e0c1f2016-09-15 12:41:13 -0700104 protected SortController mSortController;
Steve McKay0269fb62015-04-22 15:55:34 -0700105
Steve McKay5b0a2c12016-10-07 11:22:31 -0700106 private final List<EventListener> mEventListeners = new ArrayList<>();
Steve McKayf2c8b0d2015-09-23 15:44:24 -0700107 private final String mTag;
Steve McKay95c79f52016-02-04 19:40:45 -0800108
Steve McKay0fbfc652015-08-20 16:48:49 -0700109 @LayoutRes
110 private int mLayoutId;
Steve McKayb68dd222015-04-20 17:18:15 -0700111
Steve McKay92ae43d2016-11-08 12:06:58 -0800112 private RootsMonitor<BaseActivity> mRootsMonitor;
Garfield Tan63bf8132016-10-11 11:00:49 -0700113
Daichi Hirono320a08f2016-03-25 19:04:39 +0900114 private long mStartTime;
Ivan Chiang4e010292018-12-25 19:52:59 +0800115 private boolean mHasQueryContentFromIntent;
Ben Kwa74e5d412016-02-10 07:46:35 -0800116
Chen Su7b6831b2016-12-22 16:21:32 +0000117 private PreferencesMonitor mPreferencesMonitor;
118
Steve McKay6525a192016-10-18 14:28:00 -0700119 public BaseActivity(@LayoutRes int layoutId, String tag) {
120 mLayoutId = layoutId;
121 mTag = tag;
122 }
123
Steve McKayc8889af2016-09-23 11:22:41 -0700124 protected abstract void refreshDirectory(int anim);
125 /** Allows sub-classes to include information in a newly created State instance. */
126 protected abstract void includeState(State initialState);
127 protected abstract void onDirectoryCreated(DocumentInfo doc);
128
Steve McKay92ae43d2016-11-08 12:06:58 -0800129 public abstract Injector<?> getInjector();
Garfield Tan7d75f7b2016-09-20 16:33:24 -0700130
Steve McKay18d01e82016-02-03 11:15:57 -0800131 @CallSuper
Steve McKayb68dd222015-04-20 17:18:15 -0700132 @Override
133 public void onCreate(Bundle icicle) {
Daichi Hirono320a08f2016-03-25 19:04:39 +0900134 // Record the time when onCreate is invoked for metric.
135 mStartTime = new Date().getTime();
136
Bill Lin81a9ea72019-01-16 22:27:36 +0800137 // ToDo Create tool to check resource version before applyStyle for the theme
138 // If version code is not match, we should reset overlay package to default,
139 // in case Activity continueusly encounter resource not found exception
Louis Pullen-Freilichc6717ff2019-04-05 17:51:53 +0100140 getTheme().applyStyle(R.style.DocumentsDefaultTheme, false);
Bill Lin81a9ea72019-01-16 22:27:36 +0800141
Steve McKayb68dd222015-04-20 17:18:15 -0700142 super.onCreate(icicle);
Steve McKay0fbfc652015-08-20 16:48:49 -0700143
Tomasz Mikolajewskib8373c22016-03-15 17:41:31 +0900144 final Intent intent = getIntent();
145
Daichi Hirono320a08f2016-03-25 19:04:39 +0900146 addListenerForLaunchCompletion();
Tomasz Mikolajewskib8373c22016-03-15 17:41:31 +0900147
Steve McKay18d01e82016-02-03 11:15:57 -0800148 setContentView(mLayoutId);
149
Tony Huangf7b05c42019-05-02 16:51:01 +0800150 setContainer();
151
Steve McKay92ae43d2016-11-08 12:06:58 -0800152 mInjector = getInjector();
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800153 mState = getState(icicle);
Steve McKay92ae43d2016-11-08 12:06:58 -0800154 mDrawer = DrawerController.create(this, mInjector.config);
shawnlin9cee68f2019-01-25 11:20:18 +0800155 Metrics.logActivityLaunch(mState, intent);
Ben Kwa1c0a3892016-01-26 11:50:03 -0800156
Jon Mann9bd40992017-03-24 12:34:34 -0700157 mProviders = DocumentsApplication.getProvidersCache(this);
Garfield Tan63bf8132016-10-11 11:00:49 -0700158 mDocs = DocumentsAccess.create(this);
Steve McKay18d01e82016-02-03 11:15:57 -0800159
Garfield Tand2fc4cd2017-03-15 15:39:14 -0700160 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
Tony Huang8d8d92f2018-09-13 14:41:16 +0800161 setSupportActionBar(toolbar);
Steve McKay85ec0d62016-06-24 15:05:08 -0700162
163 Breadcrumb breadcrumb =
164 Shared.findView(this, R.id.dropdown_breadcrumb, R.id.horizontal_breadcrumb);
165 assert(breadcrumb != null);
166
Tony Huang7cbde882018-11-23 12:14:34 +0800167 mNavigator = new NavigationViewManager(this, mDrawer, mState, this, breadcrumb);
Ben Lin21c54442016-09-21 14:20:12 -0700168 SearchManagerListener searchListener = new SearchManagerListener() {
169 /**
170 * Called when search results changed. Refreshes the content of the directory. It
171 * doesn't refresh elements on the action bar. e.g. The current directory name displayed
172 * on the action bar won't get updated.
173 */
174 @Override
175 public void onSearchChanged(@Nullable String query) {
shawnline0ba46d2019-01-25 15:21:26 +0800176 if (mSearchManager.isSearching()) {
177 Metrics.logSearchMode(query != null, mSearchManager.hasCheckedChip());
178 if (mInjector.pickResult != null) {
179 mInjector.pickResult.increaseActionCount();
180 }
181 }
182
Garfield Tane9670332017-03-06 18:33:23 -0800183 mInjector.actions.loadDocumentsForCurrentStack();
Tony Huang98c465a2019-04-29 14:01:25 +0800184
185 expandAppBar();
186 DirectoryFragment dir = getDirectoryFragment();
187 if (dir != null) {
188 dir.scrollToTop();
189 }
Ben Lin21c54442016-09-21 14:20:12 -0700190 }
Steve McKay18d01e82016-02-03 11:15:57 -0800191
Ben Lin21c54442016-09-21 14:20:12 -0700192 @Override
193 public void onSearchFinished() {
194 // Restores menu icons state
195 invalidateOptionsMenu();
196 }
197
198 @Override
199 public void onSearchViewChanged(boolean opened) {
Ben Lin21c54442016-09-21 14:20:12 -0700200 mNavigator.update();
201 }
shawnline0ba46d2019-01-25 15:21:26 +0800202
203 @Override
204 public void onSearchChipStateChanged(View v) {
205 final Checkable chip = (Checkable) v;
206 if (chip.isChecked()) {
207 final SearchChipData item = (SearchChipData) v.getTag();
208 Metrics.logUserAction(MetricConsts.USER_ACTION_SEARCH_CHIP);
209 Metrics.logSearchType(item.getChipType());
210 }
211 }
Tony Huang2a022ba2019-01-10 20:02:52 +0800212
213 @Override
214 public void onSearchViewFocusChanged(boolean hasFocus) {
215 final boolean isInitailSearch
216 = !TextUtils.isEmpty(mSearchManager.getCurrentSearch())
217 && TextUtils.isEmpty(mSearchManager.getSearchViewText());
218 if (hasFocus && (SearchFragment.get(getSupportFragmentManager()) == null)
219 && !isInitailSearch) {
220 SearchFragment.showFragment(getSupportFragmentManager(),
221 mSearchManager.getSearchViewText());
222 }
223 }
Tony Huangd36445c2019-03-19 18:36:29 +0800224
225 @Override
226 public void onSearchViewClearClicked() {
227 if (SearchFragment.get(getSupportFragmentManager()) == null) {
228 SearchFragment.showFragment(getSupportFragmentManager(),
229 mSearchManager.getSearchViewText());
230 }
231 }
Ben Lin21c54442016-09-21 14:20:12 -0700232 };
Steve McKay6a0a5682016-11-30 12:09:43 -0800233
234 // "Commands" are meta input for controlling system behavior.
235 // We piggy back on search input as it is the only text input
236 // area in the app. But the functionality is independent
237 // of "regular" search query processing.
Steve McKay1d49d9c2017-05-24 14:27:43 -0700238 final CommandInterceptor cmdInterceptor = new CommandInterceptor(mInjector.features);
239 cmdInterceptor.add(new CommandInterceptor.DumpRootsCacheHandler(this));
240
241 // A tiny decorator that adds support for enabling CommandInterceptor
242 // based on query input. It's sorta like CommandInterceptor, but its metaaahhh.
243 EventHandler<String> queryInterceptor =
244 CommandInterceptor.createDebugModeFlipper(
245 mInjector.features,
246 mInjector.debugHelper::toggleDebugMode,
247 cmdInterceptor);
Ivan Chiangf0ea0ed2018-12-12 11:10:01 +0800248
Ivan Chiang329131e2019-01-08 15:58:19 +0800249 ViewGroup chipGroup = findViewById(R.id.search_chip_group);
Ivan Chiangf0ea0ed2018-12-12 11:10:01 +0800250 mSearchManager = new SearchViewManager(searchListener, queryInterceptor,
251 chipGroup, icicle);
Ivan Chiang78171b32019-01-18 17:01:49 +0800252 // initialize the chip sets by accept mime types
253 mSearchManager.initChipSets(mState.acceptMimes);
Ivan Chiang52924b52019-02-15 14:45:06 +0800254 // update the chip items by the mime types of the root
255 mSearchManager.updateChips(getCurrentRoot().derivedMimeTypes);
Ivan Chiang4e010292018-12-25 19:52:59 +0800256 // parse the query content from intent when launch the
257 // activity at the first time
258 if (icicle == null) {
259 mHasQueryContentFromIntent = mSearchManager.parseQueryContentFromIntent(getIntent(),
260 mState.action);
261 }
Tony Huang7cbde882018-11-23 12:14:34 +0800262
263 mNavigator.setSearchBarClickListener(v -> {
264 mSearchManager.onSearchBarClicked();
265 mNavigator.update();
266 });
267
Steve McKay9de0da62016-08-25 15:18:23 -0700268 mSortController = SortController.create(this, mState.derivedMode, mState.sortModel);
269
Steve McKay67375772017-03-06 15:44:13 -0800270 mPreferencesMonitor = new PreferencesMonitor(
271 getApplicationContext().getPackageName(),
272 PreferenceManager.getDefaultSharedPreferences(this),
273 this::onPreferenceChanged);
274 mPreferencesMonitor.start();
Chen Su7b6831b2016-12-22 16:21:32 +0000275
Steve McKay0fbfc652015-08-20 16:48:49 -0700276 // Base classes must update result in their onCreate.
Tony Huang8d8d92f2018-09-13 14:41:16 +0800277 setResult(AppCompatActivity.RESULT_CANCELED);
Steve McKay0269fb62015-04-22 15:55:34 -0700278 }
279
Steve McKay67375772017-03-06 15:44:13 -0800280 public void onPreferenceChanged(String pref) {
281 // For now, we only work with prefs that we backup. This
282 // just limits the scope of what we expect to come flowing
283 // through here until we know we want more and fancier options.
284 assert(Preferences.shouldBackup(pref));
285
286 switch (pref) {
287 case ScopedPreferences.INCLUDE_DEVICE_ROOT:
288 updateDisplayAdvancedDevices(mInjector.prefs.getShowDeviceRoot());
289 }
290 }
291
Steve McKay0269fb62015-04-22 15:55:34 -0700292 @Override
Garfield Tan63bf8132016-10-11 11:00:49 -0700293 protected void onPostCreate(Bundle savedInstanceState) {
294 super.onPostCreate(savedInstanceState);
295
296 mRootsMonitor = new RootsMonitor<>(
297 this,
Steve McKay92ae43d2016-11-08 12:06:58 -0800298 mInjector.actions,
Jon Mann9bd40992017-03-24 12:34:34 -0700299 mProviders,
Garfield Tan63bf8132016-10-11 11:00:49 -0700300 mDocs,
301 mState,
Garfield Tan766cf142017-04-25 15:50:52 -0700302 mSearchManager,
303 mInjector.actionModeController::finishActionMode);
Garfield Tan63bf8132016-10-11 11:00:49 -0700304 mRootsMonitor.start();
305 }
306
307 @Override
Steve McKay0269fb62015-04-22 15:55:34 -0700308 public boolean onCreateOptionsMenu(Menu menu) {
309 boolean showMenu = super.onCreateOptionsMenu(menu);
310
311 getMenuInflater().inflate(R.menu.activity, menu);
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800312 mNavigator.update();
Aga Wronska8e21daa2016-03-24 18:22:09 -0700313 boolean fullBarSearch = getResources().getBoolean(R.bool.full_bar_search_view);
Tony Huangebdfe222019-03-04 16:58:07 +0800314 boolean showSearchBar = getResources().getBoolean(R.bool.show_search_bar);
315 mSearchManager.install(menu, fullBarSearch, showSearchBar);
Steve McKay0269fb62015-04-22 15:55:34 -0700316
Tony Huangadce72b2019-01-31 15:21:55 +0800317 final ActionMenuView subMenuView = findViewById(R.id.sub_menu);
318 // If size is 0, it means the menu has not inflated and it should only do once.
319 if (subMenuView.getMenu().size() == 0) {
320 subMenuView.setOnMenuItemClickListener(this::onOptionsItemSelected);
321 getMenuInflater().inflate(R.menu.sub_menu, subMenuView.getMenu());
322 }
323
Steve McKay0269fb62015-04-22 15:55:34 -0700324 return showMenu;
Steve McKayb68dd222015-04-20 17:18:15 -0700325 }
326
Steve McKay7bd32e12015-04-30 16:12:59 -0700327 @Override
Steve McKayd4800812016-02-02 11:41:03 -0800328 @CallSuper
Steve McKay7bd32e12015-04-30 16:12:59 -0700329 public boolean onPrepareOptionsMenu(Menu menu) {
Steve McKaye9809272015-10-01 11:39:24 -0700330 super.onPrepareOptionsMenu(menu);
Tomasz Mikolajewski9e047852017-02-13 14:05:17 +0900331 mSearchManager.showMenu(mState.stack);
Tony Huangadce72b2019-01-31 15:21:55 +0800332 final ActionMenuView subMenuView = findViewById(R.id.sub_menu);
333 mInjector.menuManager.updateSubMenu(subMenuView.getMenu());
Steve McKaye9809272015-10-01 11:39:24 -0700334 return true;
Steve McKay7bd32e12015-04-30 16:12:59 -0700335 }
336
Daichi Hironoe28c3c82016-01-13 13:19:02 +0900337 @Override
338 protected void onDestroy() {
Garfield Tan63bf8132016-10-11 11:00:49 -0700339 mRootsMonitor.stop();
Steve McKay67375772017-03-06 15:44:13 -0800340 mPreferencesMonitor.stop();
Garfield Tan1071cea2017-05-26 18:48:22 -0700341 mSortController.destroy();
Daichi Hironoe28c3c82016-01-13 13:19:02 +0900342 super.onDestroy();
343 }
344
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800345 private State getState(@Nullable Bundle icicle) {
346 if (icicle != null) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800347 State state = icicle.<State>getParcelable(Shared.EXTRA_STATE);
Jason Chang96f886b2019-03-29 17:59:02 +0800348 if (DEBUG) {
349 Log.d(mTag, "Recovered existing state object: " + state);
350 }
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800351 return state;
352 }
353
Ben Kwa0574b182015-09-08 07:31:19 -0700354 State state = new State();
355
356 final Intent intent = getIntent();
Ben Kwa0574b182015-09-08 07:31:19 -0700357
Garfield, Tan171e6f52016-07-29 14:44:58 -0700358 state.sortModel = SortModel.createModel();
Ben Kwa0574b182015-09-08 07:31:19 -0700359 state.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
Ben Kwa0574b182015-09-08 07:31:19 -0700360 state.excludedAuthorities = getExcludedAuthorities();
361
Aga Wronskaceb990e2016-03-30 18:07:59 -0700362 includeState(state);
363
Steve McKay92ae43d2016-11-08 12:06:58 -0800364 state.showAdvanced = Shared.mustShowDeviceRoot(intent)
365 || mInjector.prefs.getShowDeviceRoot();
Steve McKay91226ee2016-06-20 11:46:56 -0700366
Steve McKay6525a192016-10-18 14:28:00 -0700367 // Only show the toggle if advanced isn't forced enabled.
Steve McKay01996bc2016-10-21 13:38:49 -0700368 state.showDeviceStorageOption = !Shared.mustShowDeviceRoot(intent);
Aga Wronskaceb990e2016-03-30 18:07:59 -0700369
Jason Chang96f886b2019-03-29 17:59:02 +0800370 if (DEBUG) {
371 Log.d(mTag, "Created new state object: " + state);
372 }
Aga Wronskaceb990e2016-03-30 18:07:59 -0700373
Ben Kwa0574b182015-09-08 07:31:19 -0700374 return state;
375 }
376
Tony Huangf7b05c42019-05-02 16:51:01 +0800377 private void setContainer() {
378 View root = findViewById(R.id.coordinator_layout);
379 root.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
380 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
381 root.setOnApplyWindowInsetsListener((v, insets) -> {
382 root.setPadding(insets.getSystemWindowInsetLeft(),
383 insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0);
384
385 View saveContainer = findViewById(R.id.container_save);
386 saveContainer.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
387
388 View rootsContainer = findViewById(R.id.container_roots);
389 rootsContainer.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
390
391 return insets.consumeSystemWindowInsets();
392 });
393
394 getWindow().setNavigationBarDividerColor(Color.TRANSPARENT);
395 if (Build.VERSION.SDK_INT >= 29) {
396 getWindow().setNavigationBarColor(Color.TRANSPARENT);
397 getWindow().setNavigationBarContrastEnforced(true);
398 } else {
399 getWindow().setNavigationBarColor(getColor(R.color.nav_bar_translucent));
400 }
401 }
402
Garfield Tan208945c2016-10-04 14:36:38 -0700403 @Override
Steve McKaycb9eb422016-02-09 16:17:24 -0800404 public void setRootsDrawerOpen(boolean open) {
405 mNavigator.revealRootsDrawer(open);
406 }
Steve McKayb68dd222015-04-20 17:18:15 -0700407
Steve McKayc8889af2016-09-23 11:22:41 -0700408 @Override
Steve McKayd0805062016-09-15 14:30:38 -0700409 public void onRootPicked(RootInfo root) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800410 // Clicking on the current root removes search
411 mSearchManager.cancelSearch();
412
Aga Wronskafa421722016-02-08 12:00:38 -0800413 // Skip refreshing if root nor directory didn't change
414 if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
Aga Wronskafd26e8d2016-02-02 18:01:58 -0800415 return;
416 }
417
Ben Lin3b7a99d2017-01-27 15:01:57 -0800418 mInjector.actionModeController.finishActionMode();
Steve McKay743bc1b2016-08-26 16:10:39 -0700419 mSortController.onViewModeChanged(mState.derivedMode);
Steve McKay7776aa52016-01-25 19:00:22 -0800420
Garfield, Tan171e6f52016-07-29 14:44:58 -0700421 // Set summary header's visibility. Only recents and downloads root may have summary in
422 // their docs.
423 mState.sortModel.setDimensionVisibility(
424 SortModel.SORT_DIMENSION_ID_SUMMARY,
425 root.isRecents() || root.isDownloads() ? View.VISIBLE : View.INVISIBLE);
426
Steve McKayb68dd222015-04-20 17:18:15 -0700427 // Clear entire backstack and start in new root
Garfield Tan2a837422016-10-19 11:50:45 -0700428 mState.stack.changeRoot(root);
Steve McKay0269fb62015-04-22 15:55:34 -0700429
430 // Recents is always in memory, so we just load it directly.
431 // Otherwise we delegate loading data from disk to a task
432 // to ensure a responsive ui.
Jon Mann9bd40992017-03-24 12:34:34 -0700433 if (mProviders.isRecentsRoot(root)) {
Steve McKayfb4fd2f2016-03-11 10:49:32 -0800434 refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
Steve McKay0269fb62015-04-22 15:55:34 -0700435 } else {
Ben Lin30b0dc12017-03-07 15:37:16 -0800436 mInjector.actions.getRootDocument(
Garfield Tan7d75f7b2016-09-20 16:33:24 -0700437 root,
Ben Lin30b0dc12017-03-07 15:37:16 -0800438 TimeoutTask.DEFAULT_TIMEOUT,
Colin Crossa05773a2017-02-21 16:25:38 -0800439 doc -> mInjector.actions.openRootDocument(doc));
Daichi Hirono3067d0d2015-12-25 11:08:42 +0900440 }
Tony Huang0f7e8362018-11-23 12:14:34 +0800441
Tony Huang2a022ba2019-01-10 20:02:52 +0800442 expandAppBar();
Tony Huangd74f3702018-12-05 17:45:15 +0800443 updateHeaderTitle();
Daichi Hirono3067d0d2015-12-25 11:08:42 +0900444 }
445
Steve McKayb68dd222015-04-20 17:18:15 -0700446 @Override
447 public boolean onOptionsItemSelected(MenuItem item) {
Steve McKayb68dd222015-04-20 17:18:15 -0700448
Steve McKay6035b3c2015-12-04 11:19:09 -0800449 switch (item.getItemId()) {
450 case android.R.id.home:
451 onBackPressed();
452 return true;
453
Ben Linff7f3ae2017-04-25 16:08:52 -0700454 case R.id.option_menu_create_dir:
455 getInjector().actions.showCreateDirectoryDialog();
Steve McKay6035b3c2015-12-04 11:19:09 -0800456 return true;
457
Ben Linff7f3ae2017-04-25 16:08:52 -0700458 case R.id.option_menu_search:
Steve McKayf2eb8d92016-03-08 14:01:47 -0800459 // SearchViewManager listens for this directly.
Steve McKay6035b3c2015-12-04 11:19:09 -0800460 return false;
461
Ben Linff7f3ae2017-04-25 16:08:52 -0700462 case R.id.option_menu_advanced:
Steve McKay67375772017-03-06 15:44:13 -0800463 onDisplayAdvancedDevices();
Aga Wronskaceb990e2016-03-30 18:07:59 -0700464 return true;
465
Ben Linff7f3ae2017-04-25 16:08:52 -0700466 case R.id.option_menu_select_all:
Jon Mann30d8c792017-02-21 17:44:49 -0800467 getInjector().actions.selectAllFiles();
Jon Mann49be24c2017-02-13 17:29:20 -0800468 return true;
469
Ben Linff7f3ae2017-04-25 16:08:52 -0700470 case R.id.option_menu_debug:
Jon Manneb1d11b2017-04-01 15:36:59 -0700471 getInjector().actions.showDebugMessage();
472 return true;
473
Tony Huang243cf9c2018-10-16 16:15:18 +0800474 case R.id.option_menu_sort:
475 getInjector().actions.showSortDialog();
476 return true;
477
Tony Huangadce72b2019-01-31 15:21:55 +0800478 case R.id.sub_menu_grid:
479 setViewMode(State.MODE_GRID);
480 return true;
481
482 case R.id.sub_menu_list:
483 setViewMode(State.MODE_LIST);
484 return true;
485
Steve McKay6035b3c2015-12-04 11:19:09 -0800486 default:
487 return super.onOptionsItemSelected(item);
488 }
Steve McKayb68dd222015-04-20 17:18:15 -0700489 }
490
Steve McKay16e0c1f2016-09-15 12:41:13 -0700491 protected final @Nullable DirectoryFragment getDirectoryFragment() {
Tony Huang8d8d92f2018-09-13 14:41:16 +0800492 return DirectoryFragment.get(getSupportFragmentManager());
Steve McKayd4800812016-02-02 11:41:03 -0800493 }
494
Steve McKayceeb3f72015-05-19 16:10:25 -0700495 /**
496 * Returns true if a directory can be created in the current location.
497 * @return
498 */
Steve McKay16e0c1f2016-09-15 12:41:13 -0700499 protected boolean canCreateDirectory() {
Steve McKayceeb3f72015-05-19 16:10:25 -0700500 final RootInfo root = getCurrentRoot();
501 final DocumentInfo cwd = getCurrentDirectory();
502 return cwd != null
503 && cwd.isCreateSupported()
504 && !mSearchManager.isSearching()
Ben Lind04aec72017-02-24 16:48:25 -0800505 && !root.isRecents();
Steve McKayceeb3f72015-05-19 16:10:25 -0700506 }
507
Takamasa Kuramitsu59710522018-06-29 18:42:21 +0900508 /**
509 * Returns true if a directory can be inspected.
510 */
511 protected boolean canInspectDirectory() {
512 return false;
513 }
514
Garfield Tan16868832016-09-26 10:01:45 -0700515 // TODO: make navigator listen to state
516 @Override
517 public final void updateNavigator() {
518 mNavigator.update();
519 }
520
Garfield Tan5f2a9ba2017-05-26 14:35:44 -0700521 @Override
522 public void restoreRootAndDirectory() {
523 // We're trying to restore stuff in document stack from saved instance. If we didn't have a
524 // chance to spawn a fragment before we need to do it now. However if we spawned a fragment
525 // already, system will automatically restore the fragment for us so we don't need to do
526 // that manually this time.
Tony Huang8d8d92f2018-09-13 14:41:16 +0800527 if (DirectoryFragment.get(getSupportFragmentManager()) == null) {
Garfield Tan5f2a9ba2017-05-26 14:35:44 -0700528 refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
529 }
530 }
531
Steve McKayb68dd222015-04-20 17:18:15 -0700532 /**
Aga Wronska8788dad2016-01-15 17:30:15 -0800533 * Refreshes the content of the director and the menu/action bar.
534 * The current directory name and selection will get updated.
Steve McKayb68dd222015-04-20 17:18:15 -0700535 * @param anim
536 */
Steve McKay18d01e82016-02-03 11:15:57 -0800537 @Override
538 public final void refreshCurrentRootAndDirectory(int anim) {
Garfield Tan40c85052017-02-02 12:44:35 -0800539 // The following call will crash if it's called before onCreateOptionMenu() is called in
540 // which we install menu item to search view manager, and there is a search query we need to
541 // restore. This happens when we're still initializing our UI so we shouldn't cancel the
542 // search which will be restored later in onCreateOptionMenu(). Try finding a way to guard
543 // refreshCurrentRootAndDirectory() from being called while we're restoring the state of UI
544 // from the saved state passed in onCreate().
Aga Wronska8788dad2016-01-15 17:30:15 -0800545 mSearchManager.cancelSearch();
546
Ivan Chiang4e010292018-12-25 19:52:59 +0800547 // only set the query content in the first launch
548 if (mHasQueryContentFromIntent) {
549 mHasQueryContentFromIntent = false;
550 mSearchManager.setCurrentSearch(mSearchManager.getQueryContentFromIntent());
551 }
552
Ben Lina8bf2c02017-05-24 14:47:55 -0700553 mState.derivedMode = LocalPreferences.getViewMode(this, mState.stack.getRoot(), MODE_GRID);
554
Tony Huang0aaf9662019-01-18 19:22:11 +0800555 mNavigator.update();
556
Aga Wronska8788dad2016-01-15 17:30:15 -0800557 refreshDirectory(anim);
Steve McKayb68dd222015-04-20 17:18:15 -0700558
Tony Huang8d8d92f2018-09-13 14:41:16 +0800559 final RootsFragment roots = RootsFragment.get(getSupportFragmentManager());
Steve McKayb68dd222015-04-20 17:18:15 -0700560 if (roots != null) {
561 roots.onCurrentRootChanged();
562 }
563
Ben Lin37408d02016-10-21 11:30:02 -0700564 // Causes talkback to announce the activity's new title
Steve McKayefd10ac2017-07-25 13:33:57 -0700565 setTitle(mState.stack.getTitle());
566
Aga Wronska8788dad2016-01-15 17:30:15 -0800567 invalidateOptionsMenu();
Tony Huang7a7e7df2018-11-06 17:16:47 +0800568 mSortController.onViewModeChanged(mState.derivedMode);
Ivan Chiang3933cc62019-02-14 13:35:07 +0800569 mSearchManager.updateChips(getCurrentRoot().derivedMimeTypes);
Tony Huang1e671772019-02-14 15:16:45 +0800570 mAppsRowManager.updateView(this);
Aga Wronska8788dad2016-01-15 17:30:15 -0800571 }
572
Steve McKay16e0c1f2016-09-15 12:41:13 -0700573 private final List<String> getExcludedAuthorities() {
Ben Kwa0bcdec32015-05-29 15:40:31 -0700574 List<String> authorities = new ArrayList<>();
575 if (getIntent().getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false)) {
576 // Exclude roots provided by the calling package.
Steve McKay988d8a32016-09-27 09:41:17 -0700577 String packageName = Shared.getCallingPackageName(this);
Ben Kwa0bcdec32015-05-29 15:40:31 -0700578 try {
579 PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
580 PackageManager.GET_PROVIDERS);
581 for (ProviderInfo provider: pkgInfo.providers) {
582 authorities.add(provider.authority);
583 }
584 } catch (PackageManager.NameNotFoundException e) {
585 Log.e(mTag, "Calling package name does not resolve: " + packageName);
586 }
587 }
588 return authorities;
589 }
590
Steve McKay92ae43d2016-11-08 12:06:58 -0800591 public static BaseActivity get(Fragment fragment) {
592 return (BaseActivity) fragment.getActivity();
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700593 }
594
Ben Kwa0574b182015-09-08 07:31:19 -0700595 public State getDisplayState() {
596 return mState;
597 }
598
Aga Wronska94e53e42016-04-07 13:09:58 -0700599 /**
600 * Set internal storage visible based on explicit user action.
601 */
Steve McKay67375772017-03-06 15:44:13 -0800602 private void onDisplayAdvancedDevices() {
603 boolean display = !mState.showAdvanced;
shawnlin9cee68f2019-01-25 11:20:18 +0800604 Metrics.logUserAction(display
605 ? MetricConsts.USER_ACTION_SHOW_ADVANCED : MetricConsts.USER_ACTION_HIDE_ADVANCED);
Aga Wronska94e53e42016-04-07 13:09:58 -0700606
Steve McKay92ae43d2016-11-08 12:06:58 -0800607 mInjector.prefs.setShowDeviceRoot(display);
Steve McKay67375772017-03-06 15:44:13 -0800608 updateDisplayAdvancedDevices(display);
609 }
610
611 private void updateDisplayAdvancedDevices(boolean display) {
Aga Wronskaceb990e2016-03-30 18:07:59 -0700612 mState.showAdvanced = display;
Tony Huang8d8d92f2018-09-13 14:41:16 +0800613 @Nullable RootsFragment fragment = RootsFragment.get(getSupportFragmentManager());
Steve McKay67375772017-03-06 15:44:13 -0800614 if (fragment != null) {
Steve McKay0ce04b72017-06-05 13:23:54 -0700615 // This also takes care of updating launcher shortcuts (which are roots :)
Steve McKay67375772017-03-06 15:44:13 -0800616 fragment.onDisplayStateChanged();
617 }
Aga Wronskaceb990e2016-03-30 18:07:59 -0700618 invalidateOptionsMenu();
Aga Wronska64ae1f42016-03-22 14:18:43 -0700619 }
620
Aga Wronska94e53e42016-04-07 13:09:58 -0700621 /**
Steve McKay7776aa52016-01-25 19:00:22 -0800622 * Set mode based on explicit user action.
Steve McKayb68dd222015-04-20 17:18:15 -0700623 */
Steve McKay7776aa52016-01-25 19:00:22 -0800624 void setViewMode(@ViewMode int mode) {
Aga Wronska94e53e42016-04-07 13:09:58 -0700625 if (mode == State.MODE_GRID) {
shawnlin9cee68f2019-01-25 11:20:18 +0800626 Metrics.logUserAction(MetricConsts.USER_ACTION_GRID);
Aga Wronska94e53e42016-04-07 13:09:58 -0700627 } else if (mode == State.MODE_LIST) {
shawnlin9cee68f2019-01-25 11:20:18 +0800628 Metrics.logUserAction(MetricConsts.USER_ACTION_LIST);
Aga Wronska94e53e42016-04-07 13:09:58 -0700629 }
630
Steve McKay30aabb82016-02-17 11:00:55 -0800631 LocalPreferences.setViewMode(this, getCurrentRoot(), mode);
Steve McKay7776aa52016-01-25 19:00:22 -0800632 mState.derivedMode = mode;
633
Tony Huangfa4ec332019-03-14 17:05:07 +0800634 final ActionMenuView subMenuView = findViewById(R.id.sub_menu);
635 mInjector.menuManager.updateSubMenu(subMenuView.getMenu());
636
Steve McKayd4800812016-02-02 11:41:03 -0800637 DirectoryFragment dir = getDirectoryFragment();
638 if (dir != null) {
639 dir.onViewModeChanged();
Ben Kwa65f393a2016-02-17 10:48:57 -0800640 }
Garfield, Tan171e6f52016-07-29 14:44:58 -0700641
642 mSortController.onViewModeChanged(mode);
Steve McKayb68dd222015-04-20 17:18:15 -0700643 }
644
Aga Wronska3c237182016-01-20 16:32:33 -0800645 public void setPending(boolean pending) {
Steve McKay16e0c1f2016-09-15 12:41:13 -0700646 // TODO: Isolate this behavior to PickActivity.
Steve McKayb68dd222015-04-20 17:18:15 -0700647 }
648
Tony Huang2a022ba2019-01-10 20:02:52 +0800649 public void expandAppBar() {
650 final AppBarLayout appBarLayout = findViewById(R.id.app_bar);
651 if (appBarLayout != null) {
652 appBarLayout.setExpanded(true);
653 }
654 }
655
Tony Huangd74f3702018-12-05 17:45:15 +0800656 public void updateHeaderTitle() {
Tony Huangae81df42018-12-14 14:14:31 +0800657 if (!mState.stack.isInitialized()) {
658 //stack has not initialized, the header will update after the stack finishes loading
659 return;
660 }
661
Tony Huangd74f3702018-12-05 17:45:15 +0800662 final RootInfo root = mState.stack.getRoot();
663 final String rootTitle = root.title;
664 String result;
665
666 switch (root.derivedType) {
667 case RootInfo.TYPE_RECENTS:
Tony Huang74ded6e2018-11-06 17:16:47 +0800668 result = getHeaderRecentTitle();
Tony Huangd74f3702018-12-05 17:45:15 +0800669 break;
670 case RootInfo.TYPE_IMAGES:
671 case RootInfo.TYPE_VIDEO:
672 case RootInfo.TYPE_AUDIO:
673 result = getString(R.string.root_info_header_media, rootTitle);
674 break;
675 case RootInfo.TYPE_DOWNLOADS:
676 case RootInfo.TYPE_LOCAL:
677 case RootInfo.TYPE_MTP:
678 case RootInfo.TYPE_SD:
679 case RootInfo.TYPE_USB:
Tony Huang74ded6e2018-11-06 17:16:47 +0800680 result = getHeaderStorageTitle(rootTitle);
Tony Huangd74f3702018-12-05 17:45:15 +0800681 break;
682 default:
683 final String summary = root.summary;
Tony Huang74ded6e2018-11-06 17:16:47 +0800684 result = getHeaderDefaultTitle(rootTitle, summary);
Tony Huangd74f3702018-12-05 17:45:15 +0800685 break;
686 }
687
688 TextView headerTitle = findViewById(R.id.header_title);
689 headerTitle.setText(result);
690 }
691
Tony Huang74ded6e2018-11-06 17:16:47 +0800692 private String getHeaderRecentTitle() {
693 // If stack size larger than 1, it means user global search than enter a folder, but search
694 // is not expanded on that time.
695 boolean isGlobalSearch = mSearchManager.isSearching() || mState.stack.size() > 1;
696 if (mState.isPhotoPicking()) {
697 final int resId = isGlobalSearch
698 ? R.string.root_info_header_image_global_search
699 : R.string.root_info_header_image_recent;
700 return getString(resId);
701 } else {
702 final int resId = isGlobalSearch
703 ? R.string.root_info_header_global_search
704 : R.string.root_info_header_recent;
705 return getString(resId);
706 }
707 }
708
709 private String getHeaderStorageTitle(String rootTitle) {
710 final int resId = mState.isPhotoPicking()
711 ? R.string.root_info_header_image_storage : R.string.root_info_header_storage;
712 return getString(resId, rootTitle);
713 }
714
715 private String getHeaderDefaultTitle(String rootTitle, String summary) {
716 if (TextUtils.isEmpty(summary)) {
717 final int resId = mState.isPhotoPicking()
718 ? R.string.root_info_header_image_app : R.string.root_info_header_app;
719 return getString(resId, rootTitle);
720 } else {
721 final int resId = mState.isPhotoPicking()
722 ? R.string.root_info_header_image_app_with_summary
723 : R.string.root_info_header_app_with_summary;
724 return getString(resId, rootTitle, summary);
725 }
726 }
727
Tony Huang0aaf9662019-01-18 19:22:11 +0800728 /**
729 * Get title string equal to the string action bar displayed.
730 * @return current directory title name
731 */
732 public String getCurrentTitle() {
733 if (!mState.stack.isInitialized()) {
734 return null;
735 }
736
737 if (mState.stack.size() > 1) {
738 return getCurrentDirectory().displayName;
739 } else {
740 return getCurrentRoot().title;
741 }
742 }
743
Steve McKayb68dd222015-04-20 17:18:15 -0700744 @Override
745 protected void onSaveInstanceState(Bundle state) {
746 super.onSaveInstanceState(state);
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800747 state.putParcelable(Shared.EXTRA_STATE, mState);
748 mSearchManager.onSaveInstanceState(state);
Steve McKayb68dd222015-04-20 17:18:15 -0700749 }
750
751 @Override
Steve McKay18d01e82016-02-03 11:15:57 -0800752 public boolean isSearchExpanded() {
753 return mSearchManager.isExpanded();
754 }
755
756 @Override
Steve McKayf68210e2015-11-03 15:23:16 -0800757 public RootInfo getCurrentRoot() {
Garfield Tan2a837422016-10-19 11:50:45 -0700758 RootInfo root = mState.stack.getRoot();
759 if (root != null) {
760 return root;
Steve McKayb68dd222015-04-20 17:18:15 -0700761 } else {
Jon Mann9bd40992017-03-24 12:34:34 -0700762 return mProviders.getRecentsRoot();
Steve McKayb68dd222015-04-20 17:18:15 -0700763 }
764 }
765
Steve McKayeed2f4e2016-10-03 20:30:52 -0700766 @Override
Steve McKayb68dd222015-04-20 17:18:15 -0700767 public DocumentInfo getCurrentDirectory() {
Steve McKay4d0255f2015-09-25 16:02:56 -0700768 return mState.stack.peek();
Steve McKayb68dd222015-04-20 17:18:15 -0700769 }
770
Tony Huangfdbab1a2019-04-08 18:10:27 +0800771 @Override
772 public boolean isInRecents() {
773 return mState.stack.isRecents();
774 }
775
Garfield Tan63bf8132016-10-11 11:00:49 -0700776 @VisibleForTesting
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900777 public void addEventListener(EventListener listener) {
778 mEventListeners.add(listener);
779 }
780
Garfield Tan63bf8132016-10-11 11:00:49 -0700781 @VisibleForTesting
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900782 public void removeEventListener(EventListener listener) {
783 mEventListeners.remove(listener);
784 }
785
Garfield Tan63bf8132016-10-11 11:00:49 -0700786 @VisibleForTesting
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900787 public void notifyDirectoryLoaded(Uri uri) {
788 for (EventListener listener : mEventListeners) {
789 listener.onDirectoryLoaded(uri);
790 }
791 }
792
Garfield Tan63bf8132016-10-11 11:00:49 -0700793 @VisibleForTesting
794 @Override
795 public void notifyDirectoryNavigated(Uri uri) {
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900796 for (EventListener listener : mEventListeners) {
797 listener.onDirectoryNavigated(uri);
798 }
799 }
800
Jon Manneb1d11b2017-04-01 15:36:59 -0700801 @Override
802 public boolean dispatchKeyEvent(KeyEvent event) {
803 if (event.getAction() == KeyEvent.ACTION_DOWN) {
804 mInjector.debugHelper.debugCheck(event.getDownTime(), event.getKeyCode());
805 }
Garfield Tanda2c0f02017-04-11 13:47:58 -0700806
807 DocumentsApplication.getDragAndDropManager(this).onKeyEvent(event);
808
Jon Manneb1d11b2017-04-01 15:36:59 -0700809 return super.dispatchKeyEvent(event);
810 }
811
Ben Lin80030082017-05-01 18:50:05 -0700812 @Override
813 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Tony Huang8d8d92f2018-09-13 14:41:16 +0800814 super.onActivityResult(requestCode, resultCode, data);
Ben Lin80030082017-05-01 18:50:05 -0700815 mInjector.actions.onActivityResult(requestCode, resultCode, data);
816 }
817
Ben Kwa74e5d412016-02-10 07:46:35 -0800818 /**
Ben Kwa65f393a2016-02-17 10:48:57 -0800819 * Pops the top entry off the directory stack, and returns the user to the previous directory.
820 * If the directory stack only contains one item, this method does nothing.
821 *
822 * @return Whether the stack was popped.
823 */
Steve McKay04718262016-11-08 11:01:35 -0800824 protected boolean popDir() {
Ben Kwa65f393a2016-02-17 10:48:57 -0800825 if (mState.stack.size() > 1) {
Ivan Chiangb2071892019-05-02 17:07:13 +0800826 final DirectoryFragment fragment = getDirectoryFragment();
827 if (fragment != null) {
828 fragment.stopScroll();
829 }
830
Ben Kwa65f393a2016-02-17 10:48:57 -0800831 mState.stack.pop();
Steve McKayfb4fd2f2016-03-11 10:49:32 -0800832 refreshCurrentRootAndDirectory(AnimationView.ANIM_LEAVE);
Ben Kwa65f393a2016-02-17 10:48:57 -0800833 return true;
834 }
835 return false;
836 }
837
Steve McKay92ae43d2016-11-08 12:06:58 -0800838 protected boolean focusSidebar() {
Tony Huang8d8d92f2018-09-13 14:41:16 +0800839 RootsFragment rf = RootsFragment.get(getSupportFragmentManager());
Steve McKay04718262016-11-08 11:01:35 -0800840 assert (rf != null);
841 return rf.requestFocus();
842 }
843
Daichi Hirono320a08f2016-03-25 19:04:39 +0900844 /**
845 * Closes the activity when it's idle.
846 */
847 private void addListenerForLaunchCompletion() {
848 addEventListener(new EventListener() {
849 @Override
850 public void onDirectoryNavigated(Uri uri) {
851 }
852
853 @Override
854 public void onDirectoryLoaded(Uri uri) {
855 removeEventListener(this);
856 getMainLooper().getQueue().addIdleHandler(new IdleHandler() {
857 @Override
858 public boolean queueIdle() {
859 // If startup benchmark is requested by a whitelisted testing package, then
860 // close the activity once idle, and notify the testing activity.
861 if (getIntent().getBooleanExtra(EXTRA_BENCHMARK, false) &&
862 BENCHMARK_TESTING_PACKAGE.equals(getCallingPackage())) {
863 setResult(RESULT_OK);
864 finish();
865 }
866
shawnlin9cee68f2019-01-25 11:20:18 +0800867 Metrics.logStartupMs((int) (new Date().getTime() - mStartTime));
Daichi Hirono320a08f2016-03-25 19:04:39 +0900868
869 // Remove the idle handler.
870 return false;
871 }
872 });
Daichi Hirono320a08f2016-03-25 19:04:39 +0900873 }
874 });
875 }
876
Steve McKayd9caa6a2016-09-15 16:36:45 -0700877 @VisibleForTesting
878 protected interface EventListener {
879 /**
880 * @param uri Uri navigated to. If recents, then null.
881 */
882 void onDirectoryNavigated(@Nullable Uri uri);
883
884 /**
885 * @param uri Uri of the loaded directory. If recents, then null.
886 */
887 void onDirectoryLoaded(@Nullable Uri uri);
888 }
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700889}