Loading core/java/android/app/Notification.java +37 −11 Original line number Diff line number Diff line Loading @@ -2519,8 +2519,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; } // Propagate this token to all pending intents that are unmarshalled from the parcel. // Propagate this token to all pending intents that are unmarshalled from the parcel, // or keep the one we're already propagating, if that's the case. if (!parcel.hasClassCookie(PendingIntent.class)) { parcel.setClassCookie(PendingIntent.class, mAllowlistToken); } when = parcel.readLong(); creationTime = parcel.readLong(); Loading Loading @@ -2976,10 +2979,25 @@ public class Notification implements Parcelable } }); } try { boolean mustClearCookie = false; if (!parcel.hasClassCookie(Notification.class)) { // This is the "root" notification, and not an "inner" notification (including // publicVersion or anything else that might be embedded in extras). So we want // to use its token for every inner notification (might be null). parcel.setClassCookie(Notification.class, mAllowlistToken); mustClearCookie = true; } try { // IMPORTANT: Add marshaling code in writeToParcelImpl as we // want to intercept all pending events written to the parcel. writeToParcelImpl(parcel, flags); } finally { if (mustClearCookie) { parcel.removeClassCookie(Notification.class, mAllowlistToken); } } synchronized (this) { // Must be written last! parcel.writeArraySet(allPendingIntents); Loading @@ -2994,7 +3012,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); parcel.writeStrongBinder(mAllowlistToken); // Always use the same token as the root notification (might be null). IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); parcel.writeStrongBinder(rootNotificationToken); parcel.writeLong(when); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { Loading Loading @@ -3350,16 +3371,21 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * notification. * * This token is automatically set during deserialization for you, you usually won't need to * call this unless you want to change the existing token, if any. * Note: Should <em>only</em> be invoked by NotificationManagerService, since this is normally * populated by unparceling (and also used there). Any other usage is suspect. * * @hide */ public void clearAllowlistToken() { mAllowlistToken = null; public void overrideAllowlistToken(IBinder token) { mAllowlistToken = token; if (publicVersion != null) { publicVersion.clearAllowlistToken(); publicVersion.overrideAllowlistToken(token); } } /** @hide */ public IBinder getAllowlistToken() { return mAllowlistToken; } /** Loading core/java/android/os/Parcel.java +22 −0 Original line number Diff line number Diff line Loading @@ -651,6 +651,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; } /** @hide */ public void removeClassCookie(Class clz, Object expectedCookie) { if (mClassCookies != null) { Object removedCookie = mClassCookies.remove(clz); if (removedCookie != expectedCookie) { Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + ") but instead removed " + removedCookie); } } else { Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + ") but no cookies were present"); } } /** * Whether {@link #setClassCookie} has been called with the specified {@code clz}. * @hide */ public boolean hasClassCookie(Class clz) { return mClassCookies != null && mClassCookies.containsKey(clz); } /** @hide */ public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; Loading core/tests/coretests/src/android/os/ParcelTest.java +49 −0 Original line number Diff line number Diff line Loading @@ -16,15 +16,20 @@ package android.os; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; import android.util.Log; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; @Presubmit @RunWith(AndroidJUnit4.class) public class ParcelTest { Loading Loading @@ -110,4 +115,48 @@ public class ParcelTest { assertEquals(string, p.readString16()); } } @Test public void testClassCookies() { Parcel p = Parcel.obtain(); assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); p.setClassCookie(ParcelTest.class, "string_cookie"); assertThat(p.hasClassCookie(ParcelTest.class)).isTrue(); assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie"); p.removeClassCookie(ParcelTest.class, "string_cookie"); assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null); p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie"); p.recycle(); assertThat(p.getClassCookie(ParcelTest.class)).isNull(); } @Test public void testClassCookies_removeUnexpected() { Parcel p = Parcel.obtain(); assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present")); p.setClassCookie(ParcelTest.class, "value"); assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different")); assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed p.recycle(); } private static void assertLogsWtf(Runnable test) { ArrayList<Log.TerribleFailure> wtfs = new ArrayList<>(); Log.TerribleFailureHandler oldHandler = Log.setWtfHandler( (tag, what, system) -> wtfs.add(what)); try { test.run(); } finally { Log.setWtfHandler(oldHandler); } assertThat(wtfs).hasSize(1); } } packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java +23 −9 Original line number Diff line number Diff line Loading @@ -138,11 +138,13 @@ public class ServiceListing { } final PackageManager pmWrapper = mContext.getPackageManager(); // Add requesting apps, with full validation List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser( new Intent(mIntentAction), flags, user); for (ResolveInfo resolveInfo : installedServices) { ServiceInfo info = resolveInfo.serviceInfo; if (!mEnabledServices.contains(info.getComponentName())) { if (!mPermission.equals(info.permission)) { Slog.w(mTag, "Skipping " + mNoun + " service " + info.packageName + "/" + info.name Loading @@ -155,6 +157,18 @@ public class ServiceListing { } mServices.add(info); } } // Add all apps with access, in case prior approval was granted without full validation for (ComponentName cn : mEnabledServices) { List<ResolveInfo> enabledServices = pmWrapper.queryIntentServicesAsUser( new Intent().setComponent(cn), flags, user); for (ResolveInfo resolveInfo : enabledServices) { ServiceInfo info = resolveInfo.serviceInfo; mServices.add(info); } } for (Callback callback : mCallbacks) { callback.onServicesReloaded(mServices); } Loading packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java +65 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; Loading @@ -29,6 +30,7 @@ import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; Loading @@ -42,6 +44,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; Loading Loading @@ -72,19 +75,26 @@ public class ServiceListingTest { .build(); } private ArgumentMatcher<Intent> filterEquals(Intent intent) { return (test) -> { return intent.filterEquals(test); }; } @Test public void testValidator() { ServiceInfo s1 = new ServiceInfo(); s1.permission = "testPermission"; s1.packageName = "pkg"; s1.name = "Service1"; ServiceInfo s2 = new ServiceInfo(); s2.permission = "testPermission"; s2.packageName = "pkg2"; s2.name = "service2"; ResolveInfo r1 = new ResolveInfo(); r1.serviceInfo = s1; ResolveInfo r2 = new ResolveInfo(); r2.serviceInfo = s2; when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn( ImmutableList.of(r1, r2)); Loading Loading @@ -118,9 +128,11 @@ public class ServiceListingTest { ServiceInfo s1 = new ServiceInfo(); s1.permission = "testPermission"; s1.packageName = "pkg"; s1.name = "Service1"; ServiceInfo s2 = new ServiceInfo(); s2.permission = "testPermission"; s2.packageName = "pkg2"; s2.name = "service2"; ResolveInfo r1 = new ResolveInfo(); r1.serviceInfo = s1; ResolveInfo r2 = new ResolveInfo(); Loading Loading @@ -193,4 +205,56 @@ public class ServiceListingTest { assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(), TEST_SETTING)).contains(testComponent2.flattenToString()); } @Test public void testHasPermissionWithoutMeetingCurrentRegs() { ServiceInfo s1 = new ServiceInfo(); s1.permission = "testPermission"; s1.packageName = "pkg"; s1.name = "Service1"; ServiceInfo s2 = new ServiceInfo(); s2.permission = "testPermission"; s2.packageName = "pkg2"; s2.name = "service2"; ResolveInfo r1 = new ResolveInfo(); r1.serviceInfo = s1; ResolveInfo r2 = new ResolveInfo(); r2.serviceInfo = s2; ComponentName approvedComponent = new ComponentName(s2.packageName, s2.name); Settings.Secure.putString( mContext.getContentResolver(), TEST_SETTING, approvedComponent.flattenToString()); when(mPm.queryIntentServicesAsUser(argThat( filterEquals(new Intent(TEST_INTENT))), anyInt(), anyInt())) .thenReturn(ImmutableList.of(r1)); when(mPm.queryIntentServicesAsUser(argThat( filterEquals(new Intent().setComponent(approvedComponent))), anyInt(), anyInt())) .thenReturn(ImmutableList.of(r2)); mServiceListing = new ServiceListing.Builder(mContext) .setTag("testTag") .setSetting(TEST_SETTING) .setNoun("testNoun") .setIntentAction(TEST_INTENT) .setValidator(info -> { if (info.packageName.equals("pkg")) { return true; } return false; }) .setPermission("testPermission") .build(); ServiceListing.Callback callback = mock(ServiceListing.Callback.class); mServiceListing.addCallback(callback); mServiceListing.reload(); verify(mPm, times(2)).queryIntentServicesAsUser(any(), anyInt(), anyInt()); ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class); verify(callback, times(1)).onServicesReloaded(captor.capture()); assertThat(captor.getValue()).containsExactlyElementsIn(ImmutableList.of(s2, s1)); } } Loading
core/java/android/app/Notification.java +37 −11 Original line number Diff line number Diff line Loading @@ -2519,8 +2519,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; } // Propagate this token to all pending intents that are unmarshalled from the parcel. // Propagate this token to all pending intents that are unmarshalled from the parcel, // or keep the one we're already propagating, if that's the case. if (!parcel.hasClassCookie(PendingIntent.class)) { parcel.setClassCookie(PendingIntent.class, mAllowlistToken); } when = parcel.readLong(); creationTime = parcel.readLong(); Loading Loading @@ -2976,10 +2979,25 @@ public class Notification implements Parcelable } }); } try { boolean mustClearCookie = false; if (!parcel.hasClassCookie(Notification.class)) { // This is the "root" notification, and not an "inner" notification (including // publicVersion or anything else that might be embedded in extras). So we want // to use its token for every inner notification (might be null). parcel.setClassCookie(Notification.class, mAllowlistToken); mustClearCookie = true; } try { // IMPORTANT: Add marshaling code in writeToParcelImpl as we // want to intercept all pending events written to the parcel. writeToParcelImpl(parcel, flags); } finally { if (mustClearCookie) { parcel.removeClassCookie(Notification.class, mAllowlistToken); } } synchronized (this) { // Must be written last! parcel.writeArraySet(allPendingIntents); Loading @@ -2994,7 +3012,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); parcel.writeStrongBinder(mAllowlistToken); // Always use the same token as the root notification (might be null). IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); parcel.writeStrongBinder(rootNotificationToken); parcel.writeLong(when); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { Loading Loading @@ -3350,16 +3371,21 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * notification. * * This token is automatically set during deserialization for you, you usually won't need to * call this unless you want to change the existing token, if any. * Note: Should <em>only</em> be invoked by NotificationManagerService, since this is normally * populated by unparceling (and also used there). Any other usage is suspect. * * @hide */ public void clearAllowlistToken() { mAllowlistToken = null; public void overrideAllowlistToken(IBinder token) { mAllowlistToken = token; if (publicVersion != null) { publicVersion.clearAllowlistToken(); publicVersion.overrideAllowlistToken(token); } } /** @hide */ public IBinder getAllowlistToken() { return mAllowlistToken; } /** Loading
core/java/android/os/Parcel.java +22 −0 Original line number Diff line number Diff line Loading @@ -651,6 +651,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; } /** @hide */ public void removeClassCookie(Class clz, Object expectedCookie) { if (mClassCookies != null) { Object removedCookie = mClassCookies.remove(clz); if (removedCookie != expectedCookie) { Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + ") but instead removed " + removedCookie); } } else { Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + ") but no cookies were present"); } } /** * Whether {@link #setClassCookie} has been called with the specified {@code clz}. * @hide */ public boolean hasClassCookie(Class clz) { return mClassCookies != null && mClassCookies.containsKey(clz); } /** @hide */ public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; Loading
core/tests/coretests/src/android/os/ParcelTest.java +49 −0 Original line number Diff line number Diff line Loading @@ -16,15 +16,20 @@ package android.os; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; import android.util.Log; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; @Presubmit @RunWith(AndroidJUnit4.class) public class ParcelTest { Loading Loading @@ -110,4 +115,48 @@ public class ParcelTest { assertEquals(string, p.readString16()); } } @Test public void testClassCookies() { Parcel p = Parcel.obtain(); assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); p.setClassCookie(ParcelTest.class, "string_cookie"); assertThat(p.hasClassCookie(ParcelTest.class)).isTrue(); assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie"); p.removeClassCookie(ParcelTest.class, "string_cookie"); assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null); p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie"); p.recycle(); assertThat(p.getClassCookie(ParcelTest.class)).isNull(); } @Test public void testClassCookies_removeUnexpected() { Parcel p = Parcel.obtain(); assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present")); p.setClassCookie(ParcelTest.class, "value"); assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different")); assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed p.recycle(); } private static void assertLogsWtf(Runnable test) { ArrayList<Log.TerribleFailure> wtfs = new ArrayList<>(); Log.TerribleFailureHandler oldHandler = Log.setWtfHandler( (tag, what, system) -> wtfs.add(what)); try { test.run(); } finally { Log.setWtfHandler(oldHandler); } assertThat(wtfs).hasSize(1); } }
packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java +23 −9 Original line number Diff line number Diff line Loading @@ -138,11 +138,13 @@ public class ServiceListing { } final PackageManager pmWrapper = mContext.getPackageManager(); // Add requesting apps, with full validation List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser( new Intent(mIntentAction), flags, user); for (ResolveInfo resolveInfo : installedServices) { ServiceInfo info = resolveInfo.serviceInfo; if (!mEnabledServices.contains(info.getComponentName())) { if (!mPermission.equals(info.permission)) { Slog.w(mTag, "Skipping " + mNoun + " service " + info.packageName + "/" + info.name Loading @@ -155,6 +157,18 @@ public class ServiceListing { } mServices.add(info); } } // Add all apps with access, in case prior approval was granted without full validation for (ComponentName cn : mEnabledServices) { List<ResolveInfo> enabledServices = pmWrapper.queryIntentServicesAsUser( new Intent().setComponent(cn), flags, user); for (ResolveInfo resolveInfo : enabledServices) { ServiceInfo info = resolveInfo.serviceInfo; mServices.add(info); } } for (Callback callback : mCallbacks) { callback.onServicesReloaded(mServices); } Loading
packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java +65 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; Loading @@ -29,6 +30,7 @@ import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; Loading @@ -42,6 +44,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; Loading Loading @@ -72,19 +75,26 @@ public class ServiceListingTest { .build(); } private ArgumentMatcher<Intent> filterEquals(Intent intent) { return (test) -> { return intent.filterEquals(test); }; } @Test public void testValidator() { ServiceInfo s1 = new ServiceInfo(); s1.permission = "testPermission"; s1.packageName = "pkg"; s1.name = "Service1"; ServiceInfo s2 = new ServiceInfo(); s2.permission = "testPermission"; s2.packageName = "pkg2"; s2.name = "service2"; ResolveInfo r1 = new ResolveInfo(); r1.serviceInfo = s1; ResolveInfo r2 = new ResolveInfo(); r2.serviceInfo = s2; when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn( ImmutableList.of(r1, r2)); Loading Loading @@ -118,9 +128,11 @@ public class ServiceListingTest { ServiceInfo s1 = new ServiceInfo(); s1.permission = "testPermission"; s1.packageName = "pkg"; s1.name = "Service1"; ServiceInfo s2 = new ServiceInfo(); s2.permission = "testPermission"; s2.packageName = "pkg2"; s2.name = "service2"; ResolveInfo r1 = new ResolveInfo(); r1.serviceInfo = s1; ResolveInfo r2 = new ResolveInfo(); Loading Loading @@ -193,4 +205,56 @@ public class ServiceListingTest { assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(), TEST_SETTING)).contains(testComponent2.flattenToString()); } @Test public void testHasPermissionWithoutMeetingCurrentRegs() { ServiceInfo s1 = new ServiceInfo(); s1.permission = "testPermission"; s1.packageName = "pkg"; s1.name = "Service1"; ServiceInfo s2 = new ServiceInfo(); s2.permission = "testPermission"; s2.packageName = "pkg2"; s2.name = "service2"; ResolveInfo r1 = new ResolveInfo(); r1.serviceInfo = s1; ResolveInfo r2 = new ResolveInfo(); r2.serviceInfo = s2; ComponentName approvedComponent = new ComponentName(s2.packageName, s2.name); Settings.Secure.putString( mContext.getContentResolver(), TEST_SETTING, approvedComponent.flattenToString()); when(mPm.queryIntentServicesAsUser(argThat( filterEquals(new Intent(TEST_INTENT))), anyInt(), anyInt())) .thenReturn(ImmutableList.of(r1)); when(mPm.queryIntentServicesAsUser(argThat( filterEquals(new Intent().setComponent(approvedComponent))), anyInt(), anyInt())) .thenReturn(ImmutableList.of(r2)); mServiceListing = new ServiceListing.Builder(mContext) .setTag("testTag") .setSetting(TEST_SETTING) .setNoun("testNoun") .setIntentAction(TEST_INTENT) .setValidator(info -> { if (info.packageName.equals("pkg")) { return true; } return false; }) .setPermission("testPermission") .build(); ServiceListing.Callback callback = mock(ServiceListing.Callback.class); mServiceListing.addCallback(callback); mServiceListing.reload(); verify(mPm, times(2)).queryIntentServicesAsUser(any(), anyInt(), anyInt()); ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class); verify(callback, times(1)).onServicesReloaded(captor.capture()); assertThat(captor.getValue()).containsExactlyElementsIn(ImmutableList.of(s2, s1)); } }