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          */
IntRange(int startId, int endId, String client)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          */
IntRange(ClientRange clientRange)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          */
IntRange(IntRange intRange, int numElements)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          */
insert(ClientRange range)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 
ClientRange(int startId, int endId, String client)152         ClientRange(int startId, int endId, String client) {
153             mStartId = startId;
154             mEndId = endId;
155             mClient = client;
156         }
157 
158         @Override
equals(Object o)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
hashCode()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 
IntRangeManager()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      */
enableRange(int startId, int endId, String client)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      */
disableRange(int startId, int endId, String client)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      */
updateRanges()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      */
tryAddRanges(int startId, int endId, boolean selected)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      */
isEmpty()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      */
populateAllRanges()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      */
populateAllClientRanges()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      */
startUpdate()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      */
addRange(int startId, int endId, boolean selected)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      */
finishUpdate()668     protected abstract boolean finishUpdate();
669 }
670