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