/* * Copyright (C) 2010 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 android.location.provider; import static android.location.Location.EXTRA_NO_GPS_LOCATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; import android.location.Location; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * Base class for location providers outside the system server. * * Location providers should be wrapped in a non-exported service which returns the result of * {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The * service should not be exported so that components other than the system server cannot bind to it. * Alternatively, the service may be guarded by a permission that only system server can obtain. The * service may specify metadata on its capabilities: * * * *

The service should have an intent filter in place for the location provider it wishes to * implements. Defaults for some providers are specified as constants in this class. * *

Location providers are identified by their UID / package name / attribution tag. Based on this * identity, location providers may be given some special privileges (such as making special * requests to other location providers). * * @hide */ @SystemApi public abstract class LocationProviderBase { /** * Callback to be invoked when a flush operation is complete and all flushed locations have been * reported. */ public interface OnFlushCompleteCallback { /** * Should be invoked once the flush is complete. */ void onFlushComplete(); } /** * The action the wrapping service should have in its intent filter to implement the * {@link android.location.LocationManager#NETWORK_PROVIDER}. */ @SuppressLint("ActionValue") public static final String ACTION_NETWORK_PROVIDER = "com.android.location.service.v3.NetworkLocationProvider"; /** * The action the wrapping service should have in its intent filter to implement the * {@link android.location.LocationManager#FUSED_PROVIDER}. */ @SuppressLint("ActionValue") public static final String ACTION_FUSED_PROVIDER = "com.android.location.service.FusedLocationProvider"; /** * The action the wrapping service should have in its intent filter to implement the * {@link android.location.LocationManager#GPS_PROVIDER}. */ public static final String ACTION_GNSS_PROVIDER = "android.location.provider.action.GNSS_PROVIDER"; final String mTag; final @Nullable String mAttributionTag; final IBinder mBinder; // write locked on mBinder, read lock is optional depending on atomicity requirements volatile @Nullable ILocationProviderManager mManager; volatile ProviderProperties mProperties; volatile boolean mAllowed; public LocationProviderBase(@NonNull Context context, @NonNull String tag, @NonNull ProviderProperties properties) { mTag = tag; mAttributionTag = context.getAttributionTag(); mBinder = new Service(); mManager = null; mProperties = Objects.requireNonNull(properties); mAllowed = true; } /** * Returns the IBinder instance that should be returned from the * {@link android.app.Service#onBind(Intent)} method of the wrapping service. */ public final @Nullable IBinder getBinder() { return mBinder; } /** * Sets whether this provider is currently allowed or not. Note that this is specific to the * provider only, and is unrelated to global location settings. This is a hint to the location * manager that this provider will be unable to fulfill incoming requests. Setting a provider * as not allowed will result in the provider being disabled. Setting a provider as allowed * means that the provider may be in either the enabled or disabled state. * *

Some guidelines: providers should set their own allowed/disallowed status based only on * state "owned" by that provider. For instance, providers should not take into account the * state of the location master setting when setting themselves allowed or disallowed, as this * state is not owned by a particular provider. If a provider requires some additional user * consent that is particular to the provider, this should be use to set the allowed/disallowed * state. If the provider proxies to another provider, the child provider's allowed/disallowed * state should be taken into account in the parent's allowed state. For most providers, it is * expected that they will be always allowed. */ public void setAllowed(boolean allowed) { synchronized (mBinder) { if (mAllowed == allowed) { return; } mAllowed = allowed; } ILocationProviderManager manager = mManager; if (manager != null) { try { manager.onSetAllowed(mAllowed); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (RuntimeException e) { Log.w(mTag, e); } } } /** * Returns true if this provider is allowed. Providers start as allowed on construction. */ public boolean isAllowed() { return mAllowed; } /** * Sets the provider properties that may be queried by clients. Generally speaking, providers * should try to avoid changing their properties after construction. */ public void setProperties(@NonNull ProviderProperties properties) { synchronized (mBinder) { mProperties = Objects.requireNonNull(properties); } ILocationProviderManager manager = mManager; if (manager != null) { try { manager.onSetProperties(mProperties); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (RuntimeException e) { Log.w(mTag, e); } } } /** * Returns the currently set properties of the provider. */ public @NonNull ProviderProperties getProperties() { return mProperties; } /** * Reports a new location from this provider. */ public void reportLocation(@NonNull Location location) { ILocationProviderManager manager = mManager; if (manager != null) { try { manager.onReportLocation(stripExtras(location)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (RuntimeException e) { Log.w(mTag, e); } } } /** * Reports a new batch of locations from this provider. Locations must be ordered in the list * from earliest first to latest last. */ public void reportLocations(@NonNull List locations) { ILocationProviderManager manager = mManager; if (manager != null) { try { manager.onReportLocations(stripExtras(locations)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (RuntimeException e) { Log.w(mTag, e); } } } /** * Set the current {@link ProviderRequest} for this provider. Each call to this method overrides * any prior ProviderRequests. The provider should immediately attempt to provide locations (or * not provide locations) according to the parameters of the provider request. */ public abstract void onSetRequest(@NonNull ProviderRequest request); /** * Requests a flush of any pending batched locations. The callback must always be invoked once * per invocation, and should be invoked after {@link #reportLocation(Location)} or * {@link #reportLocations(List)} has been invoked with any flushed locations. The callback may * be invoked immediately if no locations are flushed. */ public abstract void onFlush(@NonNull OnFlushCompleteCallback callback); /** * Implements optional custom commands. */ public abstract void onSendExtraCommand(@NonNull String command, @SuppressLint("NullableCollection") @Nullable Bundle extras); private static Location stripExtras(Location location) { Bundle extras = location.getExtras(); if (extras != null && (extras.containsKey(EXTRA_NO_GPS_LOCATION) || extras.containsKey("indoorProbability") || extras.containsKey("coarseLocation"))) { location = new Location(location); extras = location.getExtras(); extras.remove(EXTRA_NO_GPS_LOCATION); extras.remove("indoorProbability"); extras.remove("coarseLocation"); if (extras.isEmpty()) { location.setExtras(null); } } return location; } private static List stripExtras(List locations) { List mapped = locations; final int size = locations.size(); int i = 0; for (Location location : locations) { Location newLocation = stripExtras(location); if (mapped != locations) { mapped.add(newLocation); } else if (newLocation != location) { mapped = new ArrayList<>(size); int j = 0; for (Location copiedLocation : locations) { if (j >= i) { break; } mapped.add(copiedLocation); j++; } mapped.add(newLocation); } i++; } return mapped; } private final class Service extends ILocationProvider.Stub { Service() {} @Override public void setLocationProviderManager(ILocationProviderManager manager) { synchronized (mBinder) { try { manager.onInitialize(mAllowed, mProperties, mAttributionTag); } catch (RemoteException | RuntimeException e) { Log.w(mTag, e); } mManager = manager; } } @Override public void setRequest(ProviderRequest request) { try { onSetRequest(request); } catch (RuntimeException e) { // exceptions on one-way binder threads are dropped - move to a different thread Log.w(mTag, e); new Handler(Looper.getMainLooper()).post(() -> { throw new AssertionError(e); }); } } @Override public void flush() { try { onFlush(this::onFlushComplete); } catch (RuntimeException e) { // exceptions on one-way binder threads are dropped - move to a different thread Log.w(mTag, e); new Handler(Looper.getMainLooper()).post(() -> { throw new AssertionError(e); }); } } private void onFlushComplete() { ILocationProviderManager manager = mManager; if (manager != null) { try { manager.onFlushComplete(); } catch (RemoteException | RuntimeException e) { Log.w(mTag, e); } } } @Override public void sendExtraCommand(String command, Bundle extras) { try { onSendExtraCommand(command, extras); } catch (RuntimeException e) { // exceptions on one-way binder threads are dropped - move to a different thread Log.w(mTag, e); new Handler(Looper.getMainLooper()).post(() -> { throw new AssertionError(e); }); } } } }