| /* |
| * 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 com.android.server.location.provider; |
| |
| import android.annotation.Nullable; |
| import android.location.LocationResult; |
| import android.location.provider.ProviderProperties; |
| import android.location.provider.ProviderRequest; |
| import android.location.util.identity.CallerIdentity; |
| import android.os.Binder; |
| import android.os.Bundle; |
| |
| import com.android.internal.util.Preconditions; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.Collections; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.function.UnaryOperator; |
| |
| /** |
| * Base class for all location providers. |
| * |
| * @hide |
| */ |
| public abstract class AbstractLocationProvider { |
| |
| /** |
| * Interface for listening to location providers. |
| */ |
| public interface Listener { |
| |
| /** |
| * Called when a provider's state changes. May be invoked from any thread. Will be |
| * invoked with a cleared binder identity. |
| */ |
| void onStateChanged(State oldState, State newState); |
| |
| /** |
| * Called when a provider has a new location available. May be invoked from any thread. Will |
| * be invoked with a cleared binder identity. |
| */ |
| void onReportLocation(LocationResult locationResult); |
| } |
| |
| /** |
| * Holds a representation of the public state of a provider. |
| */ |
| public static final class State { |
| |
| /** |
| * Default state value for a location provider that is disabled with no properties and an |
| * empty extra attribution tag set. |
| */ |
| public static final State EMPTY_STATE = new State(false, null, null, |
| Collections.emptySet()); |
| |
| /** |
| * The provider's allowed state. |
| */ |
| public final boolean allowed; |
| |
| /** |
| * The provider's properties. |
| */ |
| @Nullable public final ProviderProperties properties; |
| |
| /** |
| * The provider's identity - providers may be afforded special privileges. |
| */ |
| @Nullable public final CallerIdentity identity; |
| |
| /** |
| * A set of attribution tags also associated with this provider - these attribution tags may |
| * be afforded special privileges. |
| */ |
| public final Set<String> extraAttributionTags; |
| |
| private State(boolean allowed, ProviderProperties properties, CallerIdentity identity, |
| Set<String> extraAttributionTags) { |
| this.allowed = allowed; |
| this.properties = properties; |
| this.identity = identity; |
| this.extraAttributionTags = Objects.requireNonNull(extraAttributionTags); |
| } |
| |
| /** |
| * Returns a state the same as the current but with allowed set as specified. |
| */ |
| public State withAllowed(boolean allowed) { |
| if (allowed == this.allowed) { |
| return this; |
| } else { |
| return new State(allowed, properties, identity, extraAttributionTags); |
| } |
| } |
| |
| /** |
| * Returns a state the same as the current but with properties set as specified. |
| */ |
| public State withProperties(@Nullable ProviderProperties properties) { |
| if (Objects.equals(properties, this.properties)) { |
| return this; |
| } else { |
| return new State(allowed, properties, identity, extraAttributionTags); |
| } |
| } |
| |
| /** |
| * Returns a state the same as the current but with an identity set as specified. |
| */ |
| public State withIdentity(@Nullable CallerIdentity identity) { |
| if (Objects.equals(identity, this.identity)) { |
| return this; |
| } else { |
| return new State(allowed, properties, identity, extraAttributionTags); |
| } |
| } |
| |
| /** |
| * Returns a state the same as the current but with extra attribution tags set as specified. |
| */ |
| public State withExtraAttributionTags(Set<String> extraAttributionTags) { |
| if (extraAttributionTags.equals(this.extraAttributionTags)) { |
| return this; |
| } else { |
| return new State(allowed, properties, identity, extraAttributionTags); |
| } |
| } |
| |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (!(o instanceof State)) { |
| return false; |
| } |
| State state = (State) o; |
| return allowed == state.allowed && properties == state.properties |
| && Objects.equals(identity, state.identity) |
| && extraAttributionTags.equals(state.extraAttributionTags); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(allowed, properties, identity, extraAttributionTags); |
| } |
| } |
| |
| // combines listener and state information so that they can be updated atomically with respect |
| // to each other and an ordering established. |
| private static class InternalState { |
| @Nullable public final Listener listener; |
| public final State state; |
| |
| InternalState(@Nullable Listener listener, State state) { |
| this.listener = listener; |
| this.state = state; |
| } |
| |
| InternalState withListener(Listener listener) { |
| if (listener == this.listener) { |
| return this; |
| } else { |
| return new InternalState(listener, state); |
| } |
| } |
| |
| InternalState withState(State state) { |
| if (state.equals(this.state)) { |
| return this; |
| } else { |
| return new InternalState(listener, state); |
| } |
| } |
| |
| InternalState withState(UnaryOperator<State> operator) { |
| return withState(operator.apply(state)); |
| } |
| } |
| |
| protected final Executor mExecutor; |
| |
| // we use a lock-free implementation to update state to ensure atomicity between updating the |
| // provider state and setting the listener, so that the state updates a listener sees are |
| // consistent with when the listener was set (a listener should not see any updates that occur |
| // before it was set, and should not miss any updates that occur after it was set). |
| private final AtomicReference<InternalState> mInternalState; |
| |
| private final LocationProviderController mController; |
| |
| /** |
| * Creates a new location provider. |
| * |
| * All callback methods will be invoked on the given executor. A direct executor may be provided |
| * only if the provider can guarantee that all callback methods will never synchronously invoke |
| * any {@link LocationProviderController} methods. If this invariant is not held, use a normal |
| * executor or risk deadlock. |
| * |
| * An optional identity and properties may be provided to initialize the location provider. |
| */ |
| protected AbstractLocationProvider(Executor executor, @Nullable CallerIdentity identity, |
| @Nullable ProviderProperties properties, Set<String> extraAttributionTags) { |
| Preconditions.checkArgument(identity == null || identity.getListenerId() == null); |
| mExecutor = Objects.requireNonNull(executor); |
| mInternalState = new AtomicReference<>(new InternalState(null, |
| State.EMPTY_STATE |
| .withIdentity(identity) |
| .withProperties(properties) |
| .withExtraAttributionTags(extraAttributionTags))); |
| mController = new Controller(); |
| } |
| |
| /** |
| * Retrieves the controller for this location provider. Should never be invoked by subclasses, |
| * as a location provider should not be controlling itself. Using this method from subclasses |
| * could also result in deadlock. |
| */ |
| LocationProviderController getController() { |
| return mController; |
| } |
| |
| protected void setState(UnaryOperator<State> operator) { |
| AtomicReference<State> oldStateRef = new AtomicReference<>(); |
| InternalState newInternalState = mInternalState.updateAndGet( |
| internalState -> { |
| oldStateRef.set(internalState.state); |
| return internalState.withState(operator); |
| }); |
| State oldState = oldStateRef.get(); |
| |
| if (oldState.equals(newInternalState.state)) { |
| return; |
| } |
| |
| if (newInternalState.listener != null) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| newInternalState.listener.onStateChanged(oldState, newInternalState.state); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| } |
| |
| /** |
| * The current state of the provider. |
| */ |
| public final State getState() { |
| return mInternalState.get().state; |
| } |
| |
| /** |
| * Call this method to report a change in provider allowed status. |
| */ |
| protected void setAllowed(boolean allowed) { |
| setState(state -> state.withAllowed(allowed)); |
| } |
| |
| /** |
| * Call this method to report a change in provider properties. |
| */ |
| protected void setProperties(@Nullable ProviderProperties properties) { |
| setState(state -> state.withProperties(properties)); |
| } |
| |
| /** |
| * Call this method to report a change in the provider's identity. |
| */ |
| protected void setIdentity(@Nullable CallerIdentity identity) { |
| Preconditions.checkArgument(identity == null || identity.getListenerId() == null); |
| setState(state -> state.withIdentity(identity)); |
| } |
| |
| public final Set<String> getExtraAttributionTags() { |
| return mInternalState.get().state.extraAttributionTags; |
| } |
| |
| /** |
| * Call this method to report a change in the provider's extra attribution tags. |
| */ |
| protected void setExtraAttributionTags(Set<String> extraAttributionTags) { |
| setState(state -> state.withExtraAttributionTags(extraAttributionTags)); |
| } |
| |
| /** |
| * Call this method to report a new location. |
| */ |
| protected void reportLocation(LocationResult locationResult) { |
| Listener listener = mInternalState.get().listener; |
| if (listener != null) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| listener.onReportLocation(Objects.requireNonNull(locationResult)); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| } |
| |
| /** |
| * Callback invoked when the provider is started, and signals that other callback invocations |
| * can now be expected. Always implies that the provider request is set to the empty request. |
| * Always invoked on the provider executor. |
| */ |
| protected void onStart() { } |
| |
| /** |
| * Callback invoked when the provider is stopped, and signals that no further callback |
| * invocations will occur (until a further call to {@link #onStart()}. Always invoked on the |
| * provider executor. |
| */ |
| protected void onStop() { } |
| |
| /** |
| * Callback invoked to inform the provider of a new provider request which replaces any prior |
| * provider request. Always invoked on the provider executor. |
| */ |
| protected abstract void onSetRequest(ProviderRequest request); |
| |
| /** |
| * Callback invoked to request any batched locations to be flushed. The argument callback must |
| * always be invoked exactly once for every invocation of this method, and should only be |
| * invoked only after {@link #reportLocation(LocationResult)} has been called for all flushed |
| * locations. If no locations are flushed, the argument callback may be invoked immediately. |
| * Always invoked on the provider executor. |
| */ |
| protected abstract void onFlush(Runnable callback); |
| |
| /** |
| * Callback invoked to informs the provider of an extra command it may choose to respond to. |
| * Always invoked on the provider executor. |
| */ |
| protected abstract void onExtraCommand(int uid, int pid, String command, Bundle extras); |
| |
| /** |
| * Dumps debug or log information. May be invoked from any thread. |
| */ |
| protected abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args); |
| |
| private class Controller implements LocationProviderController { |
| |
| private boolean mStarted = false; |
| |
| Controller() {} |
| |
| @Override |
| public State setListener(@Nullable Listener listener) { |
| InternalState oldInternalState = mInternalState.getAndUpdate( |
| internalState -> internalState.withListener(listener)); |
| Preconditions.checkState(listener == null || oldInternalState.listener == null); |
| return oldInternalState.state; |
| } |
| |
| @Override |
| public boolean isStarted() { |
| return mStarted; |
| } |
| |
| @Override |
| public void start() { |
| Preconditions.checkState(!mStarted); |
| mStarted = true; |
| mExecutor.execute(AbstractLocationProvider.this::onStart); |
| } |
| |
| @Override |
| public void stop() { |
| Preconditions.checkState(mStarted); |
| mStarted = false; |
| mExecutor.execute(AbstractLocationProvider.this::onStop); |
| } |
| |
| @Override |
| public void setRequest(ProviderRequest request) { |
| Preconditions.checkState(mStarted); |
| mExecutor.execute(() -> onSetRequest(request)); |
| } |
| |
| @Override |
| public void flush(Runnable listener) { |
| Preconditions.checkState(mStarted); |
| mExecutor.execute(() -> onFlush(listener)); |
| } |
| |
| @Override |
| public void sendExtraCommand(int uid, int pid, String command, Bundle extras) { |
| Preconditions.checkState(mStarted); |
| mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras)); |
| } |
| } |
| } |