Home | History | Annotate | Download | only in telephony
      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