blob: f82bdf1ad4b479439747b4d2ac5715d4849c7752 [file] [log] [blame]
Steve McKaye934ce62015-03-25 14:35:33 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui;
18
Steve McKaya7e923c2016-01-28 12:02:57 -080019import static com.android.documentsui.OperationDialogFragment.DIALOG_TYPE_UNKNOWN;
Steve McKay83df8c02015-09-16 15:07:31 -070020import static com.android.documentsui.Shared.DEBUG;
Steve McKayd0d9afc2015-05-06 12:16:40 -070021
Steve McKaye934ce62015-03-25 14:35:33 -070022import android.app.Activity;
Steve McKaye934ce62015-03-25 14:35:33 -070023import android.app.FragmentManager;
24import android.content.ActivityNotFoundException;
25import android.content.ClipData;
Steve McKaye934ce62015-03-25 14:35:33 -070026import android.content.ContentResolver;
27import android.content.ContentValues;
Steve McKaye934ce62015-03-25 14:35:33 -070028import android.content.Intent;
Steve McKaye934ce62015-03-25 14:35:33 -070029import android.net.Uri;
Steve McKaye934ce62015-03-25 14:35:33 -070030import android.os.Bundle;
Steve McKay323ee3e2015-09-25 16:02:56 -070031import android.os.Parcelable;
32import android.provider.DocumentsContract;
Ben Kwa94b486d2015-09-30 10:00:10 -070033import android.support.design.widget.Snackbar;
Steve McKaye934ce62015-03-25 14:35:33 -070034import android.util.Log;
Steve McKay3da8afc2015-05-05 14:50:00 -070035import android.view.KeyEvent;
Steve McKaye934ce62015-03-25 14:35:33 -070036import android.view.Menu;
37import android.view.MenuItem;
Steve McKaye934ce62015-03-25 14:35:33 -070038
Ben Lina433c6d2016-05-31 13:24:01 -070039import com.android.documentsui.MenuManager.DirectoryDetails;
Steve McKaya7e923c2016-01-28 12:02:57 -080040import com.android.documentsui.OperationDialogFragment.DialogType;
Steve McKaye934ce62015-03-25 14:35:33 -070041import com.android.documentsui.RecentsProvider.ResumeColumns;
Steve McKayce710822016-03-11 10:49:32 -080042import com.android.documentsui.dirlist.AnimationView;
Steve McKayf8621552015-11-03 15:23:16 -080043import com.android.documentsui.dirlist.DirectoryFragment;
Ben Lina433c6d2016-05-31 13:24:01 -070044import com.android.documentsui.dirlist.FragmentTuner;
45import com.android.documentsui.dirlist.FragmentTuner.FilesTuner;
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +090046import com.android.documentsui.dirlist.Model;
Steve McKaye934ce62015-03-25 14:35:33 -070047import com.android.documentsui.model.DocumentInfo;
48import com.android.documentsui.model.DocumentStack;
49import com.android.documentsui.model.DurableUtils;
50import com.android.documentsui.model.RootInfo;
Steve McKay14e827a2016-01-06 18:32:13 -080051import com.android.documentsui.services.FileOperationService;
Steve McKaye934ce62015-03-25 14:35:33 -070052
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +090053import java.io.FileNotFoundException;
Tomasz Mikolajewski61686592015-04-13 19:38:43 +090054import java.util.ArrayList;
Steve McKaye934ce62015-03-25 14:35:33 -070055import java.util.Arrays;
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +090056import java.util.Collection;
Steve McKaye934ce62015-03-25 14:35:33 -070057import java.util.List;
Steve McKaye934ce62015-03-25 14:35:33 -070058
Steve McKayef3e2cf2015-04-20 17:18:15 -070059/**
Steve McKay273103b2015-05-12 12:49:58 -070060 * Standalone file management activity.
Steve McKayef3e2cf2015-04-20 17:18:15 -070061 */
Steve McKay12055472015-08-20 16:48:49 -070062public class FilesActivity extends BaseActivity {
Steve McKayc78bcb82015-07-31 14:35:22 -070063
Ben Kwa0f7078f02015-09-08 07:31:19 -070064 public static final String TAG = "FilesActivity";
Steve McKaye934ce62015-03-25 14:35:33 -070065
Steve McKayc95d87c2016-02-23 14:34:50 -080066 // See comments where this const is referenced for details.
67 private static final int DRAWER_NO_FIDDLE_DELAY = 1500;
68
69 // Track the time we opened the drawer in response to back being pressed.
70 // We use the time gap to figure out whether to close app or reopen the drawer.
71 private long mDrawerLastFiddled;
Steve McKaybdbd0ff2015-05-20 15:58:42 -070072 private DocumentClipper mClipper;
Ben Lina433c6d2016-05-31 13:24:01 -070073 private FilesMenuManager mMenuManager;
74 private DirectoryDetails mDetails;
Steve McKayef3e2cf2015-04-20 17:18:15 -070075
Steve McKay12055472015-08-20 16:48:49 -070076 public FilesActivity() {
77 super(R.layout.files_activity, TAG);
Steve McKayef3e2cf2015-04-20 17:18:15 -070078 }
Steve McKaye934ce62015-03-25 14:35:33 -070079
80 @Override
81 public void onCreate(Bundle icicle) {
Steve McKaye934ce62015-03-25 14:35:33 -070082 super.onCreate(icicle);
83
Garfield, Tan6d0b46e2016-05-12 14:12:18 -070084 mClipper = DocumentsApplication.getDocumentClipper(this);
Ben Lin8eafd502016-06-17 11:48:57 -070085 mMenuManager = new FilesMenuManager(mSearchManager, getDisplayState());
Ben Lina433c6d2016-05-31 13:24:01 -070086 mDetails = new DirectoryDetails(this) {
87 @Override
88 public boolean hasItemsToPaste() {
89 return mClipper.hasItemsToPaste();
90 }
91 };
Steve McKaybdbd0ff2015-05-20 15:58:42 -070092
Steve McKaye934ce62015-03-25 14:35:33 -070093 RootsFragment.show(getFragmentManager(), null);
Steve McKay83df8c02015-09-16 15:07:31 -070094
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +090095 final Intent intent = getIntent();
96 final Uri uri = intent.getData();
Steve McKay323ee3e2015-09-25 16:02:56 -070097
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +090098 if (mState.restored) {
99 if (DEBUG) Log.d(TAG, "Stack already resolved for uri: " + intent.getData());
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +0900100 } else if (!mState.stack.isEmpty()) {
Steve McKaya379fad2016-05-31 10:54:23 -0700101 // If a non-empty stack is present in our state, it was read (presumably)
Steve McKay323ee3e2015-09-25 16:02:56 -0700102 // from EXTRA_STACK intent extra. In this case, we'll skip other means of
Steve McKaya379fad2016-05-31 10:54:23 -0700103 // loading or restoring the stack (like URI).
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +0900104 //
Steve McKaya379fad2016-05-31 10:54:23 -0700105 // When restoring from a stack, if a URI is present, it should only ever be:
106 // -- a launch URI: Launch URIs support sensible activity management,
107 // but don't specify a real content target)
108 // -- a fake Uri from notifications. These URIs have no authority (TODO: details).
109 //
110 // Any other URI is *sorta* unexpected...except when browsing an archive
111 // in downloads.
112 if(uri != null
113 && uri.getAuthority() != null
114 && !uri.equals(mState.stack.peek())
115 && !LauncherActivity.isLaunchUri(uri)) {
116 if (DEBUG) Log.w(TAG,
117 "Launching with non-empty stack. Ignoring unexpected uri: " + uri);
118 } else {
119 if (DEBUG) Log.d(TAG, "Launching with non-empty stack.");
120 }
Steve McKayce710822016-03-11 10:49:32 -0800121 refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
Tomasz Mikolajewski8fa99702016-04-07 18:00:08 +0900122 } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Steve McKaya1f76802016-02-25 13:34:03 -0800123 assert(uri != null);
Steve McKay95cd85a2016-02-04 12:15:22 -0800124 new OpenUriForViewTask(this).executeOnExecutor(
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900125 ProviderExecutor.forAuthority(uri.getAuthority()), uri);
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +0900126 } else if (DocumentsContract.isRootUri(this, uri)) {
127 if (DEBUG) Log.d(TAG, "Launching with root URI.");
128 // If we've got a specific root to display, restore that root using a dedicated
129 // authority. That way a misbehaving provider won't result in an ANR.
Steve McKayc95d87c2016-02-23 14:34:50 -0800130 loadRoot(uri);
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +0900131 } else {
Steve McKay6ce903d2016-03-09 15:20:00 -0800132 if (DEBUG) Log.d(TAG, "All other means skipped. Launching into default directory.");
Aga Wronska4e627162016-03-22 14:18:43 -0700133 loadRoot(getDefaultRoot());
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +0900134 }
Tomasz Mikolajewskif8c3f322015-04-14 16:32:41 +0900135
Tomasz Mikolajewski748ea8c2016-01-22 16:22:51 +0900136 final @DialogType int dialogType = intent.getIntExtra(
137 FileOperationService.EXTRA_DIALOG_TYPE, DIALOG_TYPE_UNKNOWN);
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +0900138 // DialogFragment takes care of restoring the dialog on configuration change.
139 // Only show it manually for the first time (icicle is null).
Tomasz Mikolajewski748ea8c2016-01-22 16:22:51 +0900140 if (icicle == null && dialogType != DIALOG_TYPE_UNKNOWN) {
141 final int opType = intent.getIntExtra(
142 FileOperationService.EXTRA_OPERATION,
143 FileOperationService.OPERATION_COPY);
144 final ArrayList<DocumentInfo> srcList =
Steve McKay14e827a2016-01-06 18:32:13 -0800145 intent.getParcelableArrayListExtra(FileOperationService.EXTRA_SRC_LIST);
Tomasz Mikolajewski748ea8c2016-01-22 16:22:51 +0900146 OperationDialogFragment.show(
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +0900147 getFragmentManager(),
Tomasz Mikolajewski748ea8c2016-01-22 16:22:51 +0900148 dialogType,
149 srcList,
Tomasz Mikolajewskiaa684452015-12-25 17:06:52 +0900150 mState.stack,
Steve McKay14e827a2016-01-06 18:32:13 -0800151 opType);
Steve McKaye934ce62015-03-25 14:35:33 -0700152 }
153 }
154
Steve McKay12055472015-08-20 16:48:49 -0700155 @Override
Steve McKay95cd85a2016-02-04 12:15:22 -0800156 void includeState(State state) {
Steve McKaye934ce62015-03-25 14:35:33 -0700157 final Intent intent = getIntent();
Ben Kwa0f7078f02015-09-08 07:31:19 -0700158
Steve McKay83df8c02015-09-16 15:07:31 -0700159 state.action = State.ACTION_BROWSE;
Ben Kwa0f7078f02015-09-08 07:31:19 -0700160 state.allowMultiple = true;
Steve McKay273103b2015-05-12 12:49:58 -0700161
Steve McKay83df8c02015-09-16 15:07:31 -0700162 // Options specific to the DocumentsActivity.
Steve McKaya1f76802016-02-25 13:34:03 -0800163 assert(!intent.hasExtra(Intent.EXTRA_LOCAL_ONLY));
Steve McKay273103b2015-05-12 12:49:58 -0700164
Steve McKay323ee3e2015-09-25 16:02:56 -0700165 final DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
Steve McKay83df8c02015-09-16 15:07:31 -0700166 if (stack != null) {
Steve McKayef3e2cf2015-04-20 17:18:15 -0700167 state.stack = stack;
Steve McKay83df8c02015-09-16 15:07:31 -0700168 }
Steve McKaye934ce62015-03-25 14:35:33 -0700169 }
170
171 @Override
172 protected void onPostCreate(Bundle savedInstanceState) {
173 super.onPostCreate(savedInstanceState);
Steve McKayb67bfbf2015-12-08 17:02:03 -0800174 // This check avoids a flicker from "Recents" to "Home".
175 // Only update action bar at this point if there is an active
176 // serach. Why? Because this avoid an early (undesired) load of
177 // the recents root...which is the default root in other activities.
178 // In Files app "Home" is the default, but it is loaded async.
Steve McKay1f264a82016-02-03 11:15:57 -0800179 // update will be called once Home root is loaded.
Steve McKayb67bfbf2015-12-08 17:02:03 -0800180 // Except while searching we need this call to ensure the
181 // search bits get layed out correctly.
182 if (mSearchManager.isSearching()) {
Steve McKay1f264a82016-02-03 11:15:57 -0800183 mNavigator.update();
Steve McKayb67bfbf2015-12-08 17:02:03 -0800184 }
Steve McKaye934ce62015-03-25 14:35:33 -0700185 }
186
187 @Override
Steve McKay83df8c02015-09-16 15:07:31 -0700188 public void onResume() {
189 super.onResume();
190
191 final RootInfo root = getCurrentRoot();
192
193 // If we're browsing a specific root, and that root went away, then we
194 // have no reason to hang around.
195 // TODO: Rather than just disappearing, maybe we should inform
196 // the user what has happened, let them close us. Less surprising.
197 if (mRoots.getRootBlocking(root.authority, root.rootId) == null) {
198 finish();
199 }
200 }
201
202 @Override
Steve McKay1f264a82016-02-03 11:15:57 -0800203 public String getDrawerTitle() {
Steve McKay53f109b32016-04-04 15:26:48 -0700204 Intent intent = getIntent();
205 return (intent != null && intent.hasExtra(Intent.EXTRA_TITLE))
206 ? intent.getStringExtra(Intent.EXTRA_TITLE)
207 : getTitle().toString();
Steve McKaye934ce62015-03-25 14:35:33 -0700208 }
209
210 @Override
Steve McKaye934ce62015-03-25 14:35:33 -0700211 public boolean onPrepareOptionsMenu(Menu menu) {
Steve McKay5bbae102015-10-01 11:39:24 -0700212 super.onPrepareOptionsMenu(menu);
Ben Lina433c6d2016-05-31 13:24:01 -0700213 mMenuManager.updateOptionMenu(menu, mDetails);
Steve McKay5bbae102015-10-01 11:39:24 -0700214 return true;
Steve McKaye934ce62015-03-25 14:35:33 -0700215 }
216
217 @Override
Steve McKaybdbd0ff2015-05-20 15:58:42 -0700218 public boolean onOptionsItemSelected(MenuItem item) {
Steve McKay9f9d5b42015-09-23 15:44:24 -0700219 switch (item.getItemId()) {
220 case R.id.menu_create_dir:
Steve McKaya1f76802016-02-25 13:34:03 -0800221 assert(canCreateDirectory());
Steve McKay9f9d5b42015-09-23 15:44:24 -0700222 showCreateDirectoryDialog();
Aga Wronskacf966ae2016-03-30 13:55:19 -0700223 break;
Steve McKay9f9d5b42015-09-23 15:44:24 -0700224 case R.id.menu_new_window:
Steve McKay323ee3e2015-09-25 16:02:56 -0700225 createNewWindow();
Aga Wronskacf966ae2016-03-30 13:55:19 -0700226 break;
Steve McKay9f9d5b42015-09-23 15:44:24 -0700227 case R.id.menu_paste_from_clipboard:
Steve McKay3ce95952016-02-02 11:41:03 -0800228 DirectoryFragment dir = getDirectoryFragment();
229 if (dir != null) {
230 dir.pasteFromClipboard();
231 }
Aga Wronskacf966ae2016-03-30 13:55:19 -0700232 break;
233 default:
234 return super.onOptionsItemSelected(item);
Steve McKaybdbd0ff2015-05-20 15:58:42 -0700235 }
Aga Wronskacf966ae2016-03-30 13:55:19 -0700236 return true;
Steve McKaybdbd0ff2015-05-20 15:58:42 -0700237 }
238
Steve McKay323ee3e2015-09-25 16:02:56 -0700239 private void createNewWindow() {
Aga Wronska43df5772016-04-07 13:09:58 -0700240 Metrics.logUserAction(this, Metrics.USER_ACTION_NEW_WINDOW);
241
Steve McKay323ee3e2015-09-25 16:02:56 -0700242 Intent intent = LauncherActivity.createLaunchIntent(this);
243 intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
Steve McKaya7e923c2016-01-28 12:02:57 -0800244
245 // With new multi-window mode we have to pick how we are launched.
246 // By default we'd be launched in-place above the existing app.
247 // By setting launch-to-side ActivityManager will open us to side.
Andrii Kulian933076d2016-03-29 17:04:42 -0700248 if (isInMultiWindowMode()) {
Wale Ogunwale2a25a622016-01-30 11:27:21 -0800249 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
Steve McKaya7e923c2016-01-28 12:02:57 -0800250 }
251
Steve McKay323ee3e2015-09-25 16:02:56 -0700252 startActivity(intent);
253 }
254
Steve McKaybdbd0ff2015-05-20 15:58:42 -0700255 @Override
Aga Wronskaf6a31d32016-01-15 17:30:15 -0800256 void refreshDirectory(int anim) {
Steve McKaye934ce62015-03-25 14:35:33 -0700257 final FragmentManager fm = getFragmentManager();
258 final RootInfo root = getCurrentRoot();
259 final DocumentInfo cwd = getCurrentDirectory();
260
Steve McKaya1f76802016-02-25 13:34:03 -0800261 assert(!mSearchManager.isSearching());
Aga Wronska893390b2016-02-17 13:50:42 -0800262
Steve McKaye934ce62015-03-25 14:35:33 -0700263 if (cwd == null) {
264 DirectoryFragment.showRecentsOpen(fm, anim);
Steve McKaye934ce62015-03-25 14:35:33 -0700265 } else {
Aga Wronska893390b2016-02-17 13:50:42 -0800266 // Normal boring directory
267 DirectoryFragment.showDirectory(fm, root, cwd, anim);
Steve McKaye934ce62015-03-25 14:35:33 -0700268 }
Steve McKaye934ce62015-03-25 14:35:33 -0700269 }
270
271 @Override
Steve McKay6eaf38632015-08-04 10:11:01 -0700272 public void onDocumentsPicked(List<DocumentInfo> docs) {
273 throw new UnsupportedOperationException();
Steve McKaye934ce62015-03-25 14:35:33 -0700274 }
275
Steve McKay6eaf38632015-08-04 10:11:01 -0700276 @Override
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +0900277 public void onDocumentPicked(DocumentInfo doc, Model model) {
Steve McKay3e63e7d2016-03-11 10:11:52 -0800278 // Anything on downloads goes through the back through downloads manager
279 // (that's the MANAGE_DOCUMENT bit).
280 // This is done for two reasons:
281 // 1) The file in question might be a failed/queued or otherwise have some
282 // specialized download handling.
283 // 2) For APKs, the download manager will add on some important security stuff
284 // like origin URL.
285 // All other files not on downloads, event APKs, would get no benefit from this
286 // treatment, thusly the "isDownloads" check.
Tomasz Mikolajewski8fa99702016-04-07 18:00:08 +0900287
288 // Launch MANAGE_DOCUMENTS only for the root level files, so it's not called for
289 // files in archives. Also, if the activity is already browsing a ZIP from downloads,
290 // then skip MANAGE_DOCUMENTS.
291 final boolean isViewing = Intent.ACTION_VIEW.equals(getIntent().getAction());
292 final boolean isInArchive = mState.stack.size() > 1;
293 if (getCurrentRoot().isDownloads() && !isInArchive && !isViewing) {
Steve McKay9bb98f32016-03-07 11:49:05 -0800294 // First try managing the document; we expect manager to filter
295 // based on authority, so we don't grant.
296 final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
297 manage.setData(doc.derivedUri);
298
299 try {
300 startActivity(manage);
301 return;
302 } catch (ActivityNotFoundException ex) {
303 // fall back to regular handling below.
304 }
305 }
306
Tomasz Mikolajewski8fa99702016-04-07 18:00:08 +0900307 if (doc.isContainer()) {
308 openContainerDocument(doc);
309 } else {
310 openDocument(doc, model);
311 }
312 }
313
Garfield, Tan1d975312016-04-20 15:13:56 -0700314 @Override
315 public void springOpenDirectory(DocumentInfo doc) {
316 assert(doc.isContainer());
317 assert(!doc.isArchive());
318 openContainerDocument(doc);
319 }
320
Tomasz Mikolajewski8fa99702016-04-07 18:00:08 +0900321 /**
322 * Launches an intent to view the specified document.
323 */
324 private void openDocument(DocumentInfo doc, Model model) {
Tomasz Mikolajewski3d988a92016-02-16 12:28:43 +0900325 Intent intent = new QuickViewIntentBuilder(
326 getPackageManager(), getResources(), doc, model).build();
Steve McKay6eaf38632015-08-04 10:11:01 -0700327
Steve McKay3b2ad112015-10-15 15:27:30 -0700328 if (intent != null) {
329 // TODO: un-work around issue b/24963914. Should be fixed soon.
330 try {
331 startActivity(intent);
332 return;
333 } catch (SecurityException e) {
Steve McKay1eafb662015-10-23 09:04:09 -0700334 // Carry on to regular view mode.
Steve McKay3b2ad112015-10-15 15:27:30 -0700335 Log.e(TAG, "Caught security error: " + e.getLocalizedMessage());
336 }
Steve McKayc78bcb82015-07-31 14:35:22 -0700337 }
338
Steve McKay9bb98f32016-03-07 11:49:05 -0800339 // Fall back to traditional VIEW action...
Steve McKay3b2ad112015-10-15 15:27:30 -0700340 intent = new Intent(Intent.ACTION_VIEW);
341 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
342 intent.setData(doc.derivedUri);
343
Steve McKay6eaf38632015-08-04 10:11:01 -0700344 if (DEBUG && intent.getClipData() != null) {
345 Log.d(TAG, "Starting intent w/ clip data: " + intent.getClipData());
346 }
347
Steve McKayc78bcb82015-07-31 14:35:22 -0700348 try {
349 startActivity(intent);
Steve McKay3b2ad112015-10-15 15:27:30 -0700350 } catch (ActivityNotFoundException e) {
351 Snackbars.makeSnackbar(
352 this, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show();
Steve McKayc78bcb82015-07-31 14:35:22 -0700353 }
354 }
355
Steve McKaye934ce62015-03-25 14:35:33 -0700356 @Override
Steve McKay3da8afc2015-05-05 14:50:00 -0700357 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
358 DirectoryFragment dir;
Steve McKay3ce95952016-02-02 11:41:03 -0800359 // TODO: All key events should be statically bound using alphabeticShortcut.
360 // But not working.
Steve McKay3da8afc2015-05-05 14:50:00 -0700361 switch (keyCode) {
362 case KeyEvent.KEYCODE_A:
Steve McKay3ce95952016-02-02 11:41:03 -0800363 dir = getDirectoryFragment();
364 if (dir != null) {
365 dir.selectAllFiles();
366 }
Steve McKay3da8afc2015-05-05 14:50:00 -0700367 return true;
Ben Linb1404b72016-04-18 14:35:28 -0700368 case KeyEvent.KEYCODE_X:
369 dir = getDirectoryFragment();
370 if (dir != null) {
371 dir.cutSelectedToClipboard();
372 }
373 return true;
Steve McKaybdbd0ff2015-05-20 15:58:42 -0700374 case KeyEvent.KEYCODE_C:
Steve McKay3ce95952016-02-02 11:41:03 -0800375 dir = getDirectoryFragment();
376 if (dir != null) {
377 dir.copySelectedToClipboard();
378 }
Steve McKay8fd086a2015-12-04 11:19:09 -0800379 return true;
380 case KeyEvent.KEYCODE_V:
Steve McKay3ce95952016-02-02 11:41:03 -0800381 dir = getDirectoryFragment();
382 if (dir != null) {
383 dir.pasteFromClipboard();
384 }
Steve McKay8fd086a2015-12-04 11:19:09 -0800385 return true;
386 default:
387 return super.onKeyShortcut(keyCode, event);
Steve McKay3da8afc2015-05-05 14:50:00 -0700388 }
389 }
390
Steve McKayc95d87c2016-02-23 14:34:50 -0800391 // Do some "do what a I want" drawer fiddling, but don't
392 // do it if user already hit back recently and we recently
393 // did some fiddling.
394 @Override
395 boolean onBeforePopDir() {
396 int size = mState.stack.size();
397
398 if (mDrawer.isPresent()
399 && (System.currentTimeMillis() - mDrawerLastFiddled) > DRAWER_NO_FIDDLE_DELAY) {
400 // Close drawer if it is open.
401 if (mDrawer.isOpen()) {
402 mDrawer.setOpen(false);
403 mDrawerLastFiddled = System.currentTimeMillis();
404 return true;
405 }
406
407 // Open the Close drawer if it is closed and we're at the top of a root.
Aga Wronska96e30eb2016-03-23 17:12:33 -0700408 if (size <= 1) {
Steve McKayc95d87c2016-02-23 14:34:50 -0800409 mDrawer.setOpen(true);
410 // Remember so we don't just close it again if back is pressed again.
411 mDrawerLastFiddled = System.currentTimeMillis();
412 return true;
413 }
414 }
415
416 return false;
417 }
418
Steve McKay95cd85a2016-02-04 12:15:22 -0800419 // Turns out only DocumentsActivity was ever calling saveStackBlocking.
420 // There may be a case where we want to contribute entries from
421 // Behavior here in FilesActivity, but it isn't yet obvious.
422 // TODO: Contribute to recents, or remove this.
423 void writeStackToRecentsBlocking() {
Steve McKaye934ce62015-03-25 14:35:33 -0700424 final ContentResolver resolver = getContentResolver();
425 final ContentValues values = new ContentValues();
426
Steve McKay95cd85a2016-02-04 12:15:22 -0800427 final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
Steve McKaye934ce62015-03-25 14:35:33 -0700428
429 // Remember location for next app launch
430 final String packageName = getCallingPackageMaybeExtra();
431 values.clear();
432 values.put(ResumeColumns.STACK, rawStack);
433 values.put(ResumeColumns.EXTERNAL, 0);
434 resolver.insert(RecentsProvider.buildResume(packageName), values);
435 }
436
Steve McKayef3e2cf2015-04-20 17:18:15 -0700437 @Override
438 void onTaskFinished(Uri... uris) {
Ben Kwaffa829f2016-03-22 11:11:46 -0700439 if (DEBUG) Log.d(TAG, "onFinished() " + Arrays.toString(uris));
Steve McKaye934ce62015-03-25 14:35:33 -0700440
441 final Intent intent = new Intent();
442 if (uris.length == 1) {
443 intent.setData(uris[0]);
444 } else if (uris.length > 1) {
445 final ClipData clipData = new ClipData(
446 null, mState.acceptMimes, new ClipData.Item(uris[0]));
447 for (int i = 1; i < uris.length; i++) {
448 clipData.addItem(new ClipData.Item(uris[i]));
449 }
450 intent.setClipData(clipData);
451 }
452
453 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
454 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
455 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
456
457 setResult(Activity.RESULT_OK, intent);
458 finish();
459 }
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900460
Ben Lina433c6d2016-05-31 13:24:01 -0700461 @Override
462 public FragmentTuner createFragmentTuner() {
463 return new FilesTuner(this, getDisplayState());
464 }
465
466 @Override
467 public MenuManager getMenuManager() {
468 return mMenuManager;
469 }
470
Ben Lin9a621cb2016-06-03 13:46:52 -0700471 @Override
472 public DirectoryDetails getDirectoryDetails() {
473 return mDetails;
474 }
475
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900476 /**
477 * Builds a stack for the specific Uris. Multi roots are not supported, as it's impossible
478 * to know which root to select. Also, the stack doesn't contain intermediate directories.
479 * It's primarly used for opening ZIP archives from Downloads app.
480 */
Steve McKay95cd85a2016-02-04 12:15:22 -0800481 private static final class OpenUriForViewTask extends PairedTask<FilesActivity, Uri, Void> {
482
483 private final State mState;
484 public OpenUriForViewTask(FilesActivity activity) {
485 super(activity);
486 mState = activity.mState;
487 }
488
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900489 @Override
Steve McKayd8187962016-06-09 10:46:07 -0700490 public Void run(Uri... params) {
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900491 final Uri uri = params[0];
492
Steve McKay95cd85a2016-02-04 12:15:22 -0800493 final RootsCache rootsCache = DocumentsApplication.getRootsCache(mOwner);
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900494 final String authority = uri.getAuthority();
495
496 final Collection<RootInfo> roots =
497 rootsCache.getRootsForAuthorityBlocking(authority);
498 if (roots.isEmpty()) {
499 Log.e(TAG, "Failed to find root for the requested Uri: " + uri);
500 return null;
501 }
502
503 final RootInfo root = roots.iterator().next();
504 mState.stack.root = root;
505 try {
Steve McKay95cd85a2016-02-04 12:15:22 -0800506 mState.stack.add(DocumentInfo.fromUri(mOwner.getContentResolver(), uri));
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900507 } catch (FileNotFoundException e) {
508 Log.e(TAG, "Failed to resolve DocumentInfo from Uri: " + uri);
509 }
Steve McKay95cd85a2016-02-04 12:15:22 -0800510 mState.stack.add(mOwner.getRootDocumentBlocking(root));
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900511 return null;
512 }
513
514 @Override
Steve McKayd8187962016-06-09 10:46:07 -0700515 public void finish(Void result) {
Steve McKayce710822016-03-11 10:49:32 -0800516 mOwner.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900517 }
518 }
Steve McKaye934ce62015-03-25 14:35:33 -0700519}