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