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