1 /*
2 * Copyright (C) 2012 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.storageclient;
18 
19 import android.app.Activity;
20 import android.app.Dialog;
21 import android.content.Intent;
22 import android.database.Cursor;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.net.Uri;
26 import android.os.AsyncTask;
27 import android.os.Bundle;
28 import android.os.ParcelFileDescriptor;
29 import android.provider.OpenableColumns;
30 import android.support.v4.app.DialogFragment;
31 import android.support.v4.app.Fragment;
32 import android.support.v4.app.FragmentManager;
33 import android.view.MenuItem;
34 import android.view.Window;
35 import android.widget.ImageView;
36 
37 import com.example.android.common.logger.Log;
38 
39 import java.io.FileDescriptor;
40 import java.io.IOException;
41 
42 public class StorageClientFragment extends Fragment {
43 
44     // A request code's purpose is to match the result of a "startActivityForResult" with
45     // the type of the original request.  Choose any value.
46     private static final int READ_REQUEST_CODE = 1337;
47 
48     public static final String TAG = "StorageClientFragment";
49 
50     @Override
onCreate(Bundle savedInstanceState)51     public void onCreate(Bundle savedInstanceState) {
52         super.onCreate(savedInstanceState);
53         setHasOptionsMenu(true);
54     }
55 
56     @Override
onOptionsItemSelected(MenuItem item)57     public boolean onOptionsItemSelected(MenuItem item) {
58         if (item.getItemId() == R.id.sample_action) {
59             performFileSearch();
60         }
61         return true;
62     }
63 
64     /**
65      * Fires an intent to spin up the "file chooser" UI and select an image.
66      */
performFileSearch()67     public void performFileSearch() {
68 
69         // BEGIN_INCLUDE (use_open_document_intent)
70         // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file browser.
71         Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
72 
73         // Filter to only show results that can be "opened", such as a file (as opposed to a list
74         // of contacts or timezones)
75         intent.addCategory(Intent.CATEGORY_OPENABLE);
76 
77         // Filter to show only images, using the image MIME data type.
78         // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
79         // To search for all documents available via installed storage providers, it would be
80         // "*/*".
81         intent.setType("image/*");
82 
83         startActivityForResult(intent, READ_REQUEST_CODE);
84         // END_INCLUDE (use_open_document_intent)
85     }
86 
87     @Override
onActivityResult(int requestCode, int resultCode, Intent resultData)88     public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
89         Log.i(TAG, "Received an \"Activity Result\"");
90         // BEGIN_INCLUDE (parse_open_document_response)
91         // The ACTION_OPEN_DOCUMENT intent was sent with the request code READ_REQUEST_CODE.
92         // If the request code seen here doesn't match, it's the response to some other intent,
93         // and the below code shouldn't run at all.
94 
95         if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
96             // The document selected by the user won't be returned in the intent.
97             // Instead, a URI to that document will be contained in the return intent
98             // provided to this method as a parameter.  Pull that uri using "resultData.getData()"
99             Uri uri = null;
100             if (resultData != null) {
101                 uri = resultData.getData();
102                 Log.i(TAG, "Uri: " + uri.toString());
103                 showImage(uri);
104             }
105             // END_INCLUDE (parse_open_document_response)
106         }
107     }
108 
109     /**
110      * Given the URI of an image, shows it on the screen using a DialogFragment.
111      *
112      * @param uri the Uri of the image to display.
113      */
showImage(Uri uri)114     public void showImage(Uri uri) {
115         // BEGIN_INCLUDE (create_show_image_dialog)
116         if (uri != null) {
117             // Since the URI is to an image, create and show a DialogFragment to display the
118             // image to the user.
119             FragmentManager fm = getActivity().getSupportFragmentManager();
120             ImageDialogFragment imageDialog = new ImageDialogFragment();
121             Bundle fragmentArguments = new Bundle();
122             fragmentArguments.putParcelable("URI", uri);
123             imageDialog.setArguments(fragmentArguments);
124             imageDialog.show(fm, "image_dialog");
125         }
126         // END_INCLUDE (create_show_image_dialog)
127     }
128 
129 
130     /**
131      * DialogFragment which displays an image, given a URI.
132      */
133     public static class ImageDialogFragment extends DialogFragment {
134         private Dialog mDialog;
135         private Uri mUri;
136 
137         @Override
onCreate(Bundle savedInstanceState)138         public void onCreate(Bundle savedInstanceState) {
139             super.onCreate(savedInstanceState);
140             mUri = getArguments().getParcelable("URI");
141         }
142 
143         /** Create a Bitmap from the URI for that image and return it.
144          *
145          * @param uri the Uri for the image to return.
146          */
getBitmapFromUri(Uri uri)147         private Bitmap getBitmapFromUri(Uri uri) {
148             ParcelFileDescriptor parcelFileDescriptor = null;
149             try {
150                 parcelFileDescriptor =
151                         getActivity().getContentResolver().openFileDescriptor(uri, "r");
152                 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
153                 Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
154                 parcelFileDescriptor.close();
155                 return image;
156             } catch (Exception e) {
157                 Log.e(TAG, "Failed to load image.", e);
158                 return null;
159             } finally {
160                 try {
161                     if (parcelFileDescriptor != null) {
162                         parcelFileDescriptor.close();
163                     }
164                 } catch (IOException e) {
165                     e.printStackTrace();
166                     Log.e(TAG, "Error closing ParcelFile Descriptor");
167                 }
168             }
169         }
170 
171         @Override
onCreateDialog(Bundle savedInstanceState)172         public Dialog onCreateDialog(Bundle savedInstanceState) {
173             mDialog = super.onCreateDialog(savedInstanceState);
174             // To optimize for the "lightbox" style layout.  Since we're not actually displaying a
175             // title, remove the bar along the top of the fragment where a dialog title would
176             // normally go.
177             mDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
178             final ImageView imageView = new ImageView(getActivity());
179             mDialog.setContentView(imageView);
180 
181             // BEGIN_INCLUDE (show_image)
182             // Loading the image is going to require some sort of I/O, which must occur off the UI
183             // thread.  Changing the ImageView to display the image must occur ON the UI thread.
184             // The easiest way to divide up this labor is with an AsyncTask.  The doInBackground
185             // method will run in a separate thread, but onPostExecute will run in the main
186             // UI thread.
187             AsyncTask<Uri, Void, Bitmap> imageLoadAsyncTask = new AsyncTask<Uri, Void, Bitmap>() {
188                 @Override
189                 protected Bitmap doInBackground(Uri... uris) {
190                     dumpImageMetaData(uris[0]);
191                     return getBitmapFromUri(uris[0]);
192                 }
193 
194                 @Override
195                 protected void onPostExecute(Bitmap bitmap) {
196                     imageView.setImageBitmap(bitmap);
197                 }
198             };
199             imageLoadAsyncTask.execute(mUri);
200             // END_INCLUDE (show_image)
201 
202             return mDialog;
203         }
204 
205         @Override
onStop()206         public void onStop() {
207             super.onStop();
208             if (getDialog() != null) {
209                 getDialog().dismiss();
210             }
211         }
212 
213         /**
214          * Grabs metadata for a document specified by URI, logs it to the screen.
215          *
216          * @param uri The uri for the document whose metadata should be printed.
217          */
dumpImageMetaData(Uri uri)218         public void dumpImageMetaData(Uri uri) {
219             // BEGIN_INCLUDE (dump_metadata)
220 
221             // The query, since it only applies to a single document, will only return one row.
222             // no need to filter, sort, or select fields, since we want all fields for one
223             // document.
224             Cursor cursor = getActivity().getContentResolver()
225                     .query(uri, null, null, null, null, null);
226 
227             try {
228                 // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
229                 // "if there's anything to look at, look at it" conditionals.
230                 if (cursor != null && cursor.moveToFirst()) {
231 
232                     // Note it's called "Display Name".  This is provider-specific, and
233                     // might not necessarily be the file name.
234                     String displayName = cursor.getString(
235                             cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
236                     Log.i(TAG, "Display Name: " + displayName);
237 
238                     int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
239                     // If the size is unknown, the value stored is null.  But since an int can't be
240                     // null in java, the behavior is implementation-specific, which is just a fancy
241                     // term for "unpredictable".  So as a rule, check if it's null before assigning
242                     // to an int.  This will happen often:  The storage API allows for remote
243                     // files, whose size might not be locally known.
244                     String size = null;
245                     if (!cursor.isNull(sizeIndex)) {
246                         // Technically the column stores an int, but cursor.getString will do the
247                         // conversion automatically.
248                         size = cursor.getString(sizeIndex);
249                     } else {
250                         size = "Unknown";
251                     }
252                     Log.i(TAG, "Size: " + size);
253                 }
254             } finally {
255                 if (cursor != null) {
256                     cursor.close();
257                 }
258             }
259             // END_INCLUDE (dump_metadata)
260         }
261     }
262 }
263