• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.incallui.calllocation.impl;
18 
19 import android.content.Context;
20 import android.location.Location;
21 import android.net.ConnectivityManager;
22 import android.net.NetworkInfo;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.support.annotation.IntDef;
26 import android.support.annotation.MainThread;
27 import com.android.dialer.common.Assert;
28 import com.android.dialer.common.LogUtil;
29 import com.android.dialer.util.PermissionsUtil;
30 import com.google.android.gms.common.ConnectionResult;
31 import com.google.android.gms.common.api.GoogleApiClient;
32 import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
33 import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
34 import com.google.android.gms.common.api.ResultCallback;
35 import com.google.android.gms.common.api.Status;
36 import com.google.android.gms.location.LocationListener;
37 import com.google.android.gms.location.LocationRequest;
38 import com.google.android.gms.location.LocationServices;
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.ArrayList;
42 import java.util.List;
43 
44 /** Uses the Fused location service to get location and pass updates on to listeners. */
45 public class LocationHelper {
46 
47   private static final int MIN_UPDATE_INTERVAL_MS = 30 * 1000;
48   private static final int LAST_UPDATE_THRESHOLD_MS = 60 * 1000;
49   private static final int LOCATION_ACCURACY_THRESHOLD_METERS = 100;
50 
51   public static final int LOCATION_STATUS_UNKNOWN = 0;
52   public static final int LOCATION_STATUS_OK = 1;
53   public static final int LOCATION_STATUS_STALE = 2;
54   public static final int LOCATION_STATUS_INACCURATE = 3;
55   public static final int LOCATION_STATUS_NO_LOCATION = 4;
56 
57   /** Possible return values for {@code checkLocation()} */
58   @IntDef({
59     LOCATION_STATUS_UNKNOWN,
60     LOCATION_STATUS_OK,
61     LOCATION_STATUS_STALE,
62     LOCATION_STATUS_INACCURATE,
63     LOCATION_STATUS_NO_LOCATION
64   })
65   @Retention(RetentionPolicy.SOURCE)
66   public @interface LocationStatus {}
67 
68   private final LocationHelperInternal locationHelperInternal;
69   private final List<LocationListener> listeners = new ArrayList<>();
70 
71   @MainThread
LocationHelper(Context context)72   LocationHelper(Context context) {
73     Assert.isMainThread();
74     Assert.checkArgument(canGetLocation(context));
75     locationHelperInternal = new LocationHelperInternal(context);
76   }
77 
canGetLocation(Context context)78   static boolean canGetLocation(Context context) {
79     if (!PermissionsUtil.hasLocationPermissions(context)) {
80       LogUtil.i("LocationHelper.canGetLocation", "no location permissions.");
81       return false;
82     }
83 
84     // Ensure that both system location setting is on and google location services are enabled.
85     if (!GoogleLocationSettingHelper.isGoogleLocationServicesEnabled(context)
86         || !GoogleLocationSettingHelper.isSystemLocationSettingEnabled(context)) {
87       LogUtil.i("LocationHelper.canGetLocation", "location service is disabled.");
88       return false;
89     }
90     return true;
91   }
92 
93   /**
94    * Check whether the location is valid. We consider it valid if it was recorded within the
95    * specified time threshold of the present and has an accuracy less than the specified distance
96    * threshold.
97    *
98    * @param location The location to determine the validity of.
99    * @return {@code LocationStatus} indicating if the location is valid or the reason its not valid
100    */
checkLocation(Location location)101   static @LocationStatus int checkLocation(Location location) {
102     if (location == null) {
103       LogUtil.i("LocationHelper.checkLocation", "no location");
104       return LOCATION_STATUS_NO_LOCATION;
105     }
106 
107     long locationTimeMs = location.getTime();
108     long elapsedTimeMs = System.currentTimeMillis() - locationTimeMs;
109     if (elapsedTimeMs > LAST_UPDATE_THRESHOLD_MS) {
110       LogUtil.i("LocationHelper.checkLocation", "stale location, age: " + elapsedTimeMs);
111       return LOCATION_STATUS_STALE;
112     }
113 
114     if (location.getAccuracy() > LOCATION_ACCURACY_THRESHOLD_METERS) {
115       LogUtil.i("LocationHelper.checkLocation", "poor accuracy: " + location.getAccuracy());
116       return LOCATION_STATUS_INACCURATE;
117     }
118 
119     return LOCATION_STATUS_OK;
120   }
121 
122   @MainThread
addLocationListener(LocationListener listener)123   void addLocationListener(LocationListener listener) {
124     Assert.isMainThread();
125     listeners.add(listener);
126   }
127 
128   @MainThread
removeLocationListener(LocationListener listener)129   void removeLocationListener(LocationListener listener) {
130     Assert.isMainThread();
131     listeners.remove(listener);
132   }
133 
134   @MainThread
close()135   void close() {
136     Assert.isMainThread();
137     LogUtil.enterBlock("LocationHelper.close");
138     listeners.clear();
139 
140     if (locationHelperInternal != null) {
141       locationHelperInternal.close();
142     }
143   }
144 
145   @MainThread
onLocationChanged(Location location, boolean isConnected)146   void onLocationChanged(Location location, boolean isConnected) {
147     Assert.isMainThread();
148     LogUtil.i("LocationHelper.onLocationChanged", "location: " + location);
149 
150     for (LocationListener listener : listeners) {
151       listener.onLocationChanged(location);
152     }
153   }
154 
155   /**
156    * This class contains all the asynchronous callbacks. It only posts location changes back to the
157    * outer class on the main thread.
158    */
159   private class LocationHelperInternal
160       implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener {
161 
162     private final GoogleApiClient apiClient;
163     private final ConnectivityManager connectivityManager;
164     private final Handler mainThreadHandler = new Handler();
165 
166     @MainThread
LocationHelperInternal(Context context)167     LocationHelperInternal(Context context) {
168       Assert.isMainThread();
169       apiClient =
170           new GoogleApiClient.Builder(context)
171               .addApi(LocationServices.API)
172               .addConnectionCallbacks(this)
173               .addOnConnectionFailedListener(this)
174               .build();
175 
176       LogUtil.i("LocationHelperInternal", "Connecting to location service...");
177       apiClient.connect();
178 
179       connectivityManager =
180           (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
181     }
182 
close()183     void close() {
184       if (apiClient.isConnected()) {
185         LogUtil.i("LocationHelperInternal", "disconnecting");
186         LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, this);
187         apiClient.disconnect();
188       }
189     }
190 
191     @Override
onConnected(Bundle bundle)192     public void onConnected(Bundle bundle) {
193       LogUtil.enterBlock("LocationHelperInternal.onConnected");
194       LocationRequest locationRequest =
195           LocationRequest.create()
196               .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
197               .setInterval(MIN_UPDATE_INTERVAL_MS)
198               .setFastestInterval(MIN_UPDATE_INTERVAL_MS);
199 
200       LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, this)
201           .setResultCallback(
202               new ResultCallback<Status>() {
203                 @Override
204                 public void onResult(Status status) {
205                   if (status.getStatus().isSuccess()) {
206                     onLocationChanged(LocationServices.FusedLocationApi.getLastLocation(apiClient));
207                   }
208                 }
209               });
210     }
211 
212     @Override
onConnectionSuspended(int i)213     public void onConnectionSuspended(int i) {
214       // Do nothing.
215     }
216 
217     @Override
onConnectionFailed(ConnectionResult result)218     public void onConnectionFailed(ConnectionResult result) {
219       // Do nothing.
220     }
221 
222     @Override
onLocationChanged(Location location)223     public void onLocationChanged(Location location) {
224       // Post new location on main thread
225       mainThreadHandler.post(
226           new Runnable() {
227             @Override
228             public void run() {
229               LocationHelper.this.onLocationChanged(location, isConnected());
230             }
231           });
232     }
233 
234     /** @return Whether the phone is connected to data. */
isConnected()235     private boolean isConnected() {
236       if (connectivityManager == null) {
237         return false;
238       }
239       NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
240       return networkInfo != null && networkInfo.isConnectedOrConnecting();
241     }
242   }
243 }
244