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