Loading src/com/android/server/telecom/PackageRemovedReceiver.java 0 → 100644 +145 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.telecom; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.telecom.PhoneAccount; import com.android.server.telecom.PhoneAccountRegistrar; import android.util.Log; public class PackageRemovedReceiver extends BroadcastReceiver { private PhoneAccountRegistrar mPhoneAccountRegistrar; private Handler mBackgroundHandler; private static final String TAG = "PackageRemovedReceiver"; private UserHandleWrapper mUserHandleWrapper; /** * Default constructor required by Android. */ public PackageRemovedReceiver() { this(null, null, new UserHandleWrapper()); } public PackageRemovedReceiver(PhoneAccountRegistrar phoneAccountRegistrar, Handler backgroundHandler, UserHandleWrapper userHandleWrapper) { mPhoneAccountRegistrar = phoneAccountRegistrar; mBackgroundHandler = backgroundHandler; mUserHandleWrapper = userHandleWrapper; } @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { Uri uri = intent.getData(); if (uri == null) { Log.w(TAG, "Null URI in intent for " + action); return; } final String packageName = uri.getSchemeSpecificPart(); if (packageName == null || packageName.isEmpty()) { Log.w(TAG, "Null or empty package name for " + action); return; } // Get the UID of the package that was removed. // This UID includes the user ID. final int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); if (uid == Process.INVALID_UID) { Log.i(TAG, "Invalid UID for packageName - Cannot determine user."); return; } final UserHandle user = mUserHandleWrapper.getUserHandleForUid(uid); Log.i(TAG, "Package " + packageName + " (UID: " + uid + ") fully removed" + " for user " + user); if (mBackgroundHandler != null) { mBackgroundHandler.post(() -> { handlePackageRemovedForUserInternal(packageName, user); }); } else { handlePackageRemovedForUserInternal(packageName, user); } } } /** * Handles the removal of a package for a specific user by calling upon the * {@link PhoneAccountRegistrar} to un-register any {@link android.telecom.PhoneAccount}s * associated with the package for that user. * (This method remains largely the same as in the previous "iterate all users" version, * as it already operates on a specific user). * * @param packageName The name of the removed package. * @param user The UserHandle for whom to clear accounts. */ private void handlePackageRemovedForUserInternal( String packageName, UserHandle user) { if (mPhoneAccountRegistrar != null) { mPhoneAccountRegistrar.clearAccounts(packageName, user); } else { Log.w(TAG, "PhoneAccountRegistrar not available to clear accounts for " + packageName + " (user " + user + ")"); } } // --- Helper methods for setting dependencies --- /** * Sets the PhoneAccountRegistrar. This is useful if you are registering the * BroadcastReceiver dynamically and can't use a constructor with arguments. * Make sure this is called before the receiver is expected to handle broadcasts * if not using the constructor injection. * * @param registrar The PhoneAccountRegistrar instance. */ public void setPhoneAccountRegistrar(PhoneAccountRegistrar registrar) { this.mPhoneAccountRegistrar = registrar; } /** * Sets the Handler. * * @param handler The Handler instance. */ public void setTelecomSystemHandler(Handler handler) { this.mBackgroundHandler = handler; } /** * Sets the UserHandleWrapper. * * @param userHandle The UserHandleWrapper instance. */ public void setUserHandleWrapper(UserHandleWrapper userHandle) { this.mUserHandleWrapper = userHandle; } } src/com/android/server/telecom/TelecomServiceImpl.java +62 −0 Original line number Diff line number Diff line Loading @@ -36,10 +36,12 @@ import android.app.AppOpsManager; import android.app.UiModeManager; import android.app.compat.CompatChanges; import android.content.AttributionSource; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.PermissionChecker; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; Loading @@ -55,6 +57,8 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.Handler; import android.os.HandlerThread; import android.permission.PermissionManager; import android.provider.BlockedNumberContract; import android.provider.BlockedNumbersManager; Loading Loading @@ -92,6 +96,7 @@ import com.android.server.telecom.settings.BlockedNumbersActivity; import com.android.server.telecom.callsequencing.TransactionManager; import com.android.server.telecom.callsequencing.CallTransaction; import com.android.server.telecom.callsequencing.CallTransactionResult; import com.android.server.telecom.PackageRemovedReceiver; import java.io.FileDescriptor; import java.io.PrintWriter; Loading Loading @@ -175,6 +180,7 @@ public class TelecomServiceImpl { private final CallsManager mCallsManager; private TransactionManager mTransactionManager; private final PermissionManager mPermissionManager; private PackageRemovedReceiver mPackageRemovedReceiver; private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() { @Override Loading Loading @@ -3070,6 +3076,8 @@ public class TelecomServiceImpl { mMetricsController = metricsController; mSystemUiPackageName = sysUiPackageName; setupPackageRemovedReceiver(phoneAccountRegistrar); mDefaultDialerCache.observeDefaultDialerApplication(mContext.getMainExecutor(), userId -> { String defaultDialer = mDefaultDialerCache.getDefaultDialerApplication(userId); if (defaultDialer == null) { Loading @@ -3092,6 +3100,60 @@ public class TelecomServiceImpl { : null; } /** * Helper function to initialize and register a BroadcastReceiver for package removal events. * This setup runs on a background thread if the specified feature flag is enabled. * Assumes this method is called only once or is protected against multiple thread creations. */ private void setupPackageRemovedReceiver(PhoneAccountRegistrar phoneAccountRegistrar) { if (!mFeatureFlags.resolveHiddenDependenciesTwo()) { Log.i(TAG, "resolveHiddenDependenciesTwo' is disabled"); return; } if (mPackageRemovedReceiver != null) { Log.w(TAG, "PackageRemovedReceiver appears to be already initialized. Skipping setup."); return; } // IMPORTANT: This thread, once started, will run for the lifetime of the process // unless the process is killed, as we won't have a reference to 'quit()' it later. HandlerThread localHandlerThread = new HandlerThread("TelRemoveAcctsBckgrndThread"); localHandlerThread.start(); // The thread starts and will keep running. // Get a Handler associated with the local background thread's Looper. Handler backgroundHandler = new Handler(localHandlerThread.getLooper()); // IntentFilter for package removal events. IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); filter.addDataScheme("package"); mPackageRemovedReceiver = new PackageRemovedReceiver( phoneAccountRegistrar, backgroundHandler, new UserHandleWrapper()); try { Log.v(TAG, "Registering PackageRemovedReceiver (local thread) for all users" + " with RECEIVER_NOT_EXPORTED flag."); mContext.registerReceiverAsUser( mPackageRemovedReceiver, UserHandle.ALL, filter, null, backgroundHandler, // Handler uses the local thread's Looper Context.RECEIVER_NOT_EXPORTED); Log.v(TAG, "PackageRemovedReceiver (local thread) registered successfully."); } catch (Exception e) { if (localHandlerThread.isAlive()) { localHandlerThread.quitSafely(); // Attempt to clean up the just-started thread Log.w(TAG, "Attempted to quit localHandlerThread due to registration failure."); } mPackageRemovedReceiver = null; } } @VisibleForTesting public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter) { mAnomalyReporter = mAnomalyReporterAdapter; Loading src/com/android/server/telecom/UserHandleWrapper.java 0 → 100644 +27 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.telecom; import android.os.UserHandle; /** * Wrapper for static methods of UserHandle to allow mocking in tests. */ public class UserHandleWrapper { public UserHandle getUserHandleForUid(int uid) { return UserHandle.getUserHandleForUid(uid); } } src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java +4 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.telecom.components; import com.android.server.telecom.PhoneAccountRegistrar; import com.android.server.telecom.flags.Flags; import android.content.BroadcastReceiver; import android.content.ComponentName; Loading Loading @@ -79,6 +80,9 @@ public class AppUninstallBroadcastReceiver extends BroadcastReceiver { * @param packageName The name of the removed package. */ private void handlePackageRemoved(Context context, String packageName) { if (Flags.resolveHiddenDependenciesTwo()) { return; } final TelecomManager telecomManager = context.getSystemService(TelecomManager.class); if (telecomManager != null) { telecomManager.clearAccountsForPackage(packageName); Loading tests/src/com/android/server/telecom/tests/PackageRemovedReceiverTest.java 0 → 100644 +194 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.telecom.tests; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.util.Log; import com.android.server.telecom.PackageRemovedReceiver; import com.android.server.telecom.PhoneAccountRegistrar; import com.android.server.telecom.UserHandleWrapper; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @RunWith(JUnit4.class) public class PackageRemovedReceiverTest extends TelecomTestCase { private static final String TEST_PACKAGE_NAME = "com.example.testapp"; private static final int TEST_UID = 10123; @Mock private PhoneAccountRegistrar mMockPhoneAccountRegistrar; @Mock private Handler mMockBackgroundHandler; @Mock private Intent mMockIntent; @Mock private Uri mMockUri; @Mock private UserHandle mMockUserHandle; @Mock private UserHandleWrapper mMockUserHandleWrapper; private PackageRemovedReceiver mReceiver; @Override @Before public void setUp() throws Exception { super.setUp(); MockitoAnnotations.openMocks(this); when(mMockUserHandleWrapper.getUserHandleForUid(TEST_UID)).thenReturn(mMockUserHandle); when(mMockUserHandleWrapper.getUserHandleForUid(Process.INVALID_UID)).thenReturn(null); } @Override @After public void tearDown() throws Exception { super.tearDown(); } private void setupIntent(String action, Uri data, String packageName, int uid) { when(mMockIntent.getAction()).thenReturn(action); when(mMockIntent.getData()).thenReturn(data); if (data != null) { when(mMockUri.getSchemeSpecificPart()).thenReturn(packageName); } when(mMockIntent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID)).thenReturn(uid); } @Test public void onReceive_nullAction_shouldDoNothing() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(null, mMockUri, TEST_PACKAGE_NAME, TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_wrongAction_shouldDoNothing() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_ADDED, mMockUri, TEST_PACKAGE_NAME, TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_packageFullyRemoved_nullUri_shouldLogAndReturn() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, null, TEST_PACKAGE_NAME, TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_packageFullyRemoved_nullPackageName_shouldLogAndReturn() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, null, TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_packageFullyRemoved_emptyPackageName_shouldLogAndReturn() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, "", TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_packageFullyRemoved_invalidUid_shouldLogAndReturn() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, TEST_PACKAGE_NAME, Process.INVALID_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_validIntent_withBackgroundHandler_shouldPostToHandler() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, TEST_PACKAGE_NAME, TEST_UID); ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); when(mMockBackgroundHandler.post(runnableCaptor.capture())).thenReturn(true); mReceiver.onReceive(mContext, mMockIntent); verify(mMockBackgroundHandler).post(Mockito.any(Runnable.class)); Runnable postedRunnable = runnableCaptor.getValue(); postedRunnable.run(); verify(mMockPhoneAccountRegistrar).clearAccounts(TEST_PACKAGE_NAME, mMockUserHandle); } @Test public void onReceive_validIntent_withBackgroundHandler_nullRegistrar_shouldLogButNotCrash() { mReceiver = new PackageRemovedReceiver(null, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, TEST_PACKAGE_NAME, TEST_UID); ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); when(mMockBackgroundHandler.post(runnableCaptor.capture())).thenReturn(true); mReceiver.onReceive(mContext, mMockIntent); verify(mMockBackgroundHandler).post(Mockito.any(Runnable.class)); runnableCaptor.getValue().run(); verifyNoInteractions(mMockPhoneAccountRegistrar); } } No newline at end of file Loading
src/com/android/server/telecom/PackageRemovedReceiver.java 0 → 100644 +145 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.telecom; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.telecom.PhoneAccount; import com.android.server.telecom.PhoneAccountRegistrar; import android.util.Log; public class PackageRemovedReceiver extends BroadcastReceiver { private PhoneAccountRegistrar mPhoneAccountRegistrar; private Handler mBackgroundHandler; private static final String TAG = "PackageRemovedReceiver"; private UserHandleWrapper mUserHandleWrapper; /** * Default constructor required by Android. */ public PackageRemovedReceiver() { this(null, null, new UserHandleWrapper()); } public PackageRemovedReceiver(PhoneAccountRegistrar phoneAccountRegistrar, Handler backgroundHandler, UserHandleWrapper userHandleWrapper) { mPhoneAccountRegistrar = phoneAccountRegistrar; mBackgroundHandler = backgroundHandler; mUserHandleWrapper = userHandleWrapper; } @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { Uri uri = intent.getData(); if (uri == null) { Log.w(TAG, "Null URI in intent for " + action); return; } final String packageName = uri.getSchemeSpecificPart(); if (packageName == null || packageName.isEmpty()) { Log.w(TAG, "Null or empty package name for " + action); return; } // Get the UID of the package that was removed. // This UID includes the user ID. final int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); if (uid == Process.INVALID_UID) { Log.i(TAG, "Invalid UID for packageName - Cannot determine user."); return; } final UserHandle user = mUserHandleWrapper.getUserHandleForUid(uid); Log.i(TAG, "Package " + packageName + " (UID: " + uid + ") fully removed" + " for user " + user); if (mBackgroundHandler != null) { mBackgroundHandler.post(() -> { handlePackageRemovedForUserInternal(packageName, user); }); } else { handlePackageRemovedForUserInternal(packageName, user); } } } /** * Handles the removal of a package for a specific user by calling upon the * {@link PhoneAccountRegistrar} to un-register any {@link android.telecom.PhoneAccount}s * associated with the package for that user. * (This method remains largely the same as in the previous "iterate all users" version, * as it already operates on a specific user). * * @param packageName The name of the removed package. * @param user The UserHandle for whom to clear accounts. */ private void handlePackageRemovedForUserInternal( String packageName, UserHandle user) { if (mPhoneAccountRegistrar != null) { mPhoneAccountRegistrar.clearAccounts(packageName, user); } else { Log.w(TAG, "PhoneAccountRegistrar not available to clear accounts for " + packageName + " (user " + user + ")"); } } // --- Helper methods for setting dependencies --- /** * Sets the PhoneAccountRegistrar. This is useful if you are registering the * BroadcastReceiver dynamically and can't use a constructor with arguments. * Make sure this is called before the receiver is expected to handle broadcasts * if not using the constructor injection. * * @param registrar The PhoneAccountRegistrar instance. */ public void setPhoneAccountRegistrar(PhoneAccountRegistrar registrar) { this.mPhoneAccountRegistrar = registrar; } /** * Sets the Handler. * * @param handler The Handler instance. */ public void setTelecomSystemHandler(Handler handler) { this.mBackgroundHandler = handler; } /** * Sets the UserHandleWrapper. * * @param userHandle The UserHandleWrapper instance. */ public void setUserHandleWrapper(UserHandleWrapper userHandle) { this.mUserHandleWrapper = userHandle; } }
src/com/android/server/telecom/TelecomServiceImpl.java +62 −0 Original line number Diff line number Diff line Loading @@ -36,10 +36,12 @@ import android.app.AppOpsManager; import android.app.UiModeManager; import android.app.compat.CompatChanges; import android.content.AttributionSource; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.PermissionChecker; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; Loading @@ -55,6 +57,8 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.Handler; import android.os.HandlerThread; import android.permission.PermissionManager; import android.provider.BlockedNumberContract; import android.provider.BlockedNumbersManager; Loading Loading @@ -92,6 +96,7 @@ import com.android.server.telecom.settings.BlockedNumbersActivity; import com.android.server.telecom.callsequencing.TransactionManager; import com.android.server.telecom.callsequencing.CallTransaction; import com.android.server.telecom.callsequencing.CallTransactionResult; import com.android.server.telecom.PackageRemovedReceiver; import java.io.FileDescriptor; import java.io.PrintWriter; Loading Loading @@ -175,6 +180,7 @@ public class TelecomServiceImpl { private final CallsManager mCallsManager; private TransactionManager mTransactionManager; private final PermissionManager mPermissionManager; private PackageRemovedReceiver mPackageRemovedReceiver; private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() { @Override Loading Loading @@ -3070,6 +3076,8 @@ public class TelecomServiceImpl { mMetricsController = metricsController; mSystemUiPackageName = sysUiPackageName; setupPackageRemovedReceiver(phoneAccountRegistrar); mDefaultDialerCache.observeDefaultDialerApplication(mContext.getMainExecutor(), userId -> { String defaultDialer = mDefaultDialerCache.getDefaultDialerApplication(userId); if (defaultDialer == null) { Loading @@ -3092,6 +3100,60 @@ public class TelecomServiceImpl { : null; } /** * Helper function to initialize and register a BroadcastReceiver for package removal events. * This setup runs on a background thread if the specified feature flag is enabled. * Assumes this method is called only once or is protected against multiple thread creations. */ private void setupPackageRemovedReceiver(PhoneAccountRegistrar phoneAccountRegistrar) { if (!mFeatureFlags.resolveHiddenDependenciesTwo()) { Log.i(TAG, "resolveHiddenDependenciesTwo' is disabled"); return; } if (mPackageRemovedReceiver != null) { Log.w(TAG, "PackageRemovedReceiver appears to be already initialized. Skipping setup."); return; } // IMPORTANT: This thread, once started, will run for the lifetime of the process // unless the process is killed, as we won't have a reference to 'quit()' it later. HandlerThread localHandlerThread = new HandlerThread("TelRemoveAcctsBckgrndThread"); localHandlerThread.start(); // The thread starts and will keep running. // Get a Handler associated with the local background thread's Looper. Handler backgroundHandler = new Handler(localHandlerThread.getLooper()); // IntentFilter for package removal events. IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); filter.addDataScheme("package"); mPackageRemovedReceiver = new PackageRemovedReceiver( phoneAccountRegistrar, backgroundHandler, new UserHandleWrapper()); try { Log.v(TAG, "Registering PackageRemovedReceiver (local thread) for all users" + " with RECEIVER_NOT_EXPORTED flag."); mContext.registerReceiverAsUser( mPackageRemovedReceiver, UserHandle.ALL, filter, null, backgroundHandler, // Handler uses the local thread's Looper Context.RECEIVER_NOT_EXPORTED); Log.v(TAG, "PackageRemovedReceiver (local thread) registered successfully."); } catch (Exception e) { if (localHandlerThread.isAlive()) { localHandlerThread.quitSafely(); // Attempt to clean up the just-started thread Log.w(TAG, "Attempted to quit localHandlerThread due to registration failure."); } mPackageRemovedReceiver = null; } } @VisibleForTesting public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter) { mAnomalyReporter = mAnomalyReporterAdapter; Loading
src/com/android/server/telecom/UserHandleWrapper.java 0 → 100644 +27 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.telecom; import android.os.UserHandle; /** * Wrapper for static methods of UserHandle to allow mocking in tests. */ public class UserHandleWrapper { public UserHandle getUserHandleForUid(int uid) { return UserHandle.getUserHandleForUid(uid); } }
src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java +4 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.telecom.components; import com.android.server.telecom.PhoneAccountRegistrar; import com.android.server.telecom.flags.Flags; import android.content.BroadcastReceiver; import android.content.ComponentName; Loading Loading @@ -79,6 +80,9 @@ public class AppUninstallBroadcastReceiver extends BroadcastReceiver { * @param packageName The name of the removed package. */ private void handlePackageRemoved(Context context, String packageName) { if (Flags.resolveHiddenDependenciesTwo()) { return; } final TelecomManager telecomManager = context.getSystemService(TelecomManager.class); if (telecomManager != null) { telecomManager.clearAccountsForPackage(packageName); Loading
tests/src/com/android/server/telecom/tests/PackageRemovedReceiverTest.java 0 → 100644 +194 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.telecom.tests; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.util.Log; import com.android.server.telecom.PackageRemovedReceiver; import com.android.server.telecom.PhoneAccountRegistrar; import com.android.server.telecom.UserHandleWrapper; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @RunWith(JUnit4.class) public class PackageRemovedReceiverTest extends TelecomTestCase { private static final String TEST_PACKAGE_NAME = "com.example.testapp"; private static final int TEST_UID = 10123; @Mock private PhoneAccountRegistrar mMockPhoneAccountRegistrar; @Mock private Handler mMockBackgroundHandler; @Mock private Intent mMockIntent; @Mock private Uri mMockUri; @Mock private UserHandle mMockUserHandle; @Mock private UserHandleWrapper mMockUserHandleWrapper; private PackageRemovedReceiver mReceiver; @Override @Before public void setUp() throws Exception { super.setUp(); MockitoAnnotations.openMocks(this); when(mMockUserHandleWrapper.getUserHandleForUid(TEST_UID)).thenReturn(mMockUserHandle); when(mMockUserHandleWrapper.getUserHandleForUid(Process.INVALID_UID)).thenReturn(null); } @Override @After public void tearDown() throws Exception { super.tearDown(); } private void setupIntent(String action, Uri data, String packageName, int uid) { when(mMockIntent.getAction()).thenReturn(action); when(mMockIntent.getData()).thenReturn(data); if (data != null) { when(mMockUri.getSchemeSpecificPart()).thenReturn(packageName); } when(mMockIntent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID)).thenReturn(uid); } @Test public void onReceive_nullAction_shouldDoNothing() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(null, mMockUri, TEST_PACKAGE_NAME, TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_wrongAction_shouldDoNothing() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_ADDED, mMockUri, TEST_PACKAGE_NAME, TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_packageFullyRemoved_nullUri_shouldLogAndReturn() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, null, TEST_PACKAGE_NAME, TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_packageFullyRemoved_nullPackageName_shouldLogAndReturn() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, null, TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_packageFullyRemoved_emptyPackageName_shouldLogAndReturn() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, "", TEST_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_packageFullyRemoved_invalidUid_shouldLogAndReturn() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, TEST_PACKAGE_NAME, Process.INVALID_UID); mReceiver.onReceive(mContext, mMockIntent); verifyNoInteractions(mMockBackgroundHandler); verifyNoInteractions(mMockPhoneAccountRegistrar); } @Test public void onReceive_validIntent_withBackgroundHandler_shouldPostToHandler() { mReceiver = new PackageRemovedReceiver(mMockPhoneAccountRegistrar, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, TEST_PACKAGE_NAME, TEST_UID); ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); when(mMockBackgroundHandler.post(runnableCaptor.capture())).thenReturn(true); mReceiver.onReceive(mContext, mMockIntent); verify(mMockBackgroundHandler).post(Mockito.any(Runnable.class)); Runnable postedRunnable = runnableCaptor.getValue(); postedRunnable.run(); verify(mMockPhoneAccountRegistrar).clearAccounts(TEST_PACKAGE_NAME, mMockUserHandle); } @Test public void onReceive_validIntent_withBackgroundHandler_nullRegistrar_shouldLogButNotCrash() { mReceiver = new PackageRemovedReceiver(null, mMockBackgroundHandler, mMockUserHandleWrapper); setupIntent(Intent.ACTION_PACKAGE_FULLY_REMOVED, mMockUri, TEST_PACKAGE_NAME, TEST_UID); ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); when(mMockBackgroundHandler.post(runnableCaptor.capture())).thenReturn(true); mReceiver.onReceive(mContext, mMockIntent); verify(mMockBackgroundHandler).post(Mockito.any(Runnable.class)); runnableCaptor.getValue().run(); verifyNoInteractions(mMockPhoneAccountRegistrar); } } No newline at end of file