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