Merge "Listen for PACKAGE_RESET queries and events"

This commit is contained in:
Soonil Nagarkar
2022-09-22 19:52:55 +00:00
committed by Android (Google) Code Review
10 changed files with 417 additions and 6 deletions

View File

@@ -1,5 +1,8 @@
{
"presubmit": [
{
"name": "CtsLocationFineTestCases"
},
{
"name": "CtsLocationCoarseTestCases"
},
@@ -66,10 +69,5 @@
],
"file_patterns": ["ClipboardService\\.java"]
}
],
"postsubmit": [
{
"name": "CtsLocationFineTestCases"
}
]
}

View File

@@ -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;
}
}
}

View File

@@ -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()) {

View File

@@ -63,4 +63,7 @@ public interface Injector {
/** Returns a LocationUsageLogger. */
LocationUsageLogger getLocationUsageLogger();
/** Returns a PackageResetHelper. */
PackageResetHelper getPackageResetHelper();
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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(

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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