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