diff options
author | 2024-11-20 15:45:48 +0000 | |
---|---|---|
committer | 2024-11-27 09:43:13 +0000 | |
commit | 4f699aab2a43ec7d5fddcbb7b1be1f1dabd47057 (patch) | |
tree | 619f4af241b28ae32ec27db5c1e81b4076a1477b /location | |
parent | cd0151ceaa4460e4136435dba42d98aebe20290f (diff) |
Add PopulationDensityProvider
This change adds the concept of population density providers in AOSP. It allows the framework to query the population density at a given location. Internally, this is backed by a new type of Provider, following the current conventions in the location package. There is no app-facing change, only the LocationManagerService will access this provider (see other CLs in the topic).
Because the provider will be implemented by Play Services (or OEM implementations), we add a proxy mechanism identical to the current ProxyLocationProvider, which reads the config.xml and instantiates the correct provider.
The core of the change is IPopulationDensityProvider.aidl.
In details:
- We add a new IPopulationDensityProvider IDL with its associated implementation PopulationDensityProviderBase.
- We add the IDL for two callback type for the S2CellId and the default coarsening level.
- We change the LocationManagerService to instantiate a ProxyPopulationDensityProvider.
- This ProxyPopulationDensityProvider creates the correct provider given the XML config.
Tests:
- atest CtsLocationNoneTestCases:PopulationDensityProviderBaseTest
- atest FrameworksMockingServicesTests:LocationManagerServiceTest
NB: I also did a manual test by implementing a fake provider and querying it, this works on Pixel 7 pro, see linked commits with topic "population-density-provider-test-dns"
Test: manual atest on Pixel 7 pro (see above)
Bug: 376198890
Flag: android.location.flags.population_density_provider
Change-Id: I90663d478200c734abbf5f442dfb1bb8bb79a875
Diffstat (limited to 'location')
5 files changed, 315 insertions, 0 deletions
diff --git a/location/api/system-current.txt b/location/api/system-current.txt index cf3f74085d66..8cd08d3aad6c 100644 --- a/location/api/system-current.txt +++ b/location/api/system-current.txt @@ -642,6 +642,14 @@ package android.location.provider { method public void onFlushComplete(); } + @FlaggedApi("android.location.flags.population_density_provider") public abstract class PopulationDensityProviderBase { + ctor public PopulationDensityProviderBase(@NonNull android.content.Context, @NonNull String); + method @Nullable public final android.os.IBinder getBinder(); + method public abstract void onGetCoarsenedS2Cell(double, double, @NonNull android.os.OutcomeReceiver<long[],java.lang.Throwable>); + method public abstract void onGetDefaultCoarseningLevel(@NonNull android.os.OutcomeReceiver<java.lang.Integer,java.lang.Throwable>); + field public static final String ACTION_POPULATION_DENSITY_PROVIDER = "com.android.location.service.PopulationDensityProvider"; + } + public final class ProviderRequest implements android.os.Parcelable { method public int describeContents(); method @IntRange(from=0) public long getIntervalMillis(); diff --git a/location/java/android/location/provider/IPopulationDensityProvider.aidl b/location/java/android/location/provider/IPopulationDensityProvider.aidl new file mode 100644 index 000000000000..9b5cb5ae8c7a --- /dev/null +++ b/location/java/android/location/provider/IPopulationDensityProvider.aidl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 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 android.os.Bundle; + +import android.location.Location; +import android.location.provider.IS2CellIdsCallback; +import android.location.provider.IS2LevelCallback; + +/** + * Binder interface for services that implement a population density provider. Do not implement this + * directly, extend {@link PopulationDensityProviderBase} instead. + * @hide + */ +oneway interface IPopulationDensityProvider { + /** + * Gets the default S2 level to be used to coarsen any location, in case a more precise answer + * from the method below can't be obtained. + */ + void getDefaultCoarseningLevel(in IS2LevelCallback callback); + + /** + * Returns a list of IDs of the S2 cells to be used to coarsen a location. The answer should + * contain at least one S2 cell, which should contain the requested location. Its level + * represents the population density. Optionally, additional nearby cells can be also returned, + * to assist in coarsening nearby locations. + */ + void getCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees, in IS2CellIdsCallback + callback); +} diff --git a/location/java/android/location/provider/IS2CellIdsCallback.aidl b/location/java/android/location/provider/IS2CellIdsCallback.aidl new file mode 100644 index 000000000000..f583045ebb26 --- /dev/null +++ b/location/java/android/location/provider/IS2CellIdsCallback.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 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 android.location.Location; + +/** + * Binder interface for S2 cell IDs callbacks. + * @hide + */ +oneway interface IS2CellIdsCallback { + + /** + * Called with the resulting list of S2 cell IDs. The first cell is expected to contain + * the requested latitude/longitude. Its level represent the population density. Optionally, + * the list can also contain additional nearby cells. + */ + void onResult(in long[] s2CellIds); + + /** Called if any error occurs while processing the query. */ + void onError(); +} diff --git a/location/java/android/location/provider/IS2LevelCallback.aidl b/location/java/android/location/provider/IS2LevelCallback.aidl new file mode 100644 index 000000000000..49f96ef7e3e2 --- /dev/null +++ b/location/java/android/location/provider/IS2LevelCallback.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 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 android.location.Location; + +/** + * Binder interface for S2 level callback. + * @hide + */ +oneway interface IS2LevelCallback { + /** + * Called with the resulting default S2 level for coarsening a location, in case a better + * answer cannot be obtained for a latitude/longitude. + */ + void onResult(int s2Level); + + /** Called if any error occurs while processing the query. */ + void onError(); +} diff --git a/location/java/android/location/provider/PopulationDensityProviderBase.java b/location/java/android/location/provider/PopulationDensityProviderBase.java new file mode 100644 index 000000000000..3907516f6aaa --- /dev/null +++ b/location/java/android/location/provider/PopulationDensityProviderBase.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2024 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 android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.content.Context; +import android.location.flags.Flags; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.OutcomeReceiver; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A provider for population density. + * The population density is defined as the S2 level at which the S2 cell around the latitude / + * longitude contains at least a thousand people. + * It exposes two methods: one about providing population density around a latitude / longitude, + * and one about providing a "default" population density to fall back to in case the first API + * can't be used or returns an error. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER) +public abstract class PopulationDensityProviderBase { + + final String mTag; + final @Nullable String mAttributionTag; + final IBinder mBinder; + + /** + * The action the wrapping service should have in its intent filter to implement the + * PopulationDensity provider. + */ + @SuppressLint("ActionValue") + public static final String ACTION_POPULATION_DENSITY_PROVIDER = + "com.android.location.service.PopulationDensityProvider"; + + public PopulationDensityProviderBase(@NonNull Context context, @NonNull String tag) { + mTag = tag; + mAttributionTag = context.getAttributionTag(); + mBinder = new Service(); + } + + /** + * 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; + } + + /** + * Called upon receiving a new request for the default coarsening level. + * The callback {@link OutcomeReceiver#onResult} should be called with the result; or, in case + * an error occurs, {@link OutcomeReceiver#onError} should be called. + * The callback is single-use, calling more than any one of these two methods throws an + * AssertionException. + * + * @param callback A single-use callback that either returns the coarsening level, or an error. + */ + public abstract void onGetDefaultCoarseningLevel(@NonNull OutcomeReceiver<Integer, Throwable> + callback); + + /** + * Called upon receiving a new request for population density at a specific latitude/longitude, + * expressed in degrees. + * The answer is at least one S2CellId corresponding to the coarsening level at the specified + * location. This must be the first element of the result array. Optionally, additional nearby + * S2CellIds can be returned. One use for the optional nearby cells is when the client has a + * local cache that needs to be filled with the local area around a certain latitude/longitude. + * The callback {@link OutcomeReceiver#onResult} should be called with the result; or, in case + * an error occurs, {@link OutcomeReceiver#onError} should be called. + * The callback is single-use, calling more than any one of these two methods throws an + * AssertionException. + * + * @param callback A single-use callback that either returns S2CellIds, or an error. + */ + public abstract void onGetCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees, + @NonNull OutcomeReceiver<long[], Throwable> callback); + + private final class Service extends IPopulationDensityProvider.Stub { + @Override + public void getDefaultCoarseningLevel(@NonNull IS2LevelCallback callback) { + try { + onGetDefaultCoarseningLevel(new SingleUseS2LevelCallback(callback)); + } 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 getCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees, + @NonNull IS2CellIdsCallback callback) { + try { + onGetCoarsenedS2Cell(latitudeDegrees, longitudeDegrees, + new SingleUseS2CellIdsCallback(callback)); + } 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 static class SingleUseS2LevelCallback implements OutcomeReceiver<Integer, Throwable> { + + private final AtomicReference<IS2LevelCallback> mCallback; + + SingleUseS2LevelCallback(IS2LevelCallback callback) { + mCallback = new AtomicReference<>(callback); + } + + @Override + public void onResult(Integer level) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onResult(level); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void onError(Throwable e) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onError(); + } catch (RemoteException r) { + throw r.rethrowFromSystemServer(); + } + } + } + + private static class SingleUseS2CellIdsCallback implements OutcomeReceiver<long[], Throwable> { + + private final AtomicReference<IS2CellIdsCallback> mCallback; + + SingleUseS2CellIdsCallback(IS2CellIdsCallback callback) { + mCallback = new AtomicReference<>(callback); + } + + @Override + public void onResult(long[] s2CellIds) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onResult(s2CellIds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void onError(Throwable e) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onError(); + } catch (RemoteException r) { + throw r.rethrowFromSystemServer(); + } + } + } +} |