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.Path;
38import com.android.gallery3d.picasasource.PicasaSource;
39import com.android.gallery3d.util.GalleryUtils;
40
41import java.io.FileNotFoundException;
42import java.io.IOException;
43
44public 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
69    public static String getAuthority(Context context) {
70        return context.getPackageName() + ".provider";
71    }
72
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
83    public int delete(Uri uri, String selection, String[] selectionArgs) {
84        throw new UnsupportedOperationException();
85    }
86
87    // TODO: consider concurrent access
88    @Override
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
101    public Uri insert(Uri uri, ContentValues values) {
102        throw new UnsupportedOperationException();
103    }
104
105    @Override
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
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
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
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
196    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
197        throw new UnsupportedOperationException();
198    }
199
200    private static interface PipeDataWriter<T> {
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.
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