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