diff options
10 files changed, 417 insertions, 6 deletions
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 5c84a628c00d..ae65dcb4d714 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -1,6 +1,9 @@ { "presubmit": [ { + "name": "CtsLocationFineTestCases" + }, + { "name": "CtsLocationCoarseTestCases" }, { @@ -66,10 +69,5 @@ ], "file_patterns": ["ClipboardService\\.java"] } - ], - "postsubmit": [ - { - "name": "CtsLocationFineTestCases" - } ] } diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 1d1057f8ae9f..9bd48f2e0a58 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -115,6 +115,7 @@ import com.android.server.location.injector.Injector; import com.android.server.location.injector.LocationPermissionsHelper; import com.android.server.location.injector.LocationPowerSaveModeHelper; import com.android.server.location.injector.LocationUsageLogger; +import com.android.server.location.injector.PackageResetHelper; import com.android.server.location.injector.ScreenInteractiveHelper; import com.android.server.location.injector.SettingsHelper; import com.android.server.location.injector.SystemAlarmHelper; @@ -125,6 +126,7 @@ import com.android.server.location.injector.SystemDeviceStationaryHelper; import com.android.server.location.injector.SystemEmergencyHelper; import com.android.server.location.injector.SystemLocationPermissionsHelper; import com.android.server.location.injector.SystemLocationPowerSaveModeHelper; +import com.android.server.location.injector.SystemPackageResetHelper; import com.android.server.location.injector.SystemScreenInteractiveHelper; import com.android.server.location.injector.SystemSettingsHelper; import com.android.server.location.injector.SystemUserInfoHelper; @@ -1696,11 +1698,13 @@ public class LocationManagerService extends ILocationManager.Stub implements private final SystemDeviceStationaryHelper mDeviceStationaryHelper; private final SystemDeviceIdleHelper mDeviceIdleHelper; private final LocationUsageLogger mLocationUsageLogger; + private final PackageResetHelper mPackageResetHelper; // lazily instantiated since they may not always be used @GuardedBy("this") - private @Nullable SystemEmergencyHelper mEmergencyCallHelper; + @Nullable + private SystemEmergencyHelper mEmergencyCallHelper; @GuardedBy("this") private boolean mSystemReady; @@ -1721,6 +1725,7 @@ public class LocationManagerService extends ILocationManager.Stub implements mDeviceStationaryHelper = new SystemDeviceStationaryHelper(); mDeviceIdleHelper = new SystemDeviceIdleHelper(context); mLocationUsageLogger = new LocationUsageLogger(); + mPackageResetHelper = new SystemPackageResetHelper(context); } synchronized void onSystemReady() { @@ -1811,5 +1816,10 @@ public class LocationManagerService extends ILocationManager.Stub implements public LocationUsageLogger getLocationUsageLogger() { return mLocationUsageLogger; } + + @Override + public PackageResetHelper getPackageResetHelper() { + return mPackageResetHelper; + } } } diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java index 82bcca2b8470..e7f6e67f13ad 100644 --- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java @@ -39,6 +39,7 @@ import com.android.server.LocalServices; import com.android.server.location.injector.AppForegroundHelper; import com.android.server.location.injector.Injector; import com.android.server.location.injector.LocationPermissionsHelper; +import com.android.server.location.injector.PackageResetHelper; import com.android.server.location.injector.SettingsHelper; import com.android.server.location.injector.UserInfoHelper; import com.android.server.location.injector.UserInfoHelper.UserListener; @@ -193,6 +194,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter protected final LocationPermissionsHelper mLocationPermissionsHelper; protected final AppForegroundHelper mAppForegroundHelper; protected final LocationManagerInternal mLocationManagerInternal; + private final PackageResetHelper mPackageResetHelper; private final UserListener mUserChangedListener = this::onUserChanged; private final ProviderEnabledListener mProviderEnabledChangedListener = @@ -218,12 +220,25 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter }; private final AppForegroundHelper.AppForegroundListener mAppForegroundChangedListener = this::onAppForegroundChanged; + private final PackageResetHelper.Responder mPackageResetResponder = + new PackageResetHelper.Responder() { + @Override + public void onPackageReset(String packageName) { + GnssListenerMultiplexer.this.onPackageReset(packageName); + } + + @Override + public boolean isResetableForPackage(String packageName) { + return GnssListenerMultiplexer.this.isResetableForPackage(packageName); + } + }; protected GnssListenerMultiplexer(Injector injector) { mUserInfoHelper = injector.getUserInfoHelper(); mSettingsHelper = injector.getSettingsHelper(); mLocationPermissionsHelper = injector.getLocationPermissionsHelper(); mAppForegroundHelper = injector.getAppForegroundHelper(); + mPackageResetHelper = injector.getPackageResetHelper(); mLocationManagerInternal = Objects.requireNonNull( LocalServices.getService(LocationManagerInternal.class)); } @@ -357,6 +372,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mLocationPackageBlacklistChangedListener); mLocationPermissionsHelper.addListener(mLocationPermissionsListener); mAppForegroundHelper.addListener(mAppForegroundChangedListener); + mPackageResetHelper.register(mPackageResetResponder); } @Override @@ -374,6 +390,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mLocationPackageBlacklistChangedListener); mLocationPermissionsHelper.removeListener(mLocationPermissionsListener); mAppForegroundHelper.removeListener(mAppForegroundChangedListener); + mPackageResetHelper.unregister(mPackageResetResponder); } private void onUserChanged(int userId, int change) { @@ -407,6 +424,27 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground)); } + private void onPackageReset(String packageName) { + // invoked when a package is "force quit" - move off the main thread + FgThread.getExecutor().execute( + () -> + updateRegistrations( + registration -> { + if (registration.getIdentity().getPackageName().equals( + packageName)) { + registration.remove(); + } + + return false; + })); + } + + private boolean isResetableForPackage(String packageName) { + // invoked to find out if the given package has any state that can be "force quit" + return findRegistration( + registration -> registration.getIdentity().getPackageName().equals(packageName)); + } + @Override protected String getServiceState() { if (!isSupported()) { diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java index c0ce3a6853cf..b2c86721fdcc 100644 --- a/services/core/java/com/android/server/location/injector/Injector.java +++ b/services/core/java/com/android/server/location/injector/Injector.java @@ -63,4 +63,7 @@ public interface Injector { /** Returns a LocationUsageLogger. */ LocationUsageLogger getLocationUsageLogger(); + + /** Returns a PackageResetHelper. */ + PackageResetHelper getPackageResetHelper(); } diff --git a/services/core/java/com/android/server/location/injector/PackageResetHelper.java b/services/core/java/com/android/server/location/injector/PackageResetHelper.java new file mode 100644 index 000000000000..721c576b1b10 --- /dev/null +++ b/services/core/java/com/android/server/location/injector/PackageResetHelper.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 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.injector; + +import static com.android.server.location.LocationManagerService.D; +import static com.android.server.location.LocationManagerService.TAG; + +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** Helpers for tracking queries and resets of package state. */ +public abstract class PackageResetHelper { + + /** Interface for responding to reset events. */ + public interface Responder { + + /** + * Called when a package's runtime state is being reset for whatever reason and any + * components carrying runtime state on behalf of the package should clear that state. + * + * @param packageName The name of the package. + */ + void onPackageReset(String packageName); + + /** + * Called when the system queries whether this package has any active state for the given + * package. Should return true if the component has some runtime state that is resetable of + * behalf of the given package, and false otherwise. + * + * @param packageName The name of the package. + * @return True if this component has resetable state for the given package. + */ + boolean isResetableForPackage(String packageName); + } + + private final CopyOnWriteArrayList<Responder> mResponders; + + public PackageResetHelper() { + mResponders = new CopyOnWriteArrayList<>(); + } + + /** Begin listening for package reset events. */ + public synchronized void register(Responder responder) { + boolean empty = mResponders.isEmpty(); + mResponders.add(responder); + if (empty) { + onRegister(); + } + } + + /** Stop listening for package reset events. */ + public synchronized void unregister(Responder responder) { + mResponders.remove(responder); + if (mResponders.isEmpty()) { + onUnregister(); + } + } + + @GuardedBy("this") + protected abstract void onRegister(); + + @GuardedBy("this") + protected abstract void onUnregister(); + + protected final void notifyPackageReset(String packageName) { + if (D) { + Log.d(TAG, "package " + packageName + " reset"); + } + + for (Responder responder : mResponders) { + responder.onPackageReset(packageName); + } + } + + protected final boolean queryResetableForPackage(String packageName) { + for (Responder responder : mResponders) { + if (responder.isResetableForPackage(packageName)) { + return true; + } + } + + return false; + } +} diff --git a/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java new file mode 100644 index 000000000000..91b0212df734 --- /dev/null +++ b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2022 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.injector; + +import android.annotation.Nullable; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; + +import com.android.internal.util.Preconditions; + +/** Listens to appropriate broadcasts for queries and resets. */ +public class SystemPackageResetHelper extends PackageResetHelper { + + private final Context mContext; + + @Nullable + private BroadcastReceiver mReceiver; + + public SystemPackageResetHelper(Context context) { + mContext = context; + } + + @Override + protected void onRegister() { + Preconditions.checkState(mReceiver == null); + mReceiver = new Receiver(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); + filter.addDataScheme("package"); + + // We don't filter for Intent.ACTION_PACKAGE_DATA_CLEARED as 1) it refers to persistent + // data, and 2) it should always be preceded by Intent.ACTION_PACKAGE_RESTARTED, which + // refers to runtime data. in this way we also avoid redundant callbacks. + + mContext.registerReceiver(mReceiver, filter); + } + + @Override + protected void onUnregister() { + Preconditions.checkState(mReceiver != null); + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + } + + private class Receiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + + Uri data = intent.getData(); + if (data == null) { + return; + } + + String packageName = data.getSchemeSpecificPart(); + if (packageName == null) { + return; + } + + switch (action) { + case Intent.ACTION_QUERY_PACKAGE_RESTART: + String[] packages = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); + if (packages != null) { + // it would be more efficient to pass through the whole array, but at the + // moment the array is always size 1, and this makes for a nicer callback. + for (String pkg : packages) { + if (queryResetableForPackage(pkg)) { + setResultCode(Activity.RESULT_OK); + break; + } + } + } + break; + case Intent.ACTION_PACKAGE_CHANGED: + // make sure this is an enabled/disabled change to the package as a whole, not + // just some of its components. This avoids unnecessary work in the callback. + boolean isPackageChange = false; + String[] components = intent.getStringArrayExtra( + Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); + if (components != null) { + for (String component : components) { + if (packageName.equals(component)) { + isPackageChange = true; + break; + } + } + } + + if (isPackageChange) { + try { + ApplicationInfo appInfo = + context.getPackageManager().getApplicationInfo(packageName, + PackageManager.ApplicationInfoFlags.of(0)); + if (!appInfo.enabled) { + notifyPackageReset(packageName); + } + } catch (PackageManager.NameNotFoundException e) { + return; + } + } + break; + case Intent.ACTION_PACKAGE_REMOVED: + // fall through + case Intent.ACTION_PACKAGE_RESTARTED: + notifyPackageReset(packageName); + break; + default: + break; + } + } + } +} diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index a69a079b679d..bd7525133624 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -106,6 +106,7 @@ import com.android.server.location.injector.LocationPermissionsHelper.LocationPe import com.android.server.location.injector.LocationPowerSaveModeHelper; import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener; import com.android.server.location.injector.LocationUsageLogger; +import com.android.server.location.injector.PackageResetHelper; import com.android.server.location.injector.ScreenInteractiveHelper; import com.android.server.location.injector.ScreenInteractiveHelper.ScreenInteractiveChangedListener; import com.android.server.location.injector.SettingsHelper; @@ -1373,6 +1374,7 @@ public class LocationProviderManager extends protected final ScreenInteractiveHelper mScreenInteractiveHelper; protected final LocationUsageLogger mLocationUsageLogger; protected final LocationFudger mLocationFudger; + private final PackageResetHelper mPackageResetHelper; private final UserListener mUserChangedListener = this::onUserChanged; private final LocationSettings.LocationUserSettingsListener mLocationUserSettingsListener = @@ -1407,6 +1409,18 @@ public class LocationProviderManager extends this::onLocationPowerSaveModeChanged; private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener = this::onScreenInteractiveChanged; + private final PackageResetHelper.Responder mPackageResetResponder = + new PackageResetHelper.Responder() { + @Override + public void onPackageReset(String packageName) { + LocationProviderManager.this.onPackageReset(packageName); + } + + @Override + public boolean isResetableForPackage(String packageName) { + return LocationProviderManager.this.isResetableForPackage(packageName); + } + }; // acquiring mMultiplexerLock makes operations on mProvider atomic, but is otherwise unnecessary protected final MockableLocationProvider mProvider; @@ -1442,6 +1456,7 @@ public class LocationProviderManager extends mScreenInteractiveHelper = injector.getScreenInteractiveHelper(); mLocationUsageLogger = injector.getLocationUsageLogger(); mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM()); + mPackageResetHelper = injector.getPackageResetHelper(); mProvider = new MockableLocationProvider(mMultiplexerLock); @@ -1970,6 +1985,7 @@ public class LocationProviderManager extends mAppForegroundHelper.addListener(mAppForegroundChangedListener); mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener); mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener); + mPackageResetHelper.register(mPackageResetResponder); } @GuardedBy("mMultiplexerLock") @@ -1988,6 +2004,7 @@ public class LocationProviderManager extends mAppForegroundHelper.removeListener(mAppForegroundChangedListener); mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener); mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener); + mPackageResetHelper.unregister(mPackageResetResponder); } @GuardedBy("mMultiplexerLock") @@ -2391,6 +2408,27 @@ public class LocationProviderManager extends updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid)); } + private void onPackageReset(String packageName) { + // invoked when a package is "force quit" - move off the main thread + FgThread.getExecutor().execute( + () -> + updateRegistrations( + registration -> { + if (registration.getIdentity().getPackageName().equals( + packageName)) { + registration.remove(); + } + + return false; + })); + } + + private boolean isResetableForPackage(String packageName) { + // invoked to find out if the given package has any state that can be "force quit" + return findRegistration( + registration -> registration.getIdentity().getPackageName().equals(packageName)); + } + @GuardedBy("mMultiplexerLock") @Override public void onStateChanged( diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java new file mode 100644 index 000000000000..c2768d51970a --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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.injector; + +/** Version of PackageResetHelper for testing. */ +public class FakePackageResetHelper extends PackageResetHelper { + + public FakePackageResetHelper() {} + + @Override + protected void onRegister() {} + + @Override + protected void onUnregister() {} + + public boolean isResetableForPackage(String packageName) { + return queryResetableForPackage(packageName); + } + + public void reset(String packageName) { + notifyPackageReset(packageName); + } +} + diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java index 02cacb7bc57c..ca730910943b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java @@ -35,6 +35,7 @@ public class TestInjector implements Injector { private final FakeDeviceIdleHelper mDeviceIdleHelper; private final FakeEmergencyHelper mEmergencyHelper; private final LocationUsageLogger mLocationUsageLogger; + private final FakePackageResetHelper mPackageResetHelper; public TestInjector(Context context) { mUserInfoHelper = new FakeUserInfoHelper(); @@ -50,6 +51,7 @@ public class TestInjector implements Injector { mDeviceIdleHelper = new FakeDeviceIdleHelper(); mEmergencyHelper = new FakeEmergencyHelper(); mLocationUsageLogger = new LocationUsageLogger(); + mPackageResetHelper = new FakePackageResetHelper(); } @Override @@ -116,4 +118,9 @@ public class TestInjector implements Injector { public LocationUsageLogger getLocationUsageLogger() { return mLocationUsageLogger; } + + @Override + public FakePackageResetHelper getPackageResetHelper() { + return mPackageResetHelper; + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index 0ac14432d113..20e4e8011327 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -1218,6 +1218,44 @@ public class LocationProviderManagerTest { assertThat(mProvider.getRequest().isActive()).isFalse(); } + @Test + public void testQueryPackageReset() { + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse(); + + ILocationListener listener1 = createMockLocationListener(); + mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue(); + + ILocationListener listener2 = createMockLocationListener(); + mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener1); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener2); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse(); + } + + @Test + public void testPackageReset() { + ILocationListener listener1 = createMockLocationListener(); + mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1); + ILocationListener listener2 = createMockLocationListener(); + mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2); + + assertThat(mProvider.getRequest().isActive()).isTrue(); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue(); + + mInjector.getPackageResetHelper().reset("mypackage"); + assertThat(mProvider.getRequest().isActive()).isFalse(); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse(); + } + private ILocationListener createMockLocationListener() { return spy(new ILocationListener.Stub() { @Override |