VancedMicroG/play-services-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java

243 lines
9.5 KiB
Java

/*
* Copyright (C) 2013-2017 microG Project Team
*
* 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 org.microg.gms.location;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import android.annotation.TargetApi;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.location.ILocationCallback;
import com.google.android.gms.location.ILocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.internal.LocationRequestUpdateData;
import java.util.Arrays;
import java.util.UUID;
public class LocationRequestHelper {
public static final String TAG = "GmsLocRequestHelper";
private final Context context;
public final LocationRequest locationRequest;
public final boolean initialHasFinePermission;
public final boolean initialHasCoarsePermission;
public final String packageName;
public final int uid;
private final boolean selfHasAppOpsRights;
public ILocationListener listener;
public PendingIntent pendingIntent;
public ILocationCallback callback;
public String id = UUID.randomUUID().toString();
private Location lastReport;
private int numReports = 0;
private LocationRequestHelper(Context context, LocationRequest locationRequest, String packageName, int uid) {
this.context = context;
this.locationRequest = locationRequest;
this.packageName = packageName;
this.uid = uid;
this.initialHasFinePermission = context.getPackageManager().checkPermission(ACCESS_FINE_LOCATION, packageName) == PackageManager.PERMISSION_GRANTED;
this.initialHasCoarsePermission = context.getPackageManager().checkPermission(ACCESS_COARSE_LOCATION, packageName) == PackageManager.PERMISSION_GRANTED;
this.selfHasAppOpsRights = context.getPackageManager().checkPermission("android.permission.UPDATE_APP_OPS_STATS", context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
}
public LocationRequestHelper(Context context, LocationRequest locationRequest, String packageName, int uid, ILocationListener listener) {
this(context, locationRequest, packageName, uid);
this.listener = listener;
}
public LocationRequestHelper(Context context, LocationRequest locationRequest, String packageName, int uid, PendingIntent pendingIntent) {
this(context, locationRequest, packageName, uid);
this.pendingIntent = pendingIntent;
}
public LocationRequestHelper(Context context, String packageName, int uid, LocationRequestUpdateData data) {
this(context, data.request.request, packageName, uid);
this.listener = data.listener;
this.pendingIntent = data.pendingIntent;
this.callback = data.callback;
}
public boolean isActive() {
if (!hasCoarsePermission()) return false;
if (listener != null) {
try {
return listener.asBinder().isBinderAlive();
} catch (Exception e) {
return false;
}
} else if (pendingIntent != null) {
return true;
} else if (callback != null) {
try {
return callback.asBinder().isBinderAlive();
} catch (Exception e) {
return false;
}
} else {
return false;
}
}
/**
* @return whether to continue sending reports to this {@link LocationRequestHelper}
*/
public boolean report(Location location) {
if (location == null) return true;
if (!isActive()) return false;
if (lastReport != null) {
if (location.equals(lastReport)) {
return true;
}
if (location.getTime() - lastReport.getTime() < locationRequest.getFastestInterval()) {
return true;
}
if (location.distanceTo(lastReport) < locationRequest.getSmallestDisplacement()) {
return true;
}
}
lastReport = new Location(location);
lastReport.setProvider("fused");
Log.d(TAG, "sending Location: " + location + " to " + packageName);
if (listener != null) {
try {
listener.onLocationChanged(lastReport);
} catch (RemoteException e) {
return false;
}
} else if (pendingIntent != null) {
Intent intent = new Intent();
intent.putExtra("com.google.android.location.LOCATION", lastReport);
try {
pendingIntent.send(context, 0, intent);
} catch (PendingIntent.CanceledException e) {
return false;
}
} else if (callback != null) {
try {
callback.onLocationResult(LocationResult.create(Arrays.asList(lastReport)));
} catch (RemoteException e) {
return false;
}
}
numReports++;
return numReports < locationRequest.getNumUpdates();
}
@Override
public String toString() {
return "LocationRequestHelper{" +
"locationRequest=" + locationRequest +
", hasFinePermission=" + hasFinePermission() +
", hasCoarsePermission=" + hasCoarsePermission() +
", packageName='" + packageName + '\'' +
", lastReport=" + lastReport +
'}';
}
public boolean respondsTo(ILocationListener listener) {
return this.listener != null && listener != null &&
this.listener.asBinder().equals(listener.asBinder());
}
public boolean respondsTo(ILocationCallback callback) {
return this.callback != null && callback != null &&
this.callback.asBinder().equals(callback.asBinder());
}
public boolean respondsTo(PendingIntent pendingIntent) {
return this.pendingIntent != null && this.pendingIntent.equals(pendingIntent);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LocationRequestHelper that = (LocationRequestHelper) o;
if (!locationRequest.equals(that.locationRequest)) return false;
if (packageName != null ? !packageName.equals(that.packageName) : that.packageName != null) return false;
if (listener != null ? !listener.equals(that.listener) : that.listener != null) return false;
if (pendingIntent != null ? !pendingIntent.equals(that.pendingIntent) : that.pendingIntent != null)
return false;
return !(callback != null ? !callback.equals(that.callback) : that.callback != null);
}
public boolean hasFinePermission() {
if (Build.VERSION.SDK_INT >= 19) {
return isAppOpsAllowed(AppOpsManager.OPSTR_FINE_LOCATION, initialHasFinePermission);
} else {
return initialHasFinePermission;
}
}
public boolean hasCoarsePermission() {
if (Build.VERSION.SDK_INT >= 19) {
return isAppOpsAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, initialHasCoarsePermission);
} else {
return initialHasCoarsePermission;
}
}
@TargetApi(19)
private boolean isAppOpsAllowed(String op, boolean def) {
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
if (appOpsManager == null) return def;
try {
if (Binder.getCallingUid() == uid && Build.VERSION.SDK_INT >= 23) {
return appOpsManager.noteProxyOpNoThrow(op, packageName) == AppOpsManager.MODE_ALLOWED;
} else if (Build.VERSION.SDK_INT >= 29) {
return appOpsManager.noteProxyOpNoThrow(op, packageName, uid) == AppOpsManager.MODE_ALLOWED;
} else if (selfHasAppOpsRights) {
return appOpsManager.noteOpNoThrow(op, uid, packageName) == AppOpsManager.MODE_ALLOWED;
} else {
// TODO: More variant that works pre-29 and without perms?
Log.w(TAG, "Can't check appops (yet)");
return def;
}
} catch (Exception e) {
Log.w(TAG, e);
return def;
}
}
@Override
public int hashCode() {
int result = locationRequest.hashCode();
result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
result = 31 * result + (listener != null ? listener.hashCode() : 0);
result = 31 * result + (pendingIntent != null ? pendingIntent.hashCode() : 0);
result = 31 * result + (callback != null ? callback.hashCode() : 0);
return result;
}
}