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