blob: a20e3e7e407c29426d7883bed20412cb3d477c4f [file] [log] [blame]
Steve McKay739f94b2016-09-22 14:54:23 -07001/*
2 * Copyright (C) 2016 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
Jon Mann30d8c792017-02-21 17:44:49 -080019import static com.android.documentsui.base.DocumentInfo.getCursorInt;
20import static com.android.documentsui.base.DocumentInfo.getCursorString;
Felipe Leme9de58072018-01-19 16:40:04 -080021import static com.android.documentsui.base.SharedMinimal.DEBUG;
Jon Mann30d8c792017-02-21 17:44:49 -080022
Ben Lin80030082017-05-01 18:50:05 -070023import android.app.PendingIntent;
Tony Huang94fc11a2019-10-30 18:00:20 +080024import android.content.ComponentName;
Garfield Tane9670332017-03-06 18:33:23 -080025import android.content.Context;
Steve McKay739f94b2016-09-22 14:54:23 -070026import android.content.Intent;
Ben Lin80030082017-05-01 18:50:05 -070027import android.content.IntentSender;
Tony Huang94fc11a2019-10-30 18:00:20 +080028import android.content.pm.PackageManager;
Steve McKay739f94b2016-09-22 14:54:23 -070029import android.content.pm.ResolveInfo;
Jon Mann30d8c792017-02-21 17:44:49 -080030import android.database.Cursor;
Steve McKay988d8a32016-09-27 09:41:17 -070031import android.net.Uri;
Garfield Tane9670332017-03-06 18:33:23 -080032import android.os.Bundle;
Steve McKay739f94b2016-09-22 14:54:23 -070033import android.os.Parcelable;
Garfield Tanf8969d62017-02-02 16:55:55 -080034import android.provider.DocumentsContract;
Garfield Tan16868832016-09-26 10:01:45 -070035import android.util.Log;
Jon Manneb1d11b2017-04-01 15:36:59 -070036import android.util.Pair;
Ben Lin174fc2e2017-03-01 17:53:20 -080037import android.view.DragEvent;
Steve McKay739f94b2016-09-22 14:54:23 -070038
Riddle Hsu0c375982018-06-21 22:06:43 +080039import androidx.annotation.VisibleForTesting;
Tony Huang8d8d92f2018-09-13 14:41:16 +080040import androidx.fragment.app.FragmentActivity;
Tony Huang35ad6bd2018-11-06 14:39:40 +080041import androidx.loader.app.LoaderManager.LoaderCallbacks;
42import androidx.loader.content.Loader;
Riddle Hsu0c375982018-06-21 22:06:43 +080043import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
44import androidx.recyclerview.selection.MutableSelection;
45import androidx.recyclerview.selection.SelectionTracker;
46
Steve McKayc8889af2016-09-23 11:22:41 -070047import com.android.documentsui.AbstractActionHandler.CommonAddons;
Garfield Tan16868832016-09-26 10:01:45 -070048import com.android.documentsui.LoadDocStackTask.LoadDocStackCallback;
Steve McKay739f94b2016-09-22 14:54:23 -070049import com.android.documentsui.base.BooleanConsumer;
Steve McKayc8889af2016-09-23 11:22:41 -070050import com.android.documentsui.base.DocumentInfo;
Steve McKay739f94b2016-09-22 14:54:23 -070051import com.android.documentsui.base.DocumentStack;
Steve McKay988d8a32016-09-27 09:41:17 -070052import com.android.documentsui.base.Lookup;
Steve McKay98f8c5f2017-03-03 13:52:14 -080053import com.android.documentsui.base.Providers;
Steve McKay739f94b2016-09-22 14:54:23 -070054import com.android.documentsui.base.RootInfo;
55import com.android.documentsui.base.Shared;
Steve McKay988d8a32016-09-27 09:41:17 -070056import com.android.documentsui.base.State;
Tomasz Mikolajewskidbd6b8b2016-09-29 15:27:37 +090057import com.android.documentsui.dirlist.AnimationView;
Ben Lin174fc2e2017-03-01 17:53:20 -080058import com.android.documentsui.dirlist.AnimationView.AnimationType;
Ben Lind947f012016-10-18 14:32:49 -070059import com.android.documentsui.dirlist.FocusHandler;
Steve McKayb6006b22016-09-29 09:23:45 -070060import com.android.documentsui.files.LauncherActivity;
Steve McKay3a268232016-10-19 11:15:47 -070061import com.android.documentsui.queries.SearchViewManager;
Ben Lin30b0dc12017-03-07 15:37:16 -080062import com.android.documentsui.roots.GetRootDocumentTask;
Ivan Chiang9b9a2822018-09-19 17:03:22 +080063import com.android.documentsui.roots.LoadFirstRootTask;
Steve McKay988d8a32016-09-27 09:41:17 -070064import com.android.documentsui.roots.LoadRootTask;
Jon Mann9bd40992017-03-24 12:34:34 -070065import com.android.documentsui.roots.ProvidersAccess;
Steve McKay739f94b2016-09-22 14:54:23 -070066import com.android.documentsui.sidebar.EjectRootTask;
Tony Huang243cf9c2018-10-16 16:15:18 +080067import com.android.documentsui.sorting.SortListFragment;
Jon Manneb1d11b2017-04-01 15:36:59 -070068import com.android.documentsui.ui.Snackbars;
Steve McKay739f94b2016-09-22 14:54:23 -070069
Jon Mann30d8c792017-02-21 17:44:49 -080070import java.util.ArrayList;
Steve McKayc8889af2016-09-23 11:22:41 -070071import java.util.List;
Garfield Tan16868832016-09-26 10:01:45 -070072import java.util.Objects;
Steve McKay988d8a32016-09-27 09:41:17 -070073import java.util.concurrent.Executor;
Ben Lin30b0dc12017-03-07 15:37:16 -080074import java.util.function.Consumer;
Steve McKayc8889af2016-09-23 11:22:41 -070075
Jon Mann30d8c792017-02-21 17:44:49 -080076import javax.annotation.Nullable;
77
Steve McKay739f94b2016-09-22 14:54:23 -070078/**
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +090079 * Provides support for specializing the actions (openDocument etc.) to the host activity.
Steve McKay739f94b2016-09-22 14:54:23 -070080 */
Tony Huang8d8d92f2018-09-13 14:41:16 +080081public abstract class AbstractActionHandler<T extends FragmentActivity & CommonAddons>
Steve McKayc8889af2016-09-23 11:22:41 -070082 implements ActionHandler {
Steve McKay739f94b2016-09-22 14:54:23 -070083
Garfield Tane9670332017-03-06 18:33:23 -080084 @VisibleForTesting
Ben Lin80030082017-05-01 18:50:05 -070085 public static final int CODE_FORWARD = 42;
86 public static final int CODE_AUTHENTICATION = 43;
87
88 @VisibleForTesting
Garfield Tane9670332017-03-06 18:33:23 -080089 static final int LOADER_ID = 42;
90
Garfield Tan16868832016-09-26 10:01:45 -070091 private static final String TAG = "AbstractActionHandler";
Ben Line9abd2d2016-12-06 11:39:52 -080092 private static final int REFRESH_SPINNER_TIMEOUT = 500;
Garfield Tan16868832016-09-26 10:01:45 -070093
Steve McKay739f94b2016-09-22 14:54:23 -070094 protected final T mActivity;
Steve McKay988d8a32016-09-27 09:41:17 -070095 protected final State mState;
Jon Mann9bd40992017-03-24 12:34:34 -070096 protected final ProvidersAccess mProviders;
Steve McKaydef48682016-10-03 09:07:38 -070097 protected final DocumentsAccess mDocs;
Ben Lind947f012016-10-18 14:32:49 -070098 protected final FocusHandler mFocusHandler;
Riddle Hsu0c375982018-06-21 22:06:43 +080099 protected final SelectionTracker<String> mSelectionMgr;
Garfield Tan63bf8132016-10-11 11:00:49 -0700100 protected final SearchViewManager mSearchMgr;
Steve McKay988d8a32016-09-27 09:41:17 -0700101 protected final Lookup<String, Executor> mExecutors;
Steve McKay2979b212017-04-11 14:40:01 -0700102 protected final Injector<?> mInjector;
Steve McKay739f94b2016-09-22 14:54:23 -0700103
Garfield Tane9670332017-03-06 18:33:23 -0800104 private final LoaderBindings mBindings;
105
Jon Mann30d8c792017-02-21 17:44:49 -0800106 private Runnable mDisplayStateChangedListener;
107
Steve McKay12394522017-08-24 14:14:10 -0700108 private ContentLock mContentLock;
Garfield Tane9670332017-03-06 18:33:23 -0800109
Jon Mann30d8c792017-02-21 17:44:49 -0800110 @Override
111 public void registerDisplayStateChangedListener(Runnable l) {
112 mDisplayStateChangedListener = l;
113 }
114 @Override
115 public void unregisterDisplayStateChangedListener(Runnable l) {
116 if (mDisplayStateChangedListener == l) {
117 mDisplayStateChangedListener = null;
118 }
119 }
Ben Lind947f012016-10-18 14:32:49 -0700120
Steve McKay988d8a32016-09-27 09:41:17 -0700121 public AbstractActionHandler(
122 T activity,
123 State state,
Jon Mann9bd40992017-03-24 12:34:34 -0700124 ProvidersAccess providers,
Steve McKaydef48682016-10-03 09:07:38 -0700125 DocumentsAccess docs,
Garfield Tan63bf8132016-10-11 11:00:49 -0700126 SearchViewManager searchMgr,
Jon Mann30d8c792017-02-21 17:44:49 -0800127 Lookup<String, Executor> executors,
Steve McKay2979b212017-04-11 14:40:01 -0700128 Injector<?> injector) {
Steve McKay988d8a32016-09-27 09:41:17 -0700129
Steve McKayc8889af2016-09-23 11:22:41 -0700130 assert(activity != null);
Steve McKay988d8a32016-09-27 09:41:17 -0700131 assert(state != null);
Jon Mann9bd40992017-03-24 12:34:34 -0700132 assert(providers != null);
Garfield Tan16868832016-09-26 10:01:45 -0700133 assert(searchMgr != null);
Steve McKaydef48682016-10-03 09:07:38 -0700134 assert(docs != null);
Jon Mann30d8c792017-02-21 17:44:49 -0800135 assert(injector != null);
Steve McKay988d8a32016-09-27 09:41:17 -0700136
Steve McKay739f94b2016-09-22 14:54:23 -0700137 mActivity = activity;
Steve McKay988d8a32016-09-27 09:41:17 -0700138 mState = state;
Jon Mann9bd40992017-03-24 12:34:34 -0700139 mProviders = providers;
Steve McKaydef48682016-10-03 09:07:38 -0700140 mDocs = docs;
Jon Mann30d8c792017-02-21 17:44:49 -0800141 mFocusHandler = injector.focusManager;
142 mSelectionMgr = injector.selectionMgr;
Garfield Tan63bf8132016-10-11 11:00:49 -0700143 mSearchMgr = searchMgr;
Steve McKay988d8a32016-09-27 09:41:17 -0700144 mExecutors = executors;
Jon Mann30d8c792017-02-21 17:44:49 -0800145 mInjector = injector;
Garfield Tane9670332017-03-06 18:33:23 -0800146
147 mBindings = new LoaderBindings();
Steve McKay739f94b2016-09-22 14:54:23 -0700148 }
149
150 @Override
Steve McKay739f94b2016-09-22 14:54:23 -0700151 public void ejectRoot(RootInfo root, BooleanConsumer listener) {
152 new EjectRootTask(
153 mActivity.getContentResolver(),
154 root.authority,
155 root.rootId,
156 listener).executeOnExecutor(ProviderExecutor.forAuthority(root.authority));
157 }
158
159 @Override
Ben Lin80030082017-05-01 18:50:05 -0700160 public void startAuthentication(PendingIntent intent) {
161 try {
162 mActivity.startIntentSenderForResult(intent.getIntentSender(), CODE_AUTHENTICATION,
163 null, 0, 0, 0);
164 } catch (IntentSender.SendIntentException cancelled) {
165 Log.d(TAG, "Authentication Pending Intent either canceled or ignored.");
166 }
167 }
168
169 @Override
170 public void onActivityResult(int requestCode, int resultCode, Intent data) {
171 switch (requestCode) {
172 case CODE_AUTHENTICATION:
173 onAuthenticationResult(resultCode);
174 break;
175 }
176 }
177
178 private void onAuthenticationResult(int resultCode) {
Tony Huang8d8d92f2018-09-13 14:41:16 +0800179 if (resultCode == FragmentActivity.RESULT_OK) {
Ben Lin80030082017-05-01 18:50:05 -0700180 Log.v(TAG, "Authentication was successful. Refreshing directory now.");
181 mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
182 }
183 }
184
185 @Override
Ben Lin30b0dc12017-03-07 15:37:16 -0800186 public void getRootDocument(RootInfo root, int timeout, Consumer<DocumentInfo> callback) {
187 GetRootDocumentTask task = new GetRootDocumentTask(
188 root,
189 mActivity,
190 timeout,
191 mDocs,
192 callback);
193
194 task.executeOnExecutor(mExecutors.lookup(root.authority));
195 }
196
197 @Override
Ben Line9abd2d2016-12-06 11:39:52 -0800198 public void refreshDocument(DocumentInfo doc, BooleanConsumer callback) {
Steve McKay98f8c5f2017-03-03 13:52:14 -0800199 RefreshTask task = new RefreshTask(
200 mInjector.features,
201 mState,
202 doc,
203 REFRESH_SPINNER_TIMEOUT,
204 mActivity.getApplicationContext(),
205 mActivity::isDestroyed,
Ben Line9abd2d2016-12-06 11:39:52 -0800206 callback);
207 task.executeOnExecutor(mExecutors.lookup(doc == null ? null : doc.authority));
208 }
209
210 @Override
Steve McKay5b0a2c12016-10-07 11:22:31 -0700211 public void openSelectedInNewWindow() {
212 throw new UnsupportedOperationException("Can't open in new window.");
213 }
214
215 @Override
Steve McKay739f94b2016-09-22 14:54:23 -0700216 public void openInNewWindow(DocumentStack path) {
shawnlin9cee68f2019-01-25 11:20:18 +0800217 Metrics.logUserAction(MetricConsts.USER_ACTION_NEW_WINDOW);
Steve McKay739f94b2016-09-22 14:54:23 -0700218
219 Intent intent = LauncherActivity.createLaunchIntent(mActivity);
220 intent.putExtra(Shared.EXTRA_STACK, (Parcelable) path);
221
222 // Multi-window necessitates we pick how we are launched.
223 // By default we'd be launched in-place above the existing app.
224 // By setting launch-to-side ActivityManager will open us to side.
225 if (mActivity.isInMultiWindowMode()) {
226 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
227 }
228
229 mActivity.startActivity(intent);
230 }
231
232 @Override
Riddle Hsu0c375982018-06-21 22:06:43 +0800233 public boolean openItem(ItemDetails<String> doc, @ViewType int type, @ViewType int fallback) {
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900234 throw new UnsupportedOperationException("Can't open document.");
235 }
236
Steve McKay3a24e6c2017-08-07 13:30:04 -0700237 @Override
Austin Kolanderf5042d02017-06-08 09:20:30 -0700238 public void showInspector(DocumentInfo doc) {
Dooper0930d4c2017-06-02 10:32:00 -0700239 throw new UnsupportedOperationException("Can't open properties.");
240 }
241
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900242 @Override
Ben Lind8d0ad22017-01-11 13:30:50 -0800243 public void springOpenDirectory(DocumentInfo doc) {
244 throw new UnsupportedOperationException("Can't spring open directories.");
245 }
246
247 @Override
Steve McKayc8889af2016-09-23 11:22:41 -0700248 public void openSettings(RootInfo root) {
249 throw new UnsupportedOperationException("Can't open settings.");
250 }
251
252 @Override
253 public void openRoot(ResolveInfo app) {
254 throw new UnsupportedOperationException("Can't open an app.");
255 }
256
257 @Override
258 public void showAppDetails(ResolveInfo info) {
259 throw new UnsupportedOperationException("Can't show app details.");
260 }
261
262 @Override
Ben Lin174fc2e2017-03-01 17:53:20 -0800263 public boolean dropOn(DragEvent event, RootInfo root) {
Steve McKayc8889af2016-09-23 11:22:41 -0700264 throw new UnsupportedOperationException("Can't open an app.");
265 }
266
267 @Override
Steve McKay739f94b2016-09-22 14:54:23 -0700268 public void pasteIntoFolder(RootInfo root) {
269 throw new UnsupportedOperationException("Can't paste into folder.");
270 }
271
272 @Override
Jon Mann253a9922017-03-21 18:53:27 -0700273 public void viewInOwner() {
274 throw new UnsupportedOperationException("Can't view in application.");
275 }
276
277 @Override
Jon Mann30d8c792017-02-21 17:44:49 -0800278 public void selectAllFiles() {
shawnlin9cee68f2019-01-25 11:20:18 +0800279 Metrics.logUserAction(MetricConsts.USER_ACTION_SELECT_ALL);
Jon Mann30d8c792017-02-21 17:44:49 -0800280 Model model = mInjector.getModel();
281
282 // Exclude disabled files
283 List<String> enabled = new ArrayList<>();
284 for (String id : model.getModelIds()) {
285 Cursor cursor = model.getItem(id);
286 if (cursor == null) {
287 Log.w(TAG, "Skipping selection. Can't obtain cursor for modeId: " + id);
288 continue;
289 }
290 String docMimeType = getCursorString(
291 cursor, DocumentsContract.Document.COLUMN_MIME_TYPE);
292 int docFlags = getCursorInt(cursor, DocumentsContract.Document.COLUMN_FLAGS);
293 if (mInjector.config.isDocumentEnabled(docMimeType, docFlags, mState)) {
294 enabled.add(id);
295 }
296 }
297
298 // Only select things currently visible in the adapter.
299 boolean changed = mSelectionMgr.setItemsSelected(enabled, true);
300 if (changed) {
301 mDisplayStateChangedListener.run();
302 }
303 }
304
305 @Override
Tony Huang76c6c9d2019-08-12 18:04:06 +0800306 public void deselectAllFiles() {
307 mSelectionMgr.clearSelection();
308 }
309
310 @Override
Ben Linff7f3ae2017-04-25 16:08:52 -0700311 public void showCreateDirectoryDialog() {
shawnlin9cee68f2019-01-25 11:20:18 +0800312 Metrics.logUserAction(MetricConsts.USER_ACTION_CREATE_DIR);
Ben Linff7f3ae2017-04-25 16:08:52 -0700313
Tony Huang8d8d92f2018-09-13 14:41:16 +0800314 CreateDirectoryFragment.show(mActivity.getSupportFragmentManager());
Ben Linff7f3ae2017-04-25 16:08:52 -0700315 }
316
317 @Override
Tony Huang243cf9c2018-10-16 16:15:18 +0800318 public void showSortDialog() {
Tony Huang8d8d92f2018-09-13 14:41:16 +0800319 SortListFragment.show(mActivity.getSupportFragmentManager(), mState.sortModel);
Tony Huang243cf9c2018-10-16 16:15:18 +0800320 }
321
322 @Override
Jon Mann30d8c792017-02-21 17:44:49 -0800323 @Nullable
324 public DocumentInfo renameDocument(String name, DocumentInfo document) {
325 throw new UnsupportedOperationException("Can't rename documents.");
326 }
327
328 @Override
Garfield Tan208945c2016-10-04 14:36:38 -0700329 public void showChooserForDoc(DocumentInfo doc) {
330 throw new UnsupportedOperationException("Show chooser for doc not supported!");
331 }
332
333 @Override
Garfield Tane9670332017-03-06 18:33:23 -0800334 public void openRootDocument(@Nullable DocumentInfo rootDoc) {
335 if (rootDoc == null) {
336 // There are 2 cases where rootDoc is null -- 1) loading recents; 2) failed to load root
337 // document. Either case we should call refreshCurrentRootAndDirectory() to let
338 // DirectoryFragment update UI.
339 mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
340 } else {
341 openContainerDocument(rootDoc);
342 }
343 }
344
345 @Override
Garfield Tan63bf8132016-10-11 11:00:49 -0700346 public void openContainerDocument(DocumentInfo doc) {
347 assert(doc.isContainer());
Garfield Tan16868832016-09-26 10:01:45 -0700348
349 if (mSearchMgr.isSearching()) {
350 loadDocument(
351 doc.derivedUri,
352 (@Nullable DocumentStack stack) -> openFolderInSearchResult(stack, doc));
353 } else {
354 openChildContainer(doc);
355 }
356 }
357
Tony Huang7a7e7df2018-11-06 17:16:47 +0800358 @Override
359 public boolean previewItem(ItemDetails<String> doc) {
360 throw new UnsupportedOperationException("Can't handle preview.");
361 }
362
Garfield Tan16868832016-09-26 10:01:45 -0700363 private void openFolderInSearchResult(@Nullable DocumentStack stack, DocumentInfo doc) {
364 if (stack == null) {
Garfield Tan2a837422016-10-19 11:50:45 -0700365 mState.stack.popToRootDocument();
Garfield Tan16868832016-09-26 10:01:45 -0700366
367 // Update navigator to give horizontal breadcrumb a chance to update documents. It
368 // doesn't update its content if the size of document stack doesn't change.
369 // TODO: update breadcrumb to take range update.
370 mActivity.updateNavigator();
371
Garfield Tan2a837422016-10-19 11:50:45 -0700372 mState.stack.push(doc);
Garfield Tan16868832016-09-26 10:01:45 -0700373 } else {
Garfield Tan2a837422016-10-19 11:50:45 -0700374 if (!Objects.equals(mState.stack.getRoot(), stack.getRoot())) {
375 Log.w(TAG, "Provider returns " + stack.getRoot() + " rather than expected "
376 + mState.stack.getRoot());
Garfield Tan16868832016-09-26 10:01:45 -0700377 }
378
Garfield Tan79a572b2017-07-20 16:40:51 -0700379 final DocumentInfo top = stack.peek();
380 if (top.isArchive()) {
381 // Swap the zip file in original provider and the one provided by ArchiveProvider.
382 stack.pop();
383 stack.push(mDocs.getArchiveDocument(top.derivedUri));
384 }
385
Garfield Tan2a837422016-10-19 11:50:45 -0700386 mState.stack.reset();
Garfield Tan16868832016-09-26 10:01:45 -0700387 // Update navigator to give horizontal breadcrumb a chance to update documents. It
388 // doesn't update its content if the size of document stack doesn't change.
389 // TODO: update breadcrumb to take range update.
390 mActivity.updateNavigator();
391
Garfield Tan2a837422016-10-19 11:50:45 -0700392 mState.stack.reset(stack);
Garfield Tan16868832016-09-26 10:01:45 -0700393 }
394
395 // Show an opening animation only if pressing "back" would get us back to the
396 // previous directory. Especially after opening a root document, pressing
397 // back, wouldn't go to the previous root, but close the activity.
Garfield Tan2a837422016-10-19 11:50:45 -0700398 final int anim = (mState.stack.hasLocationChanged() && mState.stack.size() > 1)
Garfield Tan16868832016-09-26 10:01:45 -0700399 ? AnimationView.ANIM_ENTER : AnimationView.ANIM_NONE;
400 mActivity.refreshCurrentRootAndDirectory(anim);
401 }
402
403 private void openChildContainer(DocumentInfo doc) {
Tomasz Mikolajewskidbd6b8b2016-09-29 15:27:37 +0900404 DocumentInfo currentDoc = null;
Garfield Tan63bf8132016-10-11 11:00:49 -0700405
Tomasz Mikolajewskidbd6b8b2016-09-29 15:27:37 +0900406 if (doc.isDirectory()) {
407 // Regular directory.
408 currentDoc = doc;
409 } else if (doc.isArchive()) {
410 // Archive.
411 currentDoc = mDocs.getArchiveDocument(doc.derivedUri);
412 }
Garfield Tan63bf8132016-10-11 11:00:49 -0700413
Tomasz Mikolajewskidbd6b8b2016-09-29 15:27:37 +0900414 assert(currentDoc != null);
415 mActivity.notifyDirectoryNavigated(currentDoc.derivedUri);
Tomasz Mikolajewskidbd6b8b2016-09-29 15:27:37 +0900416
Garfield Tan2a837422016-10-19 11:50:45 -0700417 mState.stack.push(currentDoc);
Garfield Tan63bf8132016-10-11 11:00:49 -0700418 // Show an opening animation only if pressing "back" would get us back to the
419 // previous directory. Especially after opening a root document, pressing
420 // back, wouldn't go to the previous root, but close the activity.
Garfield Tan2a837422016-10-19 11:50:45 -0700421 final int anim = (mState.stack.hasLocationChanged() && mState.stack.size() > 1)
Garfield Tan63bf8132016-10-11 11:00:49 -0700422 ? AnimationView.ANIM_ENTER : AnimationView.ANIM_NONE;
423 mActivity.refreshCurrentRootAndDirectory(anim);
424 }
425
426 @Override
Jon Manneb1d11b2017-04-01 15:36:59 -0700427 public void setDebugMode(boolean enabled) {
Steve McKay2979b212017-04-11 14:40:01 -0700428 if (!mInjector.features.isDebugSupportEnabled()) {
429 return;
430 }
431
Jon Manneb1d11b2017-04-01 15:36:59 -0700432 mState.debugMode = enabled;
Steve McKay2979b212017-04-11 14:40:01 -0700433 mInjector.features.forceFeature(R.bool.feature_command_interceptor, enabled);
Steve McKay84440eb2017-06-23 12:35:15 -0700434 mInjector.features.forceFeature(R.bool.feature_inspector, enabled);
Jon Manneb1d11b2017-04-01 15:36:59 -0700435 mActivity.invalidateOptionsMenu();
436
437 if (enabled) {
438 showDebugMessage();
439 } else {
Jon Manneb1d11b2017-04-01 15:36:59 -0700440 mActivity.getWindow().setStatusBarColor(
Tony Huang4ce11e22019-09-25 19:02:46 +0800441 mActivity.getResources().getColor(R.color.app_background_color));
Jon Manneb1d11b2017-04-01 15:36:59 -0700442 }
443 }
444
445 @Override
446 public void showDebugMessage() {
Steve McKay2979b212017-04-11 14:40:01 -0700447 assert (mInjector.features.isDebugSupportEnabled());
448
Jon Manneb1d11b2017-04-01 15:36:59 -0700449 int[] colors = mInjector.debugHelper.getNextColors();
450 Pair<String, Integer> messagePair = mInjector.debugHelper.getNextMessage();
451
452 Snackbars.showCustomTextWithImage(mActivity, messagePair.first, messagePair.second);
453
Jon Manneb1d11b2017-04-01 15:36:59 -0700454 mActivity.getWindow().setStatusBarColor(colors[1]);
455 }
456
457 @Override
Tony Huang94fc11a2019-10-30 18:00:20 +0800458 public void switchLauncherIcon() {
459 PackageManager pm = mActivity.getPackageManager();
460 if (pm != null) {
461 final boolean enalbled = Shared.isLauncherEnabled(mActivity);
462 ComponentName component = new ComponentName(
463 mActivity.getPackageName(), Shared.LAUNCHER_TARGET_CLASS);
464 pm.setComponentEnabledSetting(component, enalbled
465 ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
466 : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
467 PackageManager.DONT_KILL_APP);
468 }
469 }
470
471 @Override
Ben Lind947f012016-10-18 14:32:49 -0700472 public void cutToClipboard() {
473 throw new UnsupportedOperationException("Cut not supported!");
474 }
475
476 @Override
477 public void copyToClipboard() {
478 throw new UnsupportedOperationException("Copy not supported!");
479 }
480
481 @Override
shawnlin8dafe612019-08-14 20:10:18 +0800482 public void showDeleteDialog() {
483 throw new UnsupportedOperationException("Delete not supported!");
484 }
485
486 @Override
487 public void deleteSelectedDocuments(List<DocumentInfo> docs, DocumentInfo srcParent) {
Steve McKayc8889af2016-09-23 11:22:41 -0700488 throw new UnsupportedOperationException("Delete not supported!");
489 }
490
Steve McKay988d8a32016-09-27 09:41:17 -0700491 @Override
Steve McKayd0718952016-10-10 13:43:36 -0700492 public void shareSelectedDocuments() {
493 throw new UnsupportedOperationException("Share not supported!");
494 }
495
Garfield Tan16868832016-09-26 10:01:45 -0700496 protected final void loadDocument(Uri uri, LoadDocStackCallback callback) {
497 new LoadDocStackTask(
498 mActivity,
Jon Mann9bd40992017-03-24 12:34:34 -0700499 mProviders,
Garfield Tan16868832016-09-26 10:01:45 -0700500 mDocs,
Garfield Tan16868832016-09-26 10:01:45 -0700501 callback
Garfield Tan2a837422016-10-19 11:50:45 -0700502 ).executeOnExecutor(mExecutors.lookup(uri.getAuthority()), uri);
Steve McKaydef48682016-10-03 09:07:38 -0700503 }
504
505 @Override
Steve McKay988d8a32016-09-27 09:41:17 -0700506 public final void loadRoot(Uri uri) {
Tony Huang957be8c2019-07-30 15:49:36 +0800507 new LoadRootTask<>(mActivity, mProviders, uri, this::onRootLoaded)
Steve McKaydef48682016-10-03 09:07:38 -0700508 .executeOnExecutor(mExecutors.lookup(uri.getAuthority()));
Steve McKay988d8a32016-09-27 09:41:17 -0700509 }
510
Garfield Tane9670332017-03-06 18:33:23 -0800511 @Override
Ivan Chiang9b9a2822018-09-19 17:03:22 +0800512 public final void loadFirstRoot(Uri uri) {
Tony Huang957be8c2019-07-30 15:49:36 +0800513 new LoadFirstRootTask<>(mActivity, mProviders, uri, this::onRootLoaded)
Ivan Chiang9b9a2822018-09-19 17:03:22 +0800514 .executeOnExecutor(mExecutors.lookup(uri.getAuthority()));
515 }
516
517 @Override
Garfield Tane9670332017-03-06 18:33:23 -0800518 public void loadDocumentsForCurrentStack() {
519 DocumentStack stack = mState.stack;
520 if (!stack.isRecents() && stack.isEmpty()) {
521 DirectoryResult result = new DirectoryResult();
522
523 // TODO (b/35996595): Consider plumbing through the actual exception, though it might
524 // not be very useful (always pointing to DatabaseUtils#readExceptionFromParcel()).
525 result.exception = new IllegalStateException("Failed to load root document.");
526 mInjector.getModel().update(result);
527 return;
528 }
529
Tony Huang35ad6bd2018-11-06 14:39:40 +0800530 mActivity.getSupportLoaderManager().restartLoader(LOADER_ID, null, mBindings);
Garfield Tane9670332017-03-06 18:33:23 -0800531 }
532
Garfield Tanf8969d62017-02-02 16:55:55 -0800533 protected final boolean launchToDocument(Uri uri) {
534 // We don't support launching to a document in an archive.
Steve McKay98f8c5f2017-03-03 13:52:14 -0800535 if (!Providers.isArchiveUri(uri)) {
Garfield Tanf8969d62017-02-02 16:55:55 -0800536 loadDocument(uri, this::onStackLoaded);
537 return true;
538 }
539
540 return false;
541 }
542
543 private void onStackLoaded(@Nullable DocumentStack stack) {
544 if (stack != null) {
545 if (!stack.peek().isDirectory()) {
546 // Requested document is not a directory. Pop it so that we can launch into its
547 // parent.
548 stack.pop();
549 }
550 mState.stack.reset(stack);
551 mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
552
shawnlin9cee68f2019-01-25 11:20:18 +0800553 Metrics.logLaunchAtLocation(mState, stack.getRoot().getUri());
Garfield Tanf8969d62017-02-02 16:55:55 -0800554 } else {
555 Log.w(TAG, "Failed to launch into the given uri. Launch to default location.");
556 launchToDefaultLocation();
557
shawnlin9cee68f2019-01-25 11:20:18 +0800558 Metrics.logLaunchAtLocation(mState, null);
Garfield Tanf8969d62017-02-02 16:55:55 -0800559 }
560 }
561
Tony Huang957be8c2019-07-30 15:49:36 +0800562 private void onRootLoaded(@Nullable RootInfo root) {
Stephen Hughes3bd3e0d2019-10-07 10:49:12 +0800563 boolean invalidRootForAction =
564 (root != null
565 && !root.supportsChildren()
566 && mState.action == State.ACTION_OPEN_TREE);
567
568 if (invalidRootForAction) {
569 loadDeviceRoot();
570 } else if (root != null) {
Tony Huang957be8c2019-07-30 15:49:36 +0800571 mActivity.onRootPicked(root);
572 } else {
573 launchToDefaultLocation();
574 }
575 }
576
Garfield Tanf8969d62017-02-02 16:55:55 -0800577 protected abstract void launchToDefaultLocation();
578
Garfield Tan5f2a9ba2017-05-26 14:35:44 -0700579 protected void restoreRootAndDirectory() {
580 if (!mState.stack.getRoot().isRecents() && mState.stack.isEmpty()) {
581 mActivity.onRootPicked(mState.stack.getRoot());
582 } else {
583 mActivity.restoreRootAndDirectory();
584 }
585 }
586
Ivan Chiang462569a2019-09-25 16:16:35 +0800587 protected final void loadDeviceRoot() {
588 mActivity.onRootPicked(
589 mProviders.getRootOneshot(Providers.AUTHORITY_STORAGE, Providers.ROOT_ID_DEVICE));
590 }
591
Steve McKay988d8a32016-09-27 09:41:17 -0700592 protected final void loadHomeDir() {
593 loadRoot(Shared.getDefaultRootUri(mActivity));
594 }
595
Tony Huang4e6242a2019-01-25 11:45:31 +0800596 protected final void loadRecent() {
597 mState.stack.changeRoot(mProviders.getRecentsRoot());
598 mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
599 }
600
Riddle Hsu0c375982018-06-21 22:06:43 +0800601 protected MutableSelection<String> getStableSelection() {
602 MutableSelection<String> selection = new MutableSelection<>();
Steve McKay2d19a692017-08-22 09:05:08 -0700603 mSelectionMgr.copySelection(selection);
604 return selection;
Steve McKay5b0a2c12016-10-07 11:22:31 -0700605 }
Garfield Tan63bf8132016-10-11 11:00:49 -0700606
Steve McKay710248d2017-03-14 10:09:30 -0700607 @Override
Steve McKay12394522017-08-24 14:14:10 -0700608 public ActionHandler reset(ContentLock reloadLock) {
609 mContentLock = reloadLock;
Garfield Tane9670332017-03-06 18:33:23 -0800610 mActivity.getLoaderManager().destroyLoader(LOADER_ID);
611 return this;
612 }
613
614 private final class LoaderBindings implements LoaderCallbacks<DirectoryResult> {
615
616 @Override
617 public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
618 Context context = mActivity;
619
620 if (mState.stack.isRecents()) {
Tony Huang4d179b72019-08-15 14:25:10 +0800621 final LockingContentObserver observer = new LockingContentObserver(
622 mContentLock, AbstractActionHandler.this::loadDocumentsForCurrentStack);
623 MultiRootDocumentsLoader loader;
624
Ivan Chiang282baa42018-08-08 18:54:14 +0800625 if (mSearchMgr.isSearching()) {
626 if (DEBUG) {
Ivan Chiangf0ea0ed2018-12-12 11:10:01 +0800627 Log.d(TAG, "Creating new GlobalSearchLoader.");
Ivan Chiang282baa42018-08-08 18:54:14 +0800628 }
Tony Huang4d179b72019-08-15 14:25:10 +0800629 loader = new GlobalSearchLoader(
Ivan Chiang282baa42018-08-08 18:54:14 +0800630 context,
631 mProviders,
632 mState,
633 mExecutors,
634 mInjector.fileTypeLookup,
Ivan Chiangf0ea0ed2018-12-12 11:10:01 +0800635 mSearchMgr.buildQueryArgs());
Ivan Chiang282baa42018-08-08 18:54:14 +0800636 } else {
637 if (DEBUG) {
638 Log.d(TAG, "Creating new loader recents.");
639 }
Tony Huang4d179b72019-08-15 14:25:10 +0800640 loader = new RecentsLoader(
Ivan Chiang282baa42018-08-08 18:54:14 +0800641 context,
642 mProviders,
643 mState,
644 mExecutors,
645 mInjector.fileTypeLookup);
646 }
Tony Huang4d179b72019-08-15 14:25:10 +0800647 loader.setObserver(observer);
648 return loader;
Garfield Tane9670332017-03-06 18:33:23 -0800649 } else {
Garfield Tane9670332017-03-06 18:33:23 -0800650 Uri contentsUri = mSearchMgr.isSearching()
651 ? DocumentsContract.buildSearchDocumentsUri(
652 mState.stack.getRoot().authority,
653 mState.stack.getRoot().rootId,
654 mSearchMgr.getCurrentSearch())
655 : DocumentsContract.buildChildDocumentsUri(
656 mState.stack.peek().authority,
657 mState.stack.peek().documentId);
658
Ivan Chiangf0ea0ed2018-12-12 11:10:01 +0800659 final Bundle queryArgs = mSearchMgr.isSearching()
660 ? mSearchMgr.buildQueryArgs()
661 : null;
662
Garfield Tane9670332017-03-06 18:33:23 -0800663 if (mInjector.config.managedModeEnabled(mState.stack)) {
664 contentsUri = DocumentsContract.setManageMode(contentsUri);
665 }
666
Jason Chang96f886b2019-03-29 17:59:02 +0800667 if (DEBUG) {
668 Log.d(TAG,
Garfield Tane9670332017-03-06 18:33:23 -0800669 "Creating new directory loader for: "
Jason Chang96f886b2019-03-29 17:59:02 +0800670 + DocumentInfo.debugString(mState.stack.peek()));
671 }
Garfield Tane9670332017-03-06 18:33:23 -0800672
673 return new DirectoryLoader(
Steve McKay710248d2017-03-14 10:09:30 -0700674 mInjector.features,
Garfield Tane9670332017-03-06 18:33:23 -0800675 context,
Tony Huang74ded6e2018-11-06 17:16:47 +0800676 mState,
Garfield Tane9670332017-03-06 18:33:23 -0800677 contentsUri,
Garfield Tanb47b4b52017-05-16 12:48:53 -0700678 mInjector.fileTypeLookup,
Steve McKay12394522017-08-24 14:14:10 -0700679 mContentLock,
Ivan Chiangf0ea0ed2018-12-12 11:10:01 +0800680 queryArgs);
Garfield Tane9670332017-03-06 18:33:23 -0800681 }
682 }
683
684 @Override
685 public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
Jason Chang96f886b2019-03-29 17:59:02 +0800686 if (DEBUG) {
687 Log.d(TAG, "Loader has finished for: "
Garfield Tane9670332017-03-06 18:33:23 -0800688 + DocumentInfo.debugString(mState.stack.peek()));
Jason Chang96f886b2019-03-29 17:59:02 +0800689 }
Garfield Tane9670332017-03-06 18:33:23 -0800690 assert(result != null);
691
692 mInjector.getModel().update(result);
693 }
694
695 @Override
696 public void onLoaderReset(Loader<DirectoryResult> loader) {}
697 }
Steve McKayc8889af2016-09-23 11:22:41 -0700698 /**
699 * A class primarily for the support of isolating our tests
700 * from our concrete activity implementations.
701 */
702 public interface CommonAddons {
Garfield Tan5f2a9ba2017-05-26 14:35:44 -0700703 void restoreRootAndDirectory();
Steve McKay0f5ec832016-10-04 14:21:56 -0700704 void refreshCurrentRootAndDirectory(@AnimationType int anim);
705 void onRootPicked(RootInfo root);
706 // TODO: Move this to PickAddons as multi-document picking is exclusive to that activity.
707 void onDocumentsPicked(List<DocumentInfo> docs);
708 void onDocumentPicked(DocumentInfo doc);
Steve McKay0f5ec832016-10-04 14:21:56 -0700709 RootInfo getCurrentRoot();
710 DocumentInfo getCurrentDirectory();
Tony Huangfdbab1a2019-04-08 18:10:27 +0800711 /**
712 * Check whether current directory is root of recent.
713 */
714 boolean isInRecents();
Garfield Tan208945c2016-10-04 14:36:38 -0700715 void setRootsDrawerOpen(boolean open);
Garfield Tan63bf8132016-10-11 11:00:49 -0700716
Garfield Tan16868832016-09-26 10:01:45 -0700717 // TODO: Let navigator listens to State
718 void updateNavigator();
719
Garfield Tan63bf8132016-10-11 11:00:49 -0700720 @VisibleForTesting
721 void notifyDirectoryNavigated(Uri docUri);
Steve McKayc8889af2016-09-23 11:22:41 -0700722 }
Steve McKay739f94b2016-09-22 14:54:23 -0700723}