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