Merge "Extract LocationProviderManager"

This commit is contained in:
TreeHugger Robot
2020-08-04 23:33:20 +00:00
committed by Android (Google) Code Review
15 changed files with 3396 additions and 2133 deletions

View File

@@ -21,6 +21,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.location.util.identity.CallerIdentity;
import java.util.List;
/**
* Location manager local system service interface.
*
@@ -28,6 +30,16 @@ import android.location.util.identity.CallerIdentity;
*/
public abstract class LocationManagerInternal {
/**
* Listener for changes in provider enabled state.
*/
public interface ProviderEnabledListener {
/**
* Called when the provider enabled state changes for a particular user.
*/
void onProviderEnabledChanged(String provider, int userId, boolean enabled);
}
/**
* Returns true if the given provider is enabled for the given user.
*
@@ -37,6 +49,24 @@ public abstract class LocationManagerInternal {
*/
public abstract boolean isProviderEnabledForUser(@NonNull String provider, int userId);
/**
* Adds a provider enabled listener. The given provider must exist.
*
* @param provider The provider to listen for changes
* @param listener The listener
*/
public abstract void addProviderEnabledListener(String provider,
ProviderEnabledListener listener);
/**
* Removes a provider enabled listener. The given provider must exist.
*
* @param provider The provider to listen for changes
* @param listener The listener
*/
public abstract void removeProviderEnabledListener(String provider,
ProviderEnabledListener listener);
/**
* Returns true if the given identity is a location provider.
*
@@ -52,4 +82,10 @@ public abstract class LocationManagerInternal {
*/
// TODO: there is no reason for this to exist as part of any API. move all the logic into gnss
public abstract void sendNiResponse(int notifId, int userResponse);
/**
* Should only be used by GNSS code.
*/
// TODO: there is no reason for this to exist as part of any API. create a real batching API
public abstract void reportGnssBatchLocations(List<Location> locations);
}

View File

@@ -32,6 +32,8 @@ import android.util.TimeUtils;
import com.android.internal.util.Preconditions;
import java.util.Objects;
/**
* A data object that contains quality of service parameters for requests
@@ -150,8 +152,6 @@ public final class LocationRequest implements Parcelable {
@UnsupportedAppUsage
private String mProvider;
// if true, client requests coarse location, if false, client requests fine location
private boolean mCoarseLocation;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mQuality;
@UnsupportedAppUsage
@@ -257,7 +257,6 @@ public final class LocationRequest implements Parcelable {
public LocationRequest() {
this(
/* provider= */ LocationManager.FUSED_PROVIDER,
/* coarseLocation= */ false,
/* quality= */ POWER_LOW,
/* interval= */ DEFAULT_INTERVAL_MS,
/* fastestInterval= */ (long) (DEFAULT_INTERVAL_MS / FASTEST_INTERVAL_FACTOR),
@@ -276,7 +275,6 @@ public final class LocationRequest implements Parcelable {
public LocationRequest(LocationRequest src) {
this(
src.mProvider,
src.mCoarseLocation,
src.mQuality,
src.mInterval,
src.mFastestInterval,
@@ -293,7 +291,6 @@ public final class LocationRequest implements Parcelable {
private LocationRequest(
@NonNull String provider,
boolean coarseLocation,
int quality,
long intervalMs,
long fastestIntervalMs,
@@ -310,7 +307,6 @@ public final class LocationRequest implements Parcelable {
checkQuality(quality);
mProvider = provider;
mCoarseLocation = coarseLocation;
mQuality = quality;
mInterval = intervalMs;
mFastestInterval = fastestIntervalMs;
@@ -326,20 +322,6 @@ public final class LocationRequest implements Parcelable {
mWorkSource = workSource;
}
/**
* @hide
*/
public boolean isCoarse() {
return mCoarseLocation;
}
/**
* @hide
*/
public void setCoarse(boolean coarse) {
mCoarseLocation = coarse;
}
/**
* Set the quality of the request.
*
@@ -720,7 +702,6 @@ public final class LocationRequest implements Parcelable {
public LocationRequest createFromParcel(Parcel in) {
return new LocationRequest(
/* provider= */ in.readString(),
/* coarseLocation= */ in.readBoolean(),
/* quality= */ in.readInt(),
/* interval= */ in.readLong(),
/* fastestInterval= */ in.readLong(),
@@ -749,7 +730,6 @@ public final class LocationRequest implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(mProvider);
parcel.writeBoolean(mCoarseLocation);
parcel.writeInt(mQuality);
parcel.writeLong(mInterval);
parcel.writeLong(mFastestInterval);
@@ -784,6 +764,36 @@ public final class LocationRequest implements Parcelable {
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LocationRequest that = (LocationRequest) o;
return mQuality == that.mQuality
&& mInterval == that.mInterval
&& mFastestInterval == that.mFastestInterval
&& mExplicitFastestInterval == that.mExplicitFastestInterval
&& mExpireAt == that.mExpireAt
&& mExpireIn == that.mExpireIn
&& mNumUpdates == that.mNumUpdates
&& Float.compare(that.mSmallestDisplacement, mSmallestDisplacement) == 0
&& mHideFromAppOps == that.mHideFromAppOps
&& mLocationSettingsIgnored == that.mLocationSettingsIgnored
&& mLowPowerMode == that.mLowPowerMode
&& mProvider.equals(that.mProvider)
&& Objects.equals(mWorkSource, that.mWorkSource);
}
@Override
public int hashCode() {
return Objects.hash(mProvider, mInterval, mWorkSource);
}
@NonNull
@Override
public String toString() {
@@ -794,7 +804,7 @@ public final class LocationRequest implements Parcelable {
if (mQuality != POWER_NONE) {
s.append(" interval=");
TimeUtils.formatDuration(mInterval, s);
if (mExplicitFastestInterval) {
if (mExplicitFastestInterval && mFastestInterval != mInterval) {
s.append(" fastestInterval=");
TimeUtils.formatDuration(mFastestInterval, s);
}

View File

@@ -112,7 +112,7 @@ public class LocationFudger {
public Location createCoarse(Location fine) {
synchronized (this) {
if (fine == mCachedFineLocation) {
return new Location(mCachedCoarseLocation);
return mCachedCoarseLocation;
}
}
@@ -154,7 +154,7 @@ public class LocationFudger {
mCachedCoarseLocation = coarse;
}
return new Location(mCachedCoarseLocation);
return mCachedCoarseLocation;
}
/**

View File

@@ -1,73 +0,0 @@
/*
* Copyright (C) 2020 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;
import android.annotation.NonNull;
import android.location.util.identity.CallerIdentity;
import android.os.IBinder;
import android.os.RemoteException;
import java.util.NoSuchElementException;
/**
* Shared utilities for LocationManagerService and GnssManager.
*/
public class LocationManagerServiceUtils {
/**
* Skeleton class of listener that can be linked to a binder.
*/
public abstract static class LinkedListenerBase implements IBinder.DeathRecipient {
protected final CallerIdentity mCallerIdentity;
LinkedListenerBase(@NonNull CallerIdentity callerIdentity) {
mCallerIdentity = callerIdentity;
}
@Override
public String toString() {
return mCallerIdentity.toString();
}
public CallerIdentity getCallerIdentity() {
return mCallerIdentity;
}
/**
* Link listener (i.e. callback) to a binder, so that it will be called upon binder's death.
*/
public boolean linkToListenerDeathNotificationLocked(IBinder binder) {
try {
binder.linkToDeath(this, 0 /* flags */);
return true;
} catch (RemoteException e) {
return false;
}
}
/**
* Unlink death listener (i.e. callback) from binder.
*/
public void unlinkFromListenerDeathNotificationLocked(IBinder binder) {
try {
binder.unlinkToDeath(this, 0 /* flags */);
} catch (NoSuchElementException e) {
// ignore
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -40,9 +40,8 @@ public class MockProvider extends AbstractLocationProvider {
public MockProvider(ProviderProperties properties, CallerIdentity identity) {
// using a direct executor is ok because this class has no locks that could deadlock
super(DIRECT_EXECUTOR);
super(DIRECT_EXECUTOR, identity);
setProperties(properties);
setIdentity(identity);
}
/** Sets the allowed state of this mock provider. */

View File

@@ -241,7 +241,6 @@ public class MockableLocationProvider extends AbstractLocationProvider {
if (getState().properties != null) {
pw.println("properties=" + getState().properties);
}
pw.println("request=" + mRequest);
}
if (provider != null) {

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2020 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;
import android.annotation.Nullable;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.os.Binder;
import com.android.internal.util.Preconditions;
import com.android.server.location.util.Injector;
class PassiveLocationProviderManager extends LocationProviderManager {
PassiveLocationProviderManager(Context context, Injector injector) {
super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
}
@Override
public void setRealProvider(AbstractLocationProvider provider) {
Preconditions.checkArgument(provider instanceof PassiveProvider);
super.setRealProvider(provider);
}
@Override
public void setMockProvider(@Nullable MockProvider provider) {
if (provider != null) {
throw new IllegalArgumentException("Cannot mock the passive provider");
}
}
public void updateLocation(Location location) {
synchronized (mLock) {
PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
Preconditions.checkState(passiveProvider != null);
long identity = Binder.clearCallingIdentity();
try {
passiveProvider.updateLocation(location);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
}

View File

@@ -16,17 +16,21 @@
package com.android.server.location.gnss;
import static android.location.LocationManager.GPS_PROVIDER;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.gnss.GnssManagerService.TAG;
import android.annotation.Nullable;
import android.location.LocationManagerInternal;
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.util.identity.CallerIdentity;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Process;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.location.listeners.BinderListenerRegistration;
import com.android.server.location.listeners.ListenerMultiplexer;
@@ -161,8 +165,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
protected final LocationManagerInternal mLocationManagerInternal;
private final UserListener mUserChangedListener = this::onUserChanged;
private final SettingsHelper.UserSettingChangedListener mLocationEnabledChangedListener =
this::onLocationEnabledChanged;
private final ProviderEnabledListener mProviderEnabledChangedListener =
this::onProviderEnabledChanged;
private final SettingsHelper.GlobalSettingChangedListener
mBackgroundThrottlePackageWhitelistChangedListener =
this::onBackgroundThrottlePackageWhitelistChanged;
@@ -233,12 +237,11 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
CallerIdentity identity = registration.getIdentity();
// TODO: this should be checking if the gps provider is enabled, not if location is enabled,
// but this is the same for now.
return registration.isPermitted()
&& (registration.isForeground() || isBackgroundRestrictionExempt(identity))
&& mUserInfoHelper.isCurrentUserId(identity.getUserId())
&& mSettingsHelper.isLocationEnabled(identity.getUserId())
&& mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
identity.getUserId())
&& !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
identity.getPackageName());
}
@@ -263,7 +266,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
mUserInfoHelper.addListener(mUserChangedListener);
mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
mLocationManagerInternal.addProviderEnabledListener(GPS_PROVIDER,
mProviderEnabledChangedListener);
mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
@@ -279,7 +283,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
mUserInfoHelper.removeListener(mUserChangedListener);
mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
mLocationManagerInternal.removeProviderEnabledListener(GPS_PROVIDER,
mProviderEnabledChangedListener);
mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
@@ -294,7 +299,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
}
private void onLocationEnabledChanged(int userId) {
private void onProviderEnabledChanged(String provider, int userId, boolean enabled) {
Preconditions.checkState(GPS_PROVIDER.equals(provider));
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}

View File

@@ -104,18 +104,18 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
* should return true if a matching call to {@link #unregisterWithService()} is required to
* unregister (ie, if registration succeeds).
*
* @see #reregisterWithService(Object)
* @see #reregisterWithService(Object, Object)
*/
protected abstract boolean registerWithService(TMergedRequest mergedRequest);
protected abstract boolean registerWithService(TMergedRequest newRequest);
/**
* Invoked when the service already has a request, and it is being replaced with a new request.
* The default implementation unregisters first, then registers with the new merged request, but
* this may be overridden by subclasses in order to reregister more efficiently.
*/
protected boolean reregisterWithService(TMergedRequest mergedRequest) {
protected boolean reregisterWithService(TMergedRequest oldRequest, TMergedRequest newRequest) {
unregisterWithService();
return registerWithService(mergedRequest);
return registerWithService(newRequest);
}
/**
@@ -368,6 +368,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
mCurrentRequest = null;
if (mServiceRegistered) {
mServiceRegistered = false;
mCurrentRequest = null;
unregisterWithService();
}
return;
@@ -376,11 +377,15 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
TMergedRequest merged = mergeRequests(actives);
if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
if (mServiceRegistered) {
mServiceRegistered = reregisterWithService(merged);
mServiceRegistered = reregisterWithService(mCurrentRequest, merged);
} else {
mServiceRegistered = registerWithService(merged);
}
mCurrentRequest = merged;
if (mServiceRegistered) {
mCurrentRequest = merged;
} else {
mCurrentRequest = null;
}
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -388,29 +393,6 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
}
}
/**
* Evaluates the given predicate for all registrations, and forces an {@link #updateService()}
* if any predicate returns true for an active registration. The predicate will always be
* evaluated for all registrations, even inactive registrations, or if it has already returned
* true for a prior registration.
*/
protected final void updateService(Predicate<TRegistration> predicate) {
synchronized (mRegistrations) {
boolean updateService = false;
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (predicate.test(registration) && registration.isActive()) {
updateService = true;
}
}
if (updateService) {
updateService();
}
}
}
/**
* Begins buffering calls to {@link #updateService()} until {@link UpdateServiceLock#close()}
* is called. This is useful to prevent extra work when combining multiple calls (for example,

View File

@@ -24,6 +24,7 @@ import android.annotation.Nullable;
import android.location.util.identity.CallerIdentity;
import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.listeners.ListenerExecutor;
import com.android.server.FgThread;
@@ -39,6 +40,9 @@ import java.util.concurrent.Executor;
*/
public class ListenerRegistration<TRequest, TListener> implements ListenerExecutor {
@VisibleForTesting
public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
private final Executor mExecutor;
private final @Nullable TRequest mRequest;
private final CallerIdentity mIdentity;
@@ -55,9 +59,9 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
// there's a slight loophole here for pending intents - pending intent callbacks can
// always be run on the direct executor since they're always asynchronous, but honestly
// you shouldn't be using pending intent callbacks within the same process anyways
mExecutor = FgThread.getExecutor();
mExecutor = IN_PROCESS_EXECUTOR;
} else {
mExecutor = DIRECT_EXECUTOR;
mExecutor = DIRECT_EXECUTOR;
}
mRequest = request;
@@ -73,7 +77,7 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
/**
* Returns the request associated with this listener, or null if one wasn't supplied.
*/
public final @Nullable TRequest getRequest() {
public @Nullable TRequest getRequest() {
return mRequest;
}
@@ -107,7 +111,7 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
*/
protected void onInactive() {}
final boolean isActive() {
public final boolean isActive() {
return mActive;
}
@@ -120,7 +124,7 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
return false;
}
final boolean isRegistered() {
public final boolean isRegistered() {
return mListener != null;
}

View File

@@ -39,7 +39,7 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends
protected RemovableListenerRegistration(String tag, @Nullable TRequest request,
CallerIdentity callerIdentity, TListener listener) {
super(request, callerIdentity, listener);
mTag = tag;
mTag = Objects.requireNonNull(tag);
}
/**

View File

@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.mockingservicestests">
<uses-sdk android:targetSdkVersion="30" />
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />

View File

@@ -0,0 +1,971 @@
/*
* Copyright (C) 2020 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;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.WINDOW_EXACT;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.location.Criteria.ACCURACY_COARSE;
import static android.location.Criteria.ACCURACY_FINE;
import static android.location.Criteria.POWER_HIGH;
import static android.location.LocationManager.PASSIVE_PROVIDER;
import static androidx.test.ext.truth.location.LocationSubject.assertThat;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationUtils.createLocation;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.MockitoAnnotations.initMocks;
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.content.Context;
import android.location.ILocationCallback;
import android.location.ILocationListener;
import android.location.Location;
import android.location.LocationManagerInternal;
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.IRemoteCallback;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.location.listeners.ListenerRegistration;
import com.android.server.location.util.FakeUserInfoHelper;
import com.android.server.location.util.TestInjector;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LocationProviderManagerTest {
private static final String TAG = "LocationProviderManagerTest";
private static final long TIMEOUT_MS = 1000;
private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
private static final int OTHER_USER = CURRENT_USER + 10;
private static final String NAME = "test";
private static final ProviderProperties PROPERTIES = new ProviderProperties(false, false, false,
false, true, true, true, POWER_HIGH, ACCURACY_FINE);
private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1,
"mypackage",
"attribution");
private Random mRandom;
@Mock
private LocationManagerInternal mInternal;
@Mock
private Context mContext;
@Mock
private AlarmManager mAlarmManager;
@Mock
private PowerManager mPowerManager;
@Mock
private PowerManager.WakeLock mWakeLock;
private TestInjector mInjector;
private PassiveLocationProviderManager mPassive;
private TestProvider mProvider;
private LocationProviderManager mManager;
@Before
public void setUp() {
initMocks(this);
long seed = System.currentTimeMillis();
Log.i(TAG, "location random seed: " + seed);
mRandom = new Random(seed);
LocalServices.addService(LocationManagerInternal.class, mInternal);
doReturn("android").when(mContext).getPackageName();
doReturn(mAlarmManager).when(mContext).getSystemService(AlarmManager.class);
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
mInjector = new TestInjector();
mInjector.getUserInfoHelper().startUser(OTHER_USER);
mPassive = new PassiveLocationProviderManager(mContext, mInjector);
mPassive.startManager();
mPassive.setRealProvider(new PassiveProvider(mContext));
mProvider = new TestProvider(PROPERTIES, IDENTITY);
mProvider.setProviderAllowed(true);
mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
mManager.startManager();
mManager.setRealProvider(mProvider);
}
@After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(LocationManagerInternal.class);
// some test failures may leave the fg thread stuck, interrupt until we get out of it
CountDownLatch latch = new CountDownLatch(1);
FgThread.getExecutor().execute(latch::countDown);
int count = 0;
while (++count < 10 && !latch.await(10, TimeUnit.MILLISECONDS)) {
FgThread.get().getLooper().getThread().interrupt();
}
}
@Test
public void testProperties() {
assertThat(mManager.getName()).isEqualTo(NAME);
assertThat(mManager.getProperties()).isEqualTo(PROPERTIES);
assertThat(mManager.getIdentity()).isEqualTo(IDENTITY);
assertThat(mManager.hasProvider()).isTrue();
ProviderProperties newProperties = new ProviderProperties(true, true, true,
true, false, false, false, POWER_HIGH, ACCURACY_COARSE);
mProvider.setProperties(newProperties);
assertThat(mManager.getProperties()).isEqualTo(newProperties);
CallerIdentity newIdentity = CallerIdentity.forTest(OTHER_USER, 1, "otherpackage",
"otherattribution");
mProvider.setIdentity(newIdentity);
assertThat(mManager.getIdentity()).isEqualTo(newIdentity);
mManager.setRealProvider(null);
assertThat(mManager.hasProvider()).isFalse();
}
@Test
public void testRemoveProvider() {
mManager.setRealProvider(null);
assertThat(mManager.hasProvider()).isFalse();
}
@Test
public void testIsEnabled() {
assertThat(mManager.isEnabled(CURRENT_USER)).isTrue();
mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
mProvider.setAllowed(false);
assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
mProvider.setAllowed(true);
mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
assertThat(mManager.isEnabled(OTHER_USER)).isTrue();
mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
assertThat(mManager.isEnabled(CURRENT_USER)).isTrue();
assertThat(mManager.isEnabled(OTHER_USER)).isFalse();
}
@Test
public void testIsEnabledListener() {
ProviderEnabledListener listener = mock(ProviderEnabledListener.class);
mManager.addEnabledListener(listener);
verify(listener, never()).onProviderEnabledChanged(anyString(), anyInt(), anyBoolean());
mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, CURRENT_USER,
false);
mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, CURRENT_USER,
true);
mProvider.setAllowed(false);
verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, CURRENT_USER,
false);
mProvider.setAllowed(true);
verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, CURRENT_USER,
true);
mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, CURRENT_USER,
false);
verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, OTHER_USER,
true);
mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, CURRENT_USER,
true);
verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, OTHER_USER,
false);
mManager.removeEnabledListener(listener);
mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
verifyNoMoreInteractions(listener);
}
@Test
public void testGetLastLocation_Fine() {
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
}
@Test
public void testGetLastLocation_Coarse() {
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
Location coarse = mManager.getLastLocation(request, IDENTITY, PERMISSION_COARSE);
assertThat(coarse).isNotEqualTo(loc);
assertThat(coarse).isNearby(loc, 5000);
}
@Test
public void testGetLastLocation_Bypass() {
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
LocationRequest bypassRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false).setLocationSettingsIgnored(true);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isNull();
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
loc);
mProvider.setProviderAllowed(false);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
loc);
loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
loc);
mProvider.setProviderAllowed(true);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
loc);
loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
loc);
}
@Test
public void testGetLastLocation_ClearOnMockRemoval() {
MockProvider mockProvider = new MockProvider(PROPERTIES, IDENTITY);
mockProvider.setAllowed(true);
mManager.setMockProvider(mockProvider);
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
Location loc = createLocation(NAME, mRandom);
mockProvider.setProviderLocation(loc);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
mManager.setMockProvider(null);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
}
@Test
public void testInjectLastLocation() {
Location loc1 = createLocation(NAME, mRandom);
mManager.injectLastLocation(loc1, CURRENT_USER);
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc1);
Location loc2 = createLocation(NAME, mRandom);
mManager.injectLastLocation(loc2, CURRENT_USER);
assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc1);
}
@Test
public void testPassive_Listener() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(PASSIVE_PROVIDER, 0,
0, false);
mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
verify(listener).onLocationChanged(locationCaptor.capture(),
nullable(IRemoteCallback.class));
assertThat(locationCaptor.getValue()).isEqualTo(loc);
}
@Test
public void testPassive_LastLocation() {
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
LocationRequest request = LocationRequest.createFromDeprecatedProvider(PASSIVE_PROVIDER, 0,
0, false);
assertThat(mPassive.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
}
@Test
public void testRegisterListener() throws Exception {
ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
ILocationListener listener = createMockLocationListener();
mManager.registerLocationRequest(
LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), IDENTITY,
PERMISSION_FINE, listener);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
verify(listener, times(1)).onLocationChanged(locationCaptor.capture(),
nullable(IRemoteCallback.class));
assertThat(locationCaptor.getValue()).isEqualTo(loc);
mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, false);
loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
verify(listener, times(1)).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, true);
mProvider.setAllowed(false);
verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, false);
loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
verify(listener, times(1)).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
mProvider.setAllowed(true);
verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, true);
mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, false);
loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
verify(listener, times(1)).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, true);
loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
verify(listener, times(2)).onLocationChanged(locationCaptor.capture(),
nullable(IRemoteCallback.class));
assertThat(locationCaptor.getValue()).isEqualTo(loc);
}
@Test
public void testRegisterListener_SameProcess() throws Exception {
ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage",
"attribution");
ILocationListener listener = createMockLocationListener();
mManager.registerLocationRequest(
LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), identity,
PERMISSION_FINE, listener);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
verify(listener, timeout(TIMEOUT_MS).times(1)).onLocationChanged(locationCaptor.capture(),
nullable(IRemoteCallback.class));
assertThat(locationCaptor.getValue()).isEqualTo(loc);
}
@Test
public void testRegisterListener_Unregister() throws Exception {
ILocationListener listener = createMockLocationListener();
mManager.registerLocationRequest(
LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), IDENTITY,
PERMISSION_FINE, listener);
mManager.unregisterLocationRequest(listener);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, never()).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
verify(listener, after(TIMEOUT_MS).never()).onProviderEnabledChanged(NAME, false);
}
@Test
public void testRegisterListener_Unregister_SameProcess() throws Exception {
CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage",
"attribution");
ILocationListener listener = createMockLocationListener();
mManager.registerLocationRequest(
LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), identity,
PERMISSION_FINE, listener);
CountDownLatch blocker = new CountDownLatch(1);
ListenerRegistration.IN_PROCESS_EXECUTOR.execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {
// do nothing
}
});
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mManager.unregisterLocationRequest(listener);
blocker.countDown();
verify(listener, after(TIMEOUT_MS).never()).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_NumUpdates() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false).setNumUpdates(5);
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, times(5)).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_ExpiringAlarm() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false).setExpireIn(5000);
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
long baseTimeMs = SystemClock.elapsedRealtime();
ArgumentCaptor<Long> timeoutCapture = ArgumentCaptor.forClass(Long.class);
ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
OnAlarmListener.class);
verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), timeoutCapture.capture(),
eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
any(WorkSource.class));
assertThat(timeoutCapture.getValue()).isAtLeast(baseTimeMs + 4000);
assertThat(timeoutCapture.getValue()).isAtMost(baseTimeMs + 5000);
listenerCapture.getValue().onAlarm();
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, never()).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_ExpiringNoAlarm() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false).setExpireIn(25);
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
Thread.sleep(25);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, never()).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_AlreadyExpired() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false).setExpireIn(-1);
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, never()).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_FastestInterval() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 5000, 0,
false).setFastestInterval(5000);
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, times(1)).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_SmallestDisplacement() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 5000, 0,
false).setSmallestDisplacement(1f);
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
mProvider.setProviderLocation(loc);
verify(listener, times(1)).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_NoteOpFailure() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mInjector.getAppOpsHelper().setAppOpAllowed(OP_FINE_LOCATION, IDENTITY.getPackageName(),
false);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, never()).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_Wakelock() throws Exception {
CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage",
"attribution");
ILocationListener listener = createMockLocationListener();
mManager.registerLocationRequest(
LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), identity,
PERMISSION_FINE, listener);
CountDownLatch blocker = new CountDownLatch(1);
ListenerRegistration.IN_PROCESS_EXECUTOR.execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {
// do nothing
}
});
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(mWakeLock).acquire(anyLong());
verify(mWakeLock, never()).release();
blocker.countDown();
verify(listener, timeout(TIMEOUT_MS)).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
verify(mWakeLock).acquire(anyLong());
verify(mWakeLock, timeout(TIMEOUT_MS)).release();
}
@Test
public void testGetCurrentLocation() throws Exception {
ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false);
ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
mManager.getCurrentLocation(locationRequest, IDENTITY,
PERMISSION_FINE, cancellationSignal, listener);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, times(1)).onLocation(locationCaptor.capture());
assertThat(locationCaptor.getValue()).isEqualTo(loc);
}
@Test
public void testGetCurrentLocation_Cancel() throws Exception {
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false);
ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
mManager.getCurrentLocation(locationRequest, IDENTITY,
PERMISSION_FINE, cancellationSignal, listener);
cancellationSignal.cancel();
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, never()).onLocation(nullable(Location.class));
}
@Test
public void testGetCurrentLocation_ProviderDisabled() throws Exception {
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false);
ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
mManager.getCurrentLocation(locationRequest, IDENTITY,
PERMISSION_FINE, cancellationSignal, listener);
mProvider.setProviderAllowed(false);
mProvider.setProviderAllowed(true);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, times(1)).onLocation(isNull());
}
@Test
public void testGetCurrentLocation_ProviderAlreadyDisabled() throws Exception {
mProvider.setProviderAllowed(false);
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false);
ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
mManager.getCurrentLocation(locationRequest, IDENTITY,
PERMISSION_FINE, cancellationSignal, listener);
mProvider.setProviderAllowed(true);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, times(1)).onLocation(isNull());
}
@Test
public void testGetCurrentLocation_LastLocation() throws Exception {
ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false);
ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
mManager.getCurrentLocation(locationRequest, IDENTITY,
PERMISSION_FINE, cancellationSignal, listener);
verify(listener, times(1)).onLocation(locationCaptor.capture());
assertThat(locationCaptor.getValue()).isEqualTo(loc);
}
@Test
public void testGetCurrentLocation_Timeout() throws Exception {
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
false);
ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
mManager.getCurrentLocation(locationRequest, IDENTITY,
PERMISSION_FINE, cancellationSignal, listener);
ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
OnAlarmListener.class);
verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), anyLong(),
eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
any(WorkSource.class));
listenerCapture.getValue().onAlarm();
verify(listener, times(1)).onLocation(isNull());
}
@Test
public void testLocationMonitoring() {
assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
IDENTITY.getPackageName())).isFalse();
assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
IDENTITY.getPackageName())).isFalse();
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
IDENTITY.getPackageName())).isTrue();
assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
IDENTITY.getPackageName())).isTrue();
mInjector.getAppForegroundHelper().setAppForeground(IDENTITY.getUid(), false);
assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
IDENTITY.getPackageName())).isTrue();
assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
IDENTITY.getPackageName())).isFalse();
mManager.unregisterLocationRequest(listener);
assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
IDENTITY.getPackageName())).isFalse();
assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
IDENTITY.getPackageName())).isFalse();
}
@Test
public void testProviderRequest() {
assertThat(mProvider.getRequest().reportLocation).isFalse();
assertThat(mProvider.getRequest().locationRequests).isEmpty();
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().reportLocation).isTrue();
assertThat(mProvider.getRequest().locationRequests).containsExactly(request1);
assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
assertThat(mProvider.getRequest().interval).isEqualTo(5);
assertThat(mProvider.getRequest().lowPowerMode).isFalse();
assertThat(mProvider.getRequest().workSource).isNotNull();
ILocationListener listener2 = createMockLocationListener();
LocationRequest request2 = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0,
false).setLowPowerMode(true);
mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
assertThat(mProvider.getRequest().reportLocation).isTrue();
assertThat(mProvider.getRequest().locationRequests).containsExactly(request1, request2);
assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
assertThat(mProvider.getRequest().interval).isEqualTo(1);
assertThat(mProvider.getRequest().lowPowerMode).isFalse();
assertThat(mProvider.getRequest().workSource).isNotNull();
mManager.unregisterLocationRequest(listener1);
assertThat(mProvider.getRequest().reportLocation).isTrue();
assertThat(mProvider.getRequest().locationRequests).containsExactly(request2);
assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
assertThat(mProvider.getRequest().interval).isEqualTo(1);
assertThat(mProvider.getRequest().lowPowerMode).isTrue();
assertThat(mProvider.getRequest().workSource).isNotNull();
mManager.unregisterLocationRequest(listener2);
assertThat(mProvider.getRequest().reportLocation).isFalse();
assertThat(mProvider.getRequest().locationRequests).isEmpty();
}
@Test
public void testProviderRequest_BackgroundThrottle() {
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().interval).isEqualTo(5);
mInjector.getAppForegroundHelper().setAppForeground(IDENTITY.getUid(), false);
assertThat(mProvider.getRequest().interval).isEqualTo(
mInjector.getSettingsHelper().getBackgroundThrottleIntervalMs());
}
@Test
public void testProviderRequest_IgnoreLocationSettings() {
mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
Collections.singleton(IDENTITY.getPackageName()));
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().reportLocation).isTrue();
assertThat(mProvider.getRequest().interval).isEqualTo(5);
assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
ILocationListener listener2 = createMockLocationListener();
LocationRequest request2 = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0,
false).setLocationSettingsIgnored(true);
mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
assertThat(mProvider.getRequest().reportLocation).isTrue();
assertThat(mProvider.getRequest().interval).isEqualTo(1);
assertThat(mProvider.getRequest().locationSettingsIgnored).isTrue();
}
@Test
public void testProviderRequest_IgnoreLocationSettings_ProviderDisabled() {
mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
Collections.singleton(IDENTITY.getPackageName()));
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0, false);
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
ILocationListener listener2 = createMockLocationListener();
LocationRequest request2 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0,
false).setLocationSettingsIgnored(true);
mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
mInjector.getSettingsHelper().setLocationEnabled(false, IDENTITY.getUserId());
assertThat(mProvider.getRequest().reportLocation).isTrue();
assertThat(mProvider.getRequest().locationRequests).containsExactly(request2);
assertThat(mProvider.getRequest().interval).isEqualTo(5);
assertThat(mProvider.getRequest().locationSettingsIgnored).isTrue();
}
@Test
public void testProviderRequest_IgnoreLocationSettings_NoAllowlist() {
mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
Collections.singleton(IDENTITY.getPackageName()));
ILocationListener listener = createMockLocationListener();
LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0,
false).setLocationSettingsIgnored(true);
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(Collections.emptySet());
assertThat(mProvider.getRequest().reportLocation).isTrue();
assertThat(mProvider.getRequest().interval).isEqualTo(1);
assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
}
@Test
public void testProviderRequest_BackgroundThrottle_IgnoreLocationSettings() {
mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
Collections.singleton(IDENTITY.getPackageName()));
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0,
false).setLocationSettingsIgnored(true);
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().interval).isEqualTo(5);
mInjector.getAppForegroundHelper().setAppForeground(IDENTITY.getUid(), false);
assertThat(mProvider.getRequest().interval).isEqualTo(5);
}
private ILocationListener createMockLocationListener() {
return spy(new ILocationListener.Stub() {
@Override
public void onLocationChanged(Location location, IRemoteCallback onCompleteCallback) {
if (onCompleteCallback != null) {
try {
onCompleteCallback.sendResult(null);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
}
@Override
public void onProviderEnabledChanged(String provider, boolean enabled) {
}
});
}
private ILocationCallback createMockGetCurrentLocationListener() {
return spy(new ILocationCallback.Stub() {
@Override
public void onLocation(Location location) {
}
});
}
private static class TestProvider extends AbstractLocationProvider {
private ProviderRequest mProviderRequest = ProviderRequest.EMPTY_REQUEST;
TestProvider(ProviderProperties properties, CallerIdentity identity) {
super(DIRECT_EXECUTOR, identity);
setProperties(properties);
}
public void setProviderAllowed(boolean allowed) {
setAllowed(allowed);
}
public void setProviderLocation(Location l) {
reportLocation(new Location(l));
}
public ProviderRequest getRequest() {
return mProviderRequest;
}
@Override
public void onSetRequest(ProviderRequest request) {
mProviderRequest = request;
}
@Override
protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
}
}
}