/**
* Radius Networks, Inc.
* http://www.radiusnetworks.com
*
* @author David G. Young
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.altbeacon.beacon;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* This class represents a criteria of fields used to match beacons.
*
* The uniqueId field is used to distinguish this Region in the system. When you set up
* monitoring or ranging based on a Region and later want to stop monitoring or ranging,
* you must do so by passing a Region object that has the same uniqueId field value. If it
* doesn't match, you can't cancel the operation. There is no other purpose to this field.
*
* The region can be constructed from a multi-part identifier. The first identifier is the most
* significant, the second the second most significant, etc.
*
* When constructing a range, any or all of these identifiers may be set to null,
* which indicates that they are a wildcard and will match any value.
*
* @author dyoung
*
*/
public class Region implements Parcelable, Serializable {
private static final String TAG = "Region";
private static final Pattern MAC_PATTERN = Pattern.compile("^[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}$");
/**
* Required to make class Parcelable
*/
public static final Parcelable.Creator<Region> CREATOR
= new Parcelable.Creator<Region>() {
public Region createFromParcel(Parcel in) {
return new Region(in);
}
public Region[] newArray(int size) {
return new Region[size];
}
};
protected final List<Identifier> mIdentifiers;
protected final String mBluetoothAddress;
protected final String mUniqueId;
/**
* Constructs a new Region object to be used for Ranging or Monitoring
* @param uniqueId - A unique identifier used to later cancel Ranging and Monitoring, or change the region being Ranged/Monitored
* @param id1 - most significant identifier (can be null)
* @param id2 - second most significant identifier (can be null)
* @param id3 - third most significant identifier (can be null)
*/
public Region(String uniqueId, Identifier id1, Identifier id2, Identifier id3) {
this.mIdentifiers = new ArrayList<Identifier>(3);
this.mIdentifiers.add(id1);
this.mIdentifiers.add(id2);
this.mIdentifiers.add(id3);
this.mUniqueId = uniqueId;
this.mBluetoothAddress = null;
if (uniqueId == null) {
throw new NullPointerException("uniqueId may not be null");
}
}
/**
* Constructs a new Region object to be used for Ranging or Monitoring
* @param uniqueId - A unique identifier used to later cancel Ranging and Monitoring, or change the region being Ranged/Monitored
* @param identifiers - list of identifiers for this region
*/
public Region(String uniqueId, List<Identifier> identifiers) {
this(uniqueId, identifiers, null);
}
/**
* Constructs a new Region object to be used for Ranging or Monitoring
* @param uniqueId - A unique identifier used to later cancel Ranging and Monitoring, or change the region being Ranged/Monitored
* @param identifiers - list of identifiers for this region
* @param bluetoothAddress - mac address
*/
public Region(String uniqueId, List<Identifier> identifiers, String bluetoothAddress) {
validateMac(bluetoothAddress);
this.mIdentifiers = new ArrayList<Identifier>(identifiers);
this.mUniqueId = uniqueId;
this.mBluetoothAddress = bluetoothAddress;
if (uniqueId == null) {
throw new NullPointerException("uniqueId may not be null");
}
}
/**
* Constructs a new Region object to be used for Ranging or Monitoring
* @param uniqueId - A unique identifier used to later cancel Ranging and Monitoring, or change the region being Ranged/Monitored
* @param bluetoothAddress - mac address used to match beacons
*/
public Region(String uniqueId, String bluetoothAddress) {
validateMac(bluetoothAddress);
this.mBluetoothAddress = bluetoothAddress;
this.mUniqueId = uniqueId;
this.mIdentifiers = new ArrayList<Identifier>();
if (uniqueId == null) {
throw new NullPointerException("uniqueId may not be null");
}
}
/**
* Convenience method to get the first identifier
* @return
*/
public Identifier getId1() {
return getIdentifier(0);
}
/**
* Convenience method to get the second identifier
* @return
*/
public Identifier getId2() {
return getIdentifier(1);
}
/**
* Convenience method to get the third identifier
* @return
*/
public Identifier getId3() {
return getIdentifier(2);
}
/**
* Returns the 0-indexed identifier
* Note: IMPORTANT: to get id1, you would call getIdentifier(0);
* @param i
* @return
*/
public Identifier getIdentifier(int i) {
return mIdentifiers.size() > i ? mIdentifiers.get(i) : null;
}
/**
* Returns the identifier used to start or stop ranging/monitoring this region when calling
* the <code>BeaconManager</code> methods.
* @return
*/
public String getUniqueId() {
return mUniqueId;
}
/**
* Returns the mac address used to filter for beacons
*/
public String getBluetoothAddress() { return mBluetoothAddress; }
/**
* Checks to see if an Beacon object is included in the matching criteria of this Region
* @param beacon the beacon to check to see if it is in the Region
* @return true if is covered
*/
public boolean matchesBeacon(Beacon beacon) {
// All identifiers must match, or the corresponding region identifier must be null.
for (int i = mIdentifiers.size(); --i >= 0; ) {
final Identifier identifier = mIdentifiers.get(i);
Identifier beaconIdentifier = null;
if (i < beacon.mIdentifiers.size()) {
beaconIdentifier = beacon.getIdentifier(i);
}
if ((beaconIdentifier == null && identifier != null) ||
(beaconIdentifier != null && identifier != null && !identifier.equals(beaconIdentifier))) {
return false;
}
}
if (mBluetoothAddress != null && !mBluetoothAddress.equalsIgnoreCase(beacon.mBluetoothAddress)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return this.mUniqueId.hashCode();
}
@Override
public boolean equals(Object other) {
if (other instanceof Region) {
return ((Region)other).mUniqueId.equals(this.mUniqueId);
}
return false;
}
public boolean hasSameIdentifiers(Region region) {
if (region.mIdentifiers.size() == this.mIdentifiers.size()) {
for (int i = 0 ; i < region.mIdentifiers.size(); i++) {
if (region.getIdentifier(i) == null && this.getIdentifier(i) != null) {
return false;
}
else if (region.getIdentifier(i) != null && this.getIdentifier(i) == null) {
return false;
}
else if (!(region.getIdentifier(i) == null && this.getIdentifier(i) == null)) {
if (!region.getIdentifier(i).equals(this.getIdentifier(i))) {
return false;
}
}
}
}
else {
return false;
}
return true;
}
public String toString() {
StringBuilder sb = new StringBuilder();
int i = 1;
for (Identifier identifier: mIdentifiers) {
if (i > 1) {
sb.append(" ");
}
sb.append("id");
sb.append(i);
sb.append(": ");
sb.append(identifier == null ? "null" : identifier.toString());
i++;
}
return sb.toString();
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeString(mUniqueId);
out.writeString(mBluetoothAddress);
out.writeInt(mIdentifiers.size());
for (Identifier identifier: mIdentifiers) {
if (identifier != null) {
out.writeString(identifier.toString());
}
else {
out.writeString(null);
}
}
}
protected Region(Parcel in) {
mUniqueId = in.readString();
mBluetoothAddress = in.readString();
int size = in.readInt();
mIdentifiers = new ArrayList<Identifier>(size);
for (int i = 0; i < size; i++) {
String identifierString = in.readString();
if (identifierString == null) {
mIdentifiers.add(null);
} else {
Identifier identifier = Identifier.parse(identifierString);
mIdentifiers.add(identifier);
}
}
}
private void validateMac(String mac) throws IllegalArgumentException {
if (mac != null) {
if(!MAC_PATTERN.matcher(mac).matches()) {
throw new IllegalArgumentException("Invalid mac address: '"+mac+"' Must be 6 hex bytes separated by colons.");
}
}
}
/**
* Returns a clone of this instance.
* @deprecated instances of this class are immutable and therefore don't have to be cloned when
* used in concurrent code.
* @return a new instance of this class with the same uniqueId and identifiers
*/
@Override
@Deprecated
public Region clone() {
return new Region(mUniqueId, mIdentifiers, mBluetoothAddress);
}
}