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