Home | History | Annotate | Download | only in scopeddirectoryaccess
      1 /*
      2 * Copyright 2016 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.example.android.scopeddirectoryaccess;
     18 
     19 import android.app.Activity;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.database.Cursor;
     24 import android.net.Uri;
     25 import android.os.Build;
     26 import android.os.Bundle;
     27 import android.os.Environment;
     28 import android.os.storage.StorageManager;
     29 import android.os.storage.StorageVolume;
     30 import android.provider.DocumentsContract;
     31 import android.support.v4.app.Fragment;
     32 import android.support.v7.widget.LinearLayoutManager;
     33 import android.support.v7.widget.RecyclerView;
     34 import android.util.Log;
     35 import android.view.LayoutInflater;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 import android.widget.ArrayAdapter;
     39 import android.widget.Button;
     40 import android.widget.LinearLayout;
     41 import android.widget.Spinner;
     42 import android.widget.TextView;
     43 import android.widget.Toast;
     44 
     45 import java.util.ArrayList;
     46 import java.util.List;
     47 
     48 /**
     49  * Fragment that displays the directory contents.
     50  */
     51 public class ScopedDirectoryAccessFragment extends Fragment {
     52 
     53     private static final String TAG = "ScopedDirectorySample";
     54 
     55     private static final String DIRECTORY_ENTRIES_KEY = "directory_entries";
     56     private static final String SELECTED_DIRECTORY_KEY = "selected_directory";
     57     private static final int OPEN_DIRECTORY_REQUEST_CODE = 1;
     58 
     59     private static final String[] DIRECTORY_SELECTION = new String[]{
     60             DocumentsContract.Document.COLUMN_DISPLAY_NAME,
     61             DocumentsContract.Document.COLUMN_MIME_TYPE,
     62             DocumentsContract.Document.COLUMN_DOCUMENT_ID,
     63     };
     64 
     65     private Activity mActivity;
     66     private StorageManager mStorageManager;
     67     private TextView mCurrentDirectoryTextView;
     68     private TextView mNothingInDirectoryTextView;
     69     private TextView mPrimaryVolumeNameTextView;
     70     private Spinner mDirectoriesSpinner;
     71     private LinearLayout mDirectoryAccessSettings;
     72     private Button mLaunchDirectoryAccessSettings;
     73 
     74     private DirectoryEntryAdapter mAdapter;
     75     private ArrayList<DirectoryEntry> mDirectoryEntries;
     76 
     77     public static ScopedDirectoryAccessFragment newInstance() {
     78         ScopedDirectoryAccessFragment fragment = new ScopedDirectoryAccessFragment();
     79         return fragment;
     80     }
     81 
     82     public ScopedDirectoryAccessFragment() {
     83         // Required empty public constructor
     84     }
     85 
     86     @Override
     87     public void onAttach(Context context) {
     88         super.onAttach(context);
     89         mActivity = getActivity();
     90         mStorageManager = mActivity.getSystemService(StorageManager.class);
     91     }
     92 
     93     @Override
     94     public void onActivityResult(int requestCode, int resultCode, Intent data) {
     95         super.onActivityResult(requestCode, resultCode, data);
     96         if (requestCode == OPEN_DIRECTORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
     97             getActivity().getContentResolver().takePersistableUriPermission(data.getData(),
     98                     Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
     99             updateDirectoryEntries(data.getData());
    100         }
    101     }
    102 
    103     @Override
    104     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    105             Bundle savedInstanceState) {
    106         return inflater.inflate(R.layout.fragment_scoped_directory_access, container, false);
    107     }
    108 
    109     @Override
    110     public void onViewCreated(final View rootView, Bundle savedInstanceState) {
    111         super.onViewCreated(rootView, savedInstanceState);
    112 
    113         mCurrentDirectoryTextView = rootView.findViewById(R.id.textview_current_directory);
    114         mNothingInDirectoryTextView = rootView.findViewById(R.id.textview_nothing_in_directory);
    115         mPrimaryVolumeNameTextView = rootView.findViewById(R.id.textview_primary_volume_name);
    116 
    117         mDirectoryAccessSettings = rootView.findViewById(R.id.directory_access_settings);
    118         mLaunchDirectoryAccessSettings = rootView.findViewById(R.id.launch);
    119 
    120         // TODO: proper API check
    121         if (Build.VERSION.CODENAME.equals("P")) {
    122             mDirectoryAccessSettings.setVisibility(View.VISIBLE);
    123             mLaunchDirectoryAccessSettings.setOnClickListener(new View.OnClickListener() {
    124                 @Override
    125                 public void onClick(View view) {
    126                     // TODO: use API constant for intent name
    127                     Intent intent = new Intent("android.settings.DIRECTORY_ACCESS_SETTINGS");
    128                     startActivity(intent);
    129                 }
    130             });
    131         }
    132 
    133         // Set onClickListener for the primary volume
    134         Button openPictureButton = (Button) rootView
    135                 .findViewById(R.id.button_open_directory_primary_volume);
    136         openPictureButton.setOnClickListener(new View.OnClickListener() {
    137             @Override
    138             public void onClick(View view) {
    139                 String selected = mDirectoriesSpinner.getSelectedItem().toString();
    140                 String directoryName = getDirectoryName(selected);
    141                 Log.d(TAG, "Primary: selected=" + selected + " dir=" + directoryName);
    142                 if (directoryName == null) {
    143                     Toast.makeText(getContext(), "Cannot select ROOT on primary directory",
    144                             Toast.LENGTH_LONG).show();
    145                     return;
    146                 }
    147                 StorageVolume storageVolume = mStorageManager.getPrimaryStorageVolume();
    148                 Intent intent = storageVolume.createAccessIntent(directoryName);
    149                 startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
    150             }
    151         });
    152 
    153         // Set onClickListener for the external volumes if exists
    154         List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes();
    155         LinearLayout containerVolumes = (LinearLayout) mActivity
    156                 .findViewById(R.id.container_volumes);
    157         for (final StorageVolume volume : storageVolumes) {
    158             String volumeDescription = volume.getDescription(mActivity);
    159             if (volume.isPrimary()) {
    160                 // Primary volume area is already added...
    161                 if (volumeDescription != null) {
    162                     // ...but with a default name: set it to the real name when available.
    163                     mPrimaryVolumeNameTextView.setText(volumeDescription);
    164                 }
    165                 continue;
    166             }
    167             LinearLayout volumeArea = (LinearLayout) mActivity.getLayoutInflater()
    168                     .inflate(R.layout.volume_entry, containerVolumes);
    169             final TextView volumeName = volumeArea.findViewById(R.id.textview_volume_name);
    170             volumeName.setText(volumeDescription);
    171             Button button = (Button) volumeArea.findViewById(R.id.button_open_directory);
    172             button.setOnClickListener(new View.OnClickListener() {
    173                 @Override
    174                 public void onClick(View view) {
    175                     String selected = mDirectoriesSpinner.getSelectedItem().toString();
    176                     String directoryName = getDirectoryName(selected);
    177                     Log.d(TAG, "Secondary: volume=" + volumeName.getText() + ", selected=" + selected
    178                             + ", dir=" + directoryName);
    179                     Intent intent = volume.createAccessIntent(directoryName);
    180                     startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
    181                 }
    182             });
    183         }
    184         RecyclerView recyclerView = (RecyclerView) rootView
    185                 .findViewById(R.id.recyclerview_directory_entries);
    186         if (savedInstanceState != null) {
    187             mDirectoryEntries = savedInstanceState.getParcelableArrayList(DIRECTORY_ENTRIES_KEY);
    188             mCurrentDirectoryTextView.setText(savedInstanceState.getString(SELECTED_DIRECTORY_KEY));
    189             mAdapter = new DirectoryEntryAdapter(mDirectoryEntries);
    190             if (mAdapter.getItemCount() == 0) {
    191                 mNothingInDirectoryTextView.setVisibility(View.VISIBLE);
    192             }
    193         } else {
    194             mDirectoryEntries = new ArrayList<>();
    195             mAdapter = new DirectoryEntryAdapter();
    196         }
    197         recyclerView.setAdapter(mAdapter);
    198         recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    199         mDirectoriesSpinner = (Spinner) rootView.findViewById(R.id.spinner_directories);
    200         ArrayAdapter<CharSequence> directoriesAdapter = ArrayAdapter
    201                 .createFromResource(getActivity(),
    202                         R.array.directories, android.R.layout.simple_spinner_item);
    203         directoriesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    204         mDirectoriesSpinner.setAdapter(directoriesAdapter);
    205     }
    206 
    207     @Override
    208     public void onSaveInstanceState(Bundle outState) {
    209         super.onSaveInstanceState(outState);
    210         outState.putString(SELECTED_DIRECTORY_KEY, mCurrentDirectoryTextView.getText().toString());
    211         outState.putParcelableArrayList(DIRECTORY_ENTRIES_KEY, mDirectoryEntries);
    212     }
    213 
    214     private void updateDirectoryEntries(Uri uri) {
    215         mDirectoryEntries.clear();
    216         ContentResolver contentResolver = getActivity().getContentResolver();
    217         Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri,
    218                 DocumentsContract.getTreeDocumentId(uri));
    219         Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri,
    220                 DocumentsContract.getTreeDocumentId(uri));
    221 
    222         try (Cursor docCursor = contentResolver
    223                 .query(docUri, DIRECTORY_SELECTION, null, null, null)) {
    224             while (docCursor != null && docCursor.moveToNext()) {
    225                 mCurrentDirectoryTextView.setText(docCursor.getString(docCursor.getColumnIndex(
    226                         DocumentsContract.Document.COLUMN_DISPLAY_NAME)));
    227             }
    228         }
    229 
    230         try (Cursor childCursor = contentResolver
    231                 .query(childrenUri, DIRECTORY_SELECTION, null, null, null)) {
    232             while (childCursor != null && childCursor.moveToNext()) {
    233                 DirectoryEntry entry = new DirectoryEntry();
    234                 entry.fileName = childCursor.getString(childCursor.getColumnIndex(
    235                         DocumentsContract.Document.COLUMN_DISPLAY_NAME));
    236                 entry.mimeType = childCursor.getString(childCursor.getColumnIndex(
    237                         DocumentsContract.Document.COLUMN_MIME_TYPE));
    238                 mDirectoryEntries.add(entry);
    239             }
    240 
    241             if (mDirectoryEntries.isEmpty()) {
    242                 mNothingInDirectoryTextView.setVisibility(View.VISIBLE);
    243             } else {
    244                 mNothingInDirectoryTextView.setVisibility(View.GONE);
    245             }
    246             mAdapter.setDirectoryEntries(mDirectoryEntries);
    247             mAdapter.notifyDataSetChanged();
    248         }
    249     }
    250 
    251     private String getDirectoryName(String name) {
    252         switch (name) {
    253             case "ROOT":
    254                 return null;
    255             case "ALARMS":
    256                 return Environment.DIRECTORY_ALARMS;
    257             case "DCIM":
    258                 return Environment.DIRECTORY_DCIM;
    259             case "DOCUMENTS":
    260                 return Environment.DIRECTORY_DOCUMENTS;
    261             case "DOWNLOADS":
    262                 return Environment.DIRECTORY_DOWNLOADS;
    263             case "MOVIES":
    264                 return Environment.DIRECTORY_MOVIES;
    265             case "MUSIC":
    266                 return Environment.DIRECTORY_MUSIC;
    267             case "NOTIFICATIONS":
    268                 return Environment.DIRECTORY_NOTIFICATIONS;
    269             case "PICTURES":
    270                 return Environment.DIRECTORY_PICTURES;
    271             case "PODCASTS":
    272                 return Environment.DIRECTORY_PODCASTS;
    273             case "RINGTONES":
    274                 return Environment.DIRECTORY_RINGTONES;
    275             default:
    276                 throw new IllegalArgumentException("Invalid directory representation: " + name);
    277         }
    278     }
    279 }
    280