1 /* 2 * Copyright (C) 2011 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.internal.telephony; 18 19 import java.util.ArrayList; 20 import java.util.Iterator; 21 22 /** 23 * Clients can enable reception of SMS-CB messages for specific ranges of 24 * message identifiers (channels). This class keeps track of the currently 25 * enabled message identifiers and calls abstract methods to update the 26 * radio when the range of enabled message identifiers changes. 27 * 28 * An update is a call to {@link #startUpdate} followed by zero or more 29 * calls to {@link #addRange} followed by a call to {@link #finishUpdate}. 30 * Calls to {@link #enableRange} and {@link #disableRange} will perform 31 * an incremental update operation if the enabled ranges have changed. 32 * A full update operation (i.e. after a radio reset) can be performed 33 * by a call to {@link #updateRanges}. 34 * 35 * Clients are identified by String (the name associated with the User ID 36 * of the caller) so that a call to remove a range can be mapped to the 37 * client that enabled that range (or else rejected). 38 */ 39 public abstract class IntRangeManager { 40 41 /** 42 * Initial capacity for IntRange clients array list. There will be 43 * few cell broadcast listeners on a typical device, so this can be small. 44 */ 45 private static final int INITIAL_CLIENTS_ARRAY_SIZE = 4; 46 47 /** 48 * One or more clients forming the continuous range [startId, endId]. 49 * <p>When a client is added, the IntRange may merge with one or more 50 * adjacent IntRanges to form a single combined IntRange. 51 * <p>When a client is removed, the IntRange may divide into several 52 * non-contiguous IntRanges. 53 */ 54 private class IntRange { 55 int mStartId; 56 int mEndId; 57 // sorted by earliest start id 58 final ArrayList<ClientRange> mClients; 59 60 /** 61 * Create a new IntRange with a single client. 62 * @param startId the first id included in the range 63 * @param endId the last id included in the range 64 * @param client the client requesting the enabled range 65 */ 66 IntRange(int startId, int endId, String client) { 67 mStartId = startId; 68 mEndId = endId; 69 mClients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE); 70 mClients.add(new ClientRange(startId, endId, client)); 71 } 72 73 /** 74 * Create a new IntRange for an existing ClientRange. 75 * @param clientRange the initial ClientRange to add 76 */ 77 IntRange(ClientRange clientRange) { 78 mStartId = clientRange.mStartId; 79 mEndId = clientRange.mEndId; 80 mClients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE); 81 mClients.add(clientRange); 82 } 83 84 /** 85 * Create a new IntRange from an existing IntRange. This is used for 86 * removing a ClientRange, because new IntRanges may need to be created 87 * for any gaps that open up after the ClientRange is removed. A copy 88 * is made of the elements of the original IntRange preceding the element 89 * that is being removed. The following elements will be added to this 90 * IntRange or to a new IntRange when a gap is found. 91 * @param intRange the original IntRange to copy elements from 92 * @param numElements the number of elements to copy from the original 93 */ 94 IntRange(IntRange intRange, int numElements) { 95 mStartId = intRange.mStartId; 96 mEndId = intRange.mEndId; 97 mClients = new ArrayList<ClientRange>(intRange.mClients.size()); 98 for (int i=0; i < numElements; i++) { 99 mClients.add(intRange.mClients.get(i)); 100 } 101 } 102 103 /** 104 * Insert new ClientRange in order by start id, then by end id 105 * <p>If the new ClientRange is known to be sorted before or after the 106 * existing ClientRanges, or at a particular index, it can be added 107 * to the clients array list directly, instead of via this method. 108 * <p>Note that this can be changed from linear to binary search if the 109 * number of clients grows large enough that it would make a difference. 110 * @param range the new ClientRange to insert 111 */ 112 void insert(ClientRange range) { 113 int len = mClients.size(); 114 int insert = -1; 115 for (int i=0; i < len; i++) { 116 ClientRange nextRange = mClients.get(i); 117 if (range.mStartId <= nextRange.mStartId) { 118 // ignore duplicate ranges from the same client 119 if (!range.equals(nextRange)) { 120 // check if same startId, then order by endId 121 if (range.mStartId == nextRange.mStartId 122 && range.mEndId > nextRange.mEndId) { 123 insert = i + 1; 124 if (insert < len) { 125 // there may be more client following with same startId 126 // new [1, 5] existing [1, 2] [1, 4] [1, 7] 127 continue; 128 } 129 break; 130 } 131 mClients.add(i, range); 132 } 133 return; 134 } 135 } 136 if (insert != -1 && insert < len) { 137 mClients.add(insert, range); 138 return; 139 } 140 mClients.add(range); // append to end of list 141 } 142 } 143 144 /** 145 * The message id range for a single client. 146 */ 147 private class ClientRange { 148 final int mStartId; 149 final int mEndId; 150 final String mClient; 151 152 ClientRange(int startId, int endId, String client) { 153 mStartId = startId; 154 mEndId = endId; 155 mClient = client; 156 } 157 158 @Override 159 public boolean equals(Object o) { 160 if (o != null && o instanceof ClientRange) { 161 ClientRange other = (ClientRange) o; 162 return mStartId == other.mStartId && 163 mEndId == other.mEndId && 164 mClient.equals(other.mClient); 165 } else { 166 return false; 167 } 168 } 169 170 @Override 171 public int hashCode() { 172 return (mStartId * 31 + mEndId) * 31 + mClient.hashCode(); 173 } 174 } 175 176 /** 177 * List of integer ranges, one per client, sorted by start id. 178 */ 179 private ArrayList<IntRange> mRanges = new ArrayList<IntRange>(); 180 181 protected IntRangeManager() {} 182 183 /** 184 * Enable a range for the specified client and update ranges 185 * if necessary. If {@link #finishUpdate} returns failure, 186 * false is returned and the range is not added. 187 * 188 * @param startId the first id included in the range 189 * @param endId the last id included in the range 190 * @param client the client requesting the enabled range 191 * @return true if successful, false otherwise 192 */ 193 public synchronized boolean enableRange(int startId, int endId, String client) { 194 int len = mRanges.size(); 195 196 // empty range list: add the initial IntRange 197 if (len == 0) { 198 if (tryAddRanges(startId, endId, true)) { 199 mRanges.add(new IntRange(startId, endId, client)); 200 return true; 201 } else { 202 return false; // failed to update radio 203 } 204 } 205 206 for (int startIndex = 0; startIndex < len; startIndex++) { 207 IntRange range = mRanges.get(startIndex); 208 if ((startId) >= range.mStartId && (endId) <= range.mEndId) { 209 // exact same range: new [1, 1] existing [1, 1] 210 // range already enclosed in existing: new [3, 3], [1,3] 211 // no radio update necessary. 212 // duplicate "client" check is done in insert, attempt to insert. 213 range.insert(new ClientRange(startId, endId, client)); 214 return true; 215 } else if ((startId - 1) == range.mEndId) { 216 // new [3, x] existing [1, 2] OR new [2, 2] existing [1, 1] 217 // found missing link? check if next range can be joined 218 int newRangeEndId = endId; 219 IntRange nextRange = null; 220 if ((startIndex + 1) < len) { 221 nextRange = mRanges.get(startIndex + 1); 222 if ((nextRange.mStartId - 1) <= endId) { 223 // new [3, x] existing [1, 2] [5, 7] OR new [2 , 2] existing [1, 1] [3, 5] 224 if (endId <= nextRange.mEndId) { 225 // new [3, 6] existing [1, 2] [5, 7] 226 newRangeEndId = nextRange.mStartId - 1; // need to enable [3, 4] 227 } 228 } else { 229 // mark nextRange to be joined as null. 230 nextRange = null; 231 } 232 } 233 if (tryAddRanges(startId, newRangeEndId, true)) { 234 range.mEndId = endId; 235 range.insert(new ClientRange(startId, endId, client)); 236 237 // found missing link? check if next range can be joined 238 if (nextRange != null) { 239 if (range.mEndId < nextRange.mEndId) { 240 // new [3, 6] existing [1, 2] [5, 10] 241 range.mEndId = nextRange.mEndId; 242 } 243 range.mClients.addAll(nextRange.mClients); 244 mRanges.remove(nextRange); 245 } 246 return true; 247 } else { 248 return false; // failed to update radio 249 } 250 } else if (startId < range.mStartId) { 251 // new [1, x] , existing [5, y] 252 // test if new range completely precedes this range 253 // note that [1, 4] and [5, 6] coalesce to [1, 6] 254 if ((endId + 1) < range.mStartId) { 255 // new [1, 3] existing [5, 6] non contiguous case 256 // insert new int range before previous first range 257 if (tryAddRanges(startId, endId, true)) { 258 mRanges.add(startIndex, new IntRange(startId, endId, client)); 259 return true; 260 } else { 261 return false; // failed to update radio 262 } 263 } else if (endId <= range.mEndId) { 264 // new [1, 4] existing [5, 6] or new [1, 1] existing [2, 2] 265 // extend the start of this range 266 if (tryAddRanges(startId, range.mStartId - 1, true)) { 267 range.mStartId = startId; 268 range.mClients.add(0, new ClientRange(startId, endId, client)); 269 return true; 270 } else { 271 return false; // failed to update radio 272 } 273 } else { 274 // find last range that can coalesce into the new combined range 275 for (int endIndex = startIndex+1; endIndex < len; endIndex++) { 276 IntRange endRange = mRanges.get(endIndex); 277 if ((endId + 1) < endRange.mStartId) { 278 // new [1, 10] existing [2, 3] [14, 15] 279 // try to add entire new range 280 if (tryAddRanges(startId, endId, true)) { 281 range.mStartId = startId; 282 range.mEndId = endId; 283 // insert new ClientRange before existing ranges 284 range.mClients.add(0, new ClientRange(startId, endId, client)); 285 // coalesce range with following ranges up to endIndex-1 286 // remove each range after adding its elements, so the index 287 // of the next range to join is always startIndex+1. 288 // i is the index if no elements were removed: we only care 289 // about the number of loop iterations, not the value of i. 290 int joinIndex = startIndex + 1; 291 for (int i = joinIndex; i < endIndex; i++) { 292 // new [1, 10] existing [2, 3] [5, 6] [14, 15] 293 IntRange joinRange = mRanges.get(joinIndex); 294 range.mClients.addAll(joinRange.mClients); 295 mRanges.remove(joinRange); 296 } 297 return true; 298 } else { 299 return false; // failed to update radio 300 } 301 } else if (endId <= endRange.mEndId) { 302 // new [1, 10] existing [2, 3] [5, 15] 303 // add range from start id to start of last overlapping range, 304 // values from endRange.startId to endId are already enabled 305 if (tryAddRanges(startId, endRange.mStartId - 1, true)) { 306 range.mStartId = startId; 307 range.mEndId = endRange.mEndId; 308 // insert new ClientRange before existing ranges 309 range.mClients.add(0, new ClientRange(startId, endId, client)); 310 // coalesce range with following ranges up to endIndex 311 // remove each range after adding its elements, so the index 312 // of the next range to join is always startIndex+1. 313 // i is the index if no elements were removed: we only care 314 // about the number of loop iterations, not the value of i. 315 int joinIndex = startIndex + 1; 316 for (int i = joinIndex; i <= endIndex; i++) { 317 IntRange joinRange = mRanges.get(joinIndex); 318 range.mClients.addAll(joinRange.mClients); 319 mRanges.remove(joinRange); 320 } 321 return true; 322 } else { 323 return false; // failed to update radio 324 } 325 } 326 } 327 328 // new [1, 10] existing [2, 3] 329 // endId extends past all existing IntRanges: combine them all together 330 if (tryAddRanges(startId, endId, true)) { 331 range.mStartId = startId; 332 range.mEndId = endId; 333 // insert new ClientRange before existing ranges 334 range.mClients.add(0, new ClientRange(startId, endId, client)); 335 // coalesce range with following ranges up to len-1 336 // remove each range after adding its elements, so the index 337 // of the next range to join is always startIndex+1. 338 // i is the index if no elements were removed: we only care 339 // about the number of loop iterations, not the value of i. 340 int joinIndex = startIndex + 1; 341 for (int i = joinIndex; i < len; i++) { 342 // new [1, 10] existing [2, 3] [5, 6] 343 IntRange joinRange = mRanges.get(joinIndex); 344 range.mClients.addAll(joinRange.mClients); 345 mRanges.remove(joinRange); 346 } 347 return true; 348 } else { 349 return false; // failed to update radio 350 } 351 } 352 } else if ((startId + 1) <= range.mEndId) { 353 // new [2, x] existing [1, 4] 354 if (endId <= range.mEndId) { 355 // new [2, 3] existing [1, 4] 356 // completely contained in existing range; no radio changes 357 range.insert(new ClientRange(startId, endId, client)); 358 return true; 359 } else { 360 // new [2, 5] existing [1, 4] 361 // find last range that can coalesce into the new combined range 362 int endIndex = startIndex; 363 for (int testIndex = startIndex+1; testIndex < len; testIndex++) { 364 IntRange testRange = mRanges.get(testIndex); 365 if ((endId + 1) < testRange.mStartId) { 366 break; 367 } else { 368 endIndex = testIndex; 369 } 370 } 371 // no adjacent IntRanges to combine 372 if (endIndex == startIndex) { 373 // new [2, 5] existing [1, 4] 374 // add range from range.endId+1 to endId, 375 // values from startId to range.endId are already enabled 376 if (tryAddRanges(range.mEndId + 1, endId, true)) { 377 range.mEndId = endId; 378 range.insert(new ClientRange(startId, endId, client)); 379 return true; 380 } else { 381 return false; // failed to update radio 382 } 383 } 384 // get last range to coalesce into start range 385 IntRange endRange = mRanges.get(endIndex); 386 // Values from startId to range.endId have already been enabled. 387 // if endId > endRange.endId, then enable range from range.endId+1 to endId, 388 // else enable range from range.endId+1 to endRange.startId-1, because 389 // values from endRange.startId to endId have already been added. 390 int newRangeEndId = (endId <= endRange.mEndId) ? endRange.mStartId - 1 : endId; 391 // new [2, 10] existing [1, 4] [7, 8] OR 392 // new [2, 10] existing [1, 4] [7, 15] 393 if (tryAddRanges(range.mEndId + 1, newRangeEndId, true)) { 394 newRangeEndId = (endId <= endRange.mEndId) ? endRange.mEndId : endId; 395 range.mEndId = newRangeEndId; 396 // insert new ClientRange in place 397 range.insert(new ClientRange(startId, endId, client)); 398 // coalesce range with following ranges up to endIndex 399 // remove each range after adding its elements, so the index 400 // of the next range to join is always startIndex+1 (joinIndex). 401 // i is the index if no elements had been removed: we only care 402 // about the number of loop iterations, not the value of i. 403 int joinIndex = startIndex + 1; 404 for (int i = joinIndex; i <= endIndex; i++) { 405 IntRange joinRange = mRanges.get(joinIndex); 406 range.mClients.addAll(joinRange.mClients); 407 mRanges.remove(joinRange); 408 } 409 return true; 410 } else { 411 return false; // failed to update radio 412 } 413 } 414 } 415 } 416 417 // new [5, 6], existing [1, 3] 418 // append new range after existing IntRanges 419 if (tryAddRanges(startId, endId, true)) { 420 mRanges.add(new IntRange(startId, endId, client)); 421 return true; 422 } else { 423 return false; // failed to update radio 424 } 425 } 426 427 /** 428 * Disable a range for the specified client and update ranges 429 * if necessary. If {@link #finishUpdate} returns failure, 430 * false is returned and the range is not removed. 431 * 432 * @param startId the first id included in the range 433 * @param endId the last id included in the range 434 * @param client the client requesting to disable the range 435 * @return true if successful, false otherwise 436 */ 437 public synchronized boolean disableRange(int startId, int endId, String client) { 438 int len = mRanges.size(); 439 440 for (int i=0; i < len; i++) { 441 IntRange range = mRanges.get(i); 442 if (startId < range.mStartId) { 443 return false; // not found 444 } else if (endId <= range.mEndId) { 445 // found the IntRange that encloses the client range, if any 446 // search for it in the clients list 447 ArrayList<ClientRange> clients = range.mClients; 448 449 // handle common case of IntRange containing one ClientRange 450 int crLength = clients.size(); 451 if (crLength == 1) { 452 ClientRange cr = clients.get(0); 453 if (cr.mStartId == startId && cr.mEndId == endId && cr.mClient.equals(client)) { 454 // mRange contains only what's enabled. 455 // remove the range from mRange then update the radio 456 mRanges.remove(i); 457 if (updateRanges()) { 458 return true; 459 } else { 460 // failed to update radio. insert back the range 461 mRanges.add(i, range); 462 return false; 463 } 464 } else { 465 return false; // not found 466 } 467 } 468 469 // several ClientRanges: remove one, potentially splitting into many IntRanges. 470 // Save the original start and end id for the original IntRange 471 // in case the radio update fails and we have to revert it. If the 472 // update succeeds, we remove the client range and insert the new IntRanges. 473 // clients are ordered by startId then by endId, so client with largest endId 474 // can be anywhere. Need to loop thru to find largestEndId. 475 int largestEndId = Integer.MIN_VALUE; // largest end identifier found 476 boolean updateStarted = false; 477 478 // crlength >= 2 479 for (int crIndex=0; crIndex < crLength; crIndex++) { 480 ClientRange cr = clients.get(crIndex); 481 if (cr.mStartId == startId && cr.mEndId == endId && cr.mClient.equals(client)) { 482 // found the ClientRange to remove, check if it's the last in the list 483 if (crIndex == crLength - 1) { 484 if (range.mEndId == largestEndId) { 485 // remove [2, 5] from [1, 7] [2, 5] 486 // no channels to remove from radio; return success 487 clients.remove(crIndex); 488 return true; 489 } else { 490 // disable the channels at the end and lower the end id 491 clients.remove(crIndex); 492 range.mEndId = largestEndId; 493 if (updateRanges()) { 494 return true; 495 } else { 496 clients.add(crIndex, cr); 497 range.mEndId = cr.mEndId; 498 return false; 499 } 500 } 501 } 502 503 // copy the IntRange so that we can remove elements and modify the 504 // start and end id's in the copy, leaving the original unmodified 505 // until after the radio update succeeds 506 IntRange rangeCopy = new IntRange(range, crIndex); 507 508 if (crIndex == 0) { 509 // removing the first ClientRange, so we may need to increase 510 // the start id of the IntRange. 511 // We know there are at least two ClientRanges in the list, 512 // because check for just one ClientRanges case is already handled 513 // so clients.get(1) should always succeed. 514 int nextStartId = clients.get(1).mStartId; 515 if (nextStartId != range.mStartId) { 516 updateStarted = true; 517 rangeCopy.mStartId = nextStartId; 518 } 519 // init largestEndId 520 largestEndId = clients.get(1).mEndId; 521 } 522 523 // go through remaining ClientRanges, creating new IntRanges when 524 // there is a gap in the sequence. After radio update succeeds, 525 // remove the original IntRange and append newRanges to mRanges. 526 // Otherwise, leave the original IntRange in mRanges and return false. 527 ArrayList<IntRange> newRanges = new ArrayList<IntRange>(); 528 529 IntRange currentRange = rangeCopy; 530 for (int nextIndex = crIndex + 1; nextIndex < crLength; nextIndex++) { 531 ClientRange nextCr = clients.get(nextIndex); 532 if (nextCr.mStartId > largestEndId + 1) { 533 updateStarted = true; 534 currentRange.mEndId = largestEndId; 535 newRanges.add(currentRange); 536 currentRange = new IntRange(nextCr); 537 } else { 538 if (currentRange.mEndId < nextCr.mEndId) { 539 currentRange.mEndId = nextCr.mEndId; 540 } 541 currentRange.mClients.add(nextCr); 542 } 543 if (nextCr.mEndId > largestEndId) { 544 largestEndId = nextCr.mEndId; 545 } 546 } 547 548 // remove any channels between largestEndId and endId 549 if (largestEndId < endId) { 550 updateStarted = true; 551 currentRange.mEndId = largestEndId; 552 } 553 newRanges.add(currentRange); 554 555 // replace the original IntRange with newRanges 556 mRanges.remove(i); 557 mRanges.addAll(i, newRanges); 558 if (updateStarted && !updateRanges()) { 559 // failed to update radio. revert back mRange. 560 mRanges.removeAll(newRanges); 561 mRanges.add(i, range); 562 return false; 563 } 564 565 return true; 566 } else { 567 // not the ClientRange to remove; save highest end ID seen so far 568 if (cr.mEndId > largestEndId) { 569 largestEndId = cr.mEndId; 570 } 571 } 572 } 573 } 574 } 575 576 return false; // not found 577 } 578 579 /** 580 * Perform a complete update operation (enable all ranges). Useful 581 * after a radio reset. Calls {@link #startUpdate}, followed by zero or 582 * more calls to {@link #addRange}, followed by {@link #finishUpdate}. 583 * @return true if successful, false otherwise 584 */ 585 public boolean updateRanges() { 586 startUpdate(); 587 588 populateAllRanges(); 589 return finishUpdate(); 590 } 591 592 /** 593 * Enable or disable a single range of message identifiers. 594 * @param startId the first id included in the range 595 * @param endId the last id included in the range 596 * @param selected true to enable range, false to disable range 597 * @return true if successful, false otherwise 598 */ 599 protected boolean tryAddRanges(int startId, int endId, boolean selected) { 600 601 startUpdate(); 602 populateAllRanges(); 603 // This is the new range to be enabled 604 addRange(startId, endId, selected); // adds to mConfigList 605 return finishUpdate(); 606 } 607 608 /** 609 * Returns whether the list of ranges is completely empty. 610 * @return true if there are no enabled ranges 611 */ 612 public boolean isEmpty() { 613 return mRanges.isEmpty(); 614 } 615 616 /** 617 * Called when attempting to add a single range of message identifiers 618 * Populate all ranges of message identifiers. 619 */ 620 private void populateAllRanges() { 621 Iterator<IntRange> itr = mRanges.iterator(); 622 // Populate all ranges from mRanges 623 while (itr.hasNext()) { 624 IntRange currRange = (IntRange) itr.next(); 625 addRange(currRange.mStartId, currRange.mEndId, true); 626 } 627 } 628 629 /** 630 * Called when attempting to add a single range of message identifiers 631 * Populate all ranges of message identifiers using clients' ranges. 632 */ 633 private void populateAllClientRanges() { 634 int len = mRanges.size(); 635 for (int i = 0; i < len; i++) { 636 IntRange range = mRanges.get(i); 637 638 int clientLen = range.mClients.size(); 639 for (int j=0; j < clientLen; j++) { 640 ClientRange nextRange = range.mClients.get(j); 641 addRange(nextRange.mStartId, nextRange.mEndId, true); 642 } 643 } 644 } 645 646 /** 647 * Called when the list of enabled ranges has changed. This will be 648 * followed by zero or more calls to {@link #addRange} followed by 649 * a call to {@link #finishUpdate}. 650 */ 651 protected abstract void startUpdate(); 652 653 /** 654 * Called after {@link #startUpdate} to indicate a range of enabled 655 * or disabled values. 656 * 657 * @param startId the first id included in the range 658 * @param endId the last id included in the range 659 * @param selected true to enable range, false to disable range 660 */ 661 protected abstract void addRange(int startId, int endId, boolean selected); 662 663 /** 664 * Called to indicate the end of a range update started by the 665 * previous call to {@link #startUpdate}. 666 * @return true if successful, false otherwise 667 */ 668 protected abstract boolean finishUpdate(); 669 } 670