1 /*
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 
17 package com.android.documentsui.picker;
18 
19 import static com.android.documentsui.base.State.ACTION_CREATE;
20 import static com.android.documentsui.base.State.ACTION_GET_CONTENT;
21 import static com.android.documentsui.base.State.ACTION_OPEN;
22 import static com.android.documentsui.base.State.ACTION_OPEN_TREE;
23 import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION;
24 
25 import android.app.Fragment;
26 import android.app.FragmentManager;
27 import android.content.Intent;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.provider.DocumentsContract;
31 import android.support.annotation.CallSuper;
32 import android.view.KeyEvent;
33 import android.view.Menu;
34 
35 import com.android.documentsui.ActionModeController;
36 import com.android.documentsui.BaseActivity;
37 import com.android.documentsui.FocusManager;
38 import com.android.documentsui.Injector;
39 import com.android.documentsui.MenuManager.DirectoryDetails;
40 import com.android.documentsui.ProviderExecutor;
41 import com.android.documentsui.R;
42 import com.android.documentsui.SharedInputHandler;
43 import com.android.documentsui.base.DocumentInfo;
44 import com.android.documentsui.base.Features;
45 import com.android.documentsui.base.MimeTypes;
46 import com.android.documentsui.base.RootInfo;
47 import com.android.documentsui.base.Shared;
48 import com.android.documentsui.base.State;
49 import com.android.documentsui.dirlist.DirectoryFragment;
50 import com.android.documentsui.prefs.ScopedPreferences;
51 import com.android.documentsui.selection.SelectionManager;
52 import com.android.documentsui.services.FileOperationService;
53 import com.android.documentsui.sidebar.RootsFragment;
54 import com.android.documentsui.ui.DialogController;
55 import com.android.documentsui.ui.MessageBuilder;
56 
57 import java.util.List;
58 
59 public class PickActivity extends BaseActivity implements ActionHandler.Addons {
60 
61     static final String PREFERENCES_SCOPE = "picker";
62 
63     private static final String TAG = "PickActivity";
64 
65     private Injector<ActionHandler<PickActivity>> mInjector;
66     private SharedInputHandler mSharedInputHandler;
67 
68     private LastAccessedStorage mLastAccessed;
69 
PickActivity()70     public PickActivity() {
71         super(R.layout.documents_activity, TAG);
72     }
73 
74     @Override
onCreate(Bundle icicle)75     public void onCreate(Bundle icicle) {
76 
77         Features features = Features.create(this);
78         mInjector = new Injector<>(
79                 features,
80                 new Config(),
81                 ScopedPreferences.create(this, PREFERENCES_SCOPE),
82                 new MessageBuilder(this),
83                 DialogController.create(this, null));
84 
85         super.onCreate(icicle);
86 
87         mInjector.selectionMgr = new SelectionManager(
88                 mState.allowMultiple
89                         ? SelectionManager.MODE_MULTIPLE
90                         : SelectionManager.MODE_SINGLE);
91 
92         mInjector.focusManager = new FocusManager(
93                 mInjector.features,
94                 mInjector.selectionMgr,
95                 mDrawer,
96                 this::focusSidebar,
97                 getColor(R.color.accent_dark));
98 
99         mInjector.menuManager = new MenuManager(mSearchManager, mState, new DirectoryDetails(this));
100 
101         mInjector.actionModeController = new ActionModeController(
102                 this,
103                 mInjector.selectionMgr,
104                 mInjector.menuManager,
105                 mInjector.messages);
106 
107         mLastAccessed = LastAccessedStorage.create();
108         mInjector.actions = new ActionHandler<>(
109                 this,
110                 mState,
111                 mProviders,
112                 mDocs,
113                 mSearchManager,
114                 ProviderExecutor::forAuthority,
115                 mInjector,
116                 mLastAccessed);
117 
118         mInjector.searchManager = mSearchManager;
119 
120         Intent intent = getIntent();
121 
122         mSharedInputHandler =
123                 new SharedInputHandler(
124                         mInjector.focusManager,
125                         mInjector.selectionMgr,
126                         mInjector.searchManager::cancelSearch,
127                         this::popDir,
128                         mInjector.features);
129         setupLayout(intent);
130         mInjector.actions.initLocation(intent);
131     }
132 
setupLayout(Intent intent)133     private void setupLayout(Intent intent) {
134         if (mState.action == ACTION_CREATE) {
135             final String mimeType = intent.getType();
136             final String title = intent.getStringExtra(Intent.EXTRA_TITLE);
137             SaveFragment.show(getFragmentManager(), mimeType, title);
138         } else if (mState.action == ACTION_OPEN_TREE ||
139                    mState.action == ACTION_PICK_COPY_DESTINATION) {
140             PickFragment.show(getFragmentManager());
141         }
142 
143         if (mState.action == ACTION_GET_CONTENT) {
144             final Intent moreApps = new Intent(intent);
145             moreApps.setComponent(null);
146             moreApps.setPackage(null);
147             RootsFragment.show(getFragmentManager(), moreApps);
148         } else if (mState.action == ACTION_OPEN ||
149                    mState.action == ACTION_CREATE ||
150                    mState.action == ACTION_OPEN_TREE ||
151                    mState.action == ACTION_PICK_COPY_DESTINATION) {
152             RootsFragment.show(getFragmentManager(), (Intent) null);
153         }
154     }
155 
156     @Override
includeState(State state)157     protected void includeState(State state) {
158         final Intent intent = getIntent();
159 
160         String defaultMimeType = (intent.getType() == null) ? "*/*" : intent.getType();
161         state.initAcceptMimes(intent, defaultMimeType);
162 
163         final String action = intent.getAction();
164         if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
165             state.action = ACTION_OPEN;
166         } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
167             state.action = ACTION_CREATE;
168         } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
169             state.action = ACTION_GET_CONTENT;
170         } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
171             state.action = ACTION_OPEN_TREE;
172         } else if (Shared.ACTION_PICK_COPY_DESTINATION.equals(action)) {
173             state.action = ACTION_PICK_COPY_DESTINATION;
174         }
175 
176         if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) {
177             state.allowMultiple = intent.getBooleanExtra(
178                     Intent.EXTRA_ALLOW_MULTIPLE, false);
179         }
180 
181         if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT
182                 || state.action == ACTION_CREATE) {
183             state.openableOnly = intent.hasCategory(Intent.CATEGORY_OPENABLE);
184         }
185 
186         if (state.action == ACTION_PICK_COPY_DESTINATION) {
187             // Indicates that a copy operation (or move) includes a directory.
188             // Why? Directory creation isn't supported by some roots (like Downloads).
189             // This allows us to restrict available roots to just those with support.
190             state.directoryCopy = intent.getBooleanExtra(
191                     Shared.EXTRA_DIRECTORY_COPY, false);
192             state.copyOperationSubType = intent.getIntExtra(
193                     FileOperationService.EXTRA_OPERATION_TYPE,
194                     FileOperationService.OPERATION_COPY);
195         }
196     }
197 
198     @Override
onPostCreate(Bundle savedInstanceState)199     protected void onPostCreate(Bundle savedInstanceState) {
200         super.onPostCreate(savedInstanceState);
201         mDrawer.update();
202         mNavigator.update();
203     }
204 
205     @Override
getDrawerTitle()206     public String getDrawerTitle() {
207         String title = getIntent().getStringExtra(DocumentsContract.EXTRA_PROMPT);
208         if (title == null) {
209             if (mState.action == ACTION_OPEN ||
210                 mState.action == ACTION_GET_CONTENT ||
211                 mState.action == ACTION_OPEN_TREE) {
212                 title = getResources().getString(R.string.title_open);
213             } else if (mState.action == ACTION_CREATE ||
214                        mState.action == ACTION_PICK_COPY_DESTINATION) {
215                 title = getResources().getString(R.string.title_save);
216             } else {
217                 // If all else fails, just call it "Documents".
218                 title = getResources().getString(R.string.app_label);
219             }
220         }
221 
222         return title;
223     }
224 
225     @Override
onPrepareOptionsMenu(Menu menu)226     public boolean onPrepareOptionsMenu(Menu menu) {
227         super.onPrepareOptionsMenu(menu);
228         mInjector.menuManager.updateOptionMenu(menu);
229 
230         final DocumentInfo cwd = getCurrentDirectory();
231 
232         if (mState.action == ACTION_CREATE) {
233             final FragmentManager fm = getFragmentManager();
234             SaveFragment.get(fm).prepareForDirectory(cwd);
235         }
236 
237         return true;
238     }
239 
240     @Override
refreshDirectory(int anim)241     protected void refreshDirectory(int anim) {
242         final FragmentManager fm = getFragmentManager();
243         final RootInfo root = getCurrentRoot();
244         final DocumentInfo cwd = getCurrentDirectory();
245 
246         if (mState.stack.isRecents()) {
247             DirectoryFragment.showRecentsOpen(fm, anim);
248 
249             // In recents we pick layout mode based on the mimetype,
250             // picking GRID for visual types. We intentionally don't
251             // consult a user's saved preferences here since they are
252             // set per root (not per root and per mimetype).
253             boolean visualMimes = MimeTypes.mimeMatches(
254                     MimeTypes.VISUAL_MIMES, mState.acceptMimes);
255             mState.derivedMode = visualMimes ? State.MODE_GRID : State.MODE_LIST;
256         } else {
257                 // Normal boring directory
258                 DirectoryFragment.showDirectory(fm, root, cwd, anim);
259         }
260 
261         // Forget any replacement target
262         if (mState.action == ACTION_CREATE) {
263             final SaveFragment save = SaveFragment.get(fm);
264             if (save != null) {
265                 save.setReplaceTarget(null);
266             }
267         }
268 
269         if (mState.action == ACTION_OPEN_TREE ||
270             mState.action == ACTION_PICK_COPY_DESTINATION) {
271             final PickFragment pick = PickFragment.get(fm);
272             if (pick != null) {
273                 pick.setPickTarget(mState.action, mState.copyOperationSubType, cwd);
274             }
275         }
276     }
277 
278     @Override
onDirectoryCreated(DocumentInfo doc)279     protected void onDirectoryCreated(DocumentInfo doc) {
280         assert(doc.isDirectory());
281         mInjector.actions.openContainerDocument(doc);
282     }
283 
284     @Override
onDocumentPicked(DocumentInfo doc)285     public void onDocumentPicked(DocumentInfo doc) {
286         final FragmentManager fm = getFragmentManager();
287         // Do not inline-open archives, as otherwise it would be impossible to pick
288         // archive files. Note, that picking files inside archives is not supported.
289         if (doc.isDirectory()) {
290             mInjector.actions.openContainerDocument(doc);
291         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
292             // Explicit file picked, return
293             mInjector.actions.finishPicking(doc.derivedUri);
294         } else if (mState.action == ACTION_CREATE) {
295             // Replace selected file
296             SaveFragment.get(fm).setReplaceTarget(doc);
297         }
298     }
299 
300     @Override
onDocumentsPicked(List<DocumentInfo> docs)301     public void onDocumentsPicked(List<DocumentInfo> docs) {
302         if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
303             final int size = docs.size();
304             final Uri[] uris = new Uri[size];
305             for (int i = 0; i < size; i++) {
306                 uris[i] = docs.get(i).derivedUri;
307             }
308             mInjector.actions.finishPicking(uris);
309         }
310     }
311 
312     @CallSuper
313     @Override
onKeyDown(int keyCode, KeyEvent event)314     public boolean onKeyDown(int keyCode, KeyEvent event) {
315         return mSharedInputHandler.onKeyDown(
316                 keyCode,
317                 event)
318                 || super.onKeyDown(keyCode, event);
319     }
320 
321     @Override
setResult(int resultCode, Intent intent, int notUsed)322     public void setResult(int resultCode, Intent intent, int notUsed) {
323         setResult(resultCode, intent);
324     }
325 
get(Fragment fragment)326     public static PickActivity get(Fragment fragment) {
327         return (PickActivity) fragment.getActivity();
328     }
329 
330     @Override
getInjector()331     public Injector<ActionHandler<PickActivity>> getInjector() {
332         return mInjector;
333     }
334 }
335