1/*
2 * Copyright (C) 2008 Esmertec AG.
3 * Copyright (C) 2008 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.im.app;
19
20import com.android.im.plugin.ImConfigNames;
21import com.android.im.provider.Imps;
22
23import android.content.ContentResolver;
24import android.content.ContentUris;
25import android.content.ContentValues;
26import android.database.Cursor;
27import android.graphics.Bitmap;
28import android.graphics.BitmapFactory;
29import android.graphics.drawable.BitmapDrawable;
30import android.graphics.drawable.Drawable;
31import android.net.Uri;
32import android.util.Log;
33import android.util.Base64;
34
35import java.util.Map;
36
37public class DatabaseUtils {
38
39    private static final String TAG = ImApp.LOG_TAG;
40
41    private DatabaseUtils() {
42    }
43
44    public static Cursor queryAccountsForProvider(ContentResolver cr,
45            String[] projection, long providerId) {
46        StringBuilder where = new StringBuilder(Imps.Account.ACTIVE);
47        where.append("=1 AND ").append(Imps.Account.PROVIDER).append('=').append(providerId);
48        Cursor c = cr.query(Imps.Account.CONTENT_URI, projection, where.toString(), null, null);
49        if (c != null && !c.moveToFirst()) {
50            c.close();
51            return null;
52        }
53        return c;
54    }
55
56    public static Drawable getAvatarFromCursor(Cursor cursor, int dataColumn) {
57        byte[] rawData = cursor.getBlob(dataColumn);
58        if (rawData == null) {
59            return null;
60        }
61        return decodeAvatar(rawData);
62    }
63
64    public static Uri getAvatarUri(Uri baseUri, long providerId, long accountId) {
65        Uri.Builder builder = baseUri.buildUpon();
66        ContentUris.appendId(builder, providerId);
67        ContentUris.appendId(builder, accountId);
68        return builder.build();
69    }
70
71    public static Drawable getAvatarFromCursor(Cursor cursor, int dataColumn,
72            int encodedDataColumn, String username, boolean updateBlobUseCursor,
73            ContentResolver resolver, Uri updateBlobUri) {
74        /**
75         * Optimization: the avatar table in IM content provider have two
76         * columns, one for the raw blob data, another for the base64 encoded
77         * data. The reason for this is when the avatars are initially
78         * downloaded, they are in the base64 encoded form, and instead of
79         * base64 decode the avatars for all the buddies up front, we can just
80         * simply store the encoded data in the table, and decode them on demand
81         * when displaying them. Once we decode the avatar, we store the decoded
82         * data as a blob, and null out the encoded column in the avatars table.
83         * query the raw blob data first, if present, great; if not, query the
84         * encoded data, decode it and store as the blob, and null out the
85         * encoded column.
86         */
87        byte[] rawData = cursor.getBlob(dataColumn);
88
89        if (rawData == null) {
90            String encodedData = cursor.getString(encodedDataColumn);
91            if (encodedData == null) {
92                // Log.e(LogTag.LOG_TAG, "getAvatarFromCursor for " + username +
93                // ", no raw or encoded data!");
94                return null;
95            }
96
97            rawData = Base64.decode(encodedData, Base64.DEFAULT);
98
99            // if (DBG) {
100            // log("getAvatarFromCursor for " + username + ": found encoded
101            // data,"
102            // + " update blob with data, len=" + rawData.length);
103            // }
104
105            if (updateBlobUseCursor) {
106                cursor.updateBlob(dataColumn, rawData);
107                cursor.updateString(encodedDataColumn, null);
108                cursor.commitUpdates();
109            } else {
110                updateAvatarBlob(resolver, updateBlobUri, rawData, username);
111            }
112        }
113
114        return decodeAvatar(rawData);
115    }
116
117    private static void updateAvatarBlob(ContentResolver resolver, Uri updateUri, byte[] data,
118            String username) {
119        ContentValues values = new ContentValues(3);
120        values.put(Imps.Avatars.DATA, data);
121
122        StringBuilder buf = new StringBuilder(Imps.Avatars.CONTACT);
123        buf.append("=?");
124
125        String[] selectionArgs = new String[] {
126            username
127        };
128
129        resolver.update(updateUri, values, buf.toString(), selectionArgs);
130    }
131
132    private static Drawable decodeAvatar(byte[] data) {
133        Bitmap b = BitmapFactory.decodeByteArray(data, 0, data.length);
134        Drawable avatar = new BitmapDrawable(b);
135        return avatar;
136    }
137
138    /**
139     * Update IM provider database for a plugin using newly loaded information.
140     * @param cr the resolver
141     * @param providerName the plugin provider name
142     * @param providerFullName the full name
143     * @param signUpUrl the plugin's service signup URL
144     * @param config the plugin's settings
145     * @return the provider ID of the plugin
146     */
147    public static long updateProviderDb(ContentResolver cr,
148            String providerName, String providerFullName, String signUpUrl,
149            Map<String, String> config) {
150        boolean versionChanged;
151
152        // query provider data
153        long providerId = Imps.Provider.getProviderIdForName(cr, providerName);
154        if (providerId > 0) {
155            // already loaded, check if version changed
156            String pluginVersion = config.get(ImConfigNames.PLUGIN_VERSION);
157            if (!isPluginVersionChanged(cr, providerId, pluginVersion)) {
158                // no change, just return
159                return providerId;
160            }
161            // changed, update provider meta data
162            updateProviderRow(cr, providerId, providerFullName, signUpUrl);
163            // clear branding resource map cache
164            clearBrandingResourceMapCache(cr, providerId);
165
166            Log.d(TAG, "Plugin " + providerName + "(" + providerId +
167                    ") has a version change. Database updated.");
168        } else {
169            // new plugin, not loaded before, insert the provider data
170            providerId = insertProviderRow(cr, providerName, providerFullName, signUpUrl);
171
172            Log.d(TAG, "Plugin " + providerName + "(" + providerId +
173                    ") is new. Provider added to IM db.");
174        }
175
176        // plugin provider has been inserted/updated, we need to update settings
177        saveProviderSettings(cr, providerId, config);
178
179        return providerId;
180    }
181
182    /**
183     * Clear the branding resource map cache.
184     */
185    private static int clearBrandingResourceMapCache(ContentResolver cr, long providerId) {
186        StringBuilder where = new StringBuilder();
187        where.append(Imps.BrandingResourceMapCache.PROVIDER_ID);
188        where.append('=');
189        where.append(providerId);
190        return cr.delete(Imps.BrandingResourceMapCache.CONTENT_URI, where.toString(), null);
191    }
192
193    /**
194     * Insert the plugin settings into the database.
195     */
196    private static int saveProviderSettings(ContentResolver cr, long providerId,
197            Map<String, String> config) {
198        ContentValues[] settingValues = new ContentValues[config.size()];
199        int index = 0;
200        for (Map.Entry<String, String> entry : config.entrySet()) {
201            ContentValues settingValue = new ContentValues();
202            settingValue.put(Imps.ProviderSettings.PROVIDER, providerId);
203            settingValue.put(Imps.ProviderSettings.NAME, entry.getKey());
204            settingValue.put(Imps.ProviderSettings.VALUE, entry.getValue());
205            settingValues[index++] = settingValue;
206        }
207        return cr.bulkInsert(Imps.ProviderSettings.CONTENT_URI, settingValues);
208    }
209
210    /**
211     * Insert a new plugin provider to the provider table.
212     */
213    private static long insertProviderRow(ContentResolver cr, String providerName,
214            String providerFullName, String signUpUrl) {
215        ContentValues values = new ContentValues(3);
216        values.put(Imps.Provider.NAME, providerName);
217        values.put(Imps.Provider.FULLNAME, providerFullName);
218        values.put(Imps.Provider.CATEGORY, ImApp.IMPS_CATEGORY);
219        values.put(Imps.Provider.SIGNUP_URL, signUpUrl);
220        Uri result = cr.insert(Imps.Provider.CONTENT_URI, values);
221        return ContentUris.parseId(result);
222    }
223
224    /**
225     * Update the data of a plugin provider.
226     */
227    private static int updateProviderRow(ContentResolver cr, long providerId,
228            String providerFullName, String signUpUrl) {
229        // Update the full name, signup url and category each time when the plugin change
230        // instead of specific version change because this is called only once.
231        // It's ok to update them even the values are not changed.
232        // Note that we don't update the provider name because it's used as
233        // identifier at some place and the plugin should never change it.
234        ContentValues values = new ContentValues(3);
235        values.put(Imps.Provider.FULLNAME, providerFullName);
236        values.put(Imps.Provider.SIGNUP_URL, signUpUrl);
237        values.put(Imps.Provider.CATEGORY, ImApp.IMPS_CATEGORY);
238        Uri uri = ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId);
239        return cr.update(uri, values, null, null);
240    }
241
242    /**
243     * Compare the saved version of a plugin provider with the newly loaded version.
244     */
245    private static boolean isPluginVersionChanged(ContentResolver cr, long providerId,
246            String newVersion) {
247        String oldVersion = Imps.ProviderSettings.getStringValue(cr, providerId,
248                ImConfigNames.PLUGIN_VERSION);
249        if (oldVersion == null) {
250            return true;
251        }
252        return !oldVersion.equals(newVersion);
253    }
254}
255