1 /* 2 * Copyright (C) 2020 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.IntDef; 20 import android.annotation.NonNull; 21 import android.content.Context; 22 import android.telephony.CbGeoUtils; 23 24 import java.lang.annotation.Retention; 25 import java.lang.annotation.RetentionPolicy; 26 import java.util.List; 27 import java.util.Objects; 28 import java.util.Optional; 29 import java.util.stream.Collectors; 30 31 /** 32 * Calculates whether or not to send the message according to the inputted geofence. 33 * 34 * Designed to be run multiple times with different calls to #addCoordinate 35 * 36 * @hide 37 * 38 */ 39 public class CbSendMessageCalculator { 40 41 @NonNull 42 private final List<CbGeoUtils.Geometry> mFences; 43 44 private final double mThresholdMeters; 45 private int mAction = SEND_MESSAGE_ACTION_NO_COORDINATES; 46 47 /* 48 When false, we only check to see if a given coordinate falls within a geo or not. 49 Put another way: 50 1. The threshold is ignored 51 2. Ambiguous results are never given 52 */ 53 private final boolean mDoNewWay; 54 CbSendMessageCalculator(@onNull final Context context, @NonNull final List<CbGeoUtils.Geometry> fences)55 public CbSendMessageCalculator(@NonNull final Context context, 56 @NonNull final List<CbGeoUtils.Geometry> fences) { 57 this(context, fences, context.getResources().getInteger(R.integer.geo_fence_threshold)); 58 } 59 CbSendMessageCalculator(@onNull final Context context, @NonNull final List<CbGeoUtils.Geometry> fences, final double thresholdMeters)60 public CbSendMessageCalculator(@NonNull final Context context, 61 @NonNull final List<CbGeoUtils.Geometry> fences, final double thresholdMeters) { 62 63 mFences = fences.stream().filter(Objects::nonNull).collect(Collectors.toList()); 64 mThresholdMeters = thresholdMeters; 65 mDoNewWay = context.getResources().getBoolean(R.bool.use_new_geo_fence_calculation); 66 } 67 68 /** 69 * The given threshold the given coordinates can be outside the geo fence and still receive 70 * {@code SEND_MESSAGE_ACTION_SEND}. 71 * 72 * @return the threshold in meters 73 */ getThreshold()74 public double getThreshold() { 75 return mThresholdMeters; 76 } 77 78 /** 79 * Gets the last action calculated 80 * 81 * @return last action 82 */ 83 @SendMessageAction getAction()84 public int getAction() { 85 if (mFences.size() == 0) { 86 return SEND_MESSAGE_ACTION_SEND; 87 } 88 89 return mAction; 90 } 91 92 /** 93 * Translates the action to a readable equivalent 94 * @return readable version of action 95 */ getActionString()96 String getActionString() { 97 if (mAction == SEND_MESSAGE_ACTION_SEND) { 98 return "SEND"; 99 } else if (mAction == SEND_MESSAGE_ACTION_AMBIGUOUS) { 100 return "AMBIGUOUS"; 101 } else if (mAction == SEND_MESSAGE_ACTION_DONT_SEND) { 102 return "DONT_SEND"; 103 } else if (mAction == SEND_MESSAGE_ACTION_NO_COORDINATES) { 104 return "NO_COORDINATES"; 105 } else { 106 return "!BAD_VALUE!"; 107 } 108 } 109 110 /** No Coordinates */ 111 public static final int SEND_MESSAGE_ACTION_NO_COORDINATES = 0; 112 113 /** Send right away */ 114 public static final int SEND_MESSAGE_ACTION_SEND = 1; 115 116 /** Stop waiting for results */ 117 public static final int SEND_MESSAGE_ACTION_DONT_SEND = 2; 118 119 /** Continue polling */ 120 public static final int SEND_MESSAGE_ACTION_AMBIGUOUS = 3; 121 122 /** 123 * Send Message Action annotation 124 */ 125 @Retention(RetentionPolicy.SOURCE) 126 @IntDef({SEND_MESSAGE_ACTION_NO_COORDINATES, SEND_MESSAGE_ACTION_SEND, 127 SEND_MESSAGE_ACTION_DONT_SEND, SEND_MESSAGE_ACTION_AMBIGUOUS, 128 }) 129 public @interface SendMessageAction {} 130 131 /** 132 * Calculate action based off of the send reason. 133 * @return 134 */ addCoordinate(CbGeoUtils.LatLng coordinate, double accuracyMeters)135 public void addCoordinate(CbGeoUtils.LatLng coordinate, double accuracyMeters) { 136 if (mFences.size() == 0) { 137 //No fences mean we shouldn't bother 138 return; 139 } 140 141 calculatePersistentAction(coordinate, accuracyMeters); 142 } 143 144 /** Calculates the state of the next action based off of the new coordinate and the current 145 * action state. According to the rules: 146 * 1. SEND always wins 147 * 2. Outside always trumps an overlap with DONT_SEND 148 * 3. Otherwise we keep an overlap with AMBIGUOUS 149 * @param coordinate the geo location 150 * @param accuracyMeters the accuracy from location manager 151 * @return the action 152 */ 153 @SendMessageAction calculatePersistentAction(CbGeoUtils.LatLng coordinate, double accuracyMeters)154 private void calculatePersistentAction(CbGeoUtils.LatLng coordinate, double accuracyMeters) { 155 // If we already marked this as a send, we don't need to check anything. 156 if (this.mAction != SEND_MESSAGE_ACTION_SEND) { 157 @SendMessageAction int newAction = 158 calculateActionFromFences(coordinate, accuracyMeters); 159 160 if (newAction == SEND_MESSAGE_ACTION_SEND) { 161 /* If the new action is in SEND, it doesn't matter what the old action is is. */ 162 this.mAction = newAction; 163 } else if (mAction != SEND_MESSAGE_ACTION_DONT_SEND) { 164 /* If the old action is in DONT_SEND, then always overwrite it with ambiguous. */ 165 this.mAction = newAction; 166 } else { 167 /* No-op because if we are in a don't send state, we don't want to overwrite 168 with an ambiguous state. */ 169 } 170 } 171 } 172 173 /** 174 * Calculates the proposed action state from the fences according to the rules: 175 * 1. Any coordinate with a SEND always wins. 176 * 2. If a coordinate \ accuracy overlaps any fence, go with AMBIGUOUS. 177 * 3. Otherwise, the coordinate is very far outside every fence and we move to DONT_SEND. 178 * @param coordinate the geo location 179 * @param accuracyMeters the accuracy from location manager 180 * @return the action 181 */ 182 @SendMessageAction calculateActionFromFences(CbGeoUtils.LatLng coordinate, double accuracyMeters)183 private int calculateActionFromFences(CbGeoUtils.LatLng coordinate, double accuracyMeters) { 184 185 // If everything is outside, then we stick with outside 186 int totalAction = SEND_MESSAGE_ACTION_DONT_SEND; 187 for (int i = 0; i < mFences.size(); i++) { 188 CbGeoUtils.Geometry fence = mFences.get(i); 189 @SendMessageAction 190 final int action = calculateSingleFence(coordinate, accuracyMeters, fence); 191 192 if (action == SEND_MESSAGE_ACTION_SEND) { 193 // The send action means we always go for it. 194 return action; 195 } else if (action == SEND_MESSAGE_ACTION_AMBIGUOUS) { 196 // If we are outside a geo, but then find that the accuracies overlap, 197 // we stick to overlap while still seeing if there are any cases where we are 198 // inside 199 totalAction = SEND_MESSAGE_ACTION_AMBIGUOUS; 200 } 201 } 202 return totalAction; 203 } 204 205 @SendMessageAction calculateSingleFence(CbGeoUtils.LatLng coordinate, double accuracyMeters, CbGeoUtils.Geometry fence)206 private int calculateSingleFence(CbGeoUtils.LatLng coordinate, double accuracyMeters, 207 CbGeoUtils.Geometry fence) { 208 if (fence.contains(coordinate)) { 209 return SEND_MESSAGE_ACTION_SEND; 210 } 211 212 if (mDoNewWay) { 213 return calculateSysSingleFence(coordinate, accuracyMeters, fence); 214 } else { 215 return SEND_MESSAGE_ACTION_DONT_SEND; 216 } 217 } 218 calculateSysSingleFence(CbGeoUtils.LatLng coordinate, double accuracyMeters, CbGeoUtils.Geometry fence)219 private int calculateSysSingleFence(CbGeoUtils.LatLng coordinate, double accuracyMeters, 220 CbGeoUtils.Geometry fence) { 221 Optional<Double> maybeDistance = 222 com.android.cellbroadcastservice.CbGeoUtils.distance(fence, coordinate); 223 if (!maybeDistance.isPresent()) { 224 return SEND_MESSAGE_ACTION_DONT_SEND; 225 } 226 227 double distance = maybeDistance.get(); 228 if (accuracyMeters <= mThresholdMeters && distance <= mThresholdMeters) { 229 // The accuracy is precise and we are within the threshold of the boundary, send 230 return SEND_MESSAGE_ACTION_SEND; 231 } 232 233 if (distance <= accuracyMeters) { 234 // Ambiguous case 235 return SEND_MESSAGE_ACTION_AMBIGUOUS; 236 } else { 237 return SEND_MESSAGE_ACTION_DONT_SEND; 238 } 239 } 240 } 241