1 /* 2 * Copyright (C) 2019 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.cellbroadcastservice; 18 19 import android.annotation.NonNull; 20 import android.telephony.CbGeoUtils.Circle; 21 import android.telephony.CbGeoUtils.Geometry; 22 import android.telephony.CbGeoUtils.LatLng; 23 import android.telephony.CbGeoUtils.Polygon; 24 import android.text.TextUtils; 25 import android.util.Log; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.stream.Collectors; 30 31 /** 32 * This utils class is specifically used for geo-targeting of CellBroadcast messages. 33 * The coordinates used by this utils class are latitude and longitude, but some algorithms in this 34 * class only use them as coordinates on plane, so the calculation will be inaccurate. So don't use 35 * this class for anything other then geo-targeting of cellbroadcast messages. 36 */ 37 public class CbGeoUtils { 38 /** 39 * Tolerance for determining if the value is 0. If the absolute value of a value is less than 40 * this tolerance, it will be treated as 0. 41 */ 42 public static final double EPS = 1e-7; 43 44 private static final String TAG = "CbGeoUtils"; 45 46 /** The TLV tags of WAC, defined in ATIS-0700041 5.2.3 WAC tag coding. */ 47 public static final int GEO_FENCING_MAXIMUM_WAIT_TIME = 0x01; 48 public static final int GEOMETRY_TYPE_POLYGON = 0x02; 49 public static final int GEOMETRY_TYPE_CIRCLE = 0x03; 50 51 /** The identifier of geometry in the encoded string. */ 52 private static final String CIRCLE_SYMBOL = "circle"; 53 private static final String POLYGON_SYMBOL = "polygon"; 54 55 /** 56 * Parse the geometries from the encoded string {@code str}. The string must follow the 57 * geometry encoding specified by {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}. 58 */ 59 @NonNull parseGeometriesFromString(@onNull String str)60 public static List<Geometry> parseGeometriesFromString(@NonNull String str) { 61 List<Geometry> geometries = new ArrayList<>(); 62 for (String geometryStr : str.split("\\s*;\\s*")) { 63 String[] geoParameters = geometryStr.split("\\s*\\|\\s*"); 64 switch (geoParameters[0]) { 65 case CIRCLE_SYMBOL: 66 geometries.add(new Circle(parseLatLngFromString(geoParameters[1]), 67 Double.parseDouble(geoParameters[2]))); 68 break; 69 case POLYGON_SYMBOL: 70 List<LatLng> vertices = new ArrayList<>(geoParameters.length - 1); 71 for (int i = 1; i < geoParameters.length; i++) { 72 vertices.add(parseLatLngFromString(geoParameters[i])); 73 } 74 geometries.add(new Polygon(vertices)); 75 break; 76 default: 77 final String errorMessage = "Invalid geometry format " + geometryStr; 78 Log.e(TAG, errorMessage); 79 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR, 80 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GEOMETRY_FROM_FWK, 81 errorMessage); 82 } 83 } 84 return geometries; 85 } 86 87 /** 88 * Encode a list of geometry objects to string. The encoding format is specified by 89 * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}. 90 * 91 * @param geometries the list of geometry objects need to be encoded. 92 * @return the encoded string. 93 */ 94 @NonNull encodeGeometriesToString(@onNull List<Geometry> geometries)95 public static String encodeGeometriesToString(@NonNull List<Geometry> geometries) { 96 return geometries.stream() 97 .map(geometry -> encodeGeometryToString(geometry)) 98 .filter(encodedStr -> !TextUtils.isEmpty(encodedStr)) 99 .collect(Collectors.joining(";")); 100 } 101 102 103 /** 104 * Encode the geometry object to string. The encoding format is specified by 105 * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}. 106 * @param geometry the geometry object need to be encoded. 107 * @return the encoded string. 108 */ 109 @NonNull encodeGeometryToString(@onNull Geometry geometry)110 private static String encodeGeometryToString(@NonNull Geometry geometry) { 111 StringBuilder sb = new StringBuilder(); 112 if (geometry instanceof Polygon) { 113 sb.append(POLYGON_SYMBOL); 114 for (LatLng latLng : ((Polygon) geometry).getVertices()) { 115 sb.append("|"); 116 sb.append(latLng.lat); 117 sb.append(","); 118 sb.append(latLng.lng); 119 } 120 } else if (geometry instanceof Circle) { 121 sb.append(CIRCLE_SYMBOL); 122 Circle circle = (Circle) geometry; 123 124 // Center 125 sb.append("|"); 126 sb.append(circle.getCenter().lat); 127 sb.append(","); 128 sb.append(circle.getCenter().lng); 129 130 // Radius 131 sb.append("|"); 132 sb.append(circle.getRadius()); 133 } else { 134 Log.e(TAG, "Unsupported geometry object " + geometry); 135 return null; 136 } 137 return sb.toString(); 138 } 139 140 /** 141 * Parse {@link LatLng} from {@link String}. Latitude and longitude are separated by ",". 142 * Example: "13.56,-55.447". 143 * 144 * @param str encoded lat/lng string. 145 * @Return {@link LatLng} object. 146 */ 147 @NonNull parseLatLngFromString(@onNull String str)148 private static LatLng parseLatLngFromString(@NonNull String str) { 149 String[] latLng = str.split("\\s*,\\s*"); 150 return new LatLng(Double.parseDouble(latLng[0]), Double.parseDouble(latLng[1])); 151 } 152 } 153