Fix BroadcastReceiver leakage

This CL ensures that a shared PackageMonitor is unregistered when a
settings activity pauses.

Bug: 34541282
Test: Manually installing/uninstalling AOSP LatinIME.apk while
      Settings app is/isn't running, then search AOSP. Verified that
      intent receiver isn't leaked from Settings app.
Test: Add assertion to Robolectric test to ensure intent receiver
      isn't leaked. Build RunSettingsRoboTests and verify all passed.
Change-Id: I5bacdfe478eaa750c61341c14e79eb2cd3fdef3a
This commit is contained in:
Tadashi G. Takaoka
2017-01-24 14:21:11 +09:00
parent 86c492ffcc
commit edbe4047f7
2 changed files with 69 additions and 77 deletions

View File

@@ -62,8 +62,9 @@ public final class DynamicIndexableContentMonitor implements
// Shorten the class name because log TAG can be at most 23 chars. // Shorten the class name because log TAG can be at most 23 chars.
private static final String TAG = "DynamicContentMonitor"; private static final String TAG = "DynamicContentMonitor";
@VisibleForTesting private static final long DELAY_PROCESS_PACKAGE_CHANGE = 2000;
static final long DELAY_PROCESS_PACKAGE_CHANGE = 2000; // A PackageMonitor shared among Settings activities.
private static final PackageChangeMonitor PACKAGE_CHANGE_MONITOR = new PackageChangeMonitor();
// Null if not initialized. // Null if not initialized.
@Nullable private Index mIndex; @Nullable private Index mIndex;
@@ -87,7 +88,8 @@ public final class DynamicIndexableContentMonitor implements
@VisibleForTesting @VisibleForTesting
static void resetForTesting() { static void resetForTesting() {
InputDevicesMonitor.getInstance().resetForTesting(); InputDevicesMonitor.getInstance().resetForTesting();
PackageChangeMonitor.getInstance().resetForTesting(); AccessibilityServicesMonitor.getInstance().resetForTesting();
InputMethodServicesMonitor.getInstance().resetForTesting();
} }
/** /**
@@ -123,20 +125,23 @@ public final class DynamicIndexableContentMonitor implements
Log.w(TAG, "Skipping content monitoring because user is locked"); Log.w(TAG, "Skipping content monitoring because user is locked");
return; return;
} }
mContext = activity; final Context context = activity.getApplicationContext();
mContext = context;
mIndex = index; mIndex = index;
mHasFeaturePrinting = mContext.getPackageManager() PACKAGE_CHANGE_MONITOR.register(context);
mHasFeaturePrinting = context.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_PRINTING); .hasSystemFeature(PackageManager.FEATURE_PRINTING);
if (mHasFeaturePrinting) { if (mHasFeaturePrinting) {
activity.getLoaderManager().initLoader(loaderId, null /* args */, this /* callbacks */); activity.getLoaderManager().initLoader(loaderId, null /* args */, this /* callbacks */);
} }
// Watch for input device changes. // Watch for input device changes.
InputDevicesMonitor.getInstance().initialize(mContext, mIndex); InputDevicesMonitor.getInstance().initialize(context, index);
// Start tracking packages. // Start tracking packages.
PackageChangeMonitor.getInstance().initialize(mContext, mIndex); AccessibilityServicesMonitor.getInstance().initialize(context, index);
InputMethodServicesMonitor.getInstance().initialize(context, index);
} }
/** /**
@@ -149,6 +154,7 @@ public final class DynamicIndexableContentMonitor implements
public void unregister(Activity activity, int loaderId) { public void unregister(Activity activity, int loaderId) {
if (mIndex == null) return; if (mIndex == null) return;
PACKAGE_CHANGE_MONITOR.unregister();
if (mHasFeaturePrinting) { if (mHasFeaturePrinting) {
activity.getLoaderManager().destroyLoader(loaderId); activity.getLoaderManager().destroyLoader(loaderId);
} }
@@ -237,33 +243,9 @@ public final class DynamicIndexableContentMonitor implements
// Null if not initialized. // Null if not initialized.
@Nullable private PackageManager mPackageManager; @Nullable private PackageManager mPackageManager;
private PackageChangeMonitor() {} public void register(Context context) {
private static class SingletonHolder {
private static final PackageChangeMonitor INSTANCE = new PackageChangeMonitor();
}
static PackageChangeMonitor getInstance() {
return SingletonHolder.INSTANCE;
}
@VisibleForTesting
synchronized void resetForTesting() {
if (mPackageManager != null) {
unregister();
}
mPackageManager = null;
AccessibilityServicesMonitor.getInstance().resetForTesting();
InputMethodServicesMonitor.getInstance().resetForTesting();
}
synchronized void initialize(Context context, Index index) {
if (mPackageManager != null) return;;
mPackageManager = context.getPackageManager(); mPackageManager = context.getPackageManager();
AccessibilityServicesMonitor.getInstance().initialize(context, index);
InputMethodServicesMonitor.getInstance().initialize(context, index);
// Start tracking packages. Use background thread for monitoring. Note that no need to // Start tracking packages. Use background thread for monitoring. Note that no need to
// unregister this monitor. This should be alive while Settings app is running. // unregister this monitor. This should be alive while Settings app is running.
register(context, null /* thread */, UserHandle.CURRENT, false); register(context, null /* thread */, UserHandle.CURRENT, false);
@@ -271,13 +253,13 @@ public final class DynamicIndexableContentMonitor implements
// Covers installed, appeared external storage with the package, upgraded. // Covers installed, appeared external storage with the package, upgraded.
@Override @Override
public void onPackageAppeared(String packageName, int uid) { public void onPackageAppeared(String packageName, int reason) {
postPackageAvailable(packageName); postPackageAvailable(packageName);
} }
// Covers uninstalled, removed external storage with the package. // Covers uninstalled, removed external storage with the package.
@Override @Override
public void onPackageDisappeared(String packageName, int uid) { public void onPackageDisappeared(String packageName, int reason) {
postPackageUnavailable(packageName); postPackageUnavailable(packageName);
} }
@@ -440,6 +422,7 @@ public final class DynamicIndexableContentMonitor implements
} }
} }
// TODO: Implements by JobScheduler with TriggerContentUri parameters.
// Watch for related content URIs. // Watch for related content URIs.
mContentResolver.registerContentObserver(UserDictionary.Words.CONTENT_URI, mContentResolver.registerContentObserver(UserDictionary.Words.CONTENT_URI,
true /* notifyForDescendants */, this /* observer */); true /* notifyForDescendants */, this /* observer */);

View File

@@ -102,7 +102,6 @@ import java.util.List;
) )
public class DynamicIndexableContentMonitorTest { public class DynamicIndexableContentMonitorTest {
private static final int USER_ID = 5678;
private static final int LOADER_ID = 1234; private static final int LOADER_ID = 1234;
private static final String A11Y_PACKAGE_1 = "a11y-1"; private static final String A11Y_PACKAGE_1 = "a11y-1";
private static final String A11Y_PACKAGE_2 = "a11y-2"; private static final String A11Y_PACKAGE_2 = "a11y-2";
@@ -151,6 +150,10 @@ public class DynamicIndexableContentMonitorTest {
@After @After
public void shutDown() { public void shutDown() {
mMonitor.unregister(mActivity, LOADER_ID);
// BroadcastReceiver must be unregistered.
assertThat(extractPackageMonitor()).isNull();
DynamicIndexableContentMonitor.resetForTesting(); DynamicIndexableContentMonitor.resetForTesting();
mRobolectricPackageManager.reset(); mRobolectricPackageManager.reset();
} }
@@ -187,6 +190,11 @@ public class DynamicIndexableContentMonitorTest {
// No destroy loader should happen. // No destroy loader should happen.
verify(mLoaderManager, never()).destroyLoader(anyInt()); verify(mLoaderManager, never()).destroyLoader(anyInt());
// BroadcastReceiver must be unregistered.
assertThat(extractPackageMonitor()).isNull();
// To suppress spurious test fail in {@link #shutDown()}.
mMonitor.register(mActivity, LOADER_ID, mIndex, true /* isUserUnlocked */);
} }
@Test @Test
@@ -220,6 +228,7 @@ public class DynamicIndexableContentMonitorTest {
/* /*
* Nothing happens on successive register calls. * Nothing happens on successive register calls.
*/ */
mMonitor.unregister(mActivity, LOADER_ID);
reset(mIndex); reset(mIndex);
mMonitor.register(mActivity, LOADER_ID, mIndex, true /* isUserUnlocked */); mMonitor.register(mActivity, LOADER_ID, mIndex, true /* isUserUnlocked */);
@@ -259,74 +268,59 @@ public class DynamicIndexableContentMonitorTest {
public void testAccessibilityServicesMonitor() throws Exception { public void testAccessibilityServicesMonitor() throws Exception {
mMonitor.register(mActivity, LOADER_ID, mIndex, true /* isUserUnlocked */); mMonitor.register(mActivity, LOADER_ID, mIndex, true /* isUserUnlocked */);
final PackageMonitor packageMonitor = extractPackageMonitor();
assertThat(packageMonitor).isNotNull();
verifyRebuildIndexing(AccessibilitySettings.class); verifyRebuildIndexing(AccessibilitySettings.class);
/* /*
* When an accessibility service package is installed, incremental indexing happen. * When an accessibility service package is installed, incremental indexing happen.
*/ */
installAccessibilityService(A11Y_PACKAGE_1);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageAppeared(A11Y_PACKAGE_1, USER_ID); installAccessibilityService(A11Y_PACKAGE_1);
Robolectric.flushBackgroundThreadScheduler();
verifyIncrementalIndexing(AccessibilitySettings.class); verifyIncrementalIndexing(AccessibilitySettings.class);
/* /*
* When another accessibility service package is installed, incremental indexing happens. * When another accessibility service package is installed, incremental indexing happens.
*/ */
installAccessibilityService(A11Y_PACKAGE_2);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageAppeared(A11Y_PACKAGE_2, USER_ID); installAccessibilityService(A11Y_PACKAGE_2);
Robolectric.flushBackgroundThreadScheduler();
verifyIncrementalIndexing(AccessibilitySettings.class); verifyIncrementalIndexing(AccessibilitySettings.class);
/* /*
* When an accessibility service is disabled, rebuild indexing happens. * When an accessibility service is disabled, rebuild indexing happens.
*/ */
((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
A11Y_PACKAGE_1, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageModified(A11Y_PACKAGE_1); disableInstalledPackage(A11Y_PACKAGE_1);
Robolectric.flushBackgroundThreadScheduler();
verifyRebuildIndexing(AccessibilitySettings.class); verifyRebuildIndexing(AccessibilitySettings.class);
/* /*
* When an accessibility service is enabled, incremental indexing happens. * When an accessibility service is enabled, incremental indexing happens.
*/ */
((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
A11Y_PACKAGE_1, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0 /* flags */);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageModified(A11Y_PACKAGE_1); enableInstalledPackage(A11Y_PACKAGE_1);
Robolectric.flushBackgroundThreadScheduler();
verifyIncrementalIndexing(AccessibilitySettings.class); verifyIncrementalIndexing(AccessibilitySettings.class);
/* /*
* When an accessibility service package is uninstalled, rebuild indexing happens. * When an accessibility service package is uninstalled, rebuild indexing happens.
*/ */
uninstallAccessibilityService(A11Y_PACKAGE_1);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageDisappeared(A11Y_PACKAGE_1, USER_ID); uninstallAccessibilityService(A11Y_PACKAGE_1);
verifyRebuildIndexing(AccessibilitySettings.class); verifyRebuildIndexing(AccessibilitySettings.class);
/* /*
* When an input method service package is installed, nothing happens. * When an input method service package is installed, nothing happens.
*/ */
installInputMethodService(IME_PACKAGE_1);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageAppeared(IME_PACKAGE_1, USER_ID); installInputMethodService(IME_PACKAGE_1);
verifyNoIndexing(AccessibilitySettings.class); verifyNoIndexing(AccessibilitySettings.class);
} }
@@ -335,9 +329,6 @@ public class DynamicIndexableContentMonitorTest {
public void testInputMethodServicesMonitor() throws Exception { public void testInputMethodServicesMonitor() throws Exception {
mMonitor.register(mActivity, LOADER_ID, mIndex, true /* isUserUnlocked */); mMonitor.register(mActivity, LOADER_ID, mIndex, true /* isUserUnlocked */);
final PackageMonitor packageMonitor = extractPackageMonitor();
assertThat(packageMonitor).isNotNull();
verifyRebuildIndexing(VirtualKeyboardFragment.class); verifyRebuildIndexing(VirtualKeyboardFragment.class);
verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class); verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class);
@@ -350,11 +341,9 @@ public class DynamicIndexableContentMonitorTest {
/* /*
* When an input method service package is installed, incremental indexing happen. * When an input method service package is installed, incremental indexing happen.
*/ */
installInputMethodService(IME_PACKAGE_1);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageAppeared(IME_PACKAGE_1, USER_ID); installInputMethodService(IME_PACKAGE_1);
Robolectric.flushBackgroundThreadScheduler();
verifyIncrementalIndexing(VirtualKeyboardFragment.class); verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class); verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
@@ -362,11 +351,9 @@ public class DynamicIndexableContentMonitorTest {
/* /*
* When another input method service package is installed, incremental indexing happens. * When another input method service package is installed, incremental indexing happens.
*/ */
installInputMethodService(IME_PACKAGE_2);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageAppeared(IME_PACKAGE_2, USER_ID); installInputMethodService(IME_PACKAGE_2);
Robolectric.flushBackgroundThreadScheduler();
verifyIncrementalIndexing(VirtualKeyboardFragment.class); verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class); verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
@@ -374,12 +361,9 @@ public class DynamicIndexableContentMonitorTest {
/* /*
* When an input method service is disabled, rebuild indexing happens. * When an input method service is disabled, rebuild indexing happens.
*/ */
((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
IME_PACKAGE_1, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageModified(IME_PACKAGE_1); disableInstalledPackage(IME_PACKAGE_1);
Robolectric.flushBackgroundThreadScheduler();
verifyRebuildIndexing(VirtualKeyboardFragment.class); verifyRebuildIndexing(VirtualKeyboardFragment.class);
verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class); verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class);
@@ -387,12 +371,9 @@ public class DynamicIndexableContentMonitorTest {
/* /*
* When an input method service is enabled, incremental indexing happens. * When an input method service is enabled, incremental indexing happens.
*/ */
((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
IME_PACKAGE_1, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0 /* flags */);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageModified(IME_PACKAGE_1); enableInstalledPackage(IME_PACKAGE_1);
Robolectric.flushBackgroundThreadScheduler();
verifyIncrementalIndexing(VirtualKeyboardFragment.class); verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class); verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
@@ -400,10 +381,9 @@ public class DynamicIndexableContentMonitorTest {
/* /*
* When an input method service package is uninstalled, rebuild indexing happens. * When an input method service package is uninstalled, rebuild indexing happens.
*/ */
uninstallInputMethodService(IME_PACKAGE_1);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageDisappeared(IME_PACKAGE_1, USER_ID); uninstallInputMethodService(IME_PACKAGE_1);
verifyRebuildIndexing(VirtualKeyboardFragment.class); verifyRebuildIndexing(VirtualKeyboardFragment.class);
verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class); verifyRebuildIndexing(AvailableVirtualKeyboardFragment.class);
@@ -411,10 +391,9 @@ public class DynamicIndexableContentMonitorTest {
/* /*
* When an accessibility service package is installed, nothing happens. * When an accessibility service package is installed, nothing happens.
*/ */
installAccessibilityService(A11Y_PACKAGE_1);
reset(mIndex); reset(mIndex);
packageMonitor.onPackageAppeared(A11Y_PACKAGE_1, USER_ID); installAccessibilityService(A11Y_PACKAGE_1);
verifyNoIndexing(VirtualKeyboardFragment.class); verifyNoIndexing(VirtualKeyboardFragment.class);
verifyNoIndexing(AvailableVirtualKeyboardFragment.class); verifyNoIndexing(AvailableVirtualKeyboardFragment.class);
@@ -528,6 +507,20 @@ public class DynamicIndexableContentMonitorTest {
return contentObserver; return contentObserver;
} }
private void enableInstalledPackage(String packageName) {
((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0 /* flags */);
extractPackageMonitor().onPackageModified(packageName);
Robolectric.flushBackgroundThreadScheduler();
}
private void disableInstalledPackage(String packageName) {
((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */);
extractPackageMonitor().onPackageModified(packageName);
Robolectric.flushBackgroundThreadScheduler();
}
private void installAccessibilityService(String packageName) throws Exception { private void installAccessibilityService(String packageName) throws Exception {
final AccessibilityServiceInfo serviceToAdd = buildAccessibilityServiceInfo(packageName); final AccessibilityServiceInfo serviceToAdd = buildAccessibilityServiceInfo(packageName);
@@ -540,6 +533,10 @@ public class DynamicIndexableContentMonitorTest {
.getAccessibilityServiceIntent(packageName); .getAccessibilityServiceIntent(packageName);
mRobolectricPackageManager.addResolveInfoForIntent(intent, serviceToAdd.getResolveInfo()); mRobolectricPackageManager.addResolveInfoForIntent(intent, serviceToAdd.getResolveInfo());
mRobolectricPackageManager.addPackage(packageName); mRobolectricPackageManager.addPackage(packageName);
extractPackageMonitor()
.onPackageAppeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
Robolectric.flushBackgroundThreadScheduler();
} }
private void uninstallAccessibilityService(String packageName) throws Exception { private void uninstallAccessibilityService(String packageName) throws Exception {
@@ -554,6 +551,10 @@ public class DynamicIndexableContentMonitorTest {
.getAccessibilityServiceIntent(packageName); .getAccessibilityServiceIntent(packageName);
mRobolectricPackageManager.removeResolveInfosForIntent(intent, packageName); mRobolectricPackageManager.removeResolveInfosForIntent(intent, packageName);
mRobolectricPackageManager.removePackage(packageName); mRobolectricPackageManager.removePackage(packageName);
extractPackageMonitor()
.onPackageDisappeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
Robolectric.flushBackgroundThreadScheduler();
} }
private void installInputMethodService(String packageName) throws Exception { private void installInputMethodService(String packageName) throws Exception {
@@ -568,6 +569,10 @@ public class DynamicIndexableContentMonitorTest {
final Intent intent = DynamicIndexableContentMonitor.getIMEServiceIntent(packageName); final Intent intent = DynamicIndexableContentMonitor.getIMEServiceIntent(packageName);
mRobolectricPackageManager.addResolveInfoForIntent(intent, resolveInfoToAdd); mRobolectricPackageManager.addResolveInfoForIntent(intent, resolveInfoToAdd);
mRobolectricPackageManager.addPackage(packageName); mRobolectricPackageManager.addPackage(packageName);
extractPackageMonitor()
.onPackageAppeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
Robolectric.flushBackgroundThreadScheduler();
} }
private void uninstallInputMethodService(String packageName) throws Exception { private void uninstallInputMethodService(String packageName) throws Exception {
@@ -582,6 +587,10 @@ public class DynamicIndexableContentMonitorTest {
final Intent intent = DynamicIndexableContentMonitor.getIMEServiceIntent(packageName); final Intent intent = DynamicIndexableContentMonitor.getIMEServiceIntent(packageName);
mRobolectricPackageManager.removeResolveInfosForIntent(intent, packageName); mRobolectricPackageManager.removeResolveInfosForIntent(intent, packageName);
mRobolectricPackageManager.removePackage(packageName); mRobolectricPackageManager.removePackage(packageName);
extractPackageMonitor()
.onPackageDisappeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
Robolectric.flushBackgroundThreadScheduler();
} }
private AccessibilityServiceInfo buildAccessibilityServiceInfo(String packageName) private AccessibilityServiceInfo buildAccessibilityServiceInfo(String packageName)