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