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