1 /* 2 * Copyright (C) 2009 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.android.gallery3d.provider; 18 19 import android.content.ContentProvider; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.database.MatrixCursor; 24 import android.net.Uri; 25 import android.os.AsyncTask; 26 import android.os.Binder; 27 import android.os.ParcelFileDescriptor; 28 import android.provider.MediaStore.Images.ImageColumns; 29 import android.util.Log; 30 31 import com.android.gallery3d.app.GalleryApp; 32 import com.android.gallery3d.common.AsyncTaskUtil; 33 import com.android.gallery3d.common.Utils; 34 import com.android.gallery3d.data.DataManager; 35 import com.android.gallery3d.data.MediaItem; 36 import com.android.gallery3d.data.MediaObject; 37 import com.android.gallery3d.data.Path; 38 import com.android.gallery3d.picasasource.PicasaSource; 39 import com.android.gallery3d.util.GalleryUtils; 40 41 import java.io.FileNotFoundException; 42 import java.io.IOException; 43 44 public class GalleryProvider extends ContentProvider { 45 private static final String TAG = "GalleryProvider"; 46 47 public static final String AUTHORITY = "com.android.gallery3d.provider"; 48 public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY); 49 50 public static interface PicasaColumns { 51 public static final String USER_ACCOUNT = "user_account"; 52 public static final String PICASA_ID = "picasa_id"; 53 } 54 55 private static final String[] SUPPORTED_PICASA_COLUMNS = { 56 PicasaColumns.USER_ACCOUNT, 57 PicasaColumns.PICASA_ID, 58 ImageColumns.DISPLAY_NAME, 59 ImageColumns.SIZE, 60 ImageColumns.MIME_TYPE, 61 ImageColumns.DATE_TAKEN, 62 ImageColumns.LATITUDE, 63 ImageColumns.LONGITUDE, 64 ImageColumns.ORIENTATION}; 65 66 private DataManager mDataManager; 67 private static Uri sBaseUri; 68 getAuthority(Context context)69 public static String getAuthority(Context context) { 70 return context.getPackageName() + ".provider"; 71 } 72 getUriFor(Context context, Path path)73 public static Uri getUriFor(Context context, Path path) { 74 if (sBaseUri == null) { 75 sBaseUri = Uri.parse("content://" + context.getPackageName() + ".provider"); 76 } 77 return sBaseUri.buildUpon() 78 .appendEncodedPath(path.toString().substring(1)) // ignore the leading '/' 79 .build(); 80 } 81 82 @Override delete(Uri uri, String selection, String[] selectionArgs)83 public int delete(Uri uri, String selection, String[] selectionArgs) { 84 throw new UnsupportedOperationException(); 85 } 86 87 // TODO: consider concurrent access 88 @Override getType(Uri uri)89 public String getType(Uri uri) { 90 long token = Binder.clearCallingIdentity(); 91 try { 92 Path path = Path.fromString(uri.getPath()); 93 MediaItem item = (MediaItem) mDataManager.getMediaObject(path); 94 return item != null ? item.getMimeType() : null; 95 } finally { 96 Binder.restoreCallingIdentity(token); 97 } 98 } 99 100 @Override insert(Uri uri, ContentValues values)101 public Uri insert(Uri uri, ContentValues values) { 102 throw new UnsupportedOperationException(); 103 } 104 105 @Override onCreate()106 public boolean onCreate() { 107 GalleryApp app = (GalleryApp) getContext().getApplicationContext(); 108 mDataManager = app.getDataManager(); 109 return true; 110 } 111 112 // TODO: consider concurrent access 113 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)114 public Cursor query(Uri uri, String[] projection, 115 String selection, String[] selectionArgs, String sortOrder) { 116 long token = Binder.clearCallingIdentity(); 117 try { 118 Path path = Path.fromString(uri.getPath()); 119 MediaObject object = mDataManager.getMediaObject(path); 120 if (object == null) { 121 Log.w(TAG, "cannot find: " + uri); 122 return null; 123 } 124 if (PicasaSource.isPicasaImage(object)) { 125 return queryPicasaItem(object, 126 projection, selection, selectionArgs, sortOrder); 127 } else { 128 return null; 129 } 130 } finally { 131 Binder.restoreCallingIdentity(token); 132 } 133 } 134 queryPicasaItem(MediaObject image, String[] projection, String selection, String[] selectionArgs, String sortOrder)135 private Cursor queryPicasaItem(MediaObject image, String[] projection, 136 String selection, String[] selectionArgs, String sortOrder) { 137 if (projection == null) projection = SUPPORTED_PICASA_COLUMNS; 138 Object[] columnValues = new Object[projection.length]; 139 double latitude = PicasaSource.getLatitude(image); 140 double longitude = PicasaSource.getLongitude(image); 141 boolean isValidLatlong = GalleryUtils.isValidLocation(latitude, longitude); 142 143 for (int i = 0, n = projection.length; i < n; ++i) { 144 String column = projection[i]; 145 if (PicasaColumns.USER_ACCOUNT.equals(column)) { 146 columnValues[i] = PicasaSource.getUserAccount(getContext(), image); 147 } else if (PicasaColumns.PICASA_ID.equals(column)) { 148 columnValues[i] = PicasaSource.getPicasaId(image); 149 } else if (ImageColumns.DISPLAY_NAME.equals(column)) { 150 columnValues[i] = PicasaSource.getImageTitle(image); 151 } else if (ImageColumns.SIZE.equals(column)){ 152 columnValues[i] = PicasaSource.getImageSize(image); 153 } else if (ImageColumns.MIME_TYPE.equals(column)) { 154 columnValues[i] = PicasaSource.getContentType(image); 155 } else if (ImageColumns.DATE_TAKEN.equals(column)) { 156 columnValues[i] = PicasaSource.getDateTaken(image); 157 } else if (ImageColumns.LATITUDE.equals(column)) { 158 columnValues[i] = isValidLatlong ? latitude : null; 159 } else if (ImageColumns.LONGITUDE.equals(column)) { 160 columnValues[i] = isValidLatlong ? longitude : null; 161 } else if (ImageColumns.ORIENTATION.equals(column)) { 162 columnValues[i] = PicasaSource.getRotation(image); 163 } else { 164 Log.w(TAG, "unsupported column: " + column); 165 } 166 } 167 MatrixCursor cursor = new MatrixCursor(projection); 168 cursor.addRow(columnValues); 169 return cursor; 170 } 171 172 @Override openFile(Uri uri, String mode)173 public ParcelFileDescriptor openFile(Uri uri, String mode) 174 throws FileNotFoundException { 175 long token = Binder.clearCallingIdentity(); 176 try { 177 if (mode.contains("w")) { 178 throw new FileNotFoundException("cannot open file for write"); 179 } 180 Path path = Path.fromString(uri.getPath()); 181 MediaObject object = mDataManager.getMediaObject(path); 182 if (object == null) { 183 throw new FileNotFoundException(uri.toString()); 184 } 185 if (PicasaSource.isPicasaImage(object)) { 186 return PicasaSource.openFile(getContext(), object, mode); 187 } else { 188 throw new FileNotFoundException("unspported type: " + object); 189 } 190 } finally { 191 Binder.restoreCallingIdentity(token); 192 } 193 } 194 195 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)196 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 197 throw new UnsupportedOperationException(); 198 } 199 200 private static interface PipeDataWriter<T> { writeDataToPipe(ParcelFileDescriptor output, T args)201 void writeDataToPipe(ParcelFileDescriptor output, T args); 202 } 203 204 // Modified from ContentProvider.openPipeHelper. We are target at API LEVEL 10. 205 // But openPipeHelper is available in API LEVEL 11. openPipeHelper( final T args, final PipeDataWriter<T> func)206 private static <T> ParcelFileDescriptor openPipeHelper( 207 final T args, final PipeDataWriter<T> func) throws FileNotFoundException { 208 try { 209 final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 210 AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() { 211 @Override 212 protected Object doInBackground(Object... params) { 213 try { 214 func.writeDataToPipe(pipe[1], args); 215 return null; 216 } finally { 217 Utils.closeSilently(pipe[1]); 218 } 219 } 220 }; 221 AsyncTaskUtil.executeInParallel(task, (Object[]) null); 222 return pipe[0]; 223 } catch (IOException e) { 224 throw new FileNotFoundException("failure making pipe"); 225 } 226 } 227 228 } 229