IntRangeManagerpublic abstract class IntRangeManager extends Object Clients can enable reception of SMS-CB messages for specific ranges of
message identifiers (channels). This class keeps track of the currently
enabled message identifiers and calls abstract methods to update the
radio when the range of enabled message identifiers changes.
An update is a call to {@link #startUpdate} followed by zero or more
calls to {@link #addRange} followed by a call to {@link #finishUpdate}.
Calls to {@link #enableRange} and {@link #disableRange} will perform
an incremental update operation if the enabled ranges have changed.
A full update operation (i.e. after a radio reset) can be performed
by a call to {@link #updateRanges}.
Clients are identified by String (the name associated with the User ID
of the caller) so that a call to remove a range can be mapped to the
client that enabled that range (or else rejected). |
Fields Summary |
---|
private static final int | INITIAL_CLIENTS_ARRAY_SIZEInitial capacity for IntRange clients array list. There will be
few cell broadcast listeners on a typical device, so this can be small. | private ArrayList | mRangesList of integer ranges, one per client, sorted by start id. |
Constructors Summary |
---|
protected IntRangeManager()
|
Methods Summary |
---|
protected abstract void | addRange(int startId, int endId, boolean selected)Called after {@link #startUpdate} to indicate a range of enabled
or disabled values.
| public synchronized boolean | disableRange(int startId, int endId, java.lang.String client)Disable a range for the specified client and update ranges
if necessary. If {@link #finishUpdate} returns failure,
false is returned and the range is not removed.
int len = mRanges.size();
for (int i=0; i < len; i++) {
IntRange range = mRanges.get(i);
if (startId < range.mStartId) {
return false; // not found
} else if (endId <= range.mEndId) {
// found the IntRange that encloses the client range, if any
// search for it in the clients list
ArrayList<ClientRange> clients = range.mClients;
// handle common case of IntRange containing one ClientRange
int crLength = clients.size();
if (crLength == 1) {
ClientRange cr = clients.get(0);
if (cr.mStartId == startId && cr.mEndId == endId && cr.mClient.equals(client)) {
// mRange contains only what's enabled.
// remove the range from mRange then update the radio
mRanges.remove(i);
if (updateRanges()) {
return true;
} else {
// failed to update radio. insert back the range
mRanges.add(i, range);
return false;
}
} else {
return false; // not found
}
}
// several ClientRanges: remove one, potentially splitting into many IntRanges.
// Save the original start and end id for the original IntRange
// in case the radio update fails and we have to revert it. If the
// update succeeds, we remove the client range and insert the new IntRanges.
// clients are ordered by startId then by endId, so client with largest endId
// can be anywhere. Need to loop thru to find largestEndId.
int largestEndId = Integer.MIN_VALUE; // largest end identifier found
boolean updateStarted = false;
// crlength >= 2
for (int crIndex=0; crIndex < crLength; crIndex++) {
ClientRange cr = clients.get(crIndex);
if (cr.mStartId == startId && cr.mEndId == endId && cr.mClient.equals(client)) {
// found the ClientRange to remove, check if it's the last in the list
if (crIndex == crLength - 1) {
if (range.mEndId == largestEndId) {
// remove [2, 5] from [1, 7] [2, 5]
// no channels to remove from radio; return success
clients.remove(crIndex);
return true;
} else {
// disable the channels at the end and lower the end id
clients.remove(crIndex);
range.mEndId = largestEndId;
if (updateRanges()) {
return true;
} else {
clients.add(crIndex, cr);
range.mEndId = cr.mEndId;
return false;
}
}
}
// copy the IntRange so that we can remove elements and modify the
// start and end id's in the copy, leaving the original unmodified
// until after the radio update succeeds
IntRange rangeCopy = new IntRange(range, crIndex);
if (crIndex == 0) {
// removing the first ClientRange, so we may need to increase
// the start id of the IntRange.
// We know there are at least two ClientRanges in the list,
// because check for just one ClientRanges case is already handled
// so clients.get(1) should always succeed.
int nextStartId = clients.get(1).mStartId;
if (nextStartId != range.mStartId) {
updateStarted = true;
rangeCopy.mStartId = nextStartId;
}
// init largestEndId
largestEndId = clients.get(1).mEndId;
}
// go through remaining ClientRanges, creating new IntRanges when
// there is a gap in the sequence. After radio update succeeds,
// remove the original IntRange and append newRanges to mRanges.
// Otherwise, leave the original IntRange in mRanges and return false.
ArrayList<IntRange> newRanges = new ArrayList<IntRange>();
IntRange currentRange = rangeCopy;
for (int nextIndex = crIndex + 1; nextIndex < crLength; nextIndex++) {
ClientRange nextCr = clients.get(nextIndex);
if (nextCr.mStartId > largestEndId + 1) {
updateStarted = true;
currentRange.mEndId = largestEndId;
newRanges.add(currentRange);
currentRange = new IntRange(nextCr);
} else {
if (currentRange.mEndId < nextCr.mEndId) {
currentRange.mEndId = nextCr.mEndId;
}
currentRange.mClients.add(nextCr);
}
if (nextCr.mEndId > largestEndId) {
largestEndId = nextCr.mEndId;
}
}
// remove any channels between largestEndId and endId
if (largestEndId < endId) {
updateStarted = true;
currentRange.mEndId = largestEndId;
}
newRanges.add(currentRange);
// replace the original IntRange with newRanges
mRanges.remove(i);
mRanges.addAll(i, newRanges);
if (updateStarted && !updateRanges()) {
// failed to update radio. revert back mRange.
mRanges.removeAll(newRanges);
mRanges.add(i, range);
return false;
}
return true;
} else {
// not the ClientRange to remove; save highest end ID seen so far
if (cr.mEndId > largestEndId) {
largestEndId = cr.mEndId;
}
}
}
}
}
return false; // not found
| public synchronized boolean | enableRange(int startId, int endId, java.lang.String client)Enable a range for the specified client and update ranges
if necessary. If {@link #finishUpdate} returns failure,
false is returned and the range is not added.
int len = mRanges.size();
// empty range list: add the initial IntRange
if (len == 0) {
if (tryAddRanges(startId, endId, true)) {
mRanges.add(new IntRange(startId, endId, client));
return true;
} else {
return false; // failed to update radio
}
}
for (int startIndex = 0; startIndex < len; startIndex++) {
IntRange range = mRanges.get(startIndex);
if ((startId) >= range.mStartId && (endId) <= range.mEndId) {
// exact same range: new [1, 1] existing [1, 1]
// range already enclosed in existing: new [3, 3], [1,3]
// no radio update necessary.
// duplicate "client" check is done in insert, attempt to insert.
range.insert(new ClientRange(startId, endId, client));
return true;
} else if ((startId - 1) == range.mEndId) {
// new [3, x] existing [1, 2] OR new [2, 2] existing [1, 1]
// found missing link? check if next range can be joined
int newRangeEndId = endId;
IntRange nextRange = null;
if ((startIndex + 1) < len) {
nextRange = mRanges.get(startIndex + 1);
if ((nextRange.mStartId - 1) <= endId) {
// new [3, x] existing [1, 2] [5, 7] OR new [2 , 2] existing [1, 1] [3, 5]
if (endId <= nextRange.mEndId) {
// new [3, 6] existing [1, 2] [5, 7]
newRangeEndId = nextRange.mStartId - 1; // need to enable [3, 4]
}
} else {
// mark nextRange to be joined as null.
nextRange = null;
}
}
if (tryAddRanges(startId, newRangeEndId, true)) {
range.mEndId = endId;
range.insert(new ClientRange(startId, endId, client));
// found missing link? check if next range can be joined
if (nextRange != null) {
if (range.mEndId < nextRange.mEndId) {
// new [3, 6] existing [1, 2] [5, 10]
range.mEndId = nextRange.mEndId;
}
range.mClients.addAll(nextRange.mClients);
mRanges.remove(nextRange);
}
return true;
} else {
return false; // failed to update radio
}
} else if (startId < range.mStartId) {
// new [1, x] , existing [5, y]
// test if new range completely precedes this range
// note that [1, 4] and [5, 6] coalesce to [1, 6]
if ((endId + 1) < range.mStartId) {
// new [1, 3] existing [5, 6] non contiguous case
// insert new int range before previous first range
if (tryAddRanges(startId, endId, true)) {
mRanges.add(startIndex, new IntRange(startId, endId, client));
return true;
} else {
return false; // failed to update radio
}
} else if (endId <= range.mEndId) {
// new [1, 4] existing [5, 6] or new [1, 1] existing [2, 2]
// extend the start of this range
if (tryAddRanges(startId, range.mStartId - 1, true)) {
range.mStartId = startId;
range.mClients.add(0, new ClientRange(startId, endId, client));
return true;
} else {
return false; // failed to update radio
}
} else {
// find last range that can coalesce into the new combined range
for (int endIndex = startIndex+1; endIndex < len; endIndex++) {
IntRange endRange = mRanges.get(endIndex);
if ((endId + 1) < endRange.mStartId) {
// new [1, 10] existing [2, 3] [14, 15]
// try to add entire new range
if (tryAddRanges(startId, endId, true)) {
range.mStartId = startId;
range.mEndId = endId;
// insert new ClientRange before existing ranges
range.mClients.add(0, new ClientRange(startId, endId, client));
// coalesce range with following ranges up to endIndex-1
// remove each range after adding its elements, so the index
// of the next range to join is always startIndex+1.
// i is the index if no elements were removed: we only care
// about the number of loop iterations, not the value of i.
int joinIndex = startIndex + 1;
for (int i = joinIndex; i < endIndex; i++) {
// new [1, 10] existing [2, 3] [5, 6] [14, 15]
IntRange joinRange = mRanges.get(joinIndex);
range.mClients.addAll(joinRange.mClients);
mRanges.remove(joinRange);
}
return true;
} else {
return false; // failed to update radio
}
} else if (endId <= endRange.mEndId) {
// new [1, 10] existing [2, 3] [5, 15]
// add range from start id to start of last overlapping range,
// values from endRange.startId to endId are already enabled
if (tryAddRanges(startId, endRange.mStartId - 1, true)) {
range.mStartId = startId;
range.mEndId = endRange.mEndId;
// insert new ClientRange before existing ranges
range.mClients.add(0, new ClientRange(startId, endId, client));
// coalesce range with following ranges up to endIndex
// remove each range after adding its elements, so the index
// of the next range to join is always startIndex+1.
// i is the index if no elements were removed: we only care
// about the number of loop iterations, not the value of i.
int joinIndex = startIndex + 1;
for (int i = joinIndex; i <= endIndex; i++) {
IntRange joinRange = mRanges.get(joinIndex);
range.mClients.addAll(joinRange.mClients);
mRanges.remove(joinRange);
}
return true;
} else {
return false; // failed to update radio
}
}
}
// new [1, 10] existing [2, 3]
// endId extends past all existing IntRanges: combine them all together
if (tryAddRanges(startId, endId, true)) {
range.mStartId = startId;
range.mEndId = endId;
// insert new ClientRange before existing ranges
range.mClients.add(0, new ClientRange(startId, endId, client));
// coalesce range with following ranges up to len-1
// remove each range after adding its elements, so the index
// of the next range to join is always startIndex+1.
// i is the index if no elements were removed: we only care
// about the number of loop iterations, not the value of i.
int joinIndex = startIndex + 1;
for (int i = joinIndex; i < len; i++) {
// new [1, 10] existing [2, 3] [5, 6]
IntRange joinRange = mRanges.get(joinIndex);
range.mClients.addAll(joinRange.mClients);
mRanges.remove(joinRange);
}
return true;
} else {
return false; // failed to update radio
}
}
} else if ((startId + 1) <= range.mEndId) {
// new [2, x] existing [1, 4]
if (endId <= range.mEndId) {
// new [2, 3] existing [1, 4]
// completely contained in existing range; no radio changes
range.insert(new ClientRange(startId, endId, client));
return true;
} else {
// new [2, 5] existing [1, 4]
// find last range that can coalesce into the new combined range
int endIndex = startIndex;
for (int testIndex = startIndex+1; testIndex < len; testIndex++) {
IntRange testRange = mRanges.get(testIndex);
if ((endId + 1) < testRange.mStartId) {
break;
} else {
endIndex = testIndex;
}
}
// no adjacent IntRanges to combine
if (endIndex == startIndex) {
// new [2, 5] existing [1, 4]
// add range from range.endId+1 to endId,
// values from startId to range.endId are already enabled
if (tryAddRanges(range.mEndId + 1, endId, true)) {
range.mEndId = endId;
range.insert(new ClientRange(startId, endId, client));
return true;
} else {
return false; // failed to update radio
}
}
// get last range to coalesce into start range
IntRange endRange = mRanges.get(endIndex);
// Values from startId to range.endId have already been enabled.
// if endId > endRange.endId, then enable range from range.endId+1 to endId,
// else enable range from range.endId+1 to endRange.startId-1, because
// values from endRange.startId to endId have already been added.
int newRangeEndId = (endId <= endRange.mEndId) ? endRange.mStartId - 1 : endId;
// new [2, 10] existing [1, 4] [7, 8] OR
// new [2, 10] existing [1, 4] [7, 15]
if (tryAddRanges(range.mEndId + 1, newRangeEndId, true)) {
newRangeEndId = (endId <= endRange.mEndId) ? endRange.mEndId : endId;
range.mEndId = newRangeEndId;
// insert new ClientRange in place
range.insert(new ClientRange(startId, endId, client));
// coalesce range with following ranges up to endIndex
// remove each range after adding its elements, so the index
// of the next range to join is always startIndex+1 (joinIndex).
// i is the index if no elements had been removed: we only care
// about the number of loop iterations, not the value of i.
int joinIndex = startIndex + 1;
for (int i = joinIndex; i <= endIndex; i++) {
IntRange joinRange = mRanges.get(joinIndex);
range.mClients.addAll(joinRange.mClients);
mRanges.remove(joinRange);
}
return true;
} else {
return false; // failed to update radio
}
}
}
}
// new [5, 6], existing [1, 3]
// append new range after existing IntRanges
if (tryAddRanges(startId, endId, true)) {
mRanges.add(new IntRange(startId, endId, client));
return true;
} else {
return false; // failed to update radio
}
| protected abstract boolean | finishUpdate()Called to indicate the end of a range update started by the
previous call to {@link #startUpdate}.
| public boolean | isEmpty()Returns whether the list of ranges is completely empty.
return mRanges.isEmpty();
| private void | populateAllClientRanges()Called when attempting to add a single range of message identifiers
Populate all ranges of message identifiers using clients' ranges.
int len = mRanges.size();
for (int i = 0; i < len; i++) {
IntRange range = mRanges.get(i);
int clientLen = range.mClients.size();
for (int j=0; j < clientLen; j++) {
ClientRange nextRange = range.mClients.get(j);
addRange(nextRange.mStartId, nextRange.mEndId, true);
}
}
| private void | populateAllRanges()Called when attempting to add a single range of message identifiers
Populate all ranges of message identifiers.
Iterator<IntRange> itr = mRanges.iterator();
// Populate all ranges from mRanges
while (itr.hasNext()) {
IntRange currRange = (IntRange) itr.next();
addRange(currRange.mStartId, currRange.mEndId, true);
}
| protected abstract void | startUpdate()Called when the list of enabled ranges has changed. This will be
followed by zero or more calls to {@link #addRange} followed by
a call to {@link #finishUpdate}.
| protected boolean | tryAddRanges(int startId, int endId, boolean selected)Enable or disable a single range of message identifiers.
startUpdate();
populateAllRanges();
// This is the new range to be enabled
addRange(startId, endId, selected); // adds to mConfigList
return finishUpdate();
| public boolean | updateRanges()Perform a complete update operation (enable all ranges). Useful
after a radio reset. Calls {@link #startUpdate}, followed by zero or
more calls to {@link #addRange}, followed by {@link #finishUpdate}.
startUpdate();
populateAllRanges();
return finishUpdate();
|
|