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