blob: 12a41862e436380cdcd1b0378671c0c87cca9e23 [file] [log] [blame]
Jeff Sharkeye22d02e2013-04-26 16:54:55 -07001/*
2 * Copyright (C) 2013 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 McKayc7dc0cf2016-02-04 12:15:22 -080019import static com.android.documentsui.Shared.DEBUG;
Steve McKayf8a5e082015-09-23 17:21:40 -070020import static com.android.documentsui.State.ACTION_CREATE;
21import static com.android.documentsui.State.ACTION_GET_CONTENT;
22import static com.android.documentsui.State.ACTION_OPEN;
Steve McKayf8a5e082015-09-23 17:21:40 -070023import static com.android.documentsui.State.ACTION_OPEN_TREE;
Ben Kwab8a5e082015-12-07 13:25:27 -080024import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
Steve McKayf68210e2015-11-03 15:23:16 -080025import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
Jeff Sharkey311a7d82015-04-11 21:27:21 -070026
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070027import android.app.Activity;
Steve McKayb68dd222015-04-20 17:18:15 -070028import android.app.Fragment;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070029import android.app.FragmentManager;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070030import android.content.ClipData;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070031import android.content.ComponentName;
Jeff Sharkey3fd11772013-09-30 14:26:27 -070032import android.content.ContentProviderClient;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070033import android.content.ContentResolver;
Jeff Sharkeyf7adc932013-07-30 17:08:39 -070034import android.content.ContentValues;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070035import android.content.Intent;
Jeff Sharkey1d890e02013-08-15 11:24:03 -070036import android.content.pm.ResolveInfo;
Steve McKayc7dc0cf2016-02-04 12:15:22 -080037import android.database.Cursor;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070038import android.net.Uri;
39import android.os.Bundle;
Tomasz Mikolajewski2023fcf2015-04-10 10:30:33 +090040import android.os.Parcelable;
Jeff Sharkeyf339f252013-08-15 16:17:41 -070041import android.provider.DocumentsContract;
Ben Kwac4693342015-09-30 10:00:10 -070042import android.support.design.widget.Snackbar;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070043import android.util.Log;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070044import android.view.Menu;
45import android.view.MenuItem;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070046
Jeff Sharkeya4d1f222013-09-07 14:45:03 -070047import com.android.documentsui.RecentsProvider.RecentColumns;
48import com.android.documentsui.RecentsProvider.ResumeColumns;
Steve McKayf68210e2015-11-03 15:23:16 -080049import com.android.documentsui.dirlist.DirectoryFragment;
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090050import com.android.documentsui.dirlist.Model;
Jeff Sharkey724deeb2013-08-31 15:02:20 -070051import com.android.documentsui.model.DocumentInfo;
Jeff Sharkeyb3620442013-09-01 18:41:04 -070052import com.android.documentsui.model.DurableUtils;
Jeff Sharkey724deeb2013-08-31 15:02:20 -070053import com.android.documentsui.model.RootInfo;
Steve McKayc83baa02016-01-06 18:32:13 -080054import com.android.documentsui.services.FileOperationService;
Jeff Sharkeyf7adc932013-07-30 17:08:39 -070055
Steve McKayc7dc0cf2016-02-04 12:15:22 -080056import libcore.io.IoUtils;
57
58import java.io.FileNotFoundException;
59import java.io.IOException;
Ben Kwa0e66b9b2015-05-18 13:18:05 -070060import java.util.Arrays;
Steve McKayc7dc0cf2016-02-04 12:15:22 -080061import java.util.Collection;
Ben Kwa0e66b9b2015-05-18 13:18:05 -070062import java.util.List;
63
Steve McKayd0a2a2c2015-03-25 14:35:33 -070064public class DocumentsActivity extends BaseActivity {
Steve McKayb68dd222015-04-20 17:18:15 -070065 private static final int CODE_FORWARD = 42;
Ben Kwa0574b182015-09-08 07:31:19 -070066 private static final String TAG = "DocumentsActivity";
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070067
Steve McKayb68dd222015-04-20 17:18:15 -070068 public DocumentsActivity() {
Steve McKay18d01e82016-02-03 11:15:57 -080069 super(R.layout.documents_activity, TAG);
Steve McKayb68dd222015-04-20 17:18:15 -070070 }
71
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070072 @Override
73 public void onCreate(Bundle icicle) {
Steve McKay0fbfc652015-08-20 16:48:49 -070074 super.onCreate(icicle);
Oren Blasbergb243af72015-06-25 19:00:43 -070075
Jeff Sharkeyb3620442013-09-01 18:41:04 -070076 if (mState.action == ACTION_CREATE) {
77 final String mimeType = getIntent().getType();
78 final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
79 SaveFragment.show(getFragmentManager(), mimeType, title);
Daichi Hironocaadd412015-04-10 15:50:38 +090080 } else if (mState.action == ACTION_OPEN_TREE ||
Ben Kwaae967802015-09-25 14:48:29 -070081 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkey6e565ff2014-04-05 19:05:24 -070082 PickFragment.show(getFragmentManager());
Jeff Sharkeyb3620442013-09-01 18:41:04 -070083 }
84
85 if (mState.action == ACTION_GET_CONTENT) {
86 final Intent moreApps = new Intent(getIntent());
87 moreApps.setComponent(null);
88 moreApps.setPackage(null);
89 RootsFragment.show(getFragmentManager(), moreApps);
Daichi Hironocaadd412015-04-10 15:50:38 +090090 } else if (mState.action == ACTION_OPEN ||
91 mState.action == ACTION_CREATE ||
92 mState.action == ACTION_OPEN_TREE ||
Ben Kwaae967802015-09-25 14:48:29 -070093 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkeyb3620442013-09-01 18:41:04 -070094 RootsFragment.show(getFragmentManager(), null);
95 }
96
Steve McKayfd8425a2016-02-23 14:34:50 -080097 if (mState.restored) {
Aga Wronska8788dad2016-01-15 17:30:15 -080098 refreshCurrentRootAndDirectory(ANIM_NONE);
Steve McKayfd8425a2016-02-23 14:34:50 -080099 } else {
100 // We set the activity title in AsyncTask.onPostExecute().
101 // To prevent talkback from reading aloud the default title, we clear it here.
102 setTitle("");
103
104 // As a matter of policy we don't load the last used stack for the copy
105 // destination picker (user is already in Files app).
106 // Concensus was that the experice was too confusing.
107 // In all other cases, where the user is visiting us from another app
108 // we restore the stack as last used from that app.
109 if (mState.action == ACTION_PICK_COPY_DESTINATION) {
110 if (DEBUG) Log.d(TAG, "Launching directly into Home directory.");
111 Uri homeUri = DocumentsContract.buildHomeUri();
112 new LoadRootTask(this, homeUri).executeOnExecutor(
113 ProviderExecutor.forAuthority(homeUri.getAuthority()));
114 } else {
115 if (DEBUG) Log.d(TAG, "Attempting to load last used stack for calling package.");
116 new LoadLastUsedStackTask(this).execute();
117 }
Jeff Sharkey8b997042013-09-19 15:25:56 -0700118 }
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700119 }
120
Steve McKay0fbfc652015-08-20 16:48:49 -0700121 @Override
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800122 void includeState(State state) {
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700123 final Intent intent = getIntent();
124 final String action = intent.getAction();
125 if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
Steve McKayb68dd222015-04-20 17:18:15 -0700126 state.action = ACTION_OPEN;
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700127 } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
Steve McKayb68dd222015-04-20 17:18:15 -0700128 state.action = ACTION_CREATE;
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700129 } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
Steve McKayb68dd222015-04-20 17:18:15 -0700130 state.action = ACTION_GET_CONTENT;
Jeff Sharkeyf4943e12014-06-04 16:42:47 -0700131 } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
Steve McKayb68dd222015-04-20 17:18:15 -0700132 state.action = ACTION_OPEN_TREE;
Ben Kwaae967802015-09-25 14:48:29 -0700133 } else if (Shared.ACTION_PICK_COPY_DESTINATION.equals(action)) {
134 state.action = ACTION_PICK_COPY_DESTINATION;
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700135 }
136
Steve McKayb68dd222015-04-20 17:18:15 -0700137 if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) {
138 state.allowMultiple = intent.getBooleanExtra(
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700139 Intent.EXTRA_ALLOW_MULTIPLE, false);
140 }
141
Tomasz Mikolajewski95d9c252015-11-16 11:41:28 +0900142 if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT
143 || state.action == ACTION_CREATE) {
144 state.openableOnly = intent.hasCategory(Intent.CATEGORY_OPENABLE);
145 }
146
Ben Kwaae967802015-09-25 14:48:29 -0700147 if (state.action == ACTION_PICK_COPY_DESTINATION) {
Steve McKayf1719342016-02-17 15:02:01 -0800148 // Indicates that a copy operation (or move) includes a directory.
149 // Why? Directory creation isn't supported by some roots (like Downloads).
150 // This allows us to restrict available roots to just those with support.
Steve McKayb68dd222015-04-20 17:18:15 -0700151 state.directoryCopy = intent.getBooleanExtra(
Ben Kwaae967802015-09-25 14:48:29 -0700152 Shared.EXTRA_DIRECTORY_COPY, false);
Steve McKayf1719342016-02-17 15:02:01 -0800153 state.copyOperationSubType = intent.getIntExtra(
154 FileOperationService.EXTRA_OPERATION,
Steve McKayc83baa02016-01-06 18:32:13 -0800155 FileOperationService.OPERATION_COPY);
Daichi Hironof2a822d2015-04-14 17:12:54 +0900156 }
Jeff Sharkey8b997042013-09-19 15:25:56 -0700157 }
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700158
Steve McKaycb9eb422016-02-09 16:17:24 -0800159 private void onStackRestored(boolean restored, boolean external) {
Steve McKayb68dd222015-04-20 17:18:15 -0700160 // Show drawer when no stack restored, but only when requesting
161 // non-visual content. However, if we last used an external app,
162 // drawer is always shown.
Jeff Sharkey8b997042013-09-19 15:25:56 -0700163
Steve McKayb68dd222015-04-20 17:18:15 -0700164 boolean showDrawer = false;
165 if (!restored) {
166 showDrawer = true;
167 }
168 if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
169 showDrawer = false;
170 }
171 if (external && mState.action == ACTION_GET_CONTENT) {
172 showDrawer = true;
Jeff Sharkey8b997042013-09-19 15:25:56 -0700173 }
Steve McKayf1719342016-02-17 15:02:01 -0800174 if (mState.action == ACTION_PICK_COPY_DESTINATION) {
175 showDrawer = true;
176 }
Jeff Sharkey8b997042013-09-19 15:25:56 -0700177
Steve McKayb68dd222015-04-20 17:18:15 -0700178 if (showDrawer) {
Steve McKay18d01e82016-02-03 11:15:57 -0800179 mNavigator.revealRootsDrawer(true);
Steve McKayb68dd222015-04-20 17:18:15 -0700180 }
181 }
Jeff Sharkey8b997042013-09-19 15:25:56 -0700182
Steve McKayb68dd222015-04-20 17:18:15 -0700183 public void onAppPicked(ResolveInfo info) {
184 final Intent intent = new Intent(getIntent());
185 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
186 intent.setComponent(new ComponentName(
187 info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
188 startActivityForResult(intent, CODE_FORWARD);
189 }
Jeff Sharkey38ec2522013-09-24 12:07:12 -0700190
Steve McKayb68dd222015-04-20 17:18:15 -0700191 @Override
192 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
193 Log.d(TAG, "onActivityResult() code=" + resultCode);
Jeff Sharkey38ec2522013-09-24 12:07:12 -0700194
Steve McKayb68dd222015-04-20 17:18:15 -0700195 // Only relay back results when not canceled; otherwise stick around to
196 // let the user pick another app/backend.
197 if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
Jeff Sharkey8b997042013-09-19 15:25:56 -0700198
Steve McKayb68dd222015-04-20 17:18:15 -0700199 // Remember that we last picked via external app
200 final String packageName = getCallingPackageMaybeExtra();
201 final ContentValues values = new ContentValues();
202 values.put(ResumeColumns.EXTERNAL, 1);
203 getContentResolver().insert(RecentsProvider.buildResume(packageName), values);
204
205 // Pass back result to original caller
206 setResult(resultCode, data);
207 finish();
208 } else {
209 super.onActivityResult(requestCode, resultCode, data);
Jeff Sharkeyf339f252013-08-15 16:17:41 -0700210 }
211 }
212
Jeff Sharkey46165b52013-07-31 20:53:22 -0700213 @Override
214 protected void onPostCreate(Bundle savedInstanceState) {
215 super.onPostCreate(savedInstanceState);
Steve McKay18d01e82016-02-03 11:15:57 -0800216 mDrawer.update();
217 mNavigator.update();
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700218 }
219
Steve McKayb68dd222015-04-20 17:18:15 -0700220 @Override
Steve McKay18d01e82016-02-03 11:15:57 -0800221 public String getDrawerTitle() {
222 String title = getIntent().getStringExtra(DocumentsContract.EXTRA_PROMPT);
223 if (title == null) {
224 if (mState.action == ACTION_OPEN ||
225 mState.action == ACTION_GET_CONTENT ||
226 mState.action == ACTION_OPEN_TREE) {
227 title = getResources().getString(R.string.title_open);
228 } else if (mState.action == ACTION_CREATE ||
229 mState.action == ACTION_PICK_COPY_DESTINATION) {
230 title = getResources().getString(R.string.title_save);
Tomasz Mikolajewski77fd4022015-06-12 16:22:17 -0700231 } else {
Steve McKay18d01e82016-02-03 11:15:57 -0800232 // If all else fails, just call it "Files".
233 title = getResources().getString(R.string.files_label);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700234 }
Jeff Sharkeye6fcceb2014-07-28 16:38:52 -0700235 }
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700236
Steve McKay18d01e82016-02-03 11:15:57 -0800237 return title;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700238 }
239
240 @Override
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700241 public boolean onPrepareOptionsMenu(Menu menu) {
242 super.onPrepareOptionsMenu(menu);
243
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700244 final DocumentInfo cwd = getCurrentDirectory();
Jeff Sharkey5b535922013-08-02 15:55:26 -0700245
Steve McKayd4800812016-02-02 11:41:03 -0800246 boolean picking = mState.action == ACTION_CREATE
247 || mState.action == ACTION_OPEN_TREE
248 || mState.action == ACTION_PICK_COPY_DESTINATION;
249
250 if (picking) {
251 // May already be hidden because the root
252 // doesn't support search.
253 mSearchManager.showMenu(false);
254 }
255
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700256 final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
Jeff Sharkey4c5fe522013-09-03 14:17:06 -0700257 final MenuItem grid = menu.findItem(R.id.menu_grid);
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700258 final MenuItem list = menu.findItem(R.id.menu_list);
Jeff Sharkeye8d13ea2014-08-08 15:10:03 -0700259 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
Jeff Sharkey5b535922013-08-02 15:55:26 -0700260
Steve McKaye9809272015-10-01 11:39:24 -0700261 boolean recents = cwd == null;
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700262
Steve McKaye9809272015-10-01 11:39:24 -0700263 createDir.setVisible(picking && !recents && cwd.isCreateSupported());
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700264
Steve McKaye9809272015-10-01 11:39:24 -0700265 // No display options in recent directories
Steve McKay8446b292016-01-08 14:58:33 -0800266 if (picking && recents) {
267 grid.setVisible(false);
268 list.setVisible(false);
269 }
Steve McKaye9809272015-10-01 11:39:24 -0700270
271 fileSize.setVisible(fileSize.isVisible() && !picking);
Steve McKaye9809272015-10-01 11:39:24 -0700272
273 if (mState.action == ACTION_CREATE) {
274 final FragmentManager fm = getFragmentManager();
Steve McKay0fe95042015-10-14 17:41:13 -0700275 SaveFragment.get(fm).prepareForDirectory(cwd);
Jeff Sharkey5b535922013-08-02 15:55:26 -0700276 }
Jeff Sharkey46165b52013-07-31 20:53:22 -0700277
Steve McKaye9809272015-10-01 11:39:24 -0700278 Menus.disableHiddenItems(menu);
Jeff Sharkey938528d2015-04-12 21:52:24 -0700279
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700280 return true;
281 }
282
283 @Override
Aga Wronska8788dad2016-01-15 17:30:15 -0800284 void refreshDirectory(int anim) {
Jeff Sharkey5b535922013-08-02 15:55:26 -0700285 final FragmentManager fm = getFragmentManager();
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700286 final RootInfo root = getCurrentRoot();
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700287 final DocumentInfo cwd = getCurrentDirectory();
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700288
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700289 if (cwd == null) {
290 // No directory means recents
Daichi Hironocaadd412015-04-10 15:50:38 +0900291 if (mState.action == ACTION_CREATE ||
292 mState.action == ACTION_OPEN_TREE ||
Ben Kwaae967802015-09-25 14:48:29 -0700293 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700294 RecentsCreateFragment.show(fm);
295 } else {
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700296 DirectoryFragment.showRecentsOpen(fm, anim);
Jeff Sharkey7d58fc62013-09-12 16:25:02 -0700297
Steve McKay7776aa52016-01-25 19:00:22 -0800298 // In recents we pick layout mode based on the mimetype,
299 // picking GRID for visual types. We intentionally don't
300 // consult a user's saved preferences here since they are
301 // set per root (not per root and per mimetype).
302 boolean visualMimes = MimePredicate.mimeMatches(
Jeff Sharkey38ec2522013-09-24 12:07:12 -0700303 MimePredicate.VISUAL_MIMES, mState.acceptMimes);
Steve McKay7776aa52016-01-25 19:00:22 -0800304 mState.derivedMode = visualMimes ? State.MODE_GRID : State.MODE_LIST;
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700305 }
306 } else {
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700307 // Normal boring directory
Aga Wronska8788dad2016-01-15 17:30:15 -0800308 DirectoryFragment.showDirectory(fm, root, cwd, anim);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700309 }
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700310
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700311 // Forget any replacement target
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700312 if (mState.action == ACTION_CREATE) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700313 final SaveFragment save = SaveFragment.get(fm);
314 if (save != null) {
315 save.setReplaceTarget(null);
316 }
317 }
318
Daichi Hironocaadd412015-04-10 15:50:38 +0900319 if (mState.action == ACTION_OPEN_TREE ||
Ben Kwaae967802015-09-25 14:48:29 -0700320 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkey6e565ff2014-04-05 19:05:24 -0700321 final PickFragment pick = PickFragment.get(fm);
322 if (pick != null) {
Steve McKayf1719342016-02-17 15:02:01 -0800323 pick.setPickTarget(mState.action, mState.copyOperationSubType, cwd);
Jeff Sharkey6e565ff2014-04-05 19:05:24 -0700324 }
325 }
Steve McKayb68dd222015-04-20 17:18:15 -0700326 }
Jeff Sharkey6e565ff2014-04-05 19:05:24 -0700327
Steve McKayb68dd222015-04-20 17:18:15 -0700328 void onSaveRequested(DocumentInfo replaceTarget) {
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800329 new ExistingFinishTask(this, replaceTarget.derivedUri)
330 .executeOnExecutor(getExecutorForCurrentDirectory());
Steve McKayb68dd222015-04-20 17:18:15 -0700331 }
Jeff Sharkey42d26792013-09-06 13:22:09 -0700332
Steve McKay5e1acc92016-02-19 12:57:05 -0800333 @Override
334 void onDirectoryCreated(DocumentInfo doc) {
Steve McKay0af8afd2016-02-25 13:34:03 -0800335 assert(doc.isDirectory());
Steve McKay5e1acc92016-02-19 12:57:05 -0800336 openContainerDocument(doc);
337 }
338
Steve McKayb68dd222015-04-20 17:18:15 -0700339 void onSaveRequested(String mimeType, String displayName) {
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800340 new CreateFinishTask(this, mimeType, displayName)
341 .executeOnExecutor(getExecutorForCurrentDirectory());
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700342 }
343
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700344 @Override
Steve McKayb68dd222015-04-20 17:18:15 -0700345 void onRootPicked(RootInfo root) {
346 super.onRootPicked(root);
Steve McKay18d01e82016-02-03 11:15:57 -0800347 mNavigator.revealRootsDrawer(false);
348 }
349
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700350 @Override
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900351 public void onDocumentPicked(DocumentInfo doc, Model model) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700352 final FragmentManager fm = getFragmentManager();
Tomasz Mikolajewski734936a2015-11-25 13:01:18 +0900353 if (doc.isContainer()) {
354 openContainerDocument(doc);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700355 } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700356 // Explicit file picked, return
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800357 new ExistingFinishTask(this, doc.derivedUri)
358 .executeOnExecutor(getExecutorForCurrentDirectory());
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700359 } else if (mState.action == ACTION_CREATE) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700360 // Replace selected file
361 SaveFragment.get(fm).setReplaceTarget(doc);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700362 }
363 }
364
Steve McKayd0a2a2c2015-03-25 14:35:33 -0700365 @Override
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700366 public void onDocumentsPicked(List<DocumentInfo> docs) {
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700367 if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700368 final int size = docs.size();
369 final Uri[] uris = new Uri[size];
370 for (int i = 0; i < size; i++) {
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700371 uris[i] = docs.get(i).derivedUri;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700372 }
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800373 new ExistingFinishTask(this, uris)
374 .executeOnExecutor(getExecutorForCurrentDirectory());
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700375 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700376 }
377
Jeff Sharkey6e565ff2014-04-05 19:05:24 -0700378 public void onPickRequested(DocumentInfo pickTarget) {
Daichi Hironocaadd412015-04-10 15:50:38 +0900379 Uri result;
380 if (mState.action == ACTION_OPEN_TREE) {
381 result = DocumentsContract.buildTreeDocumentUri(
382 pickTarget.authority, pickTarget.documentId);
Ben Kwaae967802015-09-25 14:48:29 -0700383 } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
Daichi Hironocaadd412015-04-10 15:50:38 +0900384 result = pickTarget.derivedUri;
385 } else {
386 // Should not be reached.
387 throw new IllegalStateException("Invalid mState.action.");
388 }
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800389 new PickFinishTask(this, result).executeOnExecutor(getExecutorForCurrentDirectory());
Jeff Sharkey6e565ff2014-04-05 19:05:24 -0700390 }
391
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800392 void writeStackToRecentsBlocking() {
Jeff Sharkey5b535922013-08-02 15:55:26 -0700393 final ContentResolver resolver = getContentResolver();
394 final ContentValues values = new ContentValues();
395
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700396 final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
Daichi Hironocaadd412015-04-10 15:50:38 +0900397 if (mState.action == ACTION_CREATE ||
398 mState.action == ACTION_OPEN_TREE ||
Ben Kwaae967802015-09-25 14:48:29 -0700399 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkey5b535922013-08-02 15:55:26 -0700400 // Remember stack for last create
401 values.clear();
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700402 values.put(RecentColumns.KEY, mState.stack.buildKey());
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700403 values.put(RecentColumns.STACK, rawStack);
404 resolver.insert(RecentsProvider.buildRecent(), values);
Jeff Sharkey5b535922013-08-02 15:55:26 -0700405 }
406
407 // Remember location for next app launch
Jeff Sharkey0b744d82013-10-09 13:52:17 -0700408 final String packageName = getCallingPackageMaybeExtra();
Jeff Sharkey5b535922013-08-02 15:55:26 -0700409 values.clear();
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700410 values.put(ResumeColumns.STACK, rawStack);
Jeff Sharkey38ec2522013-09-24 12:07:12 -0700411 values.put(ResumeColumns.EXTERNAL, 0);
Jeff Sharkey5b535922013-08-02 15:55:26 -0700412 resolver.insert(RecentsProvider.buildResume(packageName), values);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700413 }
414
Steve McKayb68dd222015-04-20 17:18:15 -0700415 @Override
416 void onTaskFinished(Uri... uris) {
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700417 Log.d(TAG, "onFinished() " + Arrays.toString(uris));
Jeff Sharkey5b535922013-08-02 15:55:26 -0700418
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700419 final Intent intent = new Intent();
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700420 if (uris.length == 1) {
421 intent.setData(uris[0]);
422 } else if (uris.length > 1) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700423 final ClipData clipData = new ClipData(
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700424 null, mState.acceptMimes, new ClipData.Item(uris[0]));
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700425 for (int i = 1; i < uris.length; i++) {
426 clipData.addItem(new ClipData.Item(uris[i]));
427 }
428 intent.setClipData(clipData);
429 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700430
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700431 if (mState.action == ACTION_GET_CONTENT) {
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700432 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Jeff Sharkey6c3164d2015-07-15 09:13:14 -0700433 } else if (mState.action == ACTION_OPEN_TREE) {
Jeff Sharkey6e565ff2014-04-05 19:05:24 -0700434 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
435 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
436 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
437 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
Ben Kwaae967802015-09-25 14:48:29 -0700438 } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkey6c3164d2015-07-15 09:13:14 -0700439 // Picking a copy destination is only used internally by us, so we
440 // don't need to extend permissions to the caller.
Steve McKay4d0255f2015-09-25 16:02:56 -0700441 intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
Steve McKayf1719342016-02-17 15:02:01 -0800442 intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.copyOperationSubType);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700443 } else {
444 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
445 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
Jeff Sharkey13d369f2013-09-20 14:30:59 -0700446 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700447 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700448
449 setResult(Activity.RESULT_OK, intent);
450 finish();
451 }
452
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800453
Steve McKayb68dd222015-04-20 17:18:15 -0700454 public static DocumentsActivity get(Fragment fragment) {
455 return (DocumentsActivity) fragment.getActivity();
456 }
457
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800458 /**
Steve McKayfd8425a2016-02-23 14:34:50 -0800459 * Loads the last used path (stack) from Recents (history).
460 * The path selected is based on the calling package name. So the last
461 * path for an app like Gmail can be different than the last path
462 * for an app like DropBox.
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800463 */
Steve McKayfd8425a2016-02-23 14:34:50 -0800464 private static final class LoadLastUsedStackTask
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800465 extends PairedTask<DocumentsActivity, Void, Void> {
466
467 private volatile boolean mRestoredStack;
468 private volatile boolean mExternal;
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800469 private State mState;
470
Steve McKayfd8425a2016-02-23 14:34:50 -0800471 public LoadLastUsedStackTask(DocumentsActivity activity) {
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800472 super(activity);
473 mState = activity.mState;
474 }
475
476 @Override
477 protected Void run(Void... params) {
478 if (DEBUG && !mState.stack.isEmpty()) {
479 Log.w(TAG, "Overwriting existing stack.");
480 }
Ben Kwa22e00002016-02-05 06:44:15 -0800481 RootsCache roots = DocumentsApplication.getRootsCache(mOwner);
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800482
483 String packageName = mOwner.getCallingPackageMaybeExtra();
484 Uri resumeUri = RecentsProvider.buildResume(packageName);
485 Cursor cursor = mOwner.getContentResolver().query(resumeUri, null, null, null, null);
486 try {
487 if (cursor.moveToFirst()) {
488 mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
489 final byte[] rawStack = cursor.getBlob(
490 cursor.getColumnIndex(ResumeColumns.STACK));
491 DurableUtils.readFromArray(rawStack, mState.stack);
492 mRestoredStack = true;
493 }
494 } catch (IOException e) {
495 Log.w(TAG, "Failed to resume: " + e);
496 } finally {
497 IoUtils.closeQuietly(cursor);
498 }
499
500 if (mRestoredStack) {
501 // Update the restored stack to ensure we have freshest data
502 final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
503 try {
504 mState.stack.updateRoot(matchingRoots);
505 mState.stack.updateDocuments(mOwner.getContentResolver());
506 } catch (FileNotFoundException e) {
507 Log.w(TAG, "Failed to restore stack for package: " + packageName
508 + " because of error: "+ e);
509 mState.stack.reset();
510 mRestoredStack = false;
511 }
512 }
513
514 return null;
515 }
516
517 @Override
518 protected void finish(Void result) {
519 mState.restored = true;
520 mOwner.refreshCurrentRootAndDirectory(ANIM_NONE);
521 mOwner.onStackRestored(mRestoredStack, mExternal);
522 }
523 }
524
525 private static final class PickFinishTask extends PairedTask<DocumentsActivity, Void, Void> {
Steve McKayb68dd222015-04-20 17:18:15 -0700526 private final Uri mUri;
527
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800528 public PickFinishTask(DocumentsActivity activity, Uri uri) {
529 super(activity);
Steve McKayb68dd222015-04-20 17:18:15 -0700530 mUri = uri;
531 }
532
533 @Override
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800534 protected Void run(Void... params) {
535 mOwner.writeStackToRecentsBlocking();
Steve McKayb68dd222015-04-20 17:18:15 -0700536 return null;
537 }
538
539 @Override
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800540 protected void finish(Void result) {
541 mOwner.onTaskFinished(mUri);
Steve McKayb68dd222015-04-20 17:18:15 -0700542 }
543 }
544
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800545 private static final class ExistingFinishTask extends PairedTask<DocumentsActivity, Void, Void> {
Steve McKayb68dd222015-04-20 17:18:15 -0700546 private final Uri[] mUris;
547
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800548 public ExistingFinishTask(DocumentsActivity activity, Uri... uris) {
549 super(activity);
Steve McKayb68dd222015-04-20 17:18:15 -0700550 mUris = uris;
551 }
552
553 @Override
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800554 protected Void run(Void... params) {
555 mOwner.writeStackToRecentsBlocking();
Steve McKayb68dd222015-04-20 17:18:15 -0700556 return null;
557 }
558
559 @Override
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800560 protected void finish(Void result) {
561 mOwner.onTaskFinished(mUris);
Steve McKayb68dd222015-04-20 17:18:15 -0700562 }
563 }
564
565 /**
566 * Task that creates a new document in the background.
567 */
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800568 private static final class CreateFinishTask extends PairedTask<DocumentsActivity, Void, Uri> {
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700569 private final String mMimeType;
570 private final String mDisplayName;
571
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800572 public CreateFinishTask(DocumentsActivity activity, String mimeType, String displayName) {
573 super(activity);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700574 mMimeType = mimeType;
575 mDisplayName = displayName;
576 }
577
578 @Override
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800579 protected void prepare() {
580 mOwner.setPending(true);
Jeff Sharkey4be51f12013-10-23 15:46:38 -0700581 }
582
583 @Override
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800584 protected Uri run(Void... params) {
585 final ContentResolver resolver = mOwner.getContentResolver();
586 final DocumentInfo cwd = mOwner.getCurrentDirectory();
Jeff Sharkey3fd11772013-09-30 14:26:27 -0700587
588 ContentProviderClient client = null;
589 Uri childUri = null;
590 try {
591 client = DocumentsApplication.acquireUnstableProviderOrThrow(
592 resolver, cwd.derivedUri.getAuthority());
593 childUri = DocumentsContract.createDocument(
594 client, cwd.derivedUri, mMimeType, mDisplayName);
595 } catch (Exception e) {
596 Log.w(TAG, "Failed to create document", e);
597 } finally {
598 ContentProviderClient.releaseQuietly(client);
599 }
600
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700601 if (childUri != null) {
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800602 mOwner.writeStackToRecentsBlocking();
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700603 }
Jeff Sharkey3fd11772013-09-30 14:26:27 -0700604
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700605 return childUri;
606 }
607
608 @Override
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800609 protected void finish(Uri result) {
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700610 if (result != null) {
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800611 mOwner.onTaskFinished(result);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700612 } else {
Steve McKaye9809272015-10-01 11:39:24 -0700613 Snackbars.makeSnackbar(
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800614 mOwner, R.string.save_error, Snackbar.LENGTH_SHORT).show();
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700615 }
Jeff Sharkey4be51f12013-10-23 15:46:38 -0700616
Steve McKayc7dc0cf2016-02-04 12:15:22 -0800617 mOwner.setPending(false);
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700618 }
619 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700620}