Home | History | Annotate | Download | only in picker
      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