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