blob: ed531a8e1be3b002f41113d337e121c890a32107 [file] [log] [blame]
Jeff Sharkey9e0036e2013-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 McKay95cd85a2016-02-04 12:15:22 -080019import static com.android.documentsui.Shared.DEBUG;
Steve McKay7a3b88c2015-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 McKay7a3b88c2015-09-23 17:21:40 -070023import static com.android.documentsui.State.ACTION_OPEN_TREE;
Ben Kwa862b96412015-12-07 13:25:27 -080024import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
Steve McKayf8621552015-11-03 15:23:16 -080025import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
Steve McKayb9a20d12016-02-19 12:57:05 -080026import static com.android.internal.util.Preconditions.checkArgument;
Jeff Sharkey59d577a2015-04-11 21:27:21 -070027
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070028import android.app.Activity;
Steve McKayef3e2cf2015-04-20 17:18:15 -070029import android.app.Fragment;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070030import android.app.FragmentManager;
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070031import android.content.ClipData;
Jeff Sharkey54ca29a2013-08-15 11:24:03 -070032import android.content.ComponentName;
Jeff Sharkey7aa76012013-09-30 14:26:27 -070033import android.content.ContentProviderClient;
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070034import android.content.ContentResolver;
Jeff Sharkey20d96d82013-07-30 17:08:39 -070035import android.content.ContentValues;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070036import android.content.Intent;
Jeff Sharkey54ca29a2013-08-15 11:24:03 -070037import android.content.pm.ResolveInfo;
Steve McKay95cd85a2016-02-04 12:15:22 -080038import android.database.Cursor;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070039import android.net.Uri;
40import android.os.Bundle;
Tomasz Mikolajewskie1a03f82015-04-10 10:30:33 +090041import android.os.Parcelable;
Jeff Sharkeya5599ef2013-08-15 16:17:41 -070042import android.provider.DocumentsContract;
Ben Kwa94b486d2015-09-30 10:00:10 -070043import android.support.design.widget.Snackbar;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070044import android.util.Log;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070045import android.view.Menu;
46import android.view.MenuItem;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070047
Jeff Sharkeyd182bb62013-09-07 14:45:03 -070048import com.android.documentsui.RecentsProvider.RecentColumns;
49import com.android.documentsui.RecentsProvider.ResumeColumns;
Steve McKayf8621552015-11-03 15:23:16 -080050import com.android.documentsui.dirlist.DirectoryFragment;
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090051import com.android.documentsui.dirlist.Model;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070052import com.android.documentsui.model.DocumentInfo;
Jeff Sharkeyb5133112013-09-01 18:41:04 -070053import com.android.documentsui.model.DurableUtils;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070054import com.android.documentsui.model.RootInfo;
Steve McKay14e827a2016-01-06 18:32:13 -080055import com.android.documentsui.services.FileOperationService;
Jeff Sharkey20d96d82013-07-30 17:08:39 -070056
Steve McKay95cd85a2016-02-04 12:15:22 -080057import libcore.io.IoUtils;
58
59import java.io.FileNotFoundException;
60import java.io.IOException;
Ben Kwacc345212015-05-18 13:18:05 -070061import java.util.Arrays;
Steve McKay95cd85a2016-02-04 12:15:22 -080062import java.util.Collection;
Ben Kwacc345212015-05-18 13:18:05 -070063import java.util.List;
64
Steve McKaye934ce62015-03-25 14:35:33 -070065public class DocumentsActivity extends BaseActivity {
Steve McKayef3e2cf2015-04-20 17:18:15 -070066 private static final int CODE_FORWARD = 42;
Ben Kwa0f7078f02015-09-08 07:31:19 -070067 private static final String TAG = "DocumentsActivity";
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070068
Steve McKayef3e2cf2015-04-20 17:18:15 -070069 public DocumentsActivity() {
Steve McKay1f264a82016-02-03 11:15:57 -080070 super(R.layout.documents_activity, TAG);
Steve McKayef3e2cf2015-04-20 17:18:15 -070071 }
72
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070073 @Override
74 public void onCreate(Bundle icicle) {
Steve McKay12055472015-08-20 16:48:49 -070075 super.onCreate(icicle);
Oren Blasberg9e9c2462015-06-25 19:00:43 -070076
Jeff Sharkeyb5133112013-09-01 18:41:04 -070077 if (mState.action == ACTION_CREATE) {
78 final String mimeType = getIntent().getType();
79 final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
80 SaveFragment.show(getFragmentManager(), mimeType, title);
Daichi Hironobbe22922015-04-10 15:50:38 +090081 } else if (mState.action == ACTION_OPEN_TREE ||
Ben Kwa84cebbe2015-09-25 14:48:29 -070082 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -070083 PickFragment.show(getFragmentManager());
Jeff Sharkeyb5133112013-09-01 18:41:04 -070084 }
85
86 if (mState.action == ACTION_GET_CONTENT) {
87 final Intent moreApps = new Intent(getIntent());
88 moreApps.setComponent(null);
89 moreApps.setPackage(null);
90 RootsFragment.show(getFragmentManager(), moreApps);
Daichi Hironobbe22922015-04-10 15:50:38 +090091 } else if (mState.action == ACTION_OPEN ||
92 mState.action == ACTION_CREATE ||
93 mState.action == ACTION_OPEN_TREE ||
Ben Kwa84cebbe2015-09-25 14:48:29 -070094 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkeyb5133112013-09-01 18:41:04 -070095 RootsFragment.show(getFragmentManager(), null);
96 }
97
Steve McKayc95d87c2016-02-23 14:34:50 -080098 if (mState.restored) {
Aga Wronskaf6a31d32016-01-15 17:30:15 -080099 refreshCurrentRootAndDirectory(ANIM_NONE);
Steve McKayc95d87c2016-02-23 14:34:50 -0800100 } else {
101 // We set the activity title in AsyncTask.onPostExecute().
102 // To prevent talkback from reading aloud the default title, we clear it here.
103 setTitle("");
104
105 // As a matter of policy we don't load the last used stack for the copy
106 // destination picker (user is already in Files app).
107 // Concensus was that the experice was too confusing.
108 // In all other cases, where the user is visiting us from another app
109 // we restore the stack as last used from that app.
110 if (mState.action == ACTION_PICK_COPY_DESTINATION) {
111 if (DEBUG) Log.d(TAG, "Launching directly into Home directory.");
112 Uri homeUri = DocumentsContract.buildHomeUri();
113 new LoadRootTask(this, homeUri).executeOnExecutor(
114 ProviderExecutor.forAuthority(homeUri.getAuthority()));
115 } else {
116 if (DEBUG) Log.d(TAG, "Attempting to load last used stack for calling package.");
117 new LoadLastUsedStackTask(this).execute();
118 }
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700119 }
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700120 }
121
Steve McKay12055472015-08-20 16:48:49 -0700122 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800123 void includeState(State state) {
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700124 final Intent intent = getIntent();
125 final String action = intent.getAction();
126 if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700127 state.action = ACTION_OPEN;
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700128 } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700129 state.action = ACTION_CREATE;
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700130 } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700131 state.action = ACTION_GET_CONTENT;
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700132 } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700133 state.action = ACTION_OPEN_TREE;
Ben Kwa84cebbe2015-09-25 14:48:29 -0700134 } else if (Shared.ACTION_PICK_COPY_DESTINATION.equals(action)) {
135 state.action = ACTION_PICK_COPY_DESTINATION;
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700136 }
137
Steve McKayef3e2cf2015-04-20 17:18:15 -0700138 if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) {
139 state.allowMultiple = intent.getBooleanExtra(
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700140 Intent.EXTRA_ALLOW_MULTIPLE, false);
141 }
142
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900143 if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT
144 || state.action == ACTION_CREATE) {
145 state.openableOnly = intent.hasCategory(Intent.CATEGORY_OPENABLE);
146 }
147
Ben Kwa84cebbe2015-09-25 14:48:29 -0700148 if (state.action == ACTION_PICK_COPY_DESTINATION) {
Steve McKaya6bbeab2016-02-17 15:02:01 -0800149 // Indicates that a copy operation (or move) includes a directory.
150 // Why? Directory creation isn't supported by some roots (like Downloads).
151 // This allows us to restrict available roots to just those with support.
Steve McKayef3e2cf2015-04-20 17:18:15 -0700152 state.directoryCopy = intent.getBooleanExtra(
Ben Kwa84cebbe2015-09-25 14:48:29 -0700153 Shared.EXTRA_DIRECTORY_COPY, false);
Steve McKaya6bbeab2016-02-17 15:02:01 -0800154 state.copyOperationSubType = intent.getIntExtra(
155 FileOperationService.EXTRA_OPERATION,
Steve McKay14e827a2016-01-06 18:32:13 -0800156 FileOperationService.OPERATION_COPY);
Daichi Hirono9be34292015-04-14 17:12:54 +0900157 }
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700158 }
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700159
Steve McKayaa15dae2016-02-09 16:17:24 -0800160 private void onStackRestored(boolean restored, boolean external) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700161 // Show drawer when no stack restored, but only when requesting
162 // non-visual content. However, if we last used an external app,
163 // drawer is always shown.
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700164
Steve McKayef3e2cf2015-04-20 17:18:15 -0700165 boolean showDrawer = false;
166 if (!restored) {
167 showDrawer = true;
168 }
169 if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
170 showDrawer = false;
171 }
172 if (external && mState.action == ACTION_GET_CONTENT) {
173 showDrawer = true;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700174 }
Steve McKaya6bbeab2016-02-17 15:02:01 -0800175 if (mState.action == ACTION_PICK_COPY_DESTINATION) {
176 showDrawer = true;
177 }
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700178
Steve McKayef3e2cf2015-04-20 17:18:15 -0700179 if (showDrawer) {
Steve McKay1f264a82016-02-03 11:15:57 -0800180 mNavigator.revealRootsDrawer(true);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700181 }
182 }
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700183
Steve McKayef3e2cf2015-04-20 17:18:15 -0700184 public void onAppPicked(ResolveInfo info) {
185 final Intent intent = new Intent(getIntent());
186 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
187 intent.setComponent(new ComponentName(
188 info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
189 startActivityForResult(intent, CODE_FORWARD);
190 }
Jeff Sharkeydeffade2013-09-24 12:07:12 -0700191
Steve McKayef3e2cf2015-04-20 17:18:15 -0700192 @Override
193 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
194 Log.d(TAG, "onActivityResult() code=" + resultCode);
Jeff Sharkeydeffade2013-09-24 12:07:12 -0700195
Steve McKayef3e2cf2015-04-20 17:18:15 -0700196 // Only relay back results when not canceled; otherwise stick around to
197 // let the user pick another app/backend.
198 if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700199
Steve McKayef3e2cf2015-04-20 17:18:15 -0700200 // Remember that we last picked via external app
201 final String packageName = getCallingPackageMaybeExtra();
202 final ContentValues values = new ContentValues();
203 values.put(ResumeColumns.EXTERNAL, 1);
204 getContentResolver().insert(RecentsProvider.buildResume(packageName), values);
205
206 // Pass back result to original caller
207 setResult(resultCode, data);
208 finish();
209 } else {
210 super.onActivityResult(requestCode, resultCode, data);
Jeff Sharkeya5599ef2013-08-15 16:17:41 -0700211 }
212 }
213
Jeff Sharkeybc2971d2013-07-31 20:53:22 -0700214 @Override
215 protected void onPostCreate(Bundle savedInstanceState) {
216 super.onPostCreate(savedInstanceState);
Steve McKay1f264a82016-02-03 11:15:57 -0800217 mDrawer.update();
218 mNavigator.update();
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700219 }
220
Steve McKayef3e2cf2015-04-20 17:18:15 -0700221 @Override
Steve McKay1f264a82016-02-03 11:15:57 -0800222 public String getDrawerTitle() {
223 String title = getIntent().getStringExtra(DocumentsContract.EXTRA_PROMPT);
224 if (title == null) {
225 if (mState.action == ACTION_OPEN ||
226 mState.action == ACTION_GET_CONTENT ||
227 mState.action == ACTION_OPEN_TREE) {
228 title = getResources().getString(R.string.title_open);
229 } else if (mState.action == ACTION_CREATE ||
230 mState.action == ACTION_PICK_COPY_DESTINATION) {
231 title = getResources().getString(R.string.title_save);
Tomasz Mikolajewski0e591f92015-06-12 16:22:17 -0700232 } else {
Steve McKay1f264a82016-02-03 11:15:57 -0800233 // If all else fails, just call it "Files".
234 title = getResources().getString(R.string.files_label);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700235 }
Jeff Sharkeyf52773f2014-07-28 16:38:52 -0700236 }
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700237
Steve McKay1f264a82016-02-03 11:15:57 -0800238 return title;
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700239 }
240
241 @Override
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700242 public boolean onPrepareOptionsMenu(Menu menu) {
243 super.onPrepareOptionsMenu(menu);
244
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700245 final DocumentInfo cwd = getCurrentDirectory();
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700246
Steve McKay3ce95952016-02-02 11:41:03 -0800247 boolean picking = mState.action == ACTION_CREATE
248 || mState.action == ACTION_OPEN_TREE
249 || mState.action == ACTION_PICK_COPY_DESTINATION;
250
251 if (picking) {
252 // May already be hidden because the root
253 // doesn't support search.
254 mSearchManager.showMenu(false);
255 }
256
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700257 final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
Jeff Sharkeyded77182013-09-03 14:17:06 -0700258 final MenuItem grid = menu.findItem(R.id.menu_grid);
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700259 final MenuItem list = menu.findItem(R.id.menu_list);
Jeff Sharkey669f8e72014-08-08 15:10:03 -0700260 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700261
Steve McKay5bbae102015-10-01 11:39:24 -0700262 boolean recents = cwd == null;
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700263
Steve McKay5bbae102015-10-01 11:39:24 -0700264 createDir.setVisible(picking && !recents && cwd.isCreateSupported());
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700265
Steve McKay5bbae102015-10-01 11:39:24 -0700266 // No display options in recent directories
Steve McKay7c7e6842016-01-08 14:58:33 -0800267 if (picking && recents) {
268 grid.setVisible(false);
269 list.setVisible(false);
270 }
Steve McKay5bbae102015-10-01 11:39:24 -0700271
272 fileSize.setVisible(fileSize.isVisible() && !picking);
Steve McKay5bbae102015-10-01 11:39:24 -0700273
274 if (mState.action == ACTION_CREATE) {
275 final FragmentManager fm = getFragmentManager();
Steve McKay0c2a0a82015-10-14 17:41:13 -0700276 SaveFragment.get(fm).prepareForDirectory(cwd);
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700277 }
Jeff Sharkeybc2971d2013-07-31 20:53:22 -0700278
Steve McKay5bbae102015-10-01 11:39:24 -0700279 Menus.disableHiddenItems(menu);
Jeff Sharkey1407d4c2015-04-12 21:52:24 -0700280
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700281 return true;
282 }
283
284 @Override
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800285 void refreshDirectory(int anim) {
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700286 final FragmentManager fm = getFragmentManager();
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700287 final RootInfo root = getCurrentRoot();
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700288 final DocumentInfo cwd = getCurrentDirectory();
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700289
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700290 if (cwd == null) {
291 // No directory means recents
Daichi Hironobbe22922015-04-10 15:50:38 +0900292 if (mState.action == ACTION_CREATE ||
293 mState.action == ACTION_OPEN_TREE ||
Ben Kwa84cebbe2015-09-25 14:48:29 -0700294 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700295 RecentsCreateFragment.show(fm);
296 } else {
Jeff Sharkeye20a3ac2013-09-18 16:26:49 -0700297 DirectoryFragment.showRecentsOpen(fm, anim);
Jeff Sharkey88f322c2013-09-12 16:25:02 -0700298
Steve McKay3eb2d072016-01-25 19:00:22 -0800299 // In recents we pick layout mode based on the mimetype,
300 // picking GRID for visual types. We intentionally don't
301 // consult a user's saved preferences here since they are
302 // set per root (not per root and per mimetype).
303 boolean visualMimes = MimePredicate.mimeMatches(
Jeff Sharkeydeffade2013-09-24 12:07:12 -0700304 MimePredicate.VISUAL_MIMES, mState.acceptMimes);
Steve McKay3eb2d072016-01-25 19:00:22 -0800305 mState.derivedMode = visualMimes ? State.MODE_GRID : State.MODE_LIST;
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700306 }
307 } else {
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700308 // Normal boring directory
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800309 DirectoryFragment.showDirectory(fm, root, cwd, anim);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700310 }
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700311
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700312 // Forget any replacement target
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700313 if (mState.action == ACTION_CREATE) {
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700314 final SaveFragment save = SaveFragment.get(fm);
315 if (save != null) {
316 save.setReplaceTarget(null);
317 }
318 }
319
Daichi Hironobbe22922015-04-10 15:50:38 +0900320 if (mState.action == ACTION_OPEN_TREE ||
Ben Kwa84cebbe2015-09-25 14:48:29 -0700321 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700322 final PickFragment pick = PickFragment.get(fm);
323 if (pick != null) {
Steve McKaya6bbeab2016-02-17 15:02:01 -0800324 pick.setPickTarget(mState.action, mState.copyOperationSubType, cwd);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700325 }
326 }
Steve McKayef3e2cf2015-04-20 17:18:15 -0700327 }
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700328
Steve McKayef3e2cf2015-04-20 17:18:15 -0700329 void onSaveRequested(DocumentInfo replaceTarget) {
Steve McKay95cd85a2016-02-04 12:15:22 -0800330 new ExistingFinishTask(this, replaceTarget.derivedUri)
331 .executeOnExecutor(getExecutorForCurrentDirectory());
Steve McKayef3e2cf2015-04-20 17:18:15 -0700332 }
Jeff Sharkey28c05ee2013-09-06 13:22:09 -0700333
Steve McKayb9a20d12016-02-19 12:57:05 -0800334 @Override
335 void onDirectoryCreated(DocumentInfo doc) {
336 checkArgument(doc.isDirectory());
337 openContainerDocument(doc);
338 }
339
Steve McKayef3e2cf2015-04-20 17:18:15 -0700340 void onSaveRequested(String mimeType, String displayName) {
Steve McKay95cd85a2016-02-04 12:15:22 -0800341 new CreateFinishTask(this, mimeType, displayName)
342 .executeOnExecutor(getExecutorForCurrentDirectory());
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700343 }
344
Steve McKaye934ce62015-03-25 14:35:33 -0700345 @Override
Steve McKayef3e2cf2015-04-20 17:18:15 -0700346 void onRootPicked(RootInfo root) {
347 super.onRootPicked(root);
Steve McKay1f264a82016-02-03 11:15:57 -0800348 mNavigator.revealRootsDrawer(false);
349 }
350
Steve McKaye934ce62015-03-25 14:35:33 -0700351 @Override
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +0900352 public void onDocumentPicked(DocumentInfo doc, Model model) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700353 final FragmentManager fm = getFragmentManager();
Tomasz Mikolajewski39acff52015-11-25 13:01:18 +0900354 if (doc.isContainer()) {
355 openContainerDocument(doc);
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700356 } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700357 // Explicit file picked, return
Steve McKay95cd85a2016-02-04 12:15:22 -0800358 new ExistingFinishTask(this, doc.derivedUri)
359 .executeOnExecutor(getExecutorForCurrentDirectory());
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700360 } else if (mState.action == ACTION_CREATE) {
Jeff Sharkey9fb567b2013-08-07 16:22:02 -0700361 // Replace selected file
362 SaveFragment.get(fm).setReplaceTarget(doc);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700363 }
364 }
365
Steve McKaye934ce62015-03-25 14:35:33 -0700366 @Override
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700367 public void onDocumentsPicked(List<DocumentInfo> docs) {
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700368 if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700369 final int size = docs.size();
370 final Uri[] uris = new Uri[size];
371 for (int i = 0; i < size; i++) {
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700372 uris[i] = docs.get(i).derivedUri;
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700373 }
Steve McKay95cd85a2016-02-04 12:15:22 -0800374 new ExistingFinishTask(this, uris)
375 .executeOnExecutor(getExecutorForCurrentDirectory());
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700376 }
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700377 }
378
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700379 public void onPickRequested(DocumentInfo pickTarget) {
Daichi Hironobbe22922015-04-10 15:50:38 +0900380 Uri result;
381 if (mState.action == ACTION_OPEN_TREE) {
382 result = DocumentsContract.buildTreeDocumentUri(
383 pickTarget.authority, pickTarget.documentId);
Ben Kwa84cebbe2015-09-25 14:48:29 -0700384 } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
Daichi Hironobbe22922015-04-10 15:50:38 +0900385 result = pickTarget.derivedUri;
386 } else {
387 // Should not be reached.
388 throw new IllegalStateException("Invalid mState.action.");
389 }
Steve McKay95cd85a2016-02-04 12:15:22 -0800390 new PickFinishTask(this, result).executeOnExecutor(getExecutorForCurrentDirectory());
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700391 }
392
Steve McKay95cd85a2016-02-04 12:15:22 -0800393 void writeStackToRecentsBlocking() {
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700394 final ContentResolver resolver = getContentResolver();
395 final ContentValues values = new ContentValues();
396
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700397 final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
Daichi Hironobbe22922015-04-10 15:50:38 +0900398 if (mState.action == ACTION_CREATE ||
399 mState.action == ACTION_OPEN_TREE ||
Ben Kwa84cebbe2015-09-25 14:48:29 -0700400 mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700401 // Remember stack for last create
402 values.clear();
Jeff Sharkey6efba222013-09-27 16:44:11 -0700403 values.put(RecentColumns.KEY, mState.stack.buildKey());
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700404 values.put(RecentColumns.STACK, rawStack);
405 resolver.insert(RecentsProvider.buildRecent(), values);
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700406 }
407
408 // Remember location for next app launch
Jeff Sharkey15be8362013-10-09 13:52:17 -0700409 final String packageName = getCallingPackageMaybeExtra();
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700410 values.clear();
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700411 values.put(ResumeColumns.STACK, rawStack);
Jeff Sharkeydeffade2013-09-24 12:07:12 -0700412 values.put(ResumeColumns.EXTERNAL, 0);
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700413 resolver.insert(RecentsProvider.buildResume(packageName), values);
Jeff Sharkey6efba222013-09-27 16:44:11 -0700414 }
415
Steve McKayef3e2cf2015-04-20 17:18:15 -0700416 @Override
417 void onTaskFinished(Uri... uris) {
Jeff Sharkey6efba222013-09-27 16:44:11 -0700418 Log.d(TAG, "onFinished() " + Arrays.toString(uris));
Jeff Sharkeydc2963a2013-08-02 15:55:26 -0700419
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700420 final Intent intent = new Intent();
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700421 if (uris.length == 1) {
422 intent.setData(uris[0]);
423 } else if (uris.length > 1) {
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700424 final ClipData clipData = new ClipData(
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700425 null, mState.acceptMimes, new ClipData.Item(uris[0]));
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700426 for (int i = 1; i < uris.length; i++) {
427 clipData.addItem(new ClipData.Item(uris[i]));
428 }
429 intent.setClipData(clipData);
430 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700431
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700432 if (mState.action == ACTION_GET_CONTENT) {
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700433 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Jeff Sharkey04ec6f42015-07-15 09:13:14 -0700434 } else if (mState.action == ACTION_OPEN_TREE) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700435 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
436 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
437 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
438 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
Ben Kwa84cebbe2015-09-25 14:48:29 -0700439 } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
Jeff Sharkey04ec6f42015-07-15 09:13:14 -0700440 // Picking a copy destination is only used internally by us, so we
441 // don't need to extend permissions to the caller.
Steve McKay323ee3e2015-09-25 16:02:56 -0700442 intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
Steve McKaya6bbeab2016-02-17 15:02:01 -0800443 intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.copyOperationSubType);
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700444 } else {
445 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
446 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
Jeff Sharkeye66c1772013-09-20 14:30:59 -0700447 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
Jeff Sharkey54ca29a2013-08-15 11:24:03 -0700448 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700449
450 setResult(Activity.RESULT_OK, intent);
451 finish();
452 }
453
Steve McKay95cd85a2016-02-04 12:15:22 -0800454
Steve McKayef3e2cf2015-04-20 17:18:15 -0700455 public static DocumentsActivity get(Fragment fragment) {
456 return (DocumentsActivity) fragment.getActivity();
457 }
458
Steve McKay95cd85a2016-02-04 12:15:22 -0800459 /**
Steve McKayc95d87c2016-02-23 14:34:50 -0800460 * Loads the last used path (stack) from Recents (history).
461 * The path selected is based on the calling package name. So the last
462 * path for an app like Gmail can be different than the last path
463 * for an app like DropBox.
Steve McKay95cd85a2016-02-04 12:15:22 -0800464 */
Steve McKayc95d87c2016-02-23 14:34:50 -0800465 private static final class LoadLastUsedStackTask
Steve McKay95cd85a2016-02-04 12:15:22 -0800466 extends PairedTask<DocumentsActivity, Void, Void> {
467
468 private volatile boolean mRestoredStack;
469 private volatile boolean mExternal;
Steve McKay95cd85a2016-02-04 12:15:22 -0800470 private State mState;
471
Steve McKayc95d87c2016-02-23 14:34:50 -0800472 public LoadLastUsedStackTask(DocumentsActivity activity) {
Steve McKay95cd85a2016-02-04 12:15:22 -0800473 super(activity);
474 mState = activity.mState;
475 }
476
477 @Override
478 protected Void run(Void... params) {
479 if (DEBUG && !mState.stack.isEmpty()) {
480 Log.w(TAG, "Overwriting existing stack.");
481 }
Ben Kwaa4761f02016-02-05 06:44:15 -0800482 RootsCache roots = DocumentsApplication.getRootsCache(mOwner);
Steve McKay95cd85a2016-02-04 12:15:22 -0800483
484 String packageName = mOwner.getCallingPackageMaybeExtra();
485 Uri resumeUri = RecentsProvider.buildResume(packageName);
486 Cursor cursor = mOwner.getContentResolver().query(resumeUri, null, null, null, null);
487 try {
488 if (cursor.moveToFirst()) {
489 mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
490 final byte[] rawStack = cursor.getBlob(
491 cursor.getColumnIndex(ResumeColumns.STACK));
492 DurableUtils.readFromArray(rawStack, mState.stack);
493 mRestoredStack = true;
494 }
495 } catch (IOException e) {
496 Log.w(TAG, "Failed to resume: " + e);
497 } finally {
498 IoUtils.closeQuietly(cursor);
499 }
500
501 if (mRestoredStack) {
502 // Update the restored stack to ensure we have freshest data
503 final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
504 try {
505 mState.stack.updateRoot(matchingRoots);
506 mState.stack.updateDocuments(mOwner.getContentResolver());
507 } catch (FileNotFoundException e) {
508 Log.w(TAG, "Failed to restore stack for package: " + packageName
509 + " because of error: "+ e);
510 mState.stack.reset();
511 mRestoredStack = false;
512 }
513 }
514
515 return null;
516 }
517
518 @Override
519 protected void finish(Void result) {
520 mState.restored = true;
521 mOwner.refreshCurrentRootAndDirectory(ANIM_NONE);
522 mOwner.onStackRestored(mRestoredStack, mExternal);
523 }
524 }
525
526 private static final class PickFinishTask extends PairedTask<DocumentsActivity, Void, Void> {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700527 private final Uri mUri;
528
Steve McKay95cd85a2016-02-04 12:15:22 -0800529 public PickFinishTask(DocumentsActivity activity, Uri uri) {
530 super(activity);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700531 mUri = uri;
532 }
533
534 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800535 protected Void run(Void... params) {
536 mOwner.writeStackToRecentsBlocking();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700537 return null;
538 }
539
540 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800541 protected void finish(Void result) {
542 mOwner.onTaskFinished(mUri);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700543 }
544 }
545
Steve McKay95cd85a2016-02-04 12:15:22 -0800546 private static final class ExistingFinishTask extends PairedTask<DocumentsActivity, Void, Void> {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700547 private final Uri[] mUris;
548
Steve McKay95cd85a2016-02-04 12:15:22 -0800549 public ExistingFinishTask(DocumentsActivity activity, Uri... uris) {
550 super(activity);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700551 mUris = uris;
552 }
553
554 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800555 protected Void run(Void... params) {
556 mOwner.writeStackToRecentsBlocking();
Steve McKayef3e2cf2015-04-20 17:18:15 -0700557 return null;
558 }
559
560 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800561 protected void finish(Void result) {
562 mOwner.onTaskFinished(mUris);
Steve McKayef3e2cf2015-04-20 17:18:15 -0700563 }
564 }
565
566 /**
567 * Task that creates a new document in the background.
568 */
Steve McKay95cd85a2016-02-04 12:15:22 -0800569 private static final class CreateFinishTask extends PairedTask<DocumentsActivity, Void, Uri> {
Jeff Sharkey6efba222013-09-27 16:44:11 -0700570 private final String mMimeType;
571 private final String mDisplayName;
572
Steve McKay95cd85a2016-02-04 12:15:22 -0800573 public CreateFinishTask(DocumentsActivity activity, String mimeType, String displayName) {
574 super(activity);
Jeff Sharkey6efba222013-09-27 16:44:11 -0700575 mMimeType = mimeType;
576 mDisplayName = displayName;
577 }
578
579 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800580 protected void prepare() {
581 mOwner.setPending(true);
Jeff Sharkey04d45a02013-10-23 15:46:38 -0700582 }
583
584 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800585 protected Uri run(Void... params) {
586 final ContentResolver resolver = mOwner.getContentResolver();
587 final DocumentInfo cwd = mOwner.getCurrentDirectory();
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700588
589 ContentProviderClient client = null;
590 Uri childUri = null;
591 try {
592 client = DocumentsApplication.acquireUnstableProviderOrThrow(
593 resolver, cwd.derivedUri.getAuthority());
594 childUri = DocumentsContract.createDocument(
595 client, cwd.derivedUri, mMimeType, mDisplayName);
596 } catch (Exception e) {
597 Log.w(TAG, "Failed to create document", e);
598 } finally {
599 ContentProviderClient.releaseQuietly(client);
600 }
601
Jeff Sharkey6efba222013-09-27 16:44:11 -0700602 if (childUri != null) {
Steve McKay95cd85a2016-02-04 12:15:22 -0800603 mOwner.writeStackToRecentsBlocking();
Jeff Sharkey6efba222013-09-27 16:44:11 -0700604 }
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700605
Jeff Sharkey6efba222013-09-27 16:44:11 -0700606 return childUri;
607 }
608
609 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800610 protected void finish(Uri result) {
Jeff Sharkey6efba222013-09-27 16:44:11 -0700611 if (result != null) {
Steve McKay95cd85a2016-02-04 12:15:22 -0800612 mOwner.onTaskFinished(result);
Jeff Sharkey6efba222013-09-27 16:44:11 -0700613 } else {
Steve McKay5bbae102015-10-01 11:39:24 -0700614 Snackbars.makeSnackbar(
Steve McKay95cd85a2016-02-04 12:15:22 -0800615 mOwner, R.string.save_error, Snackbar.LENGTH_SHORT).show();
Jeff Sharkey6efba222013-09-27 16:44:11 -0700616 }
Jeff Sharkey04d45a02013-10-23 15:46:38 -0700617
Steve McKay95cd85a2016-02-04 12:15:22 -0800618 mOwner.setPending(false);
Jeff Sharkey6efba222013-09-27 16:44:11 -0700619 }
620 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700621}