blob: 922f0c0025ac42b90f3f86fc651a097fce600f4d [file] [log] [blame]
Garfield Tan7d75f7b2016-09-20 16:33:24 -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
Steve McKayb6006b22016-09-29 09:23:45 -070017package com.android.documentsui.files;
Garfield Tan7d75f7b2016-09-20 16:33:24 -070018
Steve McKay988d8a32016-09-27 09:41:17 -070019import static com.android.documentsui.base.Shared.DEBUG;
20
Steve McKayc8889af2016-09-23 11:22:41 -070021import android.app.Activity;
Steve McKayeed2f4e2016-10-03 20:30:52 -070022import android.content.ActivityNotFoundException;
Ben Lin30b0dc12017-03-07 15:37:16 -080023import android.content.ClipData;
Jon Mann30d8c792017-02-21 17:44:49 -080024import android.content.ContentProviderClient;
25import android.content.ContentResolver;
Steve McKay739f94b2016-09-22 14:54:23 -070026import android.content.Intent;
Steve McKay988d8a32016-09-27 09:41:17 -070027import android.net.Uri;
Steve McKay739f94b2016-09-22 14:54:23 -070028import android.provider.DocumentsContract;
Steve McKay6d20d192016-09-21 17:57:10 -070029import android.util.Log;
Ben Lin174fc2e2017-03-01 17:53:20 -080030import android.view.DragEvent;
Steve McKay6d20d192016-09-21 17:57:10 -070031
Steve McKay739f94b2016-09-22 14:54:23 -070032import com.android.documentsui.AbstractActionHandler;
Steve McKaybd9f05a2016-10-10 10:18:36 -070033import com.android.documentsui.ActionModeAddons;
Steve McKay5b0a2c12016-10-07 11:22:31 -070034import com.android.documentsui.ActivityConfig;
Steve McKaydef48682016-10-03 09:07:38 -070035import com.android.documentsui.DocumentsAccess;
Garfield Tanb285b402016-09-21 17:12:18 -070036import com.android.documentsui.DocumentsApplication;
Garfield Tanda2c0f02017-04-11 13:47:58 -070037import com.android.documentsui.DragAndDropManager;
Jon Mann30d8c792017-02-21 17:44:49 -080038import com.android.documentsui.Injector;
Steve McKay739f94b2016-09-22 14:54:23 -070039import com.android.documentsui.Metrics;
Ben Lin30b0dc12017-03-07 15:37:16 -080040import com.android.documentsui.Model;
Steve McKayd0718952016-10-10 13:43:36 -070041import com.android.documentsui.R;
Ben Lin30b0dc12017-03-07 15:37:16 -080042import com.android.documentsui.TimeoutTask;
Steve McKayc8889af2016-09-23 11:22:41 -070043import com.android.documentsui.base.ConfirmationCallback;
44import com.android.documentsui.base.ConfirmationCallback.Result;
Steve McKay84440eb2017-06-23 12:35:15 -070045import com.android.documentsui.base.DebugFlags;
Steve McKay98f8c5f2017-03-03 13:52:14 -080046import com.android.documentsui.base.DocumentFilters;
Steve McKay6d20d192016-09-21 17:57:10 -070047import com.android.documentsui.base.DocumentInfo;
Garfield Tanb285b402016-09-21 17:12:18 -070048import com.android.documentsui.base.DocumentStack;
Steve McKay98f8c5f2017-03-03 13:52:14 -080049import com.android.documentsui.base.Features;
Steve McKay988d8a32016-09-27 09:41:17 -070050import com.android.documentsui.base.Lookup;
Steve McKayd0718952016-10-10 13:43:36 -070051import com.android.documentsui.base.MimeTypes;
Garfield Tan7d75f7b2016-09-20 16:33:24 -070052import com.android.documentsui.base.RootInfo;
Ben Lindf0fe892016-10-18 17:36:05 -070053import com.android.documentsui.base.Shared;
Steve McKayc8889af2016-09-23 11:22:41 -070054import com.android.documentsui.base.State;
55import com.android.documentsui.clipping.ClipStore;
Garfield Tanb285b402016-09-21 17:12:18 -070056import com.android.documentsui.clipping.DocumentClipper;
Steve McKayc8889af2016-09-23 11:22:41 -070057import com.android.documentsui.clipping.UrisSupplier;
Steve McKay988d8a32016-09-27 09:41:17 -070058import com.android.documentsui.dirlist.AnimationView;
Steve McKay6d20d192016-09-21 17:57:10 -070059import com.android.documentsui.dirlist.DocumentDetails;
Steve McKayb6006b22016-09-29 09:23:45 -070060import com.android.documentsui.files.ActionHandler.Addons;
Steve McKayf433d202017-07-12 18:46:09 -070061import com.android.documentsui.inspector.InspectorActivity;
Steve McKay3a268232016-10-19 11:15:47 -070062import com.android.documentsui.queries.SearchViewManager;
Jon Mann9bd40992017-03-24 12:34:34 -070063import com.android.documentsui.roots.ProvidersAccess;
Steve McKay4f78ba62016-10-04 16:48:49 -070064import com.android.documentsui.selection.Selection;
Steve McKayc8889af2016-09-23 11:22:41 -070065import com.android.documentsui.services.FileOperation;
66import com.android.documentsui.services.FileOperationService;
67import com.android.documentsui.services.FileOperations;
68import com.android.documentsui.ui.DialogController;
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +090069import com.android.internal.annotations.VisibleForTesting;
Steve McKayc8889af2016-09-23 11:22:41 -070070
Steve McKayd0718952016-10-10 13:43:36 -070071import java.util.ArrayList;
Steve McKayc8889af2016-09-23 11:22:41 -070072import java.util.List;
Steve McKay988d8a32016-09-27 09:41:17 -070073import java.util.concurrent.Executor;
Steve McKay6d20d192016-09-21 17:57:10 -070074
75import javax.annotation.Nullable;
Garfield Tan7d75f7b2016-09-20 16:33:24 -070076
77/**
Steve McKayb6006b22016-09-29 09:23:45 -070078 * Provides {@link FilesActivity} action specializations to fragments.
Garfield Tan7d75f7b2016-09-20 16:33:24 -070079 */
Steve McKayc8889af2016-09-23 11:22:41 -070080public class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T> {
Garfield Tan7d75f7b2016-09-20 16:33:24 -070081
Steve McKay6d20d192016-09-21 17:57:10 -070082 private static final String TAG = "ManagerActionHandler";
83
Steve McKaybd9f05a2016-10-10 10:18:36 -070084 private final ActionModeAddons mActionModeAddons;
Steve McKay98f8c5f2017-03-03 13:52:14 -080085 private final Features mFeatures;
86 private final ActivityConfig mConfig;
Steve McKayc8889af2016-09-23 11:22:41 -070087 private final DialogController mDialogs;
Steve McKay739f94b2016-09-22 14:54:23 -070088 private final DocumentClipper mClipper;
Steve McKayc8889af2016-09-23 11:22:41 -070089 private final ClipStore mClipStore;
Garfield Tanda2c0f02017-04-11 13:47:58 -070090 private final DragAndDropManager mDragAndDropManager;
Garfield Tane9670332017-03-06 18:33:23 -080091 private final Model mModel;
Steve McKay6d20d192016-09-21 17:57:10 -070092
Steve McKayc8889af2016-09-23 11:22:41 -070093 ActionHandler(
94 T activity,
Steve McKayc8889af2016-09-23 11:22:41 -070095 State state,
Jon Mann9bd40992017-03-24 12:34:34 -070096 ProvidersAccess providers,
Steve McKaydef48682016-10-03 09:07:38 -070097 DocumentsAccess docs,
Garfield Tan63bf8132016-10-11 11:00:49 -070098 SearchViewManager searchMgr,
Steve McKay988d8a32016-09-27 09:41:17 -070099 Lookup<String, Executor> executors,
Steve McKaybd9f05a2016-10-10 10:18:36 -0700100 ActionModeAddons actionModeAddons,
Steve McKayc8889af2016-09-23 11:22:41 -0700101 DocumentClipper clipper,
Jon Mann30d8c792017-02-21 17:44:49 -0800102 ClipStore clipStore,
Garfield Tanda2c0f02017-04-11 13:47:58 -0700103 DragAndDropManager dragAndDropManager,
Jon Mann30d8c792017-02-21 17:44:49 -0800104 Injector injector) {
Steve McKay988d8a32016-09-27 09:41:17 -0700105
Jon Mann9bd40992017-03-24 12:34:34 -0700106 super(activity, state, providers, docs, searchMgr, executors, injector);
Steve McKayc8889af2016-09-23 11:22:41 -0700107
Steve McKaybd9f05a2016-10-10 10:18:36 -0700108 mActionModeAddons = actionModeAddons;
Steve McKay98f8c5f2017-03-03 13:52:14 -0800109 mFeatures = injector.features;
110 mConfig = injector.config;
Jon Mann30d8c792017-02-21 17:44:49 -0800111 mDialogs = injector.dialogs;
Steve McKay739f94b2016-09-22 14:54:23 -0700112 mClipper = clipper;
Steve McKayc8889af2016-09-23 11:22:41 -0700113 mClipStore = clipStore;
Garfield Tanda2c0f02017-04-11 13:47:58 -0700114 mDragAndDropManager = dragAndDropManager;
Garfield Tane9670332017-03-06 18:33:23 -0800115 mModel = injector.getModel();
Garfield Tan7d75f7b2016-09-20 16:33:24 -0700116 }
117
118 @Override
Ben Lin174fc2e2017-03-01 17:53:20 -0800119 public boolean dropOn(DragEvent event, RootInfo root) {
Ben Lind6a85b92017-03-13 16:26:26 -0700120 if (!root.supportsCreate() || root.isLibrary()) {
121 return false;
122 }
123
Ben Lin30b0dc12017-03-07 15:37:16 -0800124 // DragEvent gets recycled, so it is possible that by the time the callback is called,
125 // event.getLocalState() and event.getClipData() returns null. Thus, we want to save
126 // references to ensure they are non null.
127 final ClipData clipData = event.getClipData();
128 final Object localState = event.getLocalState();
Garfield Tanb285b402016-09-21 17:12:18 -0700129
Garfield Tanda2c0f02017-04-11 13:47:58 -0700130 return mDragAndDropManager.drop(
131 clipData, localState, root, this, mDialogs::showFileOperationStatus);
Ben Lin174fc2e2017-03-01 17:53:20 -0800132 }
133
Steve McKay739f94b2016-09-22 14:54:23 -0700134 @Override
Steve McKay5b0a2c12016-10-07 11:22:31 -0700135 public void openSelectedInNewWindow() {
136 Selection selection = getStableSelection();
137 assert(selection.size() == 1);
Ben Linf23cbf42016-12-22 15:12:13 -0800138 DocumentInfo doc = mModel.getDocument(selection.iterator().next());
Steve McKay5b0a2c12016-10-07 11:22:31 -0700139 assert(doc != null);
140 openInNewWindow(new DocumentStack(mState.stack, doc));
141 }
142
143 @Override
Steve McKay739f94b2016-09-22 14:54:23 -0700144 public void openSettings(RootInfo root) {
145 Metrics.logUserAction(mActivity, Metrics.USER_ACTION_SETTINGS);
146 final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
147 intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
148 mActivity.startActivity(intent);
Garfield Tanb285b402016-09-21 17:12:18 -0700149 }
150
151 @Override
152 public void pasteIntoFolder(RootInfo root) {
Ben Lin30b0dc12017-03-07 15:37:16 -0800153 this.getRootDocument(
Garfield Tanb285b402016-09-21 17:12:18 -0700154 root,
Ben Lin30b0dc12017-03-07 15:37:16 -0800155 TimeoutTask.DEFAULT_TIMEOUT,
156 (DocumentInfo doc) -> pasteIntoFolder(root, doc));
Garfield Tanb285b402016-09-21 17:12:18 -0700157 }
158
Garfield Tane9670332017-03-06 18:33:23 -0800159 private void pasteIntoFolder(RootInfo root, @Nullable DocumentInfo doc) {
Garfield Tanb285b402016-09-21 17:12:18 -0700160 DocumentStack stack = new DocumentStack(root, doc);
Ben Lin30b0dc12017-03-07 15:37:16 -0800161 mClipper.copyFromClipboard(doc, stack, mDialogs::showFileOperationStatus);
Garfield Tanb285b402016-09-21 17:12:18 -0700162 }
163
164 @Override
Jon Mann30d8c792017-02-21 17:44:49 -0800165 public @Nullable DocumentInfo renameDocument(String name, DocumentInfo document) {
166 ContentResolver resolver = mActivity.getContentResolver();
167 ContentProviderClient client = null;
168
169 try {
170 client = DocumentsApplication.acquireUnstableProviderOrThrow(
171 resolver, document.derivedUri.getAuthority());
172 Uri newUri = DocumentsContract.renameDocument(
173 client, document.derivedUri, name);
174 return DocumentInfo.fromUri(resolver, newUri);
175 } catch (Exception e) {
176 Log.w(TAG, "Failed to rename file", e);
177 return null;
178 } finally {
179 ContentProviderClient.releaseQuietly(client);
180 }
181 }
182
183 @Override
Steve McKay739f94b2016-09-22 14:54:23 -0700184 public void openRoot(RootInfo root) {
Garfield Tan2a8719c2017-01-19 16:17:04 -0800185 Metrics.logRootVisited(mActivity, Metrics.FILES_SCOPE, root);
Steve McKay739f94b2016-09-22 14:54:23 -0700186 mActivity.onRootPicked(root);
187 }
188
189 @Override
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900190 public boolean openDocument(DocumentDetails details, @ViewType int type,
191 @ViewType int fallback) {
Ben Linf23cbf42016-12-22 15:12:13 -0800192 DocumentInfo doc = mModel.getDocument(details.getModelId());
Steve McKay6d20d192016-09-21 17:57:10 -0700193 if (doc == null) {
194 Log.w(TAG,
195 "Can't view item. No Document available for modeId: " + details.getModelId());
196 return false;
197 }
198
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900199 return openDocument(doc, type, fallback);
200 }
201
202 // TODO: Make this private and make tests call openDocument(DocumentDetails, int, int) instead.
203 @VisibleForTesting
204 public boolean openDocument(DocumentInfo doc, @ViewType int type, @ViewType int fallback) {
Steve McKay98f8c5f2017-03-03 13:52:14 -0800205 if (mConfig.isDocumentEnabled(doc.mimeType, doc.flags, mState)) {
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900206 onDocumentPicked(doc, type, fallback);
Steve McKay5b0a2c12016-10-07 11:22:31 -0700207 mSelectionMgr.clearSelection();
Steve McKay6d20d192016-09-21 17:57:10 -0700208 return true;
209 }
210 return false;
211 }
212
213 @Override
Ben Lind8d0ad22017-01-11 13:30:50 -0800214 public void springOpenDirectory(DocumentInfo doc) {
215 assert(doc.isDirectory());
216 mActionModeAddons.finishActionMode();
217 openContainerDocument(doc);
218 }
219
Ben Lind947f012016-10-18 14:32:49 -0700220 private Selection getSelectedOrFocused() {
221 final Selection selection = this.getStableSelection();
222 if (selection.isEmpty()) {
223 String focusModelId = mFocusHandler.getFocusModelId();
224 if (focusModelId != null) {
225 selection.add(focusModelId);
226 }
227 }
228
229 return selection;
230 }
231
232 @Override
233 public void cutToClipboard() {
234 Metrics.logUserAction(mActivity, Metrics.USER_ACTION_CUT_CLIPBOARD);
235 Selection selection = getSelectedOrFocused();
236
237 if (selection.isEmpty()) {
238 return;
239 }
Ben Linc1a32ae2017-04-19 15:19:49 -0700240
241 if (mModel.hasDocuments(selection, DocumentFilters.NOT_MOVABLE)) {
242 mDialogs.showOperationUnsupported();
243 return;
244 }
245
Ben Lind947f012016-10-18 14:32:49 -0700246 mSelectionMgr.clearSelection();
247
Ben Linf23cbf42016-12-22 15:12:13 -0800248 mClipper.clipDocumentsForCut(mModel::getItemUri, selection, mState.stack.peek());
Ben Lind947f012016-10-18 14:32:49 -0700249
250 mDialogs.showDocumentsClipped(selection.size());
251 }
252
253 @Override
254 public void copyToClipboard() {
255 Metrics.logUserAction(mActivity, Metrics.USER_ACTION_COPY_CLIPBOARD);
256 Selection selection = getSelectedOrFocused();
257
258 if (selection.isEmpty()) {
259 return;
260 }
261 mSelectionMgr.clearSelection();
262
Ben Linf23cbf42016-12-22 15:12:13 -0800263 mClipper.clipDocumentsForCopy(mModel::getItemUri, selection);
Ben Lind947f012016-10-18 14:32:49 -0700264
265 mDialogs.showDocumentsClipped(selection.size());
266 }
267
Jon Mann253a9922017-03-21 18:53:27 -0700268 @Override
269 public void viewInOwner() {
270 Metrics.logUserAction(mActivity, Metrics.USER_ACTION_VIEW_IN_APPLICATION);
271 Selection selection = getSelectedOrFocused();
272
273 if (selection.isEmpty() || selection.size() > 1) {
274 return;
275 }
276 DocumentInfo doc = mModel.getDocument(selection.iterator().next());
277 Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_SETTINGS);
Jon Mann9bd40992017-03-24 12:34:34 -0700278 intent.setPackage(mProviders.getPackageName(doc.authority));
Jon Mann253a9922017-03-21 18:53:27 -0700279 intent.addCategory(Intent.CATEGORY_DEFAULT);
280 intent.setData(doc.derivedUri);
281 try {
282 mActivity.startActivity(intent);
283 } catch (ActivityNotFoundException e) {
284 Log.e(TAG, "Failed to view settings in application for " + doc.derivedUri, e);
285 mDialogs.showNoApplicationFound();
286 }
287 }
288
Ben Lind947f012016-10-18 14:32:49 -0700289
Steve McKayc8889af2016-09-23 11:22:41 -0700290 @Override
Steve McKaybd9f05a2016-10-10 10:18:36 -0700291 public void deleteSelectedDocuments() {
Steve McKayc8889af2016-09-23 11:22:41 -0700292 Metrics.logUserAction(mActivity, Metrics.USER_ACTION_DELETE);
Ben Lind947f012016-10-18 14:32:49 -0700293 Selection selection = getSelectedOrFocused();
Steve McKayc8889af2016-09-23 11:22:41 -0700294
Ben Lind947f012016-10-18 14:32:49 -0700295 if (selection.isEmpty()) {
296 return;
297 }
Steve McKayc8889af2016-09-23 11:22:41 -0700298
Garfield Tan03a3a392016-12-12 14:06:45 -0800299 final @Nullable DocumentInfo srcParent = mState.stack.peek();
Steve McKayc8889af2016-09-23 11:22:41 -0700300
301 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
Ben Linf23cbf42016-12-22 15:12:13 -0800302 List<DocumentInfo> docs = mModel.getDocuments(selection);
Steve McKayc8889af2016-09-23 11:22:41 -0700303
304 ConfirmationCallback result = (@Result int code) -> {
305 // share the news with our caller, be it good or bad.
Steve McKaybd9f05a2016-10-10 10:18:36 -0700306 mActionModeAddons.finishOnConfirmed(code);
Steve McKayc8889af2016-09-23 11:22:41 -0700307
308 if (code != ConfirmationCallback.CONFIRM) {
309 return;
310 }
311
312 UrisSupplier srcs;
313 try {
314 srcs = UrisSupplier.create(
Steve McKay5b0a2c12016-10-07 11:22:31 -0700315 selection,
Ben Linf23cbf42016-12-22 15:12:13 -0800316 mModel::getItemUri,
Steve McKayc8889af2016-09-23 11:22:41 -0700317 mClipStore);
Jon Mannf6570252017-03-16 14:13:55 -0700318 } catch (Exception e) {
319 Log.e(TAG,"Failed to delete a file because we were unable to get item URIs.", e);
320 mDialogs.showFileOperationStatus(
321 FileOperations.Callback.STATUS_FAILED,
322 FileOperationService.OPERATION_DELETE,
323 selection.size());
324 return;
Steve McKayc8889af2016-09-23 11:22:41 -0700325 }
326
327 FileOperation operation = new FileOperation.Builder()
328 .withOpType(FileOperationService.OPERATION_DELETE)
329 .withDestination(mState.stack)
330 .withSrcs(srcs)
Garfield Tan03a3a392016-12-12 14:06:45 -0800331 .withSrcParent(srcParent == null ? null : srcParent.derivedUri)
Steve McKayc8889af2016-09-23 11:22:41 -0700332 .build();
333
Tomasz Mikolajewski0d83d322017-02-07 12:29:39 +0900334 FileOperations.start(mActivity, operation, mDialogs::showFileOperationStatus,
335 FileOperations.createJobId());
Steve McKayc8889af2016-09-23 11:22:41 -0700336 };
337
338 mDialogs.confirmDelete(docs, result);
339 }
340
Steve McKay988d8a32016-09-27 09:41:17 -0700341 @Override
Steve McKayd0718952016-10-10 13:43:36 -0700342 public void shareSelectedDocuments() {
343 Metrics.logUserAction(mActivity, Metrics.USER_ACTION_SHARE);
344
345 Selection selection = getStableSelection();
346
347 assert(!selection.isEmpty());
348
349 // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
Steve McKay98f8c5f2017-03-03 13:52:14 -0800350 List<DocumentInfo> docs = mModel.loadDocuments(
351 selection, DocumentFilters.sharable(mFeatures));
Steve McKayd0718952016-10-10 13:43:36 -0700352
353 Intent intent;
354
355 if (docs.size() == 1) {
Steve McKayd0718952016-10-10 13:43:36 -0700356 intent = new Intent(Intent.ACTION_SEND);
Tomasz Mikolajewski5b127ac2016-10-24 18:24:58 +0900357 DocumentInfo doc = docs.get(0);
Steve McKayd0718952016-10-10 13:43:36 -0700358 intent.setType(doc.mimeType);
359 intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);
360
361 } else if (docs.size() > 1) {
362 intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
Steve McKayd0718952016-10-10 13:43:36 -0700363
364 final ArrayList<String> mimeTypes = new ArrayList<>();
365 final ArrayList<Uri> uris = new ArrayList<>();
366 for (DocumentInfo doc : docs) {
367 mimeTypes.add(doc.mimeType);
368 uris.add(doc.derivedUri);
369 }
370
371 intent.setType(MimeTypes.findCommonMimeType(mimeTypes));
372 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
373
374 } else {
Tomasz Mikolajewski5b127ac2016-10-24 18:24:58 +0900375 // Everything filtered out, nothing to share.
Steve McKayd0718952016-10-10 13:43:36 -0700376 return;
377 }
378
Tomasz Mikolajewski5b127ac2016-10-24 18:24:58 +0900379 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
380 intent.addCategory(Intent.CATEGORY_DEFAULT);
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900381
Steve McKay98f8c5f2017-03-03 13:52:14 -0800382 if (mFeatures.isVirtualFilesSharingEnabled()
383 && mModel.hasDocuments(selection, DocumentFilters.VIRTUAL)) {
Tomasz Mikolajewski5b127ac2016-10-24 18:24:58 +0900384 intent.addCategory(Intent.CATEGORY_TYPED_OPENABLE);
385 }
386
Steve McKayd0718952016-10-10 13:43:36 -0700387 Intent chooserIntent = Intent.createChooser(
388 intent, mActivity.getResources().getText(R.string.share_via));
389
390 mActivity.startActivity(chooserIntent);
391 }
392
393 @Override
Steve McKay988d8a32016-09-27 09:41:17 -0700394 public void initLocation(Intent intent) {
395 assert(intent != null);
396
Garfield Tan40c85052017-02-02 12:44:35 -0800397 // stack is initialized if it's restored from bundle, which means we're restoring a
398 // previously stored state.
399 if (mState.stack.isInitialized()) {
Steve McKay988d8a32016-09-27 09:41:17 -0700400 if (DEBUG) Log.d(TAG, "Stack already resolved for uri: " + intent.getData());
Garfield Tan5f2a9ba2017-05-26 14:35:44 -0700401 restoreRootAndDirectory();
Steve McKay988d8a32016-09-27 09:41:17 -0700402 return;
403 }
404
Garfield Tan40c85052017-02-02 12:44:35 -0800405 if (launchToStackLocation(intent)) {
Steve McKay988d8a32016-09-27 09:41:17 -0700406 if (DEBUG) Log.d(TAG, "Launched to location from stack.");
407 return;
408 }
409
Steve McKay988d8a32016-09-27 09:41:17 -0700410 if (launchToRoot(intent)) {
411 if (DEBUG) Log.d(TAG, "Launched to root for browsing.");
412 return;
413 }
414
Garfield Tanf8969d62017-02-02 16:55:55 -0800415 if (launchToDocument(intent)) {
416 if (DEBUG) Log.d(TAG, "Launched to a document.");
417 return;
418 }
419
Steve McKay988d8a32016-09-27 09:41:17 -0700420 if (DEBUG) Log.d(TAG, "Launching directly into Home directory.");
421 loadHomeDir();
422 }
423
Garfield Tanf8969d62017-02-02 16:55:55 -0800424 @Override
425 protected void launchToDefaultLocation() {
426 loadHomeDir();
427 }
428
Garfield Tan40c85052017-02-02 12:44:35 -0800429 // If EXTRA_STACK is not null in intent, we'll skip other means of loading
430 // or restoring the stack (like URI).
Steve McKay988d8a32016-09-27 09:41:17 -0700431 //
432 // When restoring from a stack, if a URI is present, it should only ever be:
433 // -- a launch URI: Launch URIs support sensible activity management,
434 // but don't specify a real content target)
435 // -- a fake Uri from notifications. These URIs have no authority (TODO: details).
436 //
437 // Any other URI is *sorta* unexpected...except when browsing an archive
438 // in downloads.
Garfield Tan40c85052017-02-02 12:44:35 -0800439 private boolean launchToStackLocation(Intent intent) {
440 DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
Garfield Tan2a837422016-10-19 11:50:45 -0700441 if (stack == null || stack.getRoot() == null) {
Steve McKay988d8a32016-09-27 09:41:17 -0700442 return false;
443 }
444
Garfield Tan40c85052017-02-02 12:44:35 -0800445 mState.stack.reset(stack);
Steve McKay988d8a32016-09-27 09:41:17 -0700446 if (mState.stack.isEmpty()) {
Garfield Tan2a837422016-10-19 11:50:45 -0700447 mActivity.onRootPicked(mState.stack.getRoot());
Steve McKay988d8a32016-09-27 09:41:17 -0700448 } else {
449 mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
450 }
451
452 return true;
453 }
454
Steve McKay988d8a32016-09-27 09:41:17 -0700455 private boolean launchToRoot(Intent intent) {
Garfield Tanc5efea02017-02-22 12:58:29 -0800456 String action = intent.getAction();
Garfield Tan83392022017-05-31 18:05:40 -0700457 if (Intent.ACTION_VIEW.equals(action)) {
Steve McKay988d8a32016-09-27 09:41:17 -0700458 Uri uri = intent.getData();
459 if (DocumentsContract.isRootUri(mActivity, uri)) {
460 if (DEBUG) Log.d(TAG, "Launching with root URI.");
461 // If we've got a specific root to display, restore that root using a dedicated
462 // authority. That way a misbehaving provider won't result in an ANR.
463 loadRoot(uri);
464 return true;
465 }
466 }
467 return false;
468 }
469
Garfield Tanf8969d62017-02-02 16:55:55 -0800470 private boolean launchToDocument(Intent intent) {
Garfield Tanc5efea02017-02-22 12:58:29 -0800471 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Garfield Tanf8969d62017-02-02 16:55:55 -0800472 Uri uri = intent.getData();
473 if (DocumentsContract.isDocumentUri(mActivity, uri)) {
474 return launchToDocument(intent.getData());
475 }
476 }
477
478 return false;
479 }
480
Garfield Tan208945c2016-10-04 14:36:38 -0700481 @Override
Steve McKayeed2f4e2016-10-03 20:30:52 -0700482 public void showChooserForDoc(DocumentInfo doc) {
Tomasz Mikolajewskiac3e63e2017-02-13 10:08:58 +0900483 assert(!doc.isDirectory());
Steve McKayeed2f4e2016-10-03 20:30:52 -0700484
485 if (manageDocument(doc)) {
486 Log.w(TAG, "Open with is not yet supported for managed doc.");
487 return;
488 }
489
490 Intent intent = Intent.createChooser(buildViewIntent(doc), null);
Steve McKay98f8c5f2017-03-03 13:52:14 -0800491 if (Features.OMC_RUNTIME) {
Ben Lindf0fe892016-10-18 17:36:05 -0700492 intent.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
493 }
Steve McKayeed2f4e2016-10-03 20:30:52 -0700494 try {
495 mActivity.startActivity(intent);
496 } catch (ActivityNotFoundException e) {
497 mDialogs.showNoApplicationFound();
498 }
499 }
500
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900501 private void onDocumentPicked(DocumentInfo doc, @ViewType int type, @ViewType int fallback) {
Steve McKayeed2f4e2016-10-03 20:30:52 -0700502 if (doc.isContainer()) {
Garfield Tan63bf8132016-10-11 11:00:49 -0700503 openContainerDocument(doc);
Steve McKayeed2f4e2016-10-03 20:30:52 -0700504 return;
505 }
506
507 if (manageDocument(doc)) {
508 return;
509 }
510
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900511 switch (type) {
512 case VIEW_TYPE_REGULAR:
513 if (viewDocument(doc)) {
514 return;
515 }
516 break;
517
518 case VIEW_TYPE_PREVIEW:
519 if (previewDocument(doc)) {
520 return;
521 }
522 break;
523
524 default:
525 throw new IllegalArgumentException("Illegal view type.");
Steve McKayeed2f4e2016-10-03 20:30:52 -0700526 }
527
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900528 switch (fallback) {
529 case VIEW_TYPE_REGULAR:
530 if (viewDocument(doc)) {
531 return;
532 }
533 break;
534
535 case VIEW_TYPE_PREVIEW:
536 if (previewDocument(doc)) {
537 return;
538 }
539 break;
540
541 case VIEW_TYPE_NONE:
542 break;
543
544 default:
545 throw new IllegalArgumentException("Illegal fallback view type.");
546 }
547
548 // Failed to view including fallback, and it's in an archive.
549 if (type != VIEW_TYPE_NONE && fallback != VIEW_TYPE_NONE && doc.isInArchive()) {
550 mDialogs.showViewInArchivesUnsupported();
551 }
Steve McKayeed2f4e2016-10-03 20:30:52 -0700552 }
553
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900554 private boolean viewDocument(DocumentInfo doc) {
Steve McKayeed2f4e2016-10-03 20:30:52 -0700555 if (doc.isPartial()) {
556 Log.w(TAG, "Can't view partial file.");
557 return false;
558 }
559
Tomasz Mikolajewskie535c572016-11-25 13:58:35 +0900560 if (doc.isInArchive()) {
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900561 Log.w(TAG, "Can't view files in archives.");
Tomasz Mikolajewskie535c572016-11-25 13:58:35 +0900562 return false;
563 }
564
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900565 if (doc.isDirectory()) {
566 Log.w(TAG, "Can't view directories.");
Steve McKayeed2f4e2016-10-03 20:30:52 -0700567 return true;
568 }
569
Steve McKayeed2f4e2016-10-03 20:30:52 -0700570 Intent intent = buildViewIntent(doc);
571 if (DEBUG && intent.getClipData() != null) {
572 Log.d(TAG, "Starting intent w/ clip data: " + intent.getClipData());
573 }
574
575 try {
576 mActivity.startActivity(intent);
577 return true;
578 } catch (ActivityNotFoundException e) {
579 mDialogs.showNoApplicationFound();
580 }
581 return false;
582 }
583
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900584 private boolean previewDocument(DocumentInfo doc) {
Steve McKayeed2f4e2016-10-03 20:30:52 -0700585 if (doc.isPartial()) {
586 Log.w(TAG, "Can't view partial file.");
587 return false;
588 }
589
590 Intent intent = new QuickViewIntentBuilder(
591 mActivity.getPackageManager(),
592 mActivity.getResources(),
593 doc,
Ben Linf23cbf42016-12-22 15:12:13 -0800594 mModel).build();
Steve McKayeed2f4e2016-10-03 20:30:52 -0700595
596 if (intent != null) {
597 // TODO: un-work around issue b/24963914. Should be fixed soon.
598 try {
599 mActivity.startActivity(intent);
600 return true;
601 } catch (SecurityException e) {
602 // Carry on to regular view mode.
603 Log.e(TAG, "Caught security error: " + e.getLocalizedMessage());
604 }
605 }
606
607 return false;
608 }
609
610 private boolean manageDocument(DocumentInfo doc) {
611 if (isManagedDownload(doc)) {
612 // First try managing the document; we expect manager to filter
613 // based on authority, so we don't grant.
614 Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
615 manage.setData(doc.derivedUri);
616 try {
617 mActivity.startActivity(manage);
618 return true;
619 } catch (ActivityNotFoundException ex) {
620 // Fall back to regular handling.
621 }
622 }
623
624 return false;
625 }
626
627 private boolean isManagedDownload(DocumentInfo doc) {
628 // Anything on downloads goes through the back through downloads manager
629 // (that's the MANAGE_DOCUMENT bit).
630 // This is done for two reasons:
631 // 1) The file in question might be a failed/queued or otherwise have some
632 // specialized download handling.
633 // 2) For APKs, the download manager will add on some important security stuff
634 // like origin URL.
635 // 3) For partial files, the download manager will offer to restart/retry downloads.
636
637 // All other files not on downloads, event APKs, would get no benefit from this
638 // treatment, thusly the "isDownloads" check.
639
640 // Launch MANAGE_DOCUMENTS only for the root level files, so it's not called for
641 // files in archives. Also, if the activity is already browsing a ZIP from downloads,
642 // then skip MANAGE_DOCUMENTS.
643 if (Intent.ACTION_VIEW.equals(mActivity.getIntent().getAction())
644 && mState.stack.size() > 1) {
645 // viewing the contents of an archive.
646 return false;
647 }
648
Steve McKayd0718952016-10-10 13:43:36 -0700649 // management is only supported in downloads.
Steve McKayeed2f4e2016-10-03 20:30:52 -0700650 if (mActivity.getCurrentRoot().isDownloads()) {
651 // and only and only on APKs or partial files.
Steve McKayd0718952016-10-10 13:43:36 -0700652 return MimeTypes.isApkType(doc.mimeType)
Steve McKayeed2f4e2016-10-03 20:30:52 -0700653 || doc.isPartial();
654 }
655
656 return false;
657 }
658
659 private Intent buildViewIntent(DocumentInfo doc) {
660 Intent intent = new Intent(Intent.ACTION_VIEW);
661 intent.setDataAndType(doc.derivedUri, doc.mimeType);
662
663 // Downloads has traditionally added the WRITE permission
664 // in the TrampolineActivity. Since this behavior is long
665 // established, we set the same permission for non-managed files
666 // This ensures consistent behavior between the Downloads root
667 // and other roots.
668 int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
669 if (doc.isWriteSupported()) {
670 flags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
671 }
672 intent.setFlags(flags);
673
674 return intent;
675 }
676
Dooper0930d4c2017-06-02 10:32:00 -0700677 @Override
Austin Kolanderf5042d02017-06-08 09:20:30 -0700678 public void showInspector(DocumentInfo doc) {
Steve McKayfbf68582017-06-09 15:28:28 -0700679 Metrics.logUserAction(mActivity, Metrics.USER_ACTION_INSPECTOR);
Steve McKayf433d202017-07-12 18:46:09 -0700680 Intent intent = new Intent(mActivity, InspectorActivity.class);
Steve McKay84440eb2017-06-23 12:35:15 -0700681 intent.putExtra(
682 Shared.EXTRA_SHOW_DEBUG,
683 mFeatures.isDebugSupportEnabled()
Steve McKayf433d202017-07-12 18:46:09 -0700684 || DebugFlags.getDocumentDetailsEnabled());
Steve McKayfbf68582017-06-09 15:28:28 -0700685 intent.setData(doc.derivedUri);
Dooper0930d4c2017-06-02 10:32:00 -0700686 mActivity.startActivity(intent);
687 }
688
Steve McKayc8889af2016-09-23 11:22:41 -0700689 public interface Addons extends CommonAddons {
Steve McKayc8889af2016-09-23 11:22:41 -0700690 }
Garfield Tan7d75f7b2016-09-20 16:33:24 -0700691}