blob: 645e9b86aad330669c266ab0cfcb586604d16520 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.incallui.calllocation.impl;
import android.content.Context;
import android.location.Location;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.MainThread;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.util.PermissionsUtil;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import java.util.ArrayList;
import java.util.List;
/** Uses the Fused location service to get location and pass updates on to listeners. */
public class LocationHelper {
private static final int MIN_UPDATE_INTERVAL_MS = 30 * 1000;
private static final int LAST_UPDATE_THRESHOLD_MS = 60 * 1000;
private static final int LOCATION_ACCURACY_THRESHOLD_METERS = 100;
private final LocationHelperInternal locationHelperInternal;
private final List<LocationListener> listeners = new ArrayList<>();
@MainThread
LocationHelper(Context context) {
Assert.isMainThread();
Assert.checkArgument(canGetLocation(context));
locationHelperInternal = new LocationHelperInternal(context);
}
static boolean canGetLocation(Context context) {
if (!PermissionsUtil.hasLocationPermissions(context)) {
LogUtil.i("LocationHelper.canGetLocation", "no location permissions.");
return false;
}
// Ensure that both system location setting is on and google location services are enabled.
if (!GoogleLocationSettingHelper.isGoogleLocationServicesEnabled(context)
|| !GoogleLocationSettingHelper.isSystemLocationSettingEnabled(context)) {
LogUtil.i("LocationHelper.canGetLocation", "location service is disabled.");
return false;
}
return true;
}
/**
* Whether the location is valid. We consider it valid if it was recorded within the specified
* time threshold of the present and has an accuracy less than the specified distance threshold.
*
* @param location The location to determine the validity of.
* @return {@code true} if the location is valid, and {@code false} otherwise.
*/
static boolean isValidLocation(Location location) {
if (location != null) {
long locationTimeMs = location.getTime();
long elapsedTimeMs = System.currentTimeMillis() - locationTimeMs;
if (elapsedTimeMs > LAST_UPDATE_THRESHOLD_MS) {
LogUtil.i("LocationHelper.isValidLocation", "stale location, age: " + elapsedTimeMs);
return false;
}
if (location.getAccuracy() > LOCATION_ACCURACY_THRESHOLD_METERS) {
LogUtil.i("LocationHelper.isValidLocation", "poor accuracy: " + location.getAccuracy());
return false;
}
return true;
}
LogUtil.i("LocationHelper.isValidLocation", "no location");
return false;
}
@MainThread
void addLocationListener(LocationListener listener) {
Assert.isMainThread();
listeners.add(listener);
}
@MainThread
void removeLocationListener(LocationListener listener) {
Assert.isMainThread();
listeners.remove(listener);
}
@MainThread
void close() {
Assert.isMainThread();
LogUtil.enterBlock("LocationHelper.close");
listeners.clear();
if (locationHelperInternal != null) {
locationHelperInternal.close();
}
}
@MainThread
void onLocationChanged(Location location, boolean isConnected) {
Assert.isMainThread();
LogUtil.i("LocationHelper.onLocationChanged", "location: " + location);
for (LocationListener listener : listeners) {
listener.onLocationChanged(location);
}
}
/**
* This class contains all the asynchronous callbacks. It only posts location changes back to the
* outer class on the main thread.
*/
private class LocationHelperInternal
implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener {
private final GoogleApiClient apiClient;
private final ConnectivityManager connectivityManager;
private final Handler mainThreadHandler = new Handler();
@MainThread
LocationHelperInternal(Context context) {
Assert.isMainThread();
apiClient =
new GoogleApiClient.Builder(context)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
LogUtil.i("LocationHelperInternal", "Connecting to location service...");
apiClient.connect();
connectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
void close() {
if (apiClient.isConnected()) {
LogUtil.i("LocationHelperInternal", "disconnecting");
LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, this);
apiClient.disconnect();
}
}
@Override
public void onConnected(Bundle bundle) {
LogUtil.enterBlock("LocationHelperInternal.onConnected");
LocationRequest locationRequest =
LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
.setInterval(MIN_UPDATE_INTERVAL_MS)
.setFastestInterval(MIN_UPDATE_INTERVAL_MS);
LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, this)
.setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
if (status.getStatus().isSuccess()) {
onLocationChanged(LocationServices.FusedLocationApi.getLastLocation(apiClient));
}
}
});
}
@Override
public void onConnectionSuspended(int i) {
// Do nothing.
}
@Override
public void onConnectionFailed(ConnectionResult result) {
// Do nothing.
}
@Override
public void onLocationChanged(Location location) {
// Post new location on main thread
mainThreadHandler.post(
new Runnable() {
@Override
public void run() {
LocationHelper.this.onLocationChanged(location, isConnected());
}
});
}
/** @return Whether the phone is connected to data. */
private boolean isConnected() {
if (connectivityManager == null) {
return false;
}
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnectedOrConnecting();
}
}
}