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