1 /* 2 * Copyright (C) 2007 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.providers.im; 18 19 import android.content.ContentProvider; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.content.UriMatcher; 23 import android.content.ContentResolver; 24 import android.database.Cursor; 25 import android.database.DatabaseUtils; 26 import android.database.sqlite.SQLiteConstraintException; 27 import android.database.sqlite.SQLiteDatabase; 28 import android.database.sqlite.SQLiteOpenHelper; 29 import android.database.sqlite.SQLiteQueryBuilder; 30 import android.net.Uri; 31 import android.os.ParcelFileDescriptor; 32 import android.provider.Im; 33 import android.text.TextUtils; 34 import android.util.Log; 35 36 37 import java.io.FileNotFoundException; 38 import java.io.UnsupportedEncodingException; 39 import java.net.URLDecoder; 40 import java.util.ArrayList; 41 import java.util.HashMap; 42 43 /** 44 * A content provider for IM 45 */ 46 public class ImProvider extends ContentProvider { 47 private static final String LOG_TAG = "imProvider"; 48 private static final boolean DBG = false; 49 50 private static final String AUTHORITY = "im"; 51 52 private static final boolean USE_CONTACT_PRESENCE_TRIGGER = false; 53 54 private static final String TABLE_ACCOUNTS = "accounts"; 55 private static final String TABLE_PROVIDERS = "providers"; 56 private static final String TABLE_PROVIDER_SETTINGS = "providerSettings"; 57 58 private static final String TABLE_CONTACTS = "contacts"; 59 private static final String TABLE_CONTACTS_ETAG = "contactsEtag"; 60 private static final String TABLE_BLOCKED_LIST = "blockedList"; 61 private static final String TABLE_CONTACT_LIST = "contactList"; 62 private static final String TABLE_INVITATIONS = "invitations"; 63 private static final String TABLE_GROUP_MEMBERS = "groupMembers"; 64 private static final String TABLE_GROUP_MESSAGES = "groupMessages"; 65 private static final String TABLE_PRESENCE = "presence"; 66 private static final String USERNAME = "username"; 67 private static final String TABLE_CHATS = "chats"; 68 private static final String TABLE_AVATARS = "avatars"; 69 private static final String TABLE_SESSION_COOKIES = "sessionCookies"; 70 private static final String TABLE_MESSAGES = "messages"; 71 private static final String TABLE_OUTGOING_RMQ_MESSAGES = "outgoingRmqMessages"; 72 private static final String TABLE_LAST_RMQ_ID = "lastrmqid"; 73 private static final String TABLE_ACCOUNT_STATUS = "accountStatus"; 74 private static final String TABLE_BRANDING_RESOURCE_MAP_CACHE = "brandingResMapCache"; 75 76 private static final String DATABASE_NAME = "im.db"; 77 private static final int DATABASE_VERSION = 47; 78 79 protected static final int MATCH_PROVIDERS = 1; 80 protected static final int MATCH_PROVIDERS_BY_ID = 2; 81 protected static final int MATCH_PROVIDERS_WITH_ACCOUNT = 3; 82 protected static final int MATCH_ACCOUNTS = 10; 83 protected static final int MATCH_ACCOUNTS_BY_ID = 11; 84 protected static final int MATCH_CONTACTS = 18; 85 protected static final int MATCH_CONTACTS_JOIN_PRESENCE = 19; 86 protected static final int MATCH_CONTACTS_BAREBONE = 20; 87 protected static final int MATCH_CHATTING_CONTACTS = 21; 88 protected static final int MATCH_CONTACTS_BY_PROVIDER = 22; 89 protected static final int MATCH_CHATTING_CONTACTS_BY_PROVIDER = 23; 90 protected static final int MATCH_NO_CHATTING_CONTACTS_BY_PROVIDER = 24; 91 protected static final int MATCH_ONLINE_CONTACTS_BY_PROVIDER = 25; 92 protected static final int MATCH_OFFLINE_CONTACTS_BY_PROVIDER = 26; 93 protected static final int MATCH_CONTACT = 27; 94 protected static final int MATCH_CONTACTS_BULK = 28; 95 protected static final int MATCH_ONLINE_CONTACT_COUNT = 30; 96 protected static final int MATCH_BLOCKED_CONTACTS = 31; 97 protected static final int MATCH_CONTACTLISTS = 32; 98 protected static final int MATCH_CONTACTLISTS_BY_PROVIDER = 33; 99 protected static final int MATCH_CONTACTLIST = 34; 100 protected static final int MATCH_BLOCKEDLIST = 35; 101 protected static final int MATCH_BLOCKEDLIST_BY_PROVIDER = 36; 102 protected static final int MATCH_CONTACTS_ETAGS = 37; 103 protected static final int MATCH_CONTACTS_ETAG = 38; 104 protected static final int MATCH_PRESENCE = 40; 105 protected static final int MATCH_PRESENCE_ID = 41; 106 protected static final int MATCH_PRESENCE_BY_ACCOUNT = 42; 107 protected static final int MATCH_PRESENCE_SEED_BY_ACCOUNT = 43; 108 protected static final int MATCH_PRESENCE_BULK = 44; 109 protected static final int MATCH_MESSAGES = 50; 110 protected static final int MATCH_MESSAGES_BY_CONTACT = 51; 111 protected static final int MATCH_MESSAGE = 52; 112 protected static final int MATCH_GROUP_MESSAGES = 53; 113 protected static final int MATCH_GROUP_MESSAGE_BY = 54; 114 protected static final int MATCH_GROUP_MESSAGE = 55; 115 protected static final int MATCH_GROUP_MEMBERS = 58; 116 protected static final int MATCH_GROUP_MEMBERS_BY_GROUP = 59; 117 protected static final int MATCH_AVATARS = 60; 118 protected static final int MATCH_AVATAR = 61; 119 protected static final int MATCH_AVATAR_BY_PROVIDER = 62; 120 protected static final int MATCH_CHATS = 70; 121 protected static final int MATCH_CHATS_BY_ACCOUNT = 71; 122 protected static final int MATCH_CHATS_ID = 72; 123 protected static final int MATCH_SESSIONS = 80; 124 protected static final int MATCH_SESSIONS_BY_PROVIDER = 81; 125 protected static final int MATCH_PROVIDER_SETTINGS = 90; 126 protected static final int MATCH_PROVIDER_SETTINGS_BY_ID = 91; 127 protected static final int MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME = 92; 128 protected static final int MATCH_INVITATIONS = 100; 129 protected static final int MATCH_INVITATION = 101; 130 protected static final int MATCH_OUTGOING_RMQ_MESSAGES = 110; 131 protected static final int MATCH_OUTGOING_RMQ_MESSAGE = 111; 132 protected static final int MATCH_OUTGOING_HIGHEST_RMQ_ID = 112; 133 protected static final int MATCH_LAST_RMQ_ID = 113; 134 protected static final int MATCH_ACCOUNTS_STATUS = 114; 135 protected static final int MATCH_ACCOUNT_STATUS = 115; 136 protected static final int MATCH_BRANDING_RESOURCE_MAP_CACHE = 120; 137 138 139 protected final UriMatcher mUrlMatcher = new UriMatcher(UriMatcher.NO_MATCH); 140 private final String mTransientDbName; 141 142 private static final HashMap<String, String> sProviderAccountsProjectionMap; 143 private static final HashMap<String, String> sContactsProjectionMap; 144 private static final HashMap<String, String> sContactListProjectionMap; 145 private static final HashMap<String, String> sBlockedListProjectionMap; 146 147 private static final String PROVIDER_JOIN_ACCOUNT_TABLE = 148 "providers LEFT OUTER JOIN accounts ON " + 149 "(providers._id = accounts.provider AND accounts.active = 1) " + 150 "LEFT OUTER JOIN accountStatus ON (accounts._id = accountStatus.account)"; 151 152 153 private static final String CONTACT_JOIN_PRESENCE_TABLE = 154 "contacts LEFT OUTER JOIN presence ON (contacts._id = presence.contact_id)"; 155 156 private static final String CONTACT_JOIN_PRESENCE_CHAT_TABLE = 157 CONTACT_JOIN_PRESENCE_TABLE + 158 " LEFT OUTER JOIN chats ON (contacts._id = chats.contact_id)"; 159 160 private static final String CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE = 161 CONTACT_JOIN_PRESENCE_CHAT_TABLE + 162 " LEFT OUTER JOIN avatars ON (contacts.username = avatars.contact" + 163 " AND contacts.account = avatars.account_id)"; 164 165 private static final String BLOCKEDLIST_JOIN_AVATAR_TABLE = 166 "blockedList LEFT OUTER JOIN avatars ON (blockedList.username = avatars.contact" + 167 " AND blockedList.account = avatars.account_id)"; 168 169 /** 170 * The where clause for filtering out blocked contacts 171 */ 172 private static final String NON_BLOCKED_CONTACTS_WHERE_CLAUSE = "(" 173 + Im.Contacts.TYPE + " IS NULL OR " 174 + Im.Contacts.TYPE + "!=" 175 + String.valueOf(Im.Contacts.TYPE_BLOCKED) 176 + ")"; 177 178 private static final String BLOCKED_CONTACTS_WHERE_CLAUSE = 179 "(contacts." + Im.Contacts.TYPE + "=" + Im.Contacts.TYPE_BLOCKED + ")"; 180 181 private static final String CONTACT_ID = TABLE_CONTACTS + '.' + Im.Contacts._ID; 182 private static final String PRESENCE_CONTACT_ID = TABLE_PRESENCE + '.' + Im.Presence.CONTACT_ID; 183 184 protected SQLiteOpenHelper mOpenHelper; 185 private final String mDatabaseName; 186 private final int mDatabaseVersion; 187 188 private final String[] BACKFILL_PROJECTION = { 189 Im.Chats._ID, Im.Chats.SHORTCUT, Im.Chats.LAST_MESSAGE_DATE 190 }; 191 192 private final String[] FIND_SHORTCUT_PROJECTION = { 193 Im.Chats._ID, Im.Chats.SHORTCUT 194 }; 195 196 private class DatabaseHelper extends SQLiteOpenHelper { 197 DatabaseHelper(Context context)198 DatabaseHelper(Context context) { 199 super(context, mDatabaseName, null, mDatabaseVersion); 200 } 201 202 @Override onCreate(SQLiteDatabase db)203 public void onCreate(SQLiteDatabase db) { 204 205 if (DBG) log("##### bootstrapDatabase"); 206 207 db.execSQL("CREATE TABLE " + TABLE_PROVIDERS + " (" + 208 "_id INTEGER PRIMARY KEY," + 209 "name TEXT," + // eg AIM 210 "fullname TEXT," + // eg AOL Instance Messenger 211 "category TEXT," + // a category used for forming intent 212 "signup_url TEXT" + // web url to visit to create a new account 213 ");"); 214 215 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " (" + 216 "_id INTEGER PRIMARY KEY," + 217 "name TEXT," + 218 "provider INTEGER," + 219 "username TEXT," + 220 "pw TEXT," + 221 "active INTEGER NOT NULL DEFAULT 0," + 222 "locked INTEGER NOT NULL DEFAULT 0," + 223 "keep_signed_in INTEGER NOT NULL DEFAULT 0," + 224 "last_login_state INTEGER NOT NULL DEFAULT 0," + 225 "UNIQUE (provider, username)" + 226 ");"); 227 228 createContactsTables(db); 229 230 db.execSQL("CREATE TABLE " + TABLE_AVATARS + " (" + 231 "_id INTEGER PRIMARY KEY," + 232 "contact TEXT," + 233 "provider_id INTEGER," + 234 "account_id INTEGER," + 235 "hash TEXT," + 236 "data BLOB," + // raw image data 237 "UNIQUE (account_id, contact)" + 238 ");"); 239 240 db.execSQL("CREATE TABLE " + TABLE_PROVIDER_SETTINGS + " (" + 241 "_id INTEGER PRIMARY KEY," + 242 "provider INTEGER," + 243 "name TEXT," + 244 "value TEXT," + 245 "UNIQUE (provider, name)" + 246 ");"); 247 248 db.execSQL("create TABLE " + TABLE_OUTGOING_RMQ_MESSAGES + " (" + 249 "_id INTEGER PRIMARY KEY," + 250 "rmq_id INTEGER," + 251 "type INTEGER," + 252 "ts INTEGER," + 253 "data TEXT" + 254 ");"); 255 256 db.execSQL("create TABLE " + TABLE_LAST_RMQ_ID + " (" + 257 "_id INTEGER PRIMARY KEY," + 258 "rmq_id INTEGER" + 259 ");"); 260 261 db.execSQL("create TABLE " + TABLE_BRANDING_RESOURCE_MAP_CACHE + " (" + 262 "_id INTEGER PRIMARY KEY," + 263 "provider_id INTEGER," + 264 "app_res_id INTEGER," + 265 "plugin_res_id INTEGER" + 266 ");"); 267 268 // clean up account specific data when an account is deleted. 269 db.execSQL("CREATE TRIGGER account_cleanup " + 270 "DELETE ON " + TABLE_ACCOUNTS + 271 " BEGIN " + 272 "DELETE FROM " + TABLE_AVATARS + " WHERE account_id= OLD._id;" + 273 "END"); 274 275 // add a database trigger to clean up associated provider settings 276 // while deleting a provider 277 db.execSQL("CREATE TRIGGER provider_cleanup " + 278 "DELETE ON " + TABLE_PROVIDERS + 279 " BEGIN " + 280 "DELETE FROM " + TABLE_PROVIDER_SETTINGS + " WHERE provider= OLD._id;" + 281 "END"); 282 } 283 284 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)285 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 286 Log.d(LOG_TAG, "Upgrading database from version " + oldVersion + " to " + newVersion); 287 288 switch (oldVersion) { 289 case 43: // this is the db version shipped in Dream 1.0 290 // no-op: no schema changed from 43 to 44. The db version was changed to flush 291 // old provider settings, so new provider setting (including new name/value 292 // pairs) could be inserted by the plugins. 293 294 // follow thru. 295 case 44: 296 if (newVersion <= 44) { 297 return; 298 } 299 300 db.beginTransaction(); 301 try { 302 // add category column to the providers table 303 db.execSQL("ALTER TABLE " + TABLE_PROVIDERS + " ADD COLUMN category TEXT;"); 304 // add otr column to the contacts table 305 db.execSQL("ALTER TABLE " + TABLE_CONTACTS + " ADD COLUMN otr INTEGER;"); 306 307 db.setTransactionSuccessful(); 308 } catch (Throwable ex) { 309 Log.e(LOG_TAG, ex.getMessage(), ex); 310 break; // force to destroy all old data; 311 } finally { 312 db.endTransaction(); 313 } 314 315 case 45: 316 if (newVersion <= 45) { 317 return; 318 } 319 320 db.beginTransaction(); 321 try { 322 // add an otr_etag column to contact etag table 323 db.execSQL( 324 "ALTER TABLE " + TABLE_CONTACTS_ETAG + " ADD COLUMN otr_etag TEXT;"); 325 db.setTransactionSuccessful(); 326 } catch (Throwable ex) { 327 Log.e(LOG_TAG, ex.getMessage(), ex); 328 break; // force to destroy all old data; 329 } finally { 330 db.endTransaction(); 331 } 332 333 case 46: 334 if (newVersion <= 46) { 335 return; 336 } 337 338 db.beginTransaction(); 339 try { 340 // add branding resource map cache table 341 db.execSQL("create TABLE " + TABLE_BRANDING_RESOURCE_MAP_CACHE + " (" + 342 "_id INTEGER PRIMARY KEY," + 343 "provider_id INTEGER," + 344 "app_res_id INTEGER," + 345 "plugin_res_id INTEGER" + 346 ");"); 347 db.setTransactionSuccessful(); 348 } catch (Throwable ex) { 349 Log.e(LOG_TAG, ex.getMessage(), ex); 350 break; // force to destroy all old data; 351 } finally { 352 db.endTransaction(); 353 } 354 355 return; 356 } 357 358 Log.w(LOG_TAG, "Couldn't upgrade db to " + newVersion + ". Destroying old data."); 359 destroyOldTables(db); 360 onCreate(db); 361 } 362 destroyOldTables(SQLiteDatabase db)363 private void destroyOldTables(SQLiteDatabase db) { 364 db.execSQL("DROP TABLE IF EXISTS " + TABLE_PROVIDERS); 365 db.execSQL("DROP TABLE IF EXISTS " + TABLE_ACCOUNTS); 366 db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONTACT_LIST); 367 db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONTACTS); 368 db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONTACTS_ETAG); 369 db.execSQL("DROP TABLE IF EXISTS " + TABLE_AVATARS); 370 db.execSQL("DROP TABLE IF EXISTS " + TABLE_PROVIDER_SETTINGS); 371 db.execSQL("DROP TABLE IF EXISTS " + TABLE_OUTGOING_RMQ_MESSAGES); 372 db.execSQL("DROP TABLE IF EXISTS " + TABLE_LAST_RMQ_ID); 373 db.execSQL("DROP TABLE IF EXISTS " + TABLE_BRANDING_RESOURCE_MAP_CACHE); 374 } 375 createContactsTables(SQLiteDatabase db)376 private void createContactsTables(SQLiteDatabase db) { 377 StringBuilder buf = new StringBuilder(); 378 String contactsTableName = TABLE_CONTACTS; 379 380 // creating the "contacts" table 381 buf.append("CREATE TABLE IF NOT EXISTS "); 382 buf.append(contactsTableName); 383 buf.append(" ("); 384 buf.append("_id INTEGER PRIMARY KEY,"); 385 buf.append("username TEXT,"); 386 buf.append("nickname TEXT,"); 387 buf.append("provider INTEGER,"); 388 buf.append("account INTEGER,"); 389 buf.append("contactList INTEGER,"); 390 buf.append("type INTEGER,"); 391 buf.append("subscriptionStatus INTEGER,"); 392 buf.append("subscriptionType INTEGER,"); 393 394 // the following are derived from Google Contact Extension, we don't include all 395 // the attributes, just the ones we can use. 396 // (see http://code.google.com/apis/talk/jep_extensions/roster_attributes.html) 397 // 398 // qc: quick contact (derived from message count) 399 // rejected: if the contact has ever been rejected by the user 400 buf.append("qc INTEGER,"); 401 buf.append("rejected INTEGER,"); 402 403 // Off the record status 404 buf.append("otr INTEGER"); 405 406 buf.append(");"); 407 408 db.execSQL(buf.toString()); 409 410 buf.delete(0, buf.length()); 411 412 // creating contact etag table 413 buf.append("CREATE TABLE IF NOT EXISTS "); 414 buf.append(TABLE_CONTACTS_ETAG); 415 buf.append(" ("); 416 buf.append("_id INTEGER PRIMARY KEY,"); 417 buf.append("etag TEXT,"); 418 buf.append("otr_etag TEXT,"); 419 buf.append("account INTEGER UNIQUE"); 420 buf.append(");"); 421 422 db.execSQL(buf.toString()); 423 424 buf.delete(0, buf.length()); 425 426 // creating the "contactList" table 427 buf.append("CREATE TABLE IF NOT EXISTS "); 428 buf.append(TABLE_CONTACT_LIST); 429 buf.append(" ("); 430 buf.append("_id INTEGER PRIMARY KEY,"); 431 buf.append("name TEXT,"); 432 buf.append("provider INTEGER,"); 433 buf.append("account INTEGER"); 434 buf.append(");"); 435 436 db.execSQL(buf.toString()); 437 438 buf.delete(0, buf.length()); 439 440 // creating the "blockedList" table 441 buf.append("CREATE TABLE IF NOT EXISTS "); 442 buf.append(TABLE_BLOCKED_LIST); 443 buf.append(" ("); 444 buf.append("_id INTEGER PRIMARY KEY,"); 445 buf.append("username TEXT,"); 446 buf.append("nickname TEXT,"); 447 buf.append("provider INTEGER,"); 448 buf.append("account INTEGER"); 449 buf.append(");"); 450 451 db.execSQL(buf.toString()); 452 } 453 454 @Override onOpen(SQLiteDatabase db)455 public void onOpen(SQLiteDatabase db) { 456 if (db.isReadOnly()) { 457 Log.w(LOG_TAG, "ImProvider database opened in read only mode."); 458 Log.w(LOG_TAG, "Transient tables not created."); 459 return; 460 } 461 462 if (DBG) log("##### createTransientTables"); 463 464 // Create transient tables 465 String cpDbName; 466 db.execSQL("ATTACH DATABASE ':memory:' AS " + mTransientDbName + ";"); 467 cpDbName = mTransientDbName + "."; 468 469 // message table (since the UI currently doesn't require saving message history 470 // across IM sessions, store the message table in memory db only) 471 db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_MESSAGES + " (" + 472 "_id INTEGER PRIMARY KEY," + 473 "packet_id TEXT UNIQUE," + 474 "contact TEXT," + 475 "provider INTEGER," + 476 "account INTEGER," + 477 "body TEXT," + 478 "date INTEGER," + // in seconds 479 "type INTEGER," + 480 "err_code INTEGER NOT NULL DEFAULT 0," + 481 "err_msg TEXT" + 482 ");"); 483 484 // presence 485 db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_PRESENCE + " ("+ 486 "_id INTEGER PRIMARY KEY," + 487 "contact_id INTEGER UNIQUE," + 488 "jid_resource TEXT," + // jid resource for the presence 489 "client_type INTEGER," + // client type 490 "priority INTEGER," + // presence priority (XMPP) 491 "mode INTEGER," + // presence mode 492 "status TEXT" + // custom status 493 ");"); 494 495 // group chat invitations 496 db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_INVITATIONS + " (" + 497 "_id INTEGER PRIMARY KEY," + 498 "providerId INTEGER," + 499 "accountId INTEGER," + 500 "inviteId TEXT," + 501 "sender TEXT," + 502 "groupName TEXT," + 503 "note TEXT," + 504 "status INTEGER" + 505 ");"); 506 507 // group chat members 508 db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_GROUP_MEMBERS + " (" + 509 "_id INTEGER PRIMARY KEY," + 510 "groupId INTEGER," + 511 "username TEXT," + 512 "nickname TEXT" + 513 ");"); 514 515 // group chat messages 516 db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_GROUP_MESSAGES + " (" + 517 "_id INTEGER PRIMARY KEY," + 518 "packet_id TEXT UNIQUE," + 519 "contact TEXT," + 520 "groupId INTEGER," + 521 "body TEXT," + 522 "date INTEGER," + 523 "type INTEGER," + 524 "err_code INTEGER NOT NULL DEFAULT 0," + 525 "err_msg TEXT" + 526 ");"); 527 528 // chat sessions, including single person chats and group chats 529 db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_CHATS + " ("+ 530 "_id INTEGER PRIMARY KEY," + 531 "contact_id INTEGER UNIQUE," + 532 "jid_resource TEXT," + // the JID resource for the user, only for non-group chats 533 "groupchat INTEGER," + // 1 if group chat, 0 if not TODO: remove this column 534 "last_unread_message TEXT," + // the last unread message 535 "last_message_date INTEGER," + // in seconds 536 "unsent_composed_message TEXT," + // a composed, but not sent message 537 "shortcut INTEGER" + // which of 10 slots (if any) this chat occupies 538 ");"); 539 540 db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_ACCOUNT_STATUS + " (" + 541 "_id INTEGER PRIMARY KEY," + 542 "account INTEGER UNIQUE," + 543 "presenceStatus INTEGER," + 544 "connStatus INTEGER" + 545 ");" 546 ); 547 548 /* when we moved the contact table out of transient_db and into the main db, the 549 contact_cleanup and group_cleanup triggers don't work anymore. It seems we can't 550 create triggers that reference objects in a different database! 551 552 String contactsTableName = TABLE_CONTACTS; 553 554 if (USE_CONTACT_PRESENCE_TRIGGER) { 555 // Insert a default presence for newly inserted contact 556 db.execSQL("CREATE TRIGGER IF NOT EXISTS " + cpDbName + "contact_create_presence " + 557 "INSERT ON " + cpDbName + contactsTableName + 558 " FOR EACH ROW WHEN NEW.type != " + Im.Contacts.TYPE_GROUP + 559 " OR NEW.type != " + Im.Contacts.TYPE_BLOCKED + 560 " BEGIN " + 561 "INSERT INTO presence (contact_id) VALUES (NEW._id);" + 562 " END"); 563 } 564 565 db.execSQL("CREATE TRIGGER IF NOT EXISTS " + cpDbName + "contact_cleanup " + 566 "DELETE ON " + cpDbName + contactsTableName + 567 " BEGIN " + 568 "DELETE FROM presence WHERE contact_id = OLD._id;" + 569 "DELETE FROM chats WHERE contact_id = OLD._id;" + 570 "END"); 571 572 // Cleans up group members and group messages when a group chat is deleted 573 db.execSQL("CREATE TRIGGER IF NOT EXISTS " + cpDbName + "group_cleanup " + 574 "DELETE ON " + cpDbName + contactsTableName + 575 " FOR EACH ROW WHEN OLD.type = " + Im.Contacts.TYPE_GROUP + 576 " BEGIN " + 577 "DELETE FROM groupMembers WHERE groupId = OLD._id;" + 578 "DELETE FROM groupMessages WHERE groupId = OLD._id;" + 579 " END"); 580 */ 581 582 // only store the session cookies in memory right now. This means 583 // that we don't persist them across device reboot 584 db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_SESSION_COOKIES + " ("+ 585 "_id INTEGER PRIMARY KEY," + 586 "provider INTEGER," + 587 "account INTEGER," + 588 "name TEXT," + 589 "value TEXT" + 590 ");"); 591 592 } 593 } 594 595 static { 596 sProviderAccountsProjectionMap = new HashMap<String, String>(); sProviderAccountsProjectionMap.put(Im.Provider._ID, "providers._id AS _id")597 sProviderAccountsProjectionMap.put(Im.Provider._ID, 598 "providers._id AS _id"); sProviderAccountsProjectionMap.put(Im.Provider._COUNT, "COUNT(*) AS _account")599 sProviderAccountsProjectionMap.put(Im.Provider._COUNT, 600 "COUNT(*) AS _account"); sProviderAccountsProjectionMap.put(Im.Provider.NAME, "providers.name AS name")601 sProviderAccountsProjectionMap.put(Im.Provider.NAME, 602 "providers.name AS name"); sProviderAccountsProjectionMap.put(Im.Provider.FULLNAME, "providers.fullname AS fullname")603 sProviderAccountsProjectionMap.put(Im.Provider.FULLNAME, 604 "providers.fullname AS fullname"); sProviderAccountsProjectionMap.put(Im.Provider.CATEGORY, "providers.category AS category")605 sProviderAccountsProjectionMap.put(Im.Provider.CATEGORY, 606 "providers.category AS category"); sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_ID, "accounts._id AS account_id")607 sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_ID, 608 "accounts._id AS account_id"); sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_USERNAME, "accounts.username AS account_username")609 sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_USERNAME, 610 "accounts.username AS account_username"); sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_PW, "accounts.pw AS account_pw")611 sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_PW, 612 "accounts.pw AS account_pw"); sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_LOCKED, "accounts.locked AS account_locked")613 sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_LOCKED, 614 "accounts.locked AS account_locked"); sProviderAccountsProjectionMap.put(Im.Provider.ACCOUNT_PRESENCE_STATUS, "accountStatus.presenceStatus AS account_presenceStatus")615 sProviderAccountsProjectionMap.put(Im.Provider.ACCOUNT_PRESENCE_STATUS, 616 "accountStatus.presenceStatus AS account_presenceStatus"); sProviderAccountsProjectionMap.put(Im.Provider.ACCOUNT_CONNECTION_STATUS, "accountStatus.connStatus AS account_connStatus")617 sProviderAccountsProjectionMap.put(Im.Provider.ACCOUNT_CONNECTION_STATUS, 618 "accountStatus.connStatus AS account_connStatus"); 619 620 // contacts projection map 621 sContactsProjectionMap = new HashMap<String, String>(); 622 623 // Base column sContactsProjectionMap.put(Im.Contacts._ID, "contacts._id AS _id")624 sContactsProjectionMap.put(Im.Contacts._ID, "contacts._id AS _id"); sContactsProjectionMap.put(Im.Contacts._COUNT, "COUNT(*) AS _count")625 sContactsProjectionMap.put(Im.Contacts._COUNT, "COUNT(*) AS _count"); 626 627 // contacts column sContactsProjectionMap.put(Im.Contacts._ID, "contacts._id as _id")628 sContactsProjectionMap.put(Im.Contacts._ID, "contacts._id as _id"); sContactsProjectionMap.put(Im.Contacts.USERNAME, "contacts.username as username")629 sContactsProjectionMap.put(Im.Contacts.USERNAME, "contacts.username as username"); sContactsProjectionMap.put(Im.Contacts.NICKNAME, "contacts.nickname as nickname")630 sContactsProjectionMap.put(Im.Contacts.NICKNAME, "contacts.nickname as nickname"); sContactsProjectionMap.put(Im.Contacts.PROVIDER, "contacts.provider as provider")631 sContactsProjectionMap.put(Im.Contacts.PROVIDER, "contacts.provider as provider"); sContactsProjectionMap.put(Im.Contacts.ACCOUNT, "contacts.account as account")632 sContactsProjectionMap.put(Im.Contacts.ACCOUNT, "contacts.account as account"); sContactsProjectionMap.put(Im.Contacts.CONTACTLIST, "contacts.contactList as contactList")633 sContactsProjectionMap.put(Im.Contacts.CONTACTLIST, "contacts.contactList as contactList"); sContactsProjectionMap.put(Im.Contacts.TYPE, "contacts.type as type")634 sContactsProjectionMap.put(Im.Contacts.TYPE, "contacts.type as type"); sContactsProjectionMap.put(Im.Contacts.SUBSCRIPTION_STATUS, "contacts.subscriptionStatus as subscriptionStatus")635 sContactsProjectionMap.put(Im.Contacts.SUBSCRIPTION_STATUS, 636 "contacts.subscriptionStatus as subscriptionStatus"); sContactsProjectionMap.put(Im.Contacts.SUBSCRIPTION_TYPE, "contacts.subscriptionType as subscriptionType")637 sContactsProjectionMap.put(Im.Contacts.SUBSCRIPTION_TYPE, 638 "contacts.subscriptionType as subscriptionType"); sContactsProjectionMap.put(Im.Contacts.QUICK_CONTACT, "contacts.qc as qc")639 sContactsProjectionMap.put(Im.Contacts.QUICK_CONTACT, "contacts.qc as qc"); sContactsProjectionMap.put(Im.Contacts.REJECTED, "contacts.rejected as rejected")640 sContactsProjectionMap.put(Im.Contacts.REJECTED, "contacts.rejected as rejected"); 641 642 // Presence columns sContactsProjectionMap.put(Im.Presence.CONTACT_ID, "presence.contact_id AS contact_id")643 sContactsProjectionMap.put(Im.Presence.CONTACT_ID, 644 "presence.contact_id AS contact_id"); sContactsProjectionMap.put(Im.Contacts.PRESENCE_STATUS, "presence.mode AS mode")645 sContactsProjectionMap.put(Im.Contacts.PRESENCE_STATUS, 646 "presence.mode AS mode"); sContactsProjectionMap.put(Im.Contacts.PRESENCE_CUSTOM_STATUS, "presence.status AS status")647 sContactsProjectionMap.put(Im.Contacts.PRESENCE_CUSTOM_STATUS, 648 "presence.status AS status"); sContactsProjectionMap.put(Im.Contacts.CLIENT_TYPE, "presence.client_type AS client_type")649 sContactsProjectionMap.put(Im.Contacts.CLIENT_TYPE, 650 "presence.client_type AS client_type"); 651 652 // Chats columns sContactsProjectionMap.put(Im.Contacts.CHATS_CONTACT, "chats.contact_id AS chats_contact_id")653 sContactsProjectionMap.put(Im.Contacts.CHATS_CONTACT, 654 "chats.contact_id AS chats_contact_id"); sContactsProjectionMap.put(Im.Chats.JID_RESOURCE, "chats.jid_resource AS jid_resource")655 sContactsProjectionMap.put(Im.Chats.JID_RESOURCE, 656 "chats.jid_resource AS jid_resource"); sContactsProjectionMap.put(Im.Chats.GROUP_CHAT, "chats.groupchat AS groupchat")657 sContactsProjectionMap.put(Im.Chats.GROUP_CHAT, 658 "chats.groupchat AS groupchat"); sContactsProjectionMap.put(Im.Contacts.LAST_UNREAD_MESSAGE, "chats.last_unread_message AS last_unread_message")659 sContactsProjectionMap.put(Im.Contacts.LAST_UNREAD_MESSAGE, 660 "chats.last_unread_message AS last_unread_message"); sContactsProjectionMap.put(Im.Contacts.LAST_MESSAGE_DATE, "chats.last_message_date AS last_message_date")661 sContactsProjectionMap.put(Im.Contacts.LAST_MESSAGE_DATE, 662 "chats.last_message_date AS last_message_date"); sContactsProjectionMap.put(Im.Contacts.UNSENT_COMPOSED_MESSAGE, "chats.unsent_composed_message AS unsent_composed_message")663 sContactsProjectionMap.put(Im.Contacts.UNSENT_COMPOSED_MESSAGE, 664 "chats.unsent_composed_message AS unsent_composed_message"); sContactsProjectionMap.put(Im.Contacts.SHORTCUT, "chats.SHORTCUT AS shortcut")665 sContactsProjectionMap.put(Im.Contacts.SHORTCUT, "chats.SHORTCUT AS shortcut"); 666 667 // Avatars columns sContactsProjectionMap.put(Im.Contacts.AVATAR_HASH, "avatars.hash AS avatars_hash")668 sContactsProjectionMap.put(Im.Contacts.AVATAR_HASH, "avatars.hash AS avatars_hash"); sContactsProjectionMap.put(Im.Contacts.AVATAR_DATA, "avatars.data AS avatars_data")669 sContactsProjectionMap.put(Im.Contacts.AVATAR_DATA, "avatars.data AS avatars_data"); 670 671 // contactList projection map 672 sContactListProjectionMap = new HashMap<String, String>(); sContactListProjectionMap.put(Im.ContactList._ID, "contactList._id AS _id")673 sContactListProjectionMap.put(Im.ContactList._ID, 674 "contactList._id AS _id"); sContactListProjectionMap.put(Im.ContactList._COUNT, "COUNT(*) AS _count")675 sContactListProjectionMap.put(Im.ContactList._COUNT, 676 "COUNT(*) AS _count"); sContactListProjectionMap.put(Im.ContactList.NAME, "name")677 sContactListProjectionMap.put(Im.ContactList.NAME, "name"); sContactListProjectionMap.put(Im.ContactList.PROVIDER, "provider")678 sContactListProjectionMap.put(Im.ContactList.PROVIDER, "provider"); sContactListProjectionMap.put(Im.ContactList.ACCOUNT, "account")679 sContactListProjectionMap.put(Im.ContactList.ACCOUNT, "account"); 680 681 // blockedList projection map 682 sBlockedListProjectionMap = new HashMap<String, String>(); sBlockedListProjectionMap.put(Im.BlockedList._ID, "blockedList._id AS _id")683 sBlockedListProjectionMap.put(Im.BlockedList._ID, 684 "blockedList._id AS _id"); sBlockedListProjectionMap.put(Im.BlockedList._COUNT, "COUNT(*) AS _count")685 sBlockedListProjectionMap.put(Im.BlockedList._COUNT, 686 "COUNT(*) AS _count"); sBlockedListProjectionMap.put(Im.BlockedList.USERNAME, "username")687 sBlockedListProjectionMap.put(Im.BlockedList.USERNAME, "username"); sBlockedListProjectionMap.put(Im.BlockedList.NICKNAME, "nickname")688 sBlockedListProjectionMap.put(Im.BlockedList.NICKNAME, "nickname"); sBlockedListProjectionMap.put(Im.BlockedList.PROVIDER, "provider")689 sBlockedListProjectionMap.put(Im.BlockedList.PROVIDER, "provider"); sBlockedListProjectionMap.put(Im.BlockedList.ACCOUNT, "account")690 sBlockedListProjectionMap.put(Im.BlockedList.ACCOUNT, "account"); sBlockedListProjectionMap.put(Im.BlockedList.AVATAR_DATA, "avatars.data AS avatars_data")691 sBlockedListProjectionMap.put(Im.BlockedList.AVATAR_DATA, 692 "avatars.data AS avatars_data"); 693 } 694 ImProvider()695 public ImProvider() { 696 this(AUTHORITY, DATABASE_NAME, DATABASE_VERSION); 697 } 698 ImProvider(String authority, String dbName, int dbVersion)699 protected ImProvider(String authority, String dbName, int dbVersion) { 700 mDatabaseName = dbName; 701 mDatabaseVersion = dbVersion; 702 703 mTransientDbName = "transient_" + dbName.replace(".", "_"); 704 705 mUrlMatcher.addURI(authority, "providers", MATCH_PROVIDERS); 706 mUrlMatcher.addURI(authority, "providers/#", MATCH_PROVIDERS_BY_ID); 707 mUrlMatcher.addURI(authority, "providers/account", MATCH_PROVIDERS_WITH_ACCOUNT); 708 709 mUrlMatcher.addURI(authority, "accounts", MATCH_ACCOUNTS); 710 mUrlMatcher.addURI(authority, "accounts/#", MATCH_ACCOUNTS_BY_ID); 711 712 mUrlMatcher.addURI(authority, "contacts", MATCH_CONTACTS); 713 mUrlMatcher.addURI(authority, "contactsWithPresence", MATCH_CONTACTS_JOIN_PRESENCE); 714 mUrlMatcher.addURI(authority, "contactsBarebone", MATCH_CONTACTS_BAREBONE); 715 mUrlMatcher.addURI(authority, "contacts/#/#", MATCH_CONTACTS_BY_PROVIDER); 716 mUrlMatcher.addURI(authority, "contacts/chatting", MATCH_CHATTING_CONTACTS); 717 mUrlMatcher.addURI(authority, "contacts/chatting/#/#", MATCH_CHATTING_CONTACTS_BY_PROVIDER); 718 mUrlMatcher.addURI(authority, "contacts/online/#/#", MATCH_ONLINE_CONTACTS_BY_PROVIDER); 719 mUrlMatcher.addURI(authority, "contacts/offline/#/#", MATCH_OFFLINE_CONTACTS_BY_PROVIDER); 720 mUrlMatcher.addURI(authority, "contacts/#", MATCH_CONTACT); 721 mUrlMatcher.addURI(authority, "contacts/blocked", MATCH_BLOCKED_CONTACTS); 722 mUrlMatcher.addURI(authority, "bulk_contacts", MATCH_CONTACTS_BULK); 723 mUrlMatcher.addURI(authority, "contacts/onlineCount", MATCH_ONLINE_CONTACT_COUNT); 724 725 mUrlMatcher.addURI(authority, "contactLists", MATCH_CONTACTLISTS); 726 mUrlMatcher.addURI(authority, "contactLists/#/#", MATCH_CONTACTLISTS_BY_PROVIDER); 727 mUrlMatcher.addURI(authority, "contactLists/#", MATCH_CONTACTLIST); 728 mUrlMatcher.addURI(authority, "blockedList", MATCH_BLOCKEDLIST); 729 mUrlMatcher.addURI(authority, "blockedList/#/#", MATCH_BLOCKEDLIST_BY_PROVIDER); 730 731 mUrlMatcher.addURI(authority, "contactsEtag", MATCH_CONTACTS_ETAGS); 732 mUrlMatcher.addURI(authority, "contactsEtag/#", MATCH_CONTACTS_ETAG); 733 734 mUrlMatcher.addURI(authority, "presence", MATCH_PRESENCE); 735 mUrlMatcher.addURI(authority, "presence/#", MATCH_PRESENCE_ID); 736 mUrlMatcher.addURI(authority, "presence/account/#", MATCH_PRESENCE_BY_ACCOUNT); 737 mUrlMatcher.addURI(authority, "seed_presence/account/#", MATCH_PRESENCE_SEED_BY_ACCOUNT); 738 mUrlMatcher.addURI(authority, "bulk_presence", MATCH_PRESENCE_BULK); 739 740 mUrlMatcher.addURI(authority, "messages", MATCH_MESSAGES); 741 mUrlMatcher.addURI(authority, "messagesBy/#/#/*", MATCH_MESSAGES_BY_CONTACT); 742 mUrlMatcher.addURI(authority, "messages/#", MATCH_MESSAGE); 743 744 mUrlMatcher.addURI(authority, "groupMessages", MATCH_GROUP_MESSAGES); 745 mUrlMatcher.addURI(authority, "groupMessagesBy/#", MATCH_GROUP_MESSAGE_BY); 746 mUrlMatcher.addURI(authority, "groupMessages/#", MATCH_GROUP_MESSAGE); 747 mUrlMatcher.addURI(authority, "groupMembers", MATCH_GROUP_MEMBERS); 748 mUrlMatcher.addURI(authority, "groupMembers/#", MATCH_GROUP_MEMBERS_BY_GROUP); 749 750 mUrlMatcher.addURI(authority, "avatars", MATCH_AVATARS); 751 mUrlMatcher.addURI(authority, "avatars/#", MATCH_AVATAR); 752 mUrlMatcher.addURI(authority, "avatarsBy/#/#", MATCH_AVATAR_BY_PROVIDER); 753 mUrlMatcher.addURI(authority, "chats", MATCH_CHATS); 754 mUrlMatcher.addURI(authority, "chats/account/#", MATCH_CHATS_BY_ACCOUNT); 755 mUrlMatcher.addURI(authority, "chats/#", MATCH_CHATS_ID); 756 757 mUrlMatcher.addURI(authority, "sessionCookies", MATCH_SESSIONS); 758 mUrlMatcher.addURI(authority, "sessionCookiesBy/#/#", MATCH_SESSIONS_BY_PROVIDER); 759 mUrlMatcher.addURI(authority, "providerSettings", MATCH_PROVIDER_SETTINGS); 760 mUrlMatcher.addURI(authority, "providerSettings/#", MATCH_PROVIDER_SETTINGS_BY_ID); 761 mUrlMatcher.addURI(authority, "providerSettings/#/*", 762 MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME); 763 764 mUrlMatcher.addURI(authority, "invitations", MATCH_INVITATIONS); 765 mUrlMatcher.addURI(authority, "invitations/#", MATCH_INVITATION); 766 767 mUrlMatcher.addURI(authority, "outgoingRmqMessages", MATCH_OUTGOING_RMQ_MESSAGES); 768 mUrlMatcher.addURI(authority, "outgoingRmqMessages/#", MATCH_OUTGOING_RMQ_MESSAGE); 769 mUrlMatcher.addURI(authority, "outgoingHighestRmqId", MATCH_OUTGOING_HIGHEST_RMQ_ID); 770 mUrlMatcher.addURI(authority, "lastRmqId", MATCH_LAST_RMQ_ID); 771 772 mUrlMatcher.addURI(authority, "accountStatus", MATCH_ACCOUNTS_STATUS); 773 mUrlMatcher.addURI(authority, "accountStatus/#", MATCH_ACCOUNT_STATUS); 774 775 mUrlMatcher.addURI(authority, "brandingResMapCache", MATCH_BRANDING_RESOURCE_MAP_CACHE); 776 } 777 778 @Override onCreate()779 public boolean onCreate() { 780 mOpenHelper = new DatabaseHelper(getContext()); 781 return true; 782 } 783 784 @Override update(final Uri url, final ContentValues values, final String selection, final String[] selectionArgs)785 public final int update(final Uri url, final ContentValues values, 786 final String selection, final String[] selectionArgs) { 787 788 int result = 0; 789 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 790 db.beginTransaction(); 791 try { 792 result = updateInternal(url, values, selection, selectionArgs); 793 db.setTransactionSuccessful(); 794 } finally { 795 db.endTransaction(); 796 } 797 if (result > 0) { 798 getContext().getContentResolver() 799 .notifyChange(url, null /* observer */, false /* sync */); 800 } 801 return result; 802 } 803 804 @Override delete(final Uri url, final String selection, final String[] selectionArgs)805 public final int delete(final Uri url, final String selection, 806 final String[] selectionArgs) { 807 int result; 808 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 809 db.beginTransaction(); 810 try { 811 result = deleteInternal(url, selection, selectionArgs); 812 db.setTransactionSuccessful(); 813 } finally { 814 db.endTransaction(); 815 } 816 if (result > 0) { 817 getContext().getContentResolver() 818 .notifyChange(url, null /* observer */, false /* sync */); 819 } 820 return result; 821 } 822 823 @Override insert(final Uri url, final ContentValues values)824 public final Uri insert(final Uri url, final ContentValues values) { 825 Uri result; 826 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 827 db.beginTransaction(); 828 try { 829 result = insertInternal(url, values); 830 db.setTransactionSuccessful(); 831 } finally { 832 db.endTransaction(); 833 } 834 if (result != null) { 835 getContext().getContentResolver() 836 .notifyChange(url, null /* observer */, false /* sync */); 837 } 838 return result; 839 } 840 841 @Override query(final Uri url, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder)842 public final Cursor query(final Uri url, final String[] projection, 843 final String selection, final String[] selectionArgs, 844 final String sortOrder) { 845 return queryInternal(url, projection, selection, selectionArgs, sortOrder); 846 } 847 queryInternal(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort)848 public Cursor queryInternal(Uri url, String[] projectionIn, 849 String selection, String[] selectionArgs, String sort) { 850 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 851 StringBuilder whereClause = new StringBuilder(); 852 if(selection != null) { 853 whereClause.append(selection); 854 } 855 String groupBy = null; 856 String limit = null; 857 858 // Generate the body of the query 859 int match = mUrlMatcher.match(url); 860 861 if (DBG) { 862 log("query " + url + ", match " + match + ", where " + selection); 863 if (selectionArgs != null) { 864 for (String selectionArg : selectionArgs) { 865 log(" selectionArg: " + selectionArg); 866 } 867 } 868 } 869 870 switch (match) { 871 case MATCH_PROVIDERS_BY_ID: 872 appendWhere(whereClause, Im.Provider._ID, "=", url.getPathSegments().get(1)); 873 // fall thru. 874 875 case MATCH_PROVIDERS: 876 qb.setTables(TABLE_PROVIDERS); 877 break; 878 879 case MATCH_PROVIDERS_WITH_ACCOUNT: 880 qb.setTables(PROVIDER_JOIN_ACCOUNT_TABLE); 881 qb.setProjectionMap(sProviderAccountsProjectionMap); 882 break; 883 884 case MATCH_ACCOUNTS_BY_ID: 885 appendWhere(whereClause, Im.Account._ID, "=", url.getPathSegments().get(1)); 886 // falls down 887 case MATCH_ACCOUNTS: 888 qb.setTables(TABLE_ACCOUNTS); 889 break; 890 891 case MATCH_CONTACTS: 892 qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); 893 qb.setProjectionMap(sContactsProjectionMap); 894 break; 895 896 case MATCH_CONTACTS_JOIN_PRESENCE: 897 qb.setTables(CONTACT_JOIN_PRESENCE_TABLE); 898 qb.setProjectionMap(sContactsProjectionMap); 899 break; 900 901 case MATCH_CONTACTS_BAREBONE: 902 qb.setTables(TABLE_CONTACTS); 903 break; 904 905 case MATCH_CHATTING_CONTACTS: 906 qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); 907 qb.setProjectionMap(sContactsProjectionMap); 908 appendWhere(whereClause, "chats.last_message_date IS NOT NULL"); 909 // no need to add the non blocked contacts clause because 910 // blocked contacts can't have conversations. 911 break; 912 913 case MATCH_CONTACTS_BY_PROVIDER: 914 buildQueryContactsByProvider(qb, whereClause, url); 915 appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); 916 break; 917 918 case MATCH_CHATTING_CONTACTS_BY_PROVIDER: 919 buildQueryContactsByProvider(qb, whereClause, url); 920 appendWhere(whereClause, "chats.last_message_date IS NOT NULL"); 921 // no need to add the non blocked contacts clause because 922 // blocked contacts can't have conversations. 923 break; 924 925 case MATCH_NO_CHATTING_CONTACTS_BY_PROVIDER: 926 buildQueryContactsByProvider(qb, whereClause, url); 927 appendWhere(whereClause, "chats.last_message_date IS NULL"); 928 appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); 929 break; 930 931 case MATCH_ONLINE_CONTACTS_BY_PROVIDER: 932 buildQueryContactsByProvider(qb, whereClause, url); 933 appendWhere(whereClause, Im.Contacts.PRESENCE_STATUS, "!=", Im.Presence.OFFLINE); 934 appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); 935 break; 936 937 case MATCH_OFFLINE_CONTACTS_BY_PROVIDER: 938 buildQueryContactsByProvider(qb, whereClause, url); 939 appendWhere(whereClause, Im.Contacts.PRESENCE_STATUS, "=", Im.Presence.OFFLINE); 940 appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); 941 break; 942 943 case MATCH_BLOCKED_CONTACTS: 944 qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); 945 qb.setProjectionMap(sContactsProjectionMap); 946 appendWhere(whereClause, BLOCKED_CONTACTS_WHERE_CLAUSE); 947 break; 948 949 case MATCH_CONTACT: 950 qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); 951 qb.setProjectionMap(sContactsProjectionMap); 952 appendWhere(whereClause, "contacts._id", "=", url.getPathSegments().get(1)); 953 break; 954 955 case MATCH_ONLINE_CONTACT_COUNT: 956 qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_TABLE); 957 qb.setProjectionMap(sContactsProjectionMap); 958 appendWhere(whereClause, Im.Contacts.PRESENCE_STATUS, "!=", Im.Presence.OFFLINE); 959 appendWhere(whereClause, "chats.last_message_date IS NULL"); 960 appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); 961 groupBy = Im.Contacts.CONTACTLIST; 962 break; 963 964 case MATCH_CONTACTLISTS_BY_PROVIDER: 965 appendWhere(whereClause, Im.ContactList.ACCOUNT, "=", 966 url.getPathSegments().get(2)); 967 // fall through 968 case MATCH_CONTACTLISTS: 969 qb.setTables(TABLE_CONTACT_LIST); 970 qb.setProjectionMap(sContactListProjectionMap); 971 break; 972 973 case MATCH_CONTACTLIST: 974 qb.setTables(TABLE_CONTACT_LIST); 975 appendWhere(whereClause, Im.ContactList._ID, "=", url.getPathSegments().get(1)); 976 break; 977 978 case MATCH_BLOCKEDLIST: 979 qb.setTables(BLOCKEDLIST_JOIN_AVATAR_TABLE); 980 qb.setProjectionMap(sBlockedListProjectionMap); 981 break; 982 983 case MATCH_BLOCKEDLIST_BY_PROVIDER: 984 qb.setTables(BLOCKEDLIST_JOIN_AVATAR_TABLE); 985 qb.setProjectionMap(sBlockedListProjectionMap); 986 appendWhere(whereClause, Im.BlockedList.ACCOUNT, "=", 987 url.getPathSegments().get(2)); 988 break; 989 990 case MATCH_CONTACTS_ETAGS: 991 qb.setTables(TABLE_CONTACTS_ETAG); 992 break; 993 994 case MATCH_CONTACTS_ETAG: 995 qb.setTables(TABLE_CONTACTS_ETAG); 996 appendWhere(whereClause, "_id", "=", url.getPathSegments().get(1)); 997 break; 998 999 case MATCH_MESSAGES: 1000 qb.setTables(TABLE_MESSAGES); 1001 break; 1002 1003 case MATCH_MESSAGES_BY_CONTACT: 1004 // we don't really need the provider id in query. account id 1005 // is enough. 1006 qb.setTables(TABLE_MESSAGES); 1007 appendWhere(whereClause, Im.Messages.ACCOUNT, "=", 1008 url.getPathSegments().get(2)); 1009 appendWhere(whereClause, Im.Messages.CONTACT, "=", 1010 decodeURLSegment(url.getPathSegments().get(3))); 1011 break; 1012 1013 case MATCH_MESSAGE: 1014 qb.setTables(TABLE_MESSAGES); 1015 appendWhere(whereClause, Im.Messages._ID, "=", url.getPathSegments().get(1)); 1016 break; 1017 1018 case MATCH_INVITATIONS: 1019 qb.setTables(TABLE_INVITATIONS); 1020 break; 1021 1022 case MATCH_INVITATION: 1023 qb.setTables(TABLE_INVITATIONS); 1024 appendWhere(whereClause, Im.Invitation._ID, "=", url.getPathSegments().get(1)); 1025 break; 1026 1027 case MATCH_GROUP_MEMBERS: 1028 qb.setTables(TABLE_GROUP_MEMBERS); 1029 break; 1030 1031 case MATCH_GROUP_MEMBERS_BY_GROUP: 1032 qb.setTables(TABLE_GROUP_MEMBERS); 1033 appendWhere(whereClause, Im.GroupMembers.GROUP, "=", url.getPathSegments().get(1)); 1034 break; 1035 1036 case MATCH_GROUP_MESSAGES: 1037 qb.setTables(TABLE_GROUP_MESSAGES); 1038 break; 1039 1040 case MATCH_GROUP_MESSAGE_BY: 1041 qb.setTables(TABLE_GROUP_MESSAGES); 1042 appendWhere(whereClause, Im.GroupMessages.GROUP, "=", 1043 url.getPathSegments().get(1)); 1044 break; 1045 1046 case MATCH_GROUP_MESSAGE: 1047 qb.setTables(TABLE_GROUP_MESSAGES); 1048 appendWhere(whereClause, Im.GroupMessages._ID, "=", 1049 url.getPathSegments().get(1)); 1050 break; 1051 1052 case MATCH_AVATARS: 1053 qb.setTables(TABLE_AVATARS); 1054 break; 1055 1056 case MATCH_AVATAR_BY_PROVIDER: 1057 qb.setTables(TABLE_AVATARS); 1058 appendWhere(whereClause, Im.Avatars.ACCOUNT, "=", url.getPathSegments().get(2)); 1059 break; 1060 1061 case MATCH_CHATS: 1062 qb.setTables(TABLE_CHATS); 1063 break; 1064 1065 case MATCH_CHATS_ID: 1066 qb.setTables(TABLE_CHATS); 1067 appendWhere(whereClause, Im.Chats.CONTACT_ID, "=", url.getPathSegments().get(1)); 1068 break; 1069 1070 case MATCH_PRESENCE: 1071 qb.setTables(TABLE_PRESENCE); 1072 break; 1073 1074 case MATCH_PRESENCE_ID: 1075 qb.setTables(TABLE_PRESENCE); 1076 appendWhere(whereClause, Im.Presence.CONTACT_ID, "=", url.getPathSegments().get(1)); 1077 break; 1078 1079 case MATCH_SESSIONS: 1080 qb.setTables(TABLE_SESSION_COOKIES); 1081 break; 1082 1083 case MATCH_SESSIONS_BY_PROVIDER: 1084 qb.setTables(TABLE_SESSION_COOKIES); 1085 appendWhere(whereClause, Im.SessionCookies.ACCOUNT, "=", url.getPathSegments().get(2)); 1086 break; 1087 1088 case MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME: 1089 appendWhere(whereClause, Im.ProviderSettings.NAME, "=", url.getPathSegments().get(2)); 1090 // fall through 1091 case MATCH_PROVIDER_SETTINGS_BY_ID: 1092 appendWhere(whereClause, Im.ProviderSettings.PROVIDER, "=", url.getPathSegments().get(1)); 1093 // fall through 1094 case MATCH_PROVIDER_SETTINGS: 1095 qb.setTables(TABLE_PROVIDER_SETTINGS); 1096 break; 1097 1098 case MATCH_OUTGOING_RMQ_MESSAGES: 1099 qb.setTables(TABLE_OUTGOING_RMQ_MESSAGES); 1100 break; 1101 1102 case MATCH_OUTGOING_HIGHEST_RMQ_ID: 1103 qb.setTables(TABLE_OUTGOING_RMQ_MESSAGES); 1104 sort = "rmq_id DESC"; 1105 limit = "1"; 1106 break; 1107 1108 case MATCH_LAST_RMQ_ID: 1109 qb.setTables(TABLE_LAST_RMQ_ID); 1110 limit = "1"; 1111 break; 1112 1113 case MATCH_ACCOUNTS_STATUS: 1114 qb.setTables(TABLE_ACCOUNT_STATUS); 1115 break; 1116 1117 case MATCH_ACCOUNT_STATUS: 1118 qb.setTables(TABLE_ACCOUNT_STATUS); 1119 appendWhere(whereClause, Im.AccountStatus.ACCOUNT, "=", 1120 url.getPathSegments().get(1)); 1121 break; 1122 1123 case MATCH_BRANDING_RESOURCE_MAP_CACHE: 1124 qb.setTables(TABLE_BRANDING_RESOURCE_MAP_CACHE); 1125 break; 1126 1127 default: 1128 throw new IllegalArgumentException("Unknown URL " + url); 1129 } 1130 1131 // run the query 1132 final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 1133 Cursor c = null; 1134 1135 try { 1136 c = qb.query(db, projectionIn, whereClause.toString(), selectionArgs, 1137 groupBy, null, sort, limit); 1138 if (c != null) { 1139 switch(match) { 1140 case MATCH_CHATTING_CONTACTS: 1141 case MATCH_CONTACTS_BY_PROVIDER: 1142 case MATCH_CHATTING_CONTACTS_BY_PROVIDER: 1143 case MATCH_ONLINE_CONTACTS_BY_PROVIDER: 1144 case MATCH_OFFLINE_CONTACTS_BY_PROVIDER: 1145 case MATCH_CONTACTS_BAREBONE: 1146 case MATCH_CONTACTS_JOIN_PRESENCE: 1147 case MATCH_ONLINE_CONTACT_COUNT: 1148 url = Im.Contacts.CONTENT_URI; 1149 break; 1150 } 1151 if (DBG) log("set notify url " + url); 1152 c.setNotificationUri(getContext().getContentResolver(), url); 1153 } 1154 } catch (Exception ex) { 1155 Log.e(LOG_TAG, "query db caught ", ex); 1156 } 1157 1158 return c; 1159 } 1160 buildQueryContactsByProvider(SQLiteQueryBuilder qb, StringBuilder whereClause, Uri url)1161 private void buildQueryContactsByProvider(SQLiteQueryBuilder qb, 1162 StringBuilder whereClause, Uri url) { 1163 qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); 1164 qb.setProjectionMap(sContactsProjectionMap); 1165 // we don't really need the provider id in query. account id 1166 // is enough. 1167 appendWhere(whereClause, Im.Contacts.ACCOUNT, "=", url.getLastPathSegment()); 1168 } 1169 1170 @Override getType(Uri url)1171 public String getType(Uri url) { 1172 int match = mUrlMatcher.match(url); 1173 switch (match) { 1174 case MATCH_PROVIDERS: 1175 return Im.Provider.CONTENT_TYPE; 1176 1177 case MATCH_PROVIDERS_BY_ID: 1178 return Im.Provider.CONTENT_ITEM_TYPE; 1179 1180 case MATCH_ACCOUNTS: 1181 return Im.Account.CONTENT_TYPE; 1182 1183 case MATCH_ACCOUNTS_BY_ID: 1184 return Im.Account.CONTENT_ITEM_TYPE; 1185 1186 case MATCH_CONTACTS: 1187 case MATCH_CONTACTS_BY_PROVIDER: 1188 case MATCH_ONLINE_CONTACTS_BY_PROVIDER: 1189 case MATCH_OFFLINE_CONTACTS_BY_PROVIDER: 1190 case MATCH_CONTACTS_BULK: 1191 case MATCH_CONTACTS_BAREBONE: 1192 case MATCH_CONTACTS_JOIN_PRESENCE: 1193 return Im.Contacts.CONTENT_TYPE; 1194 1195 case MATCH_CONTACT: 1196 return Im.Contacts.CONTENT_ITEM_TYPE; 1197 1198 case MATCH_CONTACTLISTS: 1199 case MATCH_CONTACTLISTS_BY_PROVIDER: 1200 return Im.ContactList.CONTENT_TYPE; 1201 1202 case MATCH_CONTACTLIST: 1203 return Im.ContactList.CONTENT_ITEM_TYPE; 1204 1205 case MATCH_BLOCKEDLIST: 1206 case MATCH_BLOCKEDLIST_BY_PROVIDER: 1207 return Im.BlockedList.CONTENT_TYPE; 1208 1209 case MATCH_CONTACTS_ETAGS: 1210 case MATCH_CONTACTS_ETAG: 1211 return Im.ContactsEtag.CONTENT_TYPE; 1212 1213 case MATCH_MESSAGES: 1214 case MATCH_MESSAGES_BY_CONTACT: 1215 return Im.Messages.CONTENT_TYPE; 1216 1217 case MATCH_MESSAGE: 1218 return Im.Messages.CONTENT_ITEM_TYPE; 1219 1220 case MATCH_GROUP_MESSAGES: 1221 case MATCH_GROUP_MESSAGE_BY: 1222 return Im.GroupMessages.CONTENT_TYPE; 1223 1224 case MATCH_GROUP_MESSAGE: 1225 return Im.GroupMessages.CONTENT_ITEM_TYPE; 1226 1227 case MATCH_PRESENCE: 1228 case MATCH_PRESENCE_BULK: 1229 return Im.Presence.CONTENT_TYPE; 1230 1231 case MATCH_AVATARS: 1232 return Im.Avatars.CONTENT_TYPE; 1233 1234 case MATCH_AVATAR: 1235 return Im.Avatars.CONTENT_ITEM_TYPE; 1236 1237 case MATCH_CHATS: 1238 return Im.Chats.CONTENT_TYPE; 1239 1240 case MATCH_CHATS_ID: 1241 return Im.Chats.CONTENT_ITEM_TYPE; 1242 1243 case MATCH_INVITATIONS: 1244 return Im.Invitation.CONTENT_TYPE; 1245 1246 case MATCH_INVITATION: 1247 return Im.Invitation.CONTENT_ITEM_TYPE; 1248 1249 case MATCH_GROUP_MEMBERS: 1250 case MATCH_GROUP_MEMBERS_BY_GROUP: 1251 return Im.GroupMembers.CONTENT_TYPE; 1252 1253 case MATCH_SESSIONS: 1254 case MATCH_SESSIONS_BY_PROVIDER: 1255 return Im.SessionCookies.CONTENT_TYPE; 1256 1257 case MATCH_PROVIDER_SETTINGS: 1258 return Im.ProviderSettings.CONTENT_TYPE; 1259 1260 case MATCH_ACCOUNTS_STATUS: 1261 return Im.AccountStatus.CONTENT_TYPE; 1262 1263 case MATCH_ACCOUNT_STATUS: 1264 return Im.AccountStatus.CONTENT_ITEM_TYPE; 1265 1266 default: 1267 throw new IllegalArgumentException("Unknown URL"); 1268 } 1269 } 1270 1271 // package scope for testing. insertBulkContacts(ContentValues values)1272 boolean insertBulkContacts(ContentValues values) { 1273 //if (DBG) log("insertBulkContacts: begin"); 1274 1275 ArrayList<String> usernames = values.getStringArrayList(Im.Contacts.USERNAME); 1276 ArrayList<String> nicknames = values.getStringArrayList(Im.Contacts.NICKNAME); 1277 int usernameCount = usernames.size(); 1278 int nicknameCount = nicknames.size(); 1279 1280 if (usernameCount != nicknameCount) { 1281 Log.e(LOG_TAG, "[ImProvider] insertBulkContacts: input bundle " + 1282 "username & nickname lists have diff. length!"); 1283 return false; 1284 } 1285 1286 ArrayList<String> contactTypeArray = values.getStringArrayList(Im.Contacts.TYPE); 1287 ArrayList<String> subscriptionStatusArray = 1288 values.getStringArrayList(Im.Contacts.SUBSCRIPTION_STATUS); 1289 ArrayList<String> subscriptionTypeArray = 1290 values.getStringArrayList(Im.Contacts.SUBSCRIPTION_TYPE); 1291 ArrayList<String> quickContactArray = values.getStringArrayList(Im.Contacts.QUICK_CONTACT); 1292 ArrayList<String> rejectedArray = values.getStringArrayList(Im.Contacts.REJECTED); 1293 int sum = 0; 1294 1295 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1296 1297 db.beginTransaction(); 1298 try { 1299 Long provider = values.getAsLong(Im.Contacts.PROVIDER); 1300 Long account = values.getAsLong(Im.Contacts.ACCOUNT); 1301 Long listId = values.getAsLong(Im.Contacts.CONTACTLIST); 1302 1303 ContentValues contactValues = new ContentValues(); 1304 contactValues.put(Im.Contacts.PROVIDER, provider); 1305 contactValues.put(Im.Contacts.ACCOUNT, account); 1306 contactValues.put(Im.Contacts.CONTACTLIST, listId); 1307 ContentValues presenceValues = new ContentValues(); 1308 presenceValues.put(Im.Presence.PRESENCE_STATUS, 1309 Im.Presence.OFFLINE); 1310 1311 for (int i=0; i<usernameCount; i++) { 1312 String username = usernames.get(i); 1313 String nickname = nicknames.get(i); 1314 int type = 0; 1315 int subscriptionStatus = 0; 1316 int subscriptionType = 0; 1317 int quickContact = 0; 1318 int rejected = 0; 1319 1320 try { 1321 type = Integer.parseInt(contactTypeArray.get(i)); 1322 if (subscriptionStatusArray != null) { 1323 subscriptionStatus = Integer.parseInt(subscriptionStatusArray.get(i)); 1324 } 1325 if (subscriptionTypeArray != null) { 1326 subscriptionType = Integer.parseInt(subscriptionTypeArray.get(i)); 1327 } 1328 if (quickContactArray != null) { 1329 quickContact = Integer.parseInt(quickContactArray.get(i)); 1330 } 1331 if (rejectedArray != null) { 1332 rejected = Integer.parseInt(rejectedArray.get(i)); 1333 } 1334 } catch (NumberFormatException ex) { 1335 Log.e(LOG_TAG, "insertBulkContacts: caught " + ex); 1336 } 1337 1338 /* 1339 if (DBG) log("insertBulkContacts[" + i + "] username=" + 1340 username + ", nickname=" + nickname + ", type=" + type + 1341 ", subscriptionStatus=" + subscriptionStatus + ", subscriptionType=" + 1342 subscriptionType + ", qc=" + quickContact); 1343 */ 1344 1345 contactValues.put(Im.Contacts.USERNAME, username); 1346 contactValues.put(Im.Contacts.NICKNAME, nickname); 1347 contactValues.put(Im.Contacts.TYPE, type); 1348 if (subscriptionStatusArray != null) { 1349 contactValues.put(Im.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); 1350 } 1351 if (subscriptionTypeArray != null) { 1352 contactValues.put(Im.Contacts.SUBSCRIPTION_TYPE, subscriptionType); 1353 } 1354 if (quickContactArray != null) { 1355 contactValues.put(Im.Contacts.QUICK_CONTACT, quickContact); 1356 } 1357 if (rejectedArray != null) { 1358 contactValues.put(Im.Contacts.REJECTED, rejected); 1359 } 1360 1361 long rowId = 0; 1362 1363 /* save this code for when we add constraint (account, username) to the contacts 1364 table 1365 try { 1366 rowId = db.insertOrThrow(TABLE_CONTACTS, USERNAME, contactValues); 1367 } catch (android.database.sqlite.SQLiteConstraintException ex) { 1368 if (DBG) log("insertBulkContacts: insert " + username + " caught " + ex); 1369 1370 // append username to the selection clause 1371 updateSelection.delete(0, updateSelection.length()); 1372 updateSelection.append(Im.Contacts.USERNAME); 1373 updateSelection.append("=?"); 1374 updateSelectionArgs[0] = username; 1375 1376 int updated = db.update(TABLE_CONTACTS, contactValues, 1377 updateSelection.toString(), updateSelectionArgs); 1378 1379 if (DBG && updated != 1) { 1380 log("insertBulkContacts: update " + username + " failed!"); 1381 } 1382 } 1383 */ 1384 1385 rowId = db.insert(TABLE_CONTACTS, USERNAME, contactValues); 1386 if (rowId > 0) { 1387 sum++; 1388 if (!USE_CONTACT_PRESENCE_TRIGGER) { 1389 // seed the presence for the new contact 1390 //if (DBG) log("seedPresence for pid " + rowId); 1391 presenceValues.put(Im.Presence.CONTACT_ID, rowId); 1392 db.insert(TABLE_PRESENCE, null, presenceValues); 1393 } 1394 } 1395 1396 // yield the lock if anyone else is trying to 1397 // perform a db operation here. 1398 db.yieldIfContended(); 1399 } 1400 1401 db.setTransactionSuccessful(); 1402 } finally { 1403 db.endTransaction(); 1404 } 1405 1406 // We know that we succeeded becuase endTransaction throws if the transaction failed. 1407 if (DBG) log("insertBulkContacts: added " + sum + " contacts!"); 1408 return true; 1409 } 1410 1411 // package scope for testing. updateBulkContacts(ContentValues values, String userWhere)1412 int updateBulkContacts(ContentValues values, String userWhere) { 1413 ArrayList<String> usernames = values.getStringArrayList(Im.Contacts.USERNAME); 1414 ArrayList<String> nicknames = values.getStringArrayList(Im.Contacts.NICKNAME); 1415 1416 int usernameCount = usernames.size(); 1417 int nicknameCount = nicknames.size(); 1418 1419 if (usernameCount != nicknameCount) { 1420 Log.e(LOG_TAG, "[ImProvider] updateBulkContacts: input bundle " + 1421 "username & nickname lists have diff. length!"); 1422 return 0; 1423 } 1424 1425 ArrayList<String> contactTypeArray = values.getStringArrayList(Im.Contacts.TYPE); 1426 ArrayList<String> subscriptionStatusArray = 1427 values.getStringArrayList(Im.Contacts.SUBSCRIPTION_STATUS); 1428 ArrayList<String> subscriptionTypeArray = 1429 values.getStringArrayList(Im.Contacts.SUBSCRIPTION_TYPE); 1430 ArrayList<String> quickContactArray = values.getStringArrayList(Im.Contacts.QUICK_CONTACT); 1431 ArrayList<String> rejectedArray = values.getStringArrayList(Im.Contacts.REJECTED); 1432 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1433 1434 db.beginTransaction(); 1435 int sum = 0; 1436 1437 try { 1438 Long provider = values.getAsLong(Im.Contacts.PROVIDER); 1439 Long account = values.getAsLong(Im.Contacts.ACCOUNT); 1440 1441 ContentValues contactValues = new ContentValues(); 1442 contactValues.put(Im.Contacts.PROVIDER, provider); 1443 contactValues.put(Im.Contacts.ACCOUNT, account); 1444 1445 StringBuilder updateSelection = new StringBuilder(); 1446 String[] updateSelectionArgs = new String[1]; 1447 1448 for (int i=0; i<usernameCount; i++) { 1449 String username = usernames.get(i); 1450 String nickname = nicknames.get(i); 1451 int type = 0; 1452 int subscriptionStatus = 0; 1453 int subscriptionType = 0; 1454 int quickContact = 0; 1455 int rejected = 0; 1456 1457 try { 1458 type = Integer.parseInt(contactTypeArray.get(i)); 1459 subscriptionStatus = Integer.parseInt(subscriptionStatusArray.get(i)); 1460 subscriptionType = Integer.parseInt(subscriptionTypeArray.get(i)); 1461 quickContact = Integer.parseInt(quickContactArray.get(i)); 1462 rejected = Integer.parseInt(rejectedArray.get(i)); 1463 } catch (NumberFormatException ex) { 1464 Log.e(LOG_TAG, "insertBulkContacts: caught " + ex); 1465 } 1466 1467 if (DBG) log("updateBulkContacts[" + i + "] username=" + 1468 username + ", nickname=" + nickname + ", type=" + type + 1469 ", subscriptionStatus=" + subscriptionStatus + ", subscriptionType=" + 1470 subscriptionType + ", qc=" + quickContact); 1471 1472 contactValues.put(Im.Contacts.USERNAME, username); 1473 contactValues.put(Im.Contacts.NICKNAME, nickname); 1474 contactValues.put(Im.Contacts.TYPE, type); 1475 contactValues.put(Im.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); 1476 contactValues.put(Im.Contacts.SUBSCRIPTION_TYPE, subscriptionType); 1477 contactValues.put(Im.Contacts.QUICK_CONTACT, quickContact); 1478 contactValues.put(Im.Contacts.REJECTED, rejected); 1479 1480 // append username to the selection clause 1481 updateSelection.delete(0, updateSelection.length()); 1482 updateSelection.append(userWhere); 1483 updateSelection.append(" AND "); 1484 updateSelection.append(Im.Contacts.USERNAME); 1485 updateSelection.append("=?"); 1486 1487 updateSelectionArgs[0] = username; 1488 1489 int numUpdated = db.update(TABLE_CONTACTS, contactValues, 1490 updateSelection.toString(), updateSelectionArgs); 1491 if (numUpdated == 0) { 1492 Log.e(LOG_TAG, "[ImProvider] updateBulkContacts: " + 1493 " update failed for selection = " + updateSelection); 1494 } else { 1495 sum += numUpdated; 1496 } 1497 1498 // yield the lock if anyone else is trying to 1499 // perform a db operation here. 1500 db.yieldIfContended(); 1501 } 1502 1503 db.setTransactionSuccessful(); 1504 } finally { 1505 db.endTransaction(); 1506 } 1507 1508 if (DBG) log("updateBulkContacts: " + sum + " entries updated"); 1509 return sum; 1510 } 1511 1512 // constants definitions use for the query in seedInitialPresenceByAccount() 1513 private static final String[] CONTACT_ID_PROJECTION = new String[] { 1514 Im.Contacts._ID, // 0 1515 }; 1516 1517 private static final int COLUMN_ID = 0; 1518 1519 private static final String CONTACTS_WITH_NO_PRESENCE_SELECTION = 1520 Im.Contacts.ACCOUNT + "=?" + " AND " + Im.Contacts._ID + 1521 " in (select " + CONTACT_ID + " from " + TABLE_CONTACTS + 1522 " left outer join " + TABLE_PRESENCE + " on " + CONTACT_ID + '=' + 1523 PRESENCE_CONTACT_ID + " where " + PRESENCE_CONTACT_ID + " IS NULL)"; 1524 1525 // selection args for the query. 1526 private String[] mQueryContactPresenceSelectionArgs = new String[1]; 1527 1528 /** 1529 * This method first performs a query for all the contacts (for the given account) that 1530 * don't have a presence entry in the presence table. Then for each of those contacts, 1531 * the method creates a presence row. The whole thing is done inside one database transaction 1532 * to increase performance. 1533 * 1534 * @param account the account of the contacts for which we want to create seed presence rows. 1535 */ seedInitialPresenceByAccount(long account)1536 private void seedInitialPresenceByAccount(long account) { 1537 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 1538 qb.setTables(TABLE_CONTACTS); 1539 qb.setProjectionMap(sContactsProjectionMap); 1540 1541 mQueryContactPresenceSelectionArgs[0] = String.valueOf(account); 1542 1543 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1544 db.beginTransaction(); 1545 1546 Cursor c = null; 1547 1548 try { 1549 ContentValues presenceValues = new ContentValues(); 1550 presenceValues.put(Im.Presence.PRESENCE_STATUS, Im.Presence.OFFLINE); 1551 presenceValues.put(Im.Presence.PRESENCE_CUSTOM_STATUS, ""); 1552 1553 // First: update all the presence for the account so they are offline 1554 StringBuilder buf = new StringBuilder(); 1555 buf.append(Im.Presence.CONTACT_ID); 1556 buf.append(" in (select "); 1557 buf.append(Im.Contacts._ID); 1558 buf.append(" from "); 1559 buf.append(TABLE_CONTACTS); 1560 buf.append(" where "); 1561 buf.append(Im.Contacts.ACCOUNT); 1562 buf.append("=?) "); 1563 1564 String selection = buf.toString(); 1565 if (DBG) log("seedInitialPresence: reset presence selection=" + selection); 1566 1567 int count = db.update(TABLE_PRESENCE, presenceValues, selection, 1568 mQueryContactPresenceSelectionArgs); 1569 if (DBG) log("seedInitialPresence: reset " + count + " presence rows to OFFLINE"); 1570 1571 // second: add a presence row for each contact that doesn't have a presence 1572 if (DBG) { 1573 log("seedInitialPresence: contacts_with_no_presence_selection => " + 1574 CONTACTS_WITH_NO_PRESENCE_SELECTION); 1575 } 1576 1577 c = qb.query(db, 1578 CONTACT_ID_PROJECTION, 1579 CONTACTS_WITH_NO_PRESENCE_SELECTION, 1580 mQueryContactPresenceSelectionArgs, 1581 null, null, null, null); 1582 1583 if (DBG) log("seedInitialPresence: found " + c.getCount() + " contacts w/o presence"); 1584 1585 count = 0; 1586 1587 while (c.moveToNext()) { 1588 long id = c.getLong(COLUMN_ID); 1589 presenceValues.put(Im.Presence.CONTACT_ID, id); 1590 1591 try { 1592 if (db.insert(TABLE_PRESENCE, null, presenceValues) > 0) { 1593 count++; 1594 } 1595 } catch (SQLiteConstraintException ex) { 1596 // we could possibly catch this exception, since there could be a presence 1597 // row with the same contact_id. That's fine, just ignore the error 1598 if (DBG) log("seedInitialPresence: insert presence for contact_id " + id + 1599 " failed, caught " + ex); 1600 } 1601 } 1602 1603 db.setTransactionSuccessful(); 1604 1605 if (DBG) log("seedInitialPresence: added " + count + " new presence rows"); 1606 } finally { 1607 c.close(); 1608 db.endTransaction(); 1609 } 1610 } 1611 updateBulkPresence(ContentValues values, String userWhere, String[] whereArgs)1612 private int updateBulkPresence(ContentValues values, String userWhere, String[] whereArgs) { 1613 ArrayList<String> usernames = values.getStringArrayList(Im.Contacts.USERNAME); 1614 int count = usernames.size(); 1615 Long account = values.getAsLong(Im.Contacts.ACCOUNT); 1616 1617 ArrayList<String> priorityArray = values.getStringArrayList(Im.Presence.PRIORITY); 1618 ArrayList<String> modeArray = values.getStringArrayList(Im.Presence.PRESENCE_STATUS); 1619 ArrayList<String> statusArray = values.getStringArrayList( 1620 Im.Presence.PRESENCE_CUSTOM_STATUS); 1621 ArrayList<String> clientTypeArray = values.getStringArrayList(Im.Presence.CLIENT_TYPE); 1622 ArrayList<String> resourceArray = values.getStringArrayList(Im.Presence.JID_RESOURCE); 1623 1624 // append username to the selection clause 1625 StringBuilder buf = new StringBuilder(); 1626 1627 if (!TextUtils.isEmpty(userWhere)) { 1628 buf.append(userWhere); 1629 buf.append(" AND "); 1630 } 1631 1632 buf.append(Im.Presence.CONTACT_ID); 1633 buf.append(" in (select "); 1634 buf.append(Im.Contacts._ID); 1635 buf.append(" from "); 1636 buf.append(TABLE_CONTACTS); 1637 buf.append(" where "); 1638 buf.append(Im.Contacts.ACCOUNT); 1639 buf.append("=? AND "); 1640 1641 // use username LIKE ? for case insensitive comparison 1642 buf.append(Im.Contacts.USERNAME); 1643 buf.append(" LIKE ?) AND ("); 1644 1645 buf.append(Im.Presence.PRIORITY); 1646 buf.append("<=? OR "); 1647 buf.append(Im.Presence.PRIORITY); 1648 buf.append(" IS NULL OR "); 1649 buf.append(Im.Presence.JID_RESOURCE); 1650 buf.append("=?)"); 1651 1652 String selection = buf.toString(); 1653 1654 if (DBG) log("updateBulkPresence: selection => " + selection); 1655 1656 int numArgs = (whereArgs != null ? whereArgs.length + 4 : 4); 1657 String[] selectionArgs = new String[numArgs]; 1658 int selArgsIndex = 0; 1659 1660 if (whereArgs != null) { 1661 for (selArgsIndex=0; selArgsIndex<numArgs-1; selArgsIndex++) { 1662 selectionArgs[selArgsIndex] = whereArgs[selArgsIndex]; 1663 } 1664 } 1665 1666 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1667 1668 db.beginTransaction(); 1669 int sum = 0; 1670 1671 try { 1672 ContentValues presenceValues = new ContentValues(); 1673 1674 for (int i=0; i<count; i++) { 1675 String username = usernames.get(i); 1676 int priority = 0; 1677 int mode = 0; 1678 String status = statusArray.get(i); 1679 String jidResource = resourceArray == null ? "" : resourceArray.get(i); 1680 int clientType = Im.Presence.CLIENT_TYPE_DEFAULT; 1681 1682 try { 1683 if (priorityArray != null) { 1684 priority = Integer.parseInt(priorityArray.get(i)); 1685 } 1686 if (modeArray != null) { 1687 mode = Integer.parseInt(modeArray.get(i)); 1688 } 1689 if (clientTypeArray != null) { 1690 clientType = Integer.parseInt(clientTypeArray.get(i)); 1691 } 1692 } catch (NumberFormatException ex) { 1693 Log.e(LOG_TAG, "[ImProvider] updateBulkPresence: caught " + ex); 1694 } 1695 1696 /* 1697 if (DBG) { 1698 log("updateBulkPresence[" + i + "] username=" + username + ", priority=" + 1699 priority + ", mode=" + mode + ", status=" + status + ", resource=" + 1700 jidResource + ", clientType=" + clientType); 1701 } 1702 */ 1703 1704 if (modeArray != null) { 1705 presenceValues.put(Im.Presence.PRESENCE_STATUS, mode); 1706 } 1707 if (priorityArray != null) { 1708 presenceValues.put(Im.Presence.PRIORITY, priority); 1709 } 1710 presenceValues.put(Im.Presence.PRESENCE_CUSTOM_STATUS, status); 1711 if (clientTypeArray != null) { 1712 presenceValues.put(Im.Presence.CLIENT_TYPE, clientType); 1713 } 1714 1715 if (!TextUtils.isEmpty(jidResource)) { 1716 presenceValues.put(Im.Presence.JID_RESOURCE, jidResource); 1717 } 1718 1719 // fill in the selection args 1720 int idx = selArgsIndex; 1721 selectionArgs[idx++] = String.valueOf(account); 1722 selectionArgs[idx++] = username; 1723 selectionArgs[idx++] = String.valueOf(priority); 1724 selectionArgs[idx] = jidResource; 1725 1726 int numUpdated = db.update(TABLE_PRESENCE, 1727 presenceValues, selection, selectionArgs); 1728 if (numUpdated == 0) { 1729 Log.e(LOG_TAG, "[ImProvider] updateBulkPresence: failed for " + username); 1730 } else { 1731 sum += numUpdated; 1732 } 1733 1734 // yield the lock if anyone else is trying to 1735 // perform a db operation here. 1736 db.yieldIfContended(); 1737 } 1738 1739 db.setTransactionSuccessful(); 1740 } finally { 1741 db.endTransaction(); 1742 } 1743 1744 if (DBG) log("updateBulkPresence: " + sum + " entries updated"); 1745 return sum; 1746 } 1747 insertInternal(Uri url, ContentValues initialValues)1748 public Uri insertInternal(Uri url, ContentValues initialValues) { 1749 Uri resultUri = null; 1750 long rowID = 0; 1751 boolean notifyContactListContentUri = false; 1752 boolean notifyContactContentUri = false; 1753 boolean notifyMessagesContentUri = false; 1754 boolean notifyGroupMessagesContentUri = false; 1755 boolean notifyProviderAccountContentUri = false; 1756 1757 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1758 int match = mUrlMatcher.match(url); 1759 1760 if (DBG) log("insert to " + url + ", match " + match); 1761 switch (match) { 1762 case MATCH_PROVIDERS: 1763 // Insert into the providers table 1764 rowID = db.insert(TABLE_PROVIDERS, "name", initialValues); 1765 if (rowID > 0) { 1766 resultUri = Uri.parse(Im.Provider.CONTENT_URI + "/" + rowID); 1767 } 1768 notifyProviderAccountContentUri = true; 1769 break; 1770 1771 case MATCH_ACCOUNTS: 1772 // Insert into the accounts table 1773 rowID = db.insert(TABLE_ACCOUNTS, "name", initialValues); 1774 if (rowID > 0) { 1775 resultUri = Uri.parse(Im.Account.CONTENT_URI + "/" + rowID); 1776 } 1777 notifyProviderAccountContentUri = true; 1778 break; 1779 1780 case MATCH_CONTACTS_BY_PROVIDER: 1781 appendValuesFromUrl(initialValues, url, Im.Contacts.PROVIDER, 1782 Im.Contacts.ACCOUNT); 1783 // fall through 1784 case MATCH_CONTACTS: 1785 case MATCH_CONTACTS_BAREBONE: 1786 // Insert into the contacts table 1787 rowID = db.insert(TABLE_CONTACTS, "username", initialValues); 1788 if (rowID > 0) { 1789 resultUri = Uri.parse(Im.Contacts.CONTENT_URI + "/" + rowID); 1790 } 1791 1792 notifyContactContentUri = true; 1793 break; 1794 1795 case MATCH_CONTACTS_BULK: 1796 if (insertBulkContacts(initialValues)) { 1797 // notify change using the "content://im/contacts" url, 1798 // so the change will be observed by listeners interested 1799 // in contacts changes. 1800 resultUri = Im.Contacts.CONTENT_URI; 1801 } 1802 notifyContactContentUri = true; 1803 break; 1804 1805 case MATCH_CONTACTLISTS_BY_PROVIDER: 1806 appendValuesFromUrl(initialValues, url, Im.ContactList.PROVIDER, 1807 Im.ContactList.ACCOUNT); 1808 // fall through 1809 case MATCH_CONTACTLISTS: 1810 // Insert into the contactList table 1811 rowID = db.insert(TABLE_CONTACT_LIST, "name", initialValues); 1812 if (rowID > 0) { 1813 resultUri = Uri.parse(Im.ContactList.CONTENT_URI + "/" + rowID); 1814 } 1815 notifyContactListContentUri = true; 1816 break; 1817 1818 case MATCH_BLOCKEDLIST_BY_PROVIDER: 1819 appendValuesFromUrl(initialValues, url, Im.BlockedList.PROVIDER, 1820 Im.BlockedList.ACCOUNT); 1821 // fall through 1822 case MATCH_BLOCKEDLIST: 1823 // Insert into the blockedList table 1824 rowID = db.insert(TABLE_BLOCKED_LIST, "username", initialValues); 1825 if (rowID > 0) { 1826 resultUri = Uri.parse(Im.BlockedList.CONTENT_URI + "/" + rowID); 1827 } 1828 1829 break; 1830 1831 case MATCH_CONTACTS_ETAGS: 1832 rowID = db.replace(TABLE_CONTACTS_ETAG, Im.ContactsEtag.ETAG, initialValues); 1833 if (rowID > 0) { 1834 resultUri = Uri.parse(Im.ContactsEtag.CONTENT_URI + "/" + rowID); 1835 } 1836 break; 1837 1838 case MATCH_MESSAGES_BY_CONTACT: 1839 appendValuesFromUrl(initialValues, url, Im.Messages.PROVIDER, 1840 Im.Messages.ACCOUNT, Im.Messages.CONTACT); 1841 notifyMessagesContentUri = true; 1842 // fall through 1843 case MATCH_MESSAGES: 1844 // Insert into the messages table. 1845 rowID = db.insert(TABLE_MESSAGES, "contact", initialValues); 1846 if (rowID > 0) { 1847 resultUri = Uri.parse(Im.Messages.CONTENT_URI + "/" + rowID); 1848 } 1849 1850 break; 1851 1852 case MATCH_INVITATIONS: 1853 rowID = db.insert(TABLE_INVITATIONS, null, initialValues); 1854 if (rowID > 0) { 1855 resultUri = Uri.parse(Im.Invitation.CONTENT_URI + "/" + rowID); 1856 } 1857 break; 1858 1859 case MATCH_GROUP_MEMBERS: 1860 rowID = db.insert(TABLE_GROUP_MEMBERS, "nickname", initialValues); 1861 if (rowID > 0) { 1862 resultUri = Uri.parse(Im.GroupMembers.CONTENT_URI + "/" + rowID); 1863 } 1864 break; 1865 1866 case MATCH_GROUP_MEMBERS_BY_GROUP: 1867 appendValuesFromUrl(initialValues, url, Im.GroupMembers.GROUP); 1868 rowID = db.insert(TABLE_GROUP_MEMBERS, "nickname", initialValues); 1869 if (rowID > 0) { 1870 resultUri = Uri.parse(Im.GroupMembers.CONTENT_URI + "/" + rowID); 1871 } 1872 break; 1873 1874 case MATCH_GROUP_MESSAGE_BY: 1875 appendValuesFromUrl(initialValues, url, Im.GroupMembers.GROUP); 1876 notifyGroupMessagesContentUri = true; 1877 // fall through 1878 case MATCH_GROUP_MESSAGES: 1879 rowID = db.insert(TABLE_GROUP_MESSAGES, "group", initialValues); 1880 if (rowID > 0) { 1881 resultUri = Uri.parse(Im.GroupMessages.CONTENT_URI + "/" + rowID); 1882 } 1883 break; 1884 1885 case MATCH_AVATAR_BY_PROVIDER: 1886 appendValuesFromUrl(initialValues, url, Im.Avatars.PROVIDER, Im.Avatars.ACCOUNT); 1887 // fall through 1888 case MATCH_AVATARS: 1889 // Insert into the avatars table 1890 rowID = db.replace(TABLE_AVATARS, "contact", initialValues); 1891 if (rowID > 0) { 1892 resultUri = Uri.parse(Im.Avatars.CONTENT_URI + "/" + rowID); 1893 } 1894 break; 1895 1896 case MATCH_CHATS_ID: 1897 appendValuesFromUrl(initialValues, url, Im.Chats.CONTACT_ID); 1898 // fall through 1899 case MATCH_CHATS: 1900 // Insert into the chats table 1901 initialValues.put(Im.Chats.SHORTCUT, -1); 1902 rowID = db.replace(TABLE_CHATS, Im.Chats.CONTACT_ID, initialValues); 1903 if (rowID > 0) { 1904 resultUri = Uri.parse(Im.Chats.CONTENT_URI + "/" + rowID); 1905 addToQuickSwitch(rowID); 1906 } 1907 notifyContactContentUri = true; 1908 break; 1909 1910 case MATCH_PRESENCE: 1911 rowID = db.replace(TABLE_PRESENCE, null, initialValues); 1912 if (rowID > 0) { 1913 resultUri = Uri.parse(Im.Presence.CONTENT_URI + "/" + rowID); 1914 } 1915 notifyContactContentUri = true; 1916 break; 1917 1918 case MATCH_PRESENCE_SEED_BY_ACCOUNT: 1919 try { 1920 seedInitialPresenceByAccount(Long.parseLong(url.getLastPathSegment())); 1921 resultUri = Im.Presence.CONTENT_URI; 1922 } catch (NumberFormatException ex) { 1923 throw new IllegalArgumentException(); 1924 } 1925 break; 1926 1927 case MATCH_SESSIONS_BY_PROVIDER: 1928 appendValuesFromUrl(initialValues, url, Im.SessionCookies.PROVIDER, 1929 Im.SessionCookies.ACCOUNT); 1930 // fall through 1931 case MATCH_SESSIONS: 1932 rowID = db.insert(TABLE_SESSION_COOKIES, null, initialValues); 1933 if(rowID > 0) { 1934 resultUri = Uri.parse(Im.SessionCookies.CONTENT_URI + "/" + rowID); 1935 } 1936 break; 1937 1938 case MATCH_PROVIDER_SETTINGS: 1939 rowID = db.replace(TABLE_PROVIDER_SETTINGS, null, initialValues); 1940 if (rowID > 0) { 1941 resultUri = Uri.parse(Im.ProviderSettings.CONTENT_URI + "/" + rowID); 1942 } 1943 break; 1944 1945 case MATCH_OUTGOING_RMQ_MESSAGES: 1946 rowID = db.insert(TABLE_OUTGOING_RMQ_MESSAGES, null, initialValues); 1947 if (rowID > 0) { 1948 resultUri = Uri.parse(Im.OutgoingRmq.CONTENT_URI + "/" + rowID); 1949 } 1950 break; 1951 1952 case MATCH_LAST_RMQ_ID: 1953 rowID = db.replace(TABLE_LAST_RMQ_ID, null, initialValues); 1954 if (rowID > 0) { 1955 resultUri = Uri.parse(Im.LastRmqId.CONTENT_URI + "/" + rowID); 1956 } 1957 break; 1958 1959 case MATCH_ACCOUNTS_STATUS: 1960 rowID = db.replace(TABLE_ACCOUNT_STATUS, null, initialValues); 1961 if (rowID > 0) { 1962 resultUri = Uri.parse(Im.AccountStatus.CONTENT_URI + "/" + rowID); 1963 } 1964 notifyProviderAccountContentUri = true; 1965 break; 1966 1967 case MATCH_BRANDING_RESOURCE_MAP_CACHE: 1968 rowID = db.insert(TABLE_BRANDING_RESOURCE_MAP_CACHE, null, initialValues); 1969 if (rowID > 0) { 1970 resultUri = Uri.parse(Im.BrandingResourceMapCache.CONTENT_URI + "/" + rowID); 1971 } 1972 break; 1973 1974 default: 1975 throw new UnsupportedOperationException("Cannot insert into URL: " + url); 1976 } 1977 // TODO: notify the data change observer? 1978 1979 if (resultUri != null) { 1980 ContentResolver resolver = getContext().getContentResolver(); 1981 1982 // In most case, we query contacts with presence and chats joined, thus 1983 // we should also notify that contacts changes when presence or chats changed. 1984 if (notifyContactContentUri) { 1985 resolver.notifyChange(Im.Contacts.CONTENT_URI, null); 1986 } 1987 1988 if (notifyContactListContentUri) { 1989 resolver.notifyChange(Im.ContactList.CONTENT_URI, null); 1990 } 1991 1992 if (notifyMessagesContentUri) { 1993 resolver.notifyChange(Im.Messages.CONTENT_URI, null); 1994 } 1995 1996 if (notifyGroupMessagesContentUri) { 1997 resolver.notifyChange(Im.GroupMessages.CONTENT_URI, null); 1998 } 1999 2000 if (notifyProviderAccountContentUri) { 2001 if (DBG) log("notify insert for " + Im.Provider.CONTENT_URI_WITH_ACCOUNT); 2002 resolver.notifyChange(Im.Provider.CONTENT_URI_WITH_ACCOUNT, 2003 null); 2004 } 2005 } 2006 return resultUri; 2007 } 2008 appendValuesFromUrl(ContentValues values, Uri url, String...columns)2009 private void appendValuesFromUrl(ContentValues values, Uri url, String...columns){ 2010 if(url.getPathSegments().size() <= columns.length) { 2011 throw new IllegalArgumentException("Not enough values in url"); 2012 } 2013 for(int i = 0; i < columns.length; i++){ 2014 if(values.containsKey(columns[i])){ 2015 throw new UnsupportedOperationException("Cannot override the value for " + columns[i]); 2016 } 2017 values.put(columns[i], decodeURLSegment(url.getPathSegments().get(i + 1))); 2018 } 2019 } 2020 2021 // Quick-switch management 2022 // The chat UI provides slots (0, 9, .., 1) for the first 10 chats. This allows you to 2023 // quickly switch between these chats by chording menu+#. We number from the right end of 2024 // the number row and move leftward to make an easier two-hand chord with the menu button 2025 // on the left side of the keyboard. addToQuickSwitch(long newRow)2026 private void addToQuickSwitch(long newRow) { 2027 // Since there are fewer than 10, there must be an empty slot. Let's find it. 2028 int slot = findEmptyQuickSwitchSlot(); 2029 2030 if (slot == -1) { 2031 return; 2032 } 2033 2034 updateSlotForChat(newRow, slot); 2035 } 2036 2037 // If there are more than 10 chats and one with a quick switch slot ends then pick a chat 2038 // that doesn't have a slot and have it inhabit the newly emptied slot. backfillQuickSwitchSlots()2039 private void backfillQuickSwitchSlots() { 2040 // Find all the chats without a quick switch slot, and order 2041 Cursor c = query(Im.Chats.CONTENT_URI, 2042 BACKFILL_PROJECTION, 2043 Im.Chats.SHORTCUT + "=-1", null, Im.Chats.LAST_MESSAGE_DATE + " DESC"); 2044 2045 try { 2046 if (c.getCount() < 1) { 2047 return; 2048 } 2049 2050 int slot = findEmptyQuickSwitchSlot(); 2051 2052 if (slot != -1) { 2053 c.moveToFirst(); 2054 2055 long id = c.getLong(c.getColumnIndex(Im.Chats._ID)); 2056 2057 updateSlotForChat(id, slot); 2058 } 2059 } finally { 2060 c.close(); 2061 } 2062 } 2063 updateSlotForChat(long chatId, int slot)2064 private int updateSlotForChat(long chatId, int slot) { 2065 ContentValues values = new ContentValues(); 2066 2067 values.put(Im.Chats.SHORTCUT, slot); 2068 2069 return update(Im.Chats.CONTENT_URI, values, Im.Chats._ID + "=?", 2070 new String[] { Long.toString(chatId) }); 2071 } 2072 findEmptyQuickSwitchSlot()2073 private int findEmptyQuickSwitchSlot() { 2074 Cursor c = queryInternal(Im.Chats.CONTENT_URI, FIND_SHORTCUT_PROJECTION, null, null, null); 2075 final int N = c.getCount(); 2076 2077 try { 2078 // If there are 10 or more chats then all the quick switch slots are already filled 2079 if (N >= 10) { 2080 return -1; 2081 } 2082 2083 int slots = 0; 2084 int column = c.getColumnIndex(Im.Chats.SHORTCUT); 2085 2086 // The map is here because numbers go from 0-9, but we want to assign slots in 2087 // 0, 9, 8, ..., 1 order to match the right-to-left reading of the number row 2088 // on the keyboard. 2089 int[] map = new int[] { 0, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; 2090 2091 // Mark all the slots that are in use 2092 // The shortcuts represent actual keyboard number row keys, and not ordinals. 2093 // So 7 would mean the shortcut is the 7 key on the keyboard and NOT the 7th 2094 // shortcut. The passing of slot through map[] below maps these keyboard key 2095 // shortcuts into an ordinal bit position in the 'slots' bitfield. 2096 for (c.moveToFirst(); ! c.isAfterLast(); c.moveToNext()) { 2097 int slot = c.getInt(column); 2098 2099 if (slot != -1) { 2100 slots |= (1 << map[slot]); 2101 } 2102 } 2103 2104 // Try to find an empty one 2105 // As we exit this, the push of i through map[] maps the ordinal bit position 2106 // in the 'slots' bitfield onto a key on the number row of the device keyboard. 2107 // The keyboard key is what is used to designate the shortcut. 2108 for (int i = 0; i < 10; i++) { 2109 if ((slots & (1 << i)) == 0) { 2110 return map[i]; 2111 } 2112 } 2113 2114 return -1; 2115 } finally { 2116 c.close(); 2117 } 2118 } 2119 2120 /** 2121 * manual trigger for deleting contacts 2122 */ 2123 private static final String DELETE_PRESENCE_SELECTION = 2124 Im.Presence.CONTACT_ID + " in (select " + 2125 PRESENCE_CONTACT_ID + " from " + TABLE_PRESENCE + " left outer join " + TABLE_CONTACTS + 2126 " on " + PRESENCE_CONTACT_ID + '=' + CONTACT_ID + " where " + CONTACT_ID + " IS NULL)"; 2127 2128 private static final String CHATS_CONTACT_ID = TABLE_CHATS + '.' + Im.Chats.CONTACT_ID; 2129 private static final String DELETE_CHATS_SELECTION = Im.Chats.CONTACT_ID + " in (select "+ 2130 CHATS_CONTACT_ID + " from " + TABLE_CHATS + " left outer join " + TABLE_CONTACTS + 2131 " on " + CHATS_CONTACT_ID + '=' + CONTACT_ID + " where " + CONTACT_ID + " IS NULL)"; 2132 2133 private static final String GROUP_MEMBER_ID = TABLE_GROUP_MEMBERS + '.' + Im.GroupMembers.GROUP; 2134 private static final String DELETE_GROUP_MEMBER_SELECTION = 2135 Im.GroupMembers.GROUP + " in (select "+ 2136 GROUP_MEMBER_ID + " from " + TABLE_GROUP_MEMBERS + " left outer join " + TABLE_CONTACTS + 2137 " on " + GROUP_MEMBER_ID + '=' + CONTACT_ID + " where " + CONTACT_ID + " IS NULL)"; 2138 2139 private static final String GROUP_MESSAGES_ID = 2140 TABLE_GROUP_MESSAGES + '.' + Im.GroupMessages.GROUP; 2141 private static final String DELETE_GROUP_MESSAGES_SELECTION = 2142 Im.GroupMessages.GROUP + " in (select "+ GROUP_MESSAGES_ID + " from " + 2143 TABLE_GROUP_MESSAGES + " left outer join " + TABLE_CONTACTS + " on " + 2144 GROUP_MESSAGES_ID + '=' + CONTACT_ID + " where " + CONTACT_ID + " IS NULL)"; 2145 performContactRemovalCleanup(long contactId)2146 private void performContactRemovalCleanup(long contactId) { 2147 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 2148 2149 if (contactId > 0) { 2150 deleteWithContactId(db, contactId, TABLE_PRESENCE, Im.Presence.CONTACT_ID); 2151 deleteWithContactId(db, contactId, TABLE_CHATS, Im.Chats.CONTACT_ID); 2152 deleteWithContactId(db, contactId, TABLE_GROUP_MEMBERS, Im.GroupMembers.GROUP); 2153 deleteWithContactId(db, contactId, TABLE_GROUP_MESSAGES, Im.GroupMessages.GROUP); 2154 } else { 2155 performComplexDelete(db, TABLE_PRESENCE, DELETE_PRESENCE_SELECTION, null); 2156 performComplexDelete(db, TABLE_CHATS, DELETE_CHATS_SELECTION, null); 2157 performComplexDelete(db, TABLE_GROUP_MEMBERS, DELETE_GROUP_MEMBER_SELECTION, null); 2158 performComplexDelete(db, TABLE_GROUP_MESSAGES, DELETE_GROUP_MESSAGES_SELECTION, null); 2159 } 2160 } 2161 deleteWithContactId(SQLiteDatabase db, long contactId, String tableName, String columnName)2162 private void deleteWithContactId(SQLiteDatabase db, long contactId, 2163 String tableName, String columnName) { 2164 db.delete(tableName, columnName + '=' + contactId, null /* selection args */); 2165 } 2166 performComplexDelete(SQLiteDatabase db, String tableName, String selection, String[] selectionArgs)2167 private void performComplexDelete(SQLiteDatabase db, String tableName, 2168 String selection, String[] selectionArgs) { 2169 if (DBG) log("performComplexDelete for table " + tableName + ", selection => " + selection); 2170 int count = db.delete(tableName, selection, selectionArgs); 2171 if (DBG) log("performComplexDelete: deleted " + count + " rows"); 2172 } 2173 deleteInternal(Uri url, String userWhere, String[] whereArgs)2174 public int deleteInternal(Uri url, String userWhere, 2175 String[] whereArgs) { 2176 String tableToChange; 2177 String idColumnName = null; 2178 String changedItemId = null; 2179 2180 StringBuilder whereClause = new StringBuilder(); 2181 if(userWhere != null) { 2182 whereClause.append(userWhere); 2183 } 2184 2185 boolean notifyMessagesContentUri = false; 2186 boolean notifyGroupMessagesContentUri = false; 2187 boolean notifyContactListContentUri = false; 2188 boolean notifyProviderAccountContentUri = false; 2189 int match = mUrlMatcher.match(url); 2190 2191 boolean contactDeleted = false; 2192 long deletedContactId = 0; 2193 2194 boolean backfillQuickSwitchSlots = false; 2195 2196 switch (match) { 2197 case MATCH_PROVIDERS: 2198 tableToChange = TABLE_PROVIDERS; 2199 notifyProviderAccountContentUri = true; 2200 break; 2201 2202 case MATCH_ACCOUNTS_BY_ID: 2203 changedItemId = url.getPathSegments().get(1); 2204 // fall through 2205 case MATCH_ACCOUNTS: 2206 tableToChange = TABLE_ACCOUNTS; 2207 notifyProviderAccountContentUri = true; 2208 break; 2209 2210 case MATCH_ACCOUNT_STATUS: 2211 changedItemId = url.getPathSegments().get(1); 2212 // fall through 2213 case MATCH_ACCOUNTS_STATUS: 2214 tableToChange = TABLE_ACCOUNT_STATUS; 2215 notifyProviderAccountContentUri = true; 2216 break; 2217 2218 case MATCH_CONTACTS: 2219 case MATCH_CONTACTS_BAREBONE: 2220 tableToChange = TABLE_CONTACTS; 2221 contactDeleted = true; 2222 break; 2223 2224 case MATCH_CONTACT: 2225 tableToChange = TABLE_CONTACTS; 2226 changedItemId = url.getPathSegments().get(1); 2227 2228 try { 2229 deletedContactId = Long.parseLong(changedItemId); 2230 } catch (NumberFormatException ex) { 2231 } 2232 2233 contactDeleted = true; 2234 break; 2235 2236 case MATCH_CONTACTS_BY_PROVIDER: 2237 tableToChange = TABLE_CONTACTS; 2238 appendWhere(whereClause, Im.Contacts.ACCOUNT, "=", url.getPathSegments().get(2)); 2239 contactDeleted = true; 2240 break; 2241 2242 case MATCH_CONTACTLISTS_BY_PROVIDER: 2243 appendWhere(whereClause, Im.ContactList.ACCOUNT, "=", 2244 url.getPathSegments().get(2)); 2245 // fall through 2246 case MATCH_CONTACTLISTS: 2247 tableToChange = TABLE_CONTACT_LIST; 2248 notifyContactListContentUri = true; 2249 break; 2250 2251 case MATCH_CONTACTLIST: 2252 tableToChange = TABLE_CONTACT_LIST; 2253 changedItemId = url.getPathSegments().get(1); 2254 break; 2255 2256 case MATCH_BLOCKEDLIST: 2257 tableToChange = TABLE_BLOCKED_LIST; 2258 break; 2259 2260 case MATCH_BLOCKEDLIST_BY_PROVIDER: 2261 tableToChange = TABLE_BLOCKED_LIST; 2262 appendWhere(whereClause, Im.BlockedList.ACCOUNT, "=", url.getPathSegments().get(2)); 2263 break; 2264 2265 case MATCH_CONTACTS_ETAGS: 2266 tableToChange = TABLE_CONTACTS_ETAG; 2267 break; 2268 2269 case MATCH_CONTACTS_ETAG: 2270 tableToChange = TABLE_CONTACTS_ETAG; 2271 changedItemId = url.getPathSegments().get(1); 2272 break; 2273 2274 case MATCH_MESSAGES: 2275 tableToChange = TABLE_MESSAGES; 2276 break; 2277 2278 case MATCH_MESSAGES_BY_CONTACT: 2279 tableToChange = TABLE_MESSAGES; 2280 appendWhere(whereClause, Im.Messages.ACCOUNT, "=", 2281 url.getPathSegments().get(2)); 2282 appendWhere(whereClause, Im.Messages.CONTACT, "=", 2283 decodeURLSegment(url.getPathSegments().get(3))); 2284 notifyMessagesContentUri = true; 2285 break; 2286 2287 case MATCH_MESSAGE: 2288 tableToChange = TABLE_MESSAGES; 2289 changedItemId = url.getPathSegments().get(1); 2290 notifyMessagesContentUri = true; 2291 break; 2292 2293 case MATCH_GROUP_MEMBERS: 2294 tableToChange = TABLE_GROUP_MEMBERS; 2295 break; 2296 2297 case MATCH_GROUP_MEMBERS_BY_GROUP: 2298 tableToChange = TABLE_GROUP_MEMBERS; 2299 appendWhere(whereClause, Im.GroupMembers.GROUP, "=", url.getPathSegments().get(1)); 2300 break; 2301 2302 case MATCH_GROUP_MESSAGES: 2303 tableToChange = TABLE_GROUP_MESSAGES; 2304 break; 2305 2306 case MATCH_GROUP_MESSAGE_BY: 2307 tableToChange = TABLE_GROUP_MESSAGES; 2308 changedItemId = url.getPathSegments().get(1); 2309 idColumnName = Im.GroupMessages.GROUP; 2310 notifyGroupMessagesContentUri = true; 2311 break; 2312 2313 case MATCH_GROUP_MESSAGE: 2314 tableToChange = TABLE_GROUP_MESSAGES; 2315 changedItemId = url.getPathSegments().get(1); 2316 notifyGroupMessagesContentUri = true; 2317 break; 2318 2319 case MATCH_INVITATIONS: 2320 tableToChange = TABLE_INVITATIONS; 2321 break; 2322 2323 case MATCH_INVITATION: 2324 tableToChange = TABLE_INVITATIONS; 2325 changedItemId = url.getPathSegments().get(1); 2326 break; 2327 2328 case MATCH_AVATARS: 2329 tableToChange = TABLE_AVATARS; 2330 break; 2331 2332 case MATCH_AVATAR: 2333 tableToChange = TABLE_AVATARS; 2334 changedItemId = url.getPathSegments().get(1); 2335 break; 2336 2337 case MATCH_AVATAR_BY_PROVIDER: 2338 tableToChange = TABLE_AVATARS; 2339 changedItemId = url.getPathSegments().get(2); 2340 idColumnName = Im.Avatars.ACCOUNT; 2341 break; 2342 2343 case MATCH_CHATS: 2344 tableToChange = TABLE_CHATS; 2345 backfillQuickSwitchSlots = true; 2346 break; 2347 2348 case MATCH_CHATS_BY_ACCOUNT: 2349 tableToChange = TABLE_CHATS; 2350 2351 if (whereClause.length() > 0) { 2352 whereClause.append(" AND "); 2353 } 2354 whereClause.append(Im.Chats.CONTACT_ID); 2355 whereClause.append(" in (select "); 2356 whereClause.append(Im.Contacts._ID); 2357 whereClause.append(" from "); 2358 whereClause.append(TABLE_CONTACTS); 2359 whereClause.append(" where "); 2360 whereClause.append(Im.Contacts.ACCOUNT); 2361 whereClause.append("='"); 2362 whereClause.append(url.getLastPathSegment()); 2363 whereClause.append("')"); 2364 2365 if (DBG) log("deleteInternal (MATCH_CHATS_BY_ACCOUNT): sel => " + 2366 whereClause.toString()); 2367 2368 changedItemId = null; 2369 break; 2370 2371 case MATCH_CHATS_ID: 2372 tableToChange = TABLE_CHATS; 2373 changedItemId = url.getPathSegments().get(1); 2374 idColumnName = Im.Chats.CONTACT_ID; 2375 break; 2376 2377 case MATCH_PRESENCE: 2378 tableToChange = TABLE_PRESENCE; 2379 break; 2380 2381 case MATCH_PRESENCE_ID: 2382 tableToChange = TABLE_PRESENCE; 2383 changedItemId = url.getPathSegments().get(1); 2384 idColumnName = Im.Presence.CONTACT_ID; 2385 break; 2386 2387 case MATCH_PRESENCE_BY_ACCOUNT: 2388 tableToChange = TABLE_PRESENCE; 2389 2390 if (whereClause.length() > 0) { 2391 whereClause.append(" AND "); 2392 } 2393 whereClause.append(Im.Presence.CONTACT_ID); 2394 whereClause.append(" in (select "); 2395 whereClause.append(Im.Contacts._ID); 2396 whereClause.append(" from "); 2397 whereClause.append(TABLE_CONTACTS); 2398 whereClause.append(" where "); 2399 whereClause.append(Im.Contacts.ACCOUNT); 2400 whereClause.append("='"); 2401 whereClause.append(url.getLastPathSegment()); 2402 whereClause.append("')"); 2403 2404 if (DBG) log("deleteInternal (MATCH_PRESENCE_BY_ACCOUNT): sel => " + 2405 whereClause.toString()); 2406 2407 changedItemId = null; 2408 break; 2409 2410 case MATCH_SESSIONS: 2411 tableToChange = TABLE_SESSION_COOKIES; 2412 break; 2413 2414 case MATCH_SESSIONS_BY_PROVIDER: 2415 tableToChange = TABLE_SESSION_COOKIES; 2416 changedItemId = url.getPathSegments().get(2); 2417 idColumnName = Im.SessionCookies.ACCOUNT; 2418 break; 2419 2420 case MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME: 2421 tableToChange = TABLE_PROVIDER_SETTINGS; 2422 2423 String providerId = url.getPathSegments().get(1); 2424 String name = url.getPathSegments().get(2); 2425 2426 appendWhere(whereClause, Im.ProviderSettings.PROVIDER, "=", providerId); 2427 appendWhere(whereClause, Im.ProviderSettings.NAME, "=", name); 2428 break; 2429 2430 case MATCH_OUTGOING_RMQ_MESSAGES: 2431 tableToChange = TABLE_OUTGOING_RMQ_MESSAGES; 2432 break; 2433 2434 case MATCH_LAST_RMQ_ID: 2435 tableToChange = TABLE_LAST_RMQ_ID; 2436 break; 2437 2438 case MATCH_BRANDING_RESOURCE_MAP_CACHE: 2439 tableToChange = TABLE_BRANDING_RESOURCE_MAP_CACHE; 2440 break; 2441 2442 default: 2443 throw new UnsupportedOperationException("Cannot delete that URL: " + url); 2444 } 2445 2446 if (idColumnName == null) { 2447 idColumnName = "_id"; 2448 } 2449 2450 if (changedItemId != null) { 2451 appendWhere(whereClause, idColumnName, "=", changedItemId); 2452 } 2453 2454 if (DBG) log("delete from " + url + " WHERE " + whereClause); 2455 2456 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 2457 int count = db.delete(tableToChange, whereClause.toString(), whereArgs); 2458 2459 if (contactDeleted && count > 0) { 2460 // since the contact cleanup triggers no longer work for cross database tables, 2461 // we have to do it by hand here. 2462 performContactRemovalCleanup(deletedContactId); 2463 } 2464 2465 if (count > 0) { 2466 // In most case, we query contacts with presence and chats joined, thus 2467 // we should also notify that contacts changes when presence or chats changed. 2468 if (match == MATCH_CHATS || match == MATCH_CHATS_ID 2469 || match == MATCH_PRESENCE || match == MATCH_PRESENCE_ID 2470 || match == MATCH_CONTACTS_BAREBONE) { 2471 getContext().getContentResolver().notifyChange(Im.Contacts.CONTENT_URI, null); 2472 } else if (notifyMessagesContentUri) { 2473 getContext().getContentResolver().notifyChange(Im.Messages.CONTENT_URI, null); 2474 } else if (notifyGroupMessagesContentUri) { 2475 getContext().getContentResolver().notifyChange(Im.GroupMessages.CONTENT_URI, null); 2476 } else if (notifyContactListContentUri) { 2477 getContext().getContentResolver().notifyChange(Im.ContactList.CONTENT_URI, null); 2478 } else if (notifyProviderAccountContentUri) { 2479 if (DBG) log("notify delete for " + Im.Provider.CONTENT_URI_WITH_ACCOUNT); 2480 getContext().getContentResolver().notifyChange(Im.Provider.CONTENT_URI_WITH_ACCOUNT, 2481 null); 2482 } 2483 2484 if (backfillQuickSwitchSlots) { 2485 backfillQuickSwitchSlots(); 2486 } 2487 } 2488 2489 return count; 2490 } 2491 updateInternal(Uri url, ContentValues values, String userWhere, String[] whereArgs)2492 public int updateInternal(Uri url, ContentValues values, String userWhere, 2493 String[] whereArgs) { 2494 String tableToChange; 2495 String idColumnName = null; 2496 String changedItemId = null; 2497 int count; 2498 2499 StringBuilder whereClause = new StringBuilder(); 2500 if(userWhere != null) { 2501 whereClause.append(userWhere); 2502 } 2503 2504 boolean notifyMessagesContentUri = false; 2505 boolean notifyGroupMessagesContentUri = false; 2506 boolean notifyContactListContentUri = false; 2507 boolean notifyProviderAccountContentUri = false; 2508 2509 int match = mUrlMatcher.match(url); 2510 switch (match) { 2511 case MATCH_PROVIDERS_BY_ID: 2512 changedItemId = url.getPathSegments().get(1); 2513 // fall through 2514 case MATCH_PROVIDERS: 2515 tableToChange = TABLE_PROVIDERS; 2516 break; 2517 2518 case MATCH_ACCOUNTS_BY_ID: 2519 changedItemId = url.getPathSegments().get(1); 2520 // fall through 2521 case MATCH_ACCOUNTS: 2522 tableToChange = TABLE_ACCOUNTS; 2523 notifyProviderAccountContentUri = true; 2524 break; 2525 2526 case MATCH_ACCOUNT_STATUS: 2527 changedItemId = url.getPathSegments().get(1); 2528 // fall through 2529 case MATCH_ACCOUNTS_STATUS: 2530 tableToChange = TABLE_ACCOUNT_STATUS; 2531 notifyProviderAccountContentUri = true; 2532 break; 2533 2534 case MATCH_CONTACTS: 2535 case MATCH_CONTACTS_BAREBONE: 2536 tableToChange = TABLE_CONTACTS; 2537 break; 2538 2539 case MATCH_CONTACTS_BY_PROVIDER: 2540 tableToChange = TABLE_CONTACTS; 2541 changedItemId = url.getPathSegments().get(2); 2542 idColumnName = Im.Contacts.ACCOUNT; 2543 break; 2544 2545 case MATCH_CONTACT: 2546 tableToChange = TABLE_CONTACTS; 2547 changedItemId = url.getPathSegments().get(1); 2548 break; 2549 2550 case MATCH_CONTACTS_BULK: 2551 count = updateBulkContacts(values, userWhere); 2552 // notify change using the "content://im/contacts" url, 2553 // so the change will be observed by listeners interested 2554 // in contacts changes. 2555 if (count > 0) { 2556 getContext().getContentResolver().notifyChange( 2557 Im.Contacts.CONTENT_URI, null); 2558 } 2559 return count; 2560 2561 case MATCH_CONTACTLIST: 2562 tableToChange = TABLE_CONTACT_LIST; 2563 changedItemId = url.getPathSegments().get(1); 2564 notifyContactListContentUri = true; 2565 break; 2566 2567 case MATCH_CONTACTS_ETAGS: 2568 tableToChange = TABLE_CONTACTS_ETAG; 2569 break; 2570 2571 case MATCH_CONTACTS_ETAG: 2572 tableToChange = TABLE_CONTACTS_ETAG; 2573 changedItemId = url.getPathSegments().get(1); 2574 break; 2575 2576 case MATCH_MESSAGES: 2577 tableToChange = TABLE_MESSAGES; 2578 break; 2579 2580 case MATCH_MESSAGES_BY_CONTACT: 2581 tableToChange = TABLE_MESSAGES; 2582 appendWhere(whereClause, Im.Messages.ACCOUNT, "=", 2583 url.getPathSegments().get(2)); 2584 appendWhere(whereClause, Im.Messages.CONTACT, "=", 2585 decodeURLSegment(url.getPathSegments().get(3))); 2586 notifyMessagesContentUri = true; 2587 break; 2588 2589 case MATCH_MESSAGE: 2590 tableToChange = TABLE_MESSAGES; 2591 changedItemId = url.getPathSegments().get(1); 2592 notifyMessagesContentUri = true; 2593 break; 2594 2595 case MATCH_GROUP_MESSAGES: 2596 tableToChange = TABLE_GROUP_MESSAGES; 2597 break; 2598 2599 case MATCH_GROUP_MESSAGE_BY: 2600 tableToChange = TABLE_GROUP_MESSAGES; 2601 changedItemId = url.getPathSegments().get(1); 2602 idColumnName = Im.GroupMessages.GROUP; 2603 notifyGroupMessagesContentUri = true; 2604 break; 2605 2606 case MATCH_GROUP_MESSAGE: 2607 tableToChange = TABLE_GROUP_MESSAGES; 2608 changedItemId = url.getPathSegments().get(1); 2609 notifyGroupMessagesContentUri = true; 2610 break; 2611 2612 case MATCH_AVATARS: 2613 tableToChange = TABLE_AVATARS; 2614 break; 2615 2616 case MATCH_AVATAR: 2617 tableToChange = TABLE_AVATARS; 2618 changedItemId = url.getPathSegments().get(1); 2619 break; 2620 2621 case MATCH_AVATAR_BY_PROVIDER: 2622 tableToChange = TABLE_AVATARS; 2623 changedItemId = url.getPathSegments().get(2); 2624 idColumnName = Im.Avatars.ACCOUNT; 2625 break; 2626 2627 case MATCH_CHATS: 2628 tableToChange = TABLE_CHATS; 2629 break; 2630 2631 case MATCH_CHATS_ID: 2632 tableToChange = TABLE_CHATS; 2633 changedItemId = url.getPathSegments().get(1); 2634 idColumnName = Im.Chats.CONTACT_ID; 2635 break; 2636 2637 case MATCH_PRESENCE: 2638 //if (DBG) log("update presence: where='" + userWhere + "'"); 2639 tableToChange = TABLE_PRESENCE; 2640 break; 2641 2642 case MATCH_PRESENCE_ID: 2643 tableToChange = TABLE_PRESENCE; 2644 changedItemId = url.getPathSegments().get(1); 2645 idColumnName = Im.Presence.CONTACT_ID; 2646 break; 2647 2648 case MATCH_PRESENCE_BULK: 2649 count = updateBulkPresence(values, userWhere, whereArgs); 2650 // notify change using the "content://im/contacts" url, 2651 // so the change will be observed by listeners interested 2652 // in contacts changes. 2653 if (count > 0) { 2654 getContext().getContentResolver().notifyChange(Im.Contacts.CONTENT_URI, null); 2655 } 2656 2657 return count; 2658 2659 case MATCH_INVITATION: 2660 tableToChange = TABLE_INVITATIONS; 2661 changedItemId = url.getPathSegments().get(1); 2662 break; 2663 2664 case MATCH_SESSIONS: 2665 tableToChange = TABLE_SESSION_COOKIES; 2666 break; 2667 2668 case MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME: 2669 tableToChange = TABLE_PROVIDER_SETTINGS; 2670 2671 String providerId = url.getPathSegments().get(1); 2672 String name = url.getPathSegments().get(2); 2673 2674 if (values.containsKey(Im.ProviderSettings.PROVIDER) || 2675 values.containsKey(Im.ProviderSettings.NAME)) { 2676 throw new SecurityException("Cannot override the value for provider|name"); 2677 } 2678 2679 appendWhere(whereClause, Im.ProviderSettings.PROVIDER, "=", providerId); 2680 appendWhere(whereClause, Im.ProviderSettings.NAME, "=", name); 2681 2682 break; 2683 2684 case MATCH_OUTGOING_RMQ_MESSAGES: 2685 tableToChange = TABLE_OUTGOING_RMQ_MESSAGES; 2686 break; 2687 2688 case MATCH_LAST_RMQ_ID: 2689 tableToChange = TABLE_LAST_RMQ_ID; 2690 break; 2691 2692 default: 2693 throw new UnsupportedOperationException("Cannot update URL: " + url); 2694 } 2695 2696 if (idColumnName == null) { 2697 idColumnName = "_id"; 2698 } 2699 if(changedItemId != null) { 2700 appendWhere(whereClause, idColumnName, "=", changedItemId); 2701 } 2702 2703 if (DBG) log("update " + url + " WHERE " + whereClause); 2704 2705 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 2706 count = db.update(tableToChange, values, whereClause.toString(), whereArgs); 2707 2708 if (count > 0) { 2709 // In most case, we query contacts with presence and chats joined, thus 2710 // we should also notify that contacts changes when presence or chats changed. 2711 if (match == MATCH_CHATS || match == MATCH_CHATS_ID 2712 || match == MATCH_PRESENCE || match == MATCH_PRESENCE_ID 2713 || match == MATCH_CONTACTS_BAREBONE) { 2714 getContext().getContentResolver().notifyChange(Im.Contacts.CONTENT_URI, null); 2715 } else if (notifyMessagesContentUri) { 2716 if (DBG) log("notify change for " + Im.Messages.CONTENT_URI); 2717 getContext().getContentResolver().notifyChange(Im.Messages.CONTENT_URI, null); 2718 } else if (notifyGroupMessagesContentUri) { 2719 getContext().getContentResolver().notifyChange(Im.GroupMessages.CONTENT_URI, null); 2720 } else if (notifyContactListContentUri) { 2721 getContext().getContentResolver().notifyChange(Im.ContactList.CONTENT_URI, null); 2722 } else if (notifyProviderAccountContentUri) { 2723 if (DBG) log("notify change for " + Im.Provider.CONTENT_URI_WITH_ACCOUNT); 2724 getContext().getContentResolver().notifyChange(Im.Provider.CONTENT_URI_WITH_ACCOUNT, 2725 null); 2726 } 2727 } 2728 2729 return count; 2730 } 2731 2732 @Override openFile(Uri uri, String mode)2733 public ParcelFileDescriptor openFile(Uri uri, String mode) 2734 throws FileNotFoundException { 2735 return openFileHelper(uri, mode); 2736 } 2737 appendWhere(StringBuilder where, String columnName, String condition, Object value)2738 private static void appendWhere(StringBuilder where, String columnName, 2739 String condition, Object value) { 2740 if (where.length() > 0) { 2741 where.append(" AND "); 2742 } 2743 where.append(columnName).append(condition); 2744 if(value != null) { 2745 DatabaseUtils.appendValueToSql(where, value); 2746 } 2747 } 2748 appendWhere(StringBuilder where, String clause)2749 private static void appendWhere(StringBuilder where, String clause) { 2750 if (where.length() > 0) { 2751 where.append(" AND "); 2752 } 2753 where.append(clause); 2754 } 2755 decodeURLSegment(String segment)2756 private static String decodeURLSegment(String segment) { 2757 try { 2758 return URLDecoder.decode(segment, "UTF-8"); 2759 } catch (UnsupportedEncodingException e) { 2760 // impossible 2761 return segment; 2762 } 2763 } 2764 log(String message)2765 static void log(String message) { 2766 Log.d(LOG_TAG, message); 2767 } 2768 } 2769