diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 6c8a8500e4e38c282491cefbf98e42b3a92e976a..d41df4f49d48fd3d2bf7274644fc96e67352022b 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -511,17 +511,31 @@ public class DatabaseUtils { */ public static void appendEscapedSQLString(StringBuilder sb, String sqlString) { sb.append('\''); - if (sqlString.indexOf('\'') != -1) { - int length = sqlString.length(); - for (int i = 0; i < length; i++) { - char c = sqlString.charAt(i); - if (c == '\'') { - sb.append('\''); + int length = sqlString.length(); + for (int i = 0; i < length; i++) { + char c = sqlString.charAt(i); + if (Character.isHighSurrogate(c)) { + if (i == length - 1) { + continue; + } + if (Character.isLowSurrogate(sqlString.charAt(i + 1))) { + // add them both + sb.append(c); + sb.append(sqlString.charAt(i + 1)); + continue; + } else { + // this is a lone surrogate, skip it + continue; } - sb.append(c); } - } else - sb.append(sqlString); + if (Character.isLowSurrogate(c)) { + continue; + } + if (c == '\'') { + sb.append('\''); + } + sb.append(c); + } sb.append('\''); } diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 3da696ad0bc795bd33476676cb7fa83722e0a8c6..7fbaf1027af60e4cda7cbbdc7fa83fc3fc680214 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -882,10 +882,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } static Uri readFrom(Parcel parcel) { + final StringUri stringUri = new StringUri(parcel.readString8()); return new OpaqueUri( - parcel.readString8(), - Part.readFrom(parcel), - Part.readFrom(parcel) + stringUri.parseScheme(), + stringUri.getSsp(), + stringUri.getFragmentPart() ); } @@ -895,9 +896,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(TYPE_ID); - parcel.writeString8(scheme); - ssp.writeTo(parcel); - fragment.writeTo(parcel); + parcel.writeString8(toString()); } public boolean isHierarchical() { @@ -1196,22 +1195,25 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { Part query, Part fragment) { this.scheme = scheme; this.authority = Part.nonNull(authority); - this.path = path == null ? PathPart.NULL : path; + this.path = generatePath(path); this.query = Part.nonNull(query); this.fragment = Part.nonNull(fragment); } - static Uri readFrom(Parcel parcel) { - final String scheme = parcel.readString8(); - final Part authority = Part.readFrom(parcel); + private PathPart generatePath(PathPart originalPath) { // In RFC3986 the path should be determined based on whether there is a scheme or // authority present (https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3). final boolean hasSchemeOrAuthority = (scheme != null && scheme.length() > 0) || !authority.isEmpty(); - final PathPart path = PathPart.readFrom(hasSchemeOrAuthority, parcel); - final Part query = Part.readFrom(parcel); - final Part fragment = Part.readFrom(parcel); - return new HierarchicalUri(scheme, authority, path, query, fragment); + final PathPart newPath = hasSchemeOrAuthority ? PathPart.makeAbsolute(originalPath) + : originalPath; + return newPath == null ? PathPart.NULL : newPath; + } + + static Uri readFrom(Parcel parcel) { + final StringUri stringUri = new StringUri(parcel.readString8()); + return new HierarchicalUri(stringUri.getScheme(), stringUri.getAuthorityPart(), + stringUri.getPathPart(), stringUri.getQueryPart(), stringUri.getFragmentPart()); } public int describeContents() { @@ -1220,11 +1222,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(TYPE_ID); - parcel.writeString8(scheme); - authority.writeTo(parcel); - path.writeTo(parcel); - query.writeTo(parcel); - fragment.writeTo(parcel); + parcel.writeString8(toString()); } public boolean isHierarchical() { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 5019b85ca503eef43630267b34dab731548712e5..a3d1be07c51c6097d672958a4186c40737f180d8 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -69,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.SoftInputShowHideReason; +import com.android.internal.util.function.TriFunction; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -77,7 +78,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.function.BiFunction; /** * Implements {@link WindowInsetsController} on the client. @@ -621,7 +621,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final InsetsState mLastDispatchedState = new InsetsState(); private final Rect mFrame = new Rect(); - private final BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> mConsumerCreator; + private final TriFunction<InsetsController, Integer, Integer, InsetsSourceConsumer> + mConsumerCreator; private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); private final InsetsSourceConsumer mImeSourceConsumer; private final Host mHost; @@ -689,13 +690,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // Don't change the indexes of the sources while traversing. Remove it later. mPendingRemoveIndexes.add(index1); - - // Remove the consumer as well except the IME one. IME consumer should always - // be there since we need to communicate with InputMethodManager no matter we - // have the source or not. - if (source1.getType() != ime()) { - mSourceConsumers.remove(source1.getId()); - } } @Override @@ -750,12 +744,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation }; public InsetsController(Host host) { - this(host, (controller, source) -> { - if (source.getType() == ime()) { - return new ImeInsetsSourceConsumer(source.getId(), controller.mState, + this(host, (controller, id, type) -> { + if (type == ime()) { + return new ImeInsetsSourceConsumer(id, controller.mState, Transaction::new, controller); } else { - return new InsetsSourceConsumer(source.getId(), source.getType(), controller.mState, + return new InsetsSourceConsumer(id, type, controller.mState, Transaction::new, controller); } }, host.getHandler()); @@ -763,7 +757,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public InsetsController(Host host, - BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> consumerCreator, + TriFunction<InsetsController, Integer, Integer, InsetsSourceConsumer> consumerCreator, Handler handler) { mHost = host; mConsumerCreator = consumerCreator; @@ -815,7 +809,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation }; // Make mImeSourceConsumer always non-null. - mImeSourceConsumer = getSourceConsumer(new InsetsSource(ID_IME, ime())); + mImeSourceConsumer = getSourceConsumer(ID_IME, ime()); } @VisibleForTesting @@ -893,7 +887,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation cancelledUserAnimationTypes[0] |= type; } } - getSourceConsumer(source).updateSource(source, animationType); + final InsetsSourceConsumer consumer = mSourceConsumers.get(source.getId()); + if (consumer != null) { + consumer.updateSource(source, animationType); + } else { + mState.addSource(source); + } existingTypes |= type; if (source.isVisible()) { visibleTypes |= type; @@ -997,8 +996,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @InsetsType int controllableTypes = 0; int consumedControlCount = 0; - final int[] showTypes = new int[1]; - final int[] hideTypes = new int[1]; + final @InsetsType int[] showTypes = new int[1]; + final @InsetsType int[] hideTypes = new int[1]; // Ensure to update all existing source consumers for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { @@ -1014,15 +1013,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation consumer.setControl(control, showTypes, hideTypes); } + // Ensure to create source consumers if not available yet. if (consumedControlCount != mTmpControlArray.size()) { - // Whoops! The server sent us some controls without sending corresponding sources. for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { final InsetsSourceControl control = mTmpControlArray.valueAt(i); - final InsetsSourceConsumer consumer = mSourceConsumers.get(control.getId()); - if (consumer == null) { - control.release(SurfaceControl::release); - Log.e(TAG, control + " has no consumer."); - } + getSourceConsumer(control.getId(), control.getType()) + .setControl(control, showTypes, hideTypes); } } @@ -1587,6 +1583,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (type == ime()) { abortPendingImeControlRequest(); } + if (consumer.getType() != ime()) { + // IME consumer should always be there since we need to communicate with + // InputMethodManager no matter we have the control or not. + mSourceConsumers.remove(consumer.getId()); + } } private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) { @@ -1640,21 +1641,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @VisibleForTesting - public @NonNull InsetsSourceConsumer getSourceConsumer(InsetsSource source) { - final int sourceId = source.getId(); - InsetsSourceConsumer consumer = mSourceConsumers.get(sourceId); + public @NonNull InsetsSourceConsumer getSourceConsumer(int id, int type) { + InsetsSourceConsumer consumer = mSourceConsumers.get(id); if (consumer != null) { return consumer; } - if (source.getType() == ime() && mImeSourceConsumer != null) { + if (type == ime() && mImeSourceConsumer != null) { // WindowInsets.Type.ime() should be only provided by one source. mSourceConsumers.remove(mImeSourceConsumer.getId()); consumer = mImeSourceConsumer; - consumer.setId(sourceId); + consumer.setId(id); } else { - consumer = mConsumerCreator.apply(this, source); + consumer = mConsumerCreator.apply(this, id, type); } - mSourceConsumers.put(sourceId, consumer); + mSourceConsumers.put(id, consumer); return consumer; } @@ -1663,8 +1663,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return mImeSourceConsumer; } - @VisibleForTesting - public void notifyVisibilityChanged() { + void notifyVisibilityChanged() { mHost.notifyInsetsChanged(); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 467d720196b17801c0584a692e5fa076a94ef0da..34b288460a67bac11d8b3b85e66e104eb2931b30 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -149,9 +149,12 @@ public class InsetsSourceConsumer { // Check if we need to restore server visibility. final InsetsSource localSource = mState.peekSource(mId); final InsetsSource serverSource = mController.getLastDispatchedState().peekSource(mId); - if (localSource != null && serverSource != null - && localSource.isVisible() != serverSource.isVisible()) { - localSource.setVisible(serverSource.isVisible()); + final boolean localVisible = localSource != null && localSource.isVisible(); + final boolean serverVisible = serverSource != null && serverSource.isVisible(); + if (localSource != null) { + localSource.setVisible(serverVisible); + } + if (localVisible != serverVisible) { mController.notifyVisibilityChanged(); } } else { diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp index 0bc0878b73095e6c936656db754574e16f35ffbf..f97d41b6a122eaffe3a794c2b117c4019e688e07 100644 --- a/core/jni/android_view_InputDevice.cpp +++ b/core/jni/android_view_InputDevice.cpp @@ -42,6 +42,13 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi return NULL; } + // b/274058082: Pass a copy of the key character map to avoid concurrent + // access + std::shared_ptr<KeyCharacterMap> map = deviceInfo.getKeyCharacterMap(); + if (map != nullptr) { + map = std::make_shared<KeyCharacterMap>(*map); + } + ScopedLocalRef<jstring> descriptorObj(env, env->NewStringUTF(deviceInfo.getIdentifier().descriptor.c_str())); if (!descriptorObj.get()) { @@ -61,8 +68,8 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi : NULL)); ScopedLocalRef<jobject> kcmObj(env, - android_view_KeyCharacterMap_create(env, deviceInfo.getId(), - deviceInfo.getKeyCharacterMap())); + android_view_KeyCharacterMap_create(env, deviceInfo.getId(), + map)); if (!kcmObj.get()) { return NULL; } diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 89632a46267e6306f2e53dcce825e749242b0683..2a4ca79d997e835b2922e7f6c9551e42efdad9c9 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -25,8 +25,6 @@ import junit.framework.TestCase; import java.io.File; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -869,84 +867,90 @@ public class UriTest extends TestCase { return (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null); } - /** Attempting to unparcel a legacy parcel format of Uri.{,Path}Part should fail. */ - public void testUnparcelLegacyPart_fails() throws Exception { - assertUnparcelLegacyPart_fails(Class.forName("android.net.Uri$Part")); - assertUnparcelLegacyPart_fails(Class.forName("android.net.Uri$PathPart")); - } - - private static void assertUnparcelLegacyPart_fails(Class partClass) throws Exception { - Parcel parcel = Parcel.obtain(); - parcel.writeInt(0 /* BOTH */); - parcel.writeString("encoded"); - parcel.writeString("decoded"); - parcel.setDataPosition(0); - - Method readFromMethod = partClass.getDeclaredMethod("readFrom", Parcel.class); - readFromMethod.setAccessible(true); - try { - readFromMethod.invoke(null, parcel); - fail(); - } catch (InvocationTargetException expected) { - Throwable targetException = expected.getTargetException(); - // Check that the exception was thrown for the correct reason. - assertEquals("Unknown representation: 0", targetException.getMessage()); - } finally { - parcel.recycle(); - } - } - - private Uri buildUriFromRawParcel(boolean argumentsEncoded, + private Uri buildUriFromParts(boolean argumentsEncoded, String scheme, String authority, String path, String query, String fragment) { - // Representation value (from AbstractPart.REPRESENTATION_{ENCODED,DECODED}). - final int representation = argumentsEncoded ? 1 : 2; - Parcel parcel = Parcel.obtain(); - try { - parcel.writeInt(3); // hierarchical - parcel.writeString8(scheme); - parcel.writeInt(representation); - parcel.writeString8(authority); - parcel.writeInt(representation); - parcel.writeString8(path); - parcel.writeInt(representation); - parcel.writeString8(query); - parcel.writeInt(representation); - parcel.writeString8(fragment); - parcel.setDataPosition(0); - return Uri.CREATOR.createFromParcel(parcel); - } finally { - parcel.recycle(); + final Uri.Builder builder = new Uri.Builder(); + builder.scheme(scheme); + if (argumentsEncoded) { + builder.encodedAuthority(authority); + builder.encodedPath(path); + builder.encodedQuery(query); + builder.encodedFragment(fragment); + } else { + builder.authority(authority); + builder.path(path); + builder.query(query); + builder.fragment(fragment); } + return builder.build(); } public void testUnparcelMalformedPath() { // Regression tests for b/171966843. // Test cases with arguments encoded (covering testing `scheme` * `authority` options). - Uri uri0 = buildUriFromRawParcel(true, "https", "google.com", "@evil.com", null, null); + Uri uri0 = buildUriFromParts(true, "https", "google.com", "@evil.com", null, null); assertEquals("https://google.com/@evil.com", uri0.toString()); - Uri uri1 = buildUriFromRawParcel(true, null, "google.com", "@evil.com", "name=spark", "x"); + Uri uri1 = buildUriFromParts(true, null, "google.com", "@evil.com", "name=spark", "x"); assertEquals("//google.com/@evil.com?name=spark#x", uri1.toString()); - Uri uri2 = buildUriFromRawParcel(true, "http:", null, "@evil.com", null, null); + Uri uri2 = buildUriFromParts(true, "http:", null, "@evil.com", null, null); assertEquals("http::/@evil.com", uri2.toString()); - Uri uri3 = buildUriFromRawParcel(true, null, null, "@evil.com", null, null); + Uri uri3 = buildUriFromParts(true, null, null, "@evil.com", null, null); assertEquals("@evil.com", uri3.toString()); // Test cases with arguments not encoded (covering testing `scheme` * `authority` options). - Uri uriA = buildUriFromRawParcel(false, "https", "google.com", "@evil.com", null, null); + Uri uriA = buildUriFromParts(false, "https", "google.com", "@evil.com", null, null); assertEquals("https://google.com/%40evil.com", uriA.toString()); - Uri uriB = buildUriFromRawParcel(false, null, "google.com", "@evil.com", null, null); + Uri uriB = buildUriFromParts(false, null, "google.com", "@evil.com", null, null); assertEquals("//google.com/%40evil.com", uriB.toString()); - Uri uriC = buildUriFromRawParcel(false, "http:", null, "@evil.com", null, null); + Uri uriC = buildUriFromParts(false, "http:", null, "@evil.com", null, null); assertEquals("http::/%40evil.com", uriC.toString()); - Uri uriD = buildUriFromRawParcel(false, null, null, "@evil.com", "name=spark", "y"); + Uri uriD = buildUriFromParts(false, null, null, "@evil.com", "name=spark", "y"); assertEquals("%40evil.com?name%3Dspark#y", uriD.toString()); } + public void testParsedUriFromStringEquality() { + Uri uri = buildUriFromParts( + true, "https", "google.com", "@evil.com", null, null); + assertEquals(uri, Uri.parse(uri.toString())); + Uri uri2 = buildUriFromParts( + true, "content://evil.authority?foo=", "safe.authority", "@evil.com", null, null); + assertEquals(uri2, Uri.parse(uri2.toString())); + Uri uri3 = buildUriFromParts( + false, "content://evil.authority?foo=", "safe.authority", "@evil.com", null, null); + assertEquals(uri3, Uri.parse(uri3.toString())); + } + + public void testParceledUrisAreEqual() { + Uri opaqueUri = Uri.fromParts("fake://uri#", "ssp", "fragment"); + Parcel parcel = Parcel.obtain(); + try { + opaqueUri.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + Uri postParcelUri = Uri.CREATOR.createFromParcel(parcel); + Uri parsedUri = Uri.parse(postParcelUri.toString()); + assertEquals(parsedUri.getScheme(), postParcelUri.getScheme()); + } finally { + parcel.recycle(); + } + + Uri hierarchicalUri = new Uri.Builder().scheme("fake://uri#").authority("auth").build(); + parcel = Parcel.obtain(); + try { + hierarchicalUri.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + Uri postParcelUri = Uri.CREATOR.createFromParcel(parcel); + Uri parsedUri = Uri.parse(postParcelUri.toString()); + assertEquals(parsedUri.getScheme(), postParcelUri.getScheme()); + } finally { + parcel.recycle(); + } + } + public void testToSafeString() { checkToSafeString("tel:xxxxxx", "tel:Google"); checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890"); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 06920524acfc54ede2e88929fa68fd6ffd22aeca..b8f0d5c82eaccd584e2041bde0d525471e2cdafd 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -131,10 +131,10 @@ public class InsetsControllerTest { mTestClock = new OffsettableClock(); mTestHandler = new TestHandler(null, mTestClock); mTestHost = spy(new TestHost(mViewRoot)); - mController = new InsetsController(mTestHost, (controller, source) -> { - if (source.getType() == ime()) { - return new InsetsSourceConsumer(source.getId(), source.getType(), - controller.getState(), Transaction::new, controller) { + mController = new InsetsController(mTestHost, (controller, id, type) -> { + if (type == ime()) { + return new InsetsSourceConsumer(id, type, controller.getState(), + Transaction::new, controller) { private boolean mImeRequestedShow; @@ -150,8 +150,8 @@ public class InsetsControllerTest { } }; } else { - return new InsetsSourceConsumer(source.getId(), source.getType(), - controller.getState(), Transaction::new, controller); + return new InsetsSourceConsumer(id, type, controller.getState(), + Transaction::new, controller); } }, mTestHandler); final Rect rect = new Rect(5, 5, 5, 5); @@ -182,7 +182,8 @@ public class InsetsControllerTest { @Test public void testControlsChanged() { mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars())); - assertNotNull(mController.getSourceConsumer(mStatusSource).getControl().getLeash()); + assertNotNull( + mController.getSourceConsumer(ID_STATUS_BAR, statusBars()).getControl().getLeash()); mController.addOnControllableInsetsChangedListener( ((controller, typeMask) -> assertEquals(statusBars(), typeMask))); } @@ -194,7 +195,7 @@ public class InsetsControllerTest { mController.addOnControllableInsetsChangedListener(listener); mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars())); mController.onControlsChanged(new InsetsSourceControl[0]); - assertNull(mController.getSourceConsumer(mStatusSource).getControl()); + assertNull(mController.getSourceConsumer(ID_STATUS_BAR, statusBars()).getControl()); InOrder inOrder = Mockito.inOrder(listener); inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(0)); inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(statusBars())); @@ -254,7 +255,7 @@ public class InsetsControllerTest { // only the original thread that created view hierarchy can touch its views InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener); - mController.getSourceConsumer(mImeSource).onWindowFocusGained(true); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); // since there is no focused view, forcefully make IME visible. mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); // When using the animation thread, this must not invoke onReady() @@ -271,7 +272,7 @@ public class InsetsControllerTest { prepareControls(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - mController.getSourceConsumer(mImeSource).onWindowFocusGained(true); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); // since there is no focused view, forcefully make IME visible. mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); mController.show(all()); @@ -284,7 +285,7 @@ public class InsetsControllerTest { mController.hide(all()); mController.cancelExistingAnimations(); assertEquals(0, mController.getRequestedVisibleTypes() & types); - mController.getSourceConsumer(mImeSource).onWindowFocusLost(); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -294,14 +295,14 @@ public class InsetsControllerTest { InsetsSourceControl ime = createControl(ID_IME, ime()); mController.onControlsChanged(new InsetsSourceControl[] { ime }); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - mController.getSourceConsumer(mImeSource).onWindowFocusGained(true); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); mController.cancelExistingAnimations(); assertTrue(isRequestedVisible(mController, ime())); mController.hide(ime(), true /* fromIme */, null /* statsToken */); mController.cancelExistingAnimations(); assertFalse(isRequestedVisible(mController, ime())); - mController.getSourceConsumer(mImeSource).onWindowFocusLost(); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -914,7 +915,7 @@ public class InsetsControllerTest { // Simulate IME insets is not controllable mController.onControlsChanged(new InsetsSourceControl[0]); final InsetsSourceConsumer imeInsetsConsumer = - mController.getSourceConsumer(mImeSource); + mController.getSourceConsumer(ID_IME, ime()); assertNull(imeInsetsConsumer.getControl()); // Verify IME requested visibility should be updated to IME consumer from controller. diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 988e69008008e7e02e2810f54a6666b773f79fbd..655cb4519d3c5519289d004e58bec2b0eb2cc82a 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -219,10 +219,10 @@ public class InsetsSourceConsumerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { InsetsState state = new InsetsState(); ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot); - InsetsController insetsController = new InsetsController(host, (controller, source) -> { - if (source.getType() == ime()) { + InsetsController insetsController = new InsetsController(host, (ic, id, type) -> { + if (type == ime()) { return new InsetsSourceConsumer(ID_IME, ime(), state, - () -> mMockTransaction, controller) { + () -> mMockTransaction, ic) { @Override public int requestShow(boolean fromController, ImeTracker.Token statsToken) { @@ -230,11 +230,9 @@ public class InsetsSourceConsumerTest { } }; } - return new InsetsSourceConsumer(source.getId(), source.getType(), - controller.getState(), Transaction::new, controller); + return new InsetsSourceConsumer(id, type, ic.getState(), Transaction::new, ic); }, host.getHandler()); - InsetsSource imeSource = new InsetsSource(ID_IME, ime()); - InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(imeSource); + InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ID_IME, ime()); // Initial IME insets source control with its leash. imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash, diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index a044602f7ec02f59f57021fe851c7f4b034aff9c..b05507e7e128b8f5641a2bd9b99d2a2345a8da3b 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -257,6 +257,7 @@ applications that come with the platform <permission name="android.permission.CLEAR_APP_CACHE"/> <permission name="android.permission.ACCESS_INSTANT_APPS" /> <permission name="android.permission.CONNECTIVITY_INTERNAL"/> + <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" /> <permission name="android.permission.DELETE_CACHE_FILES"/> <permission name="android.permission.DELETE_PACKAGES"/> <permission name="android.permission.DUMP"/> diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 16b35ffcabac69a1ddba3d6c738adb75801965c5..504dfaa2a1f5ab9e85b03e6e9af1c8450ee9b18c 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -193,6 +193,7 @@ void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) { void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { ATRACE_CALL(); + startHintSession(); if (window) { mNativeSurface = std::make_unique<ReliableSurface>(window); mNativeSurface->init(); diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp index 814ac4d90028d6dac427535477eb6be0b6253496..1f338ee523eb1325ffc1f42a529bed86ee27cc3d 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.cpp +++ b/libs/hwui/renderthread/HintSessionWrapper.cpp @@ -93,8 +93,17 @@ HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId) : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {} HintSessionWrapper::~HintSessionWrapper() { + destroy(); +} + +void HintSessionWrapper::destroy() { + if (mHintSessionFuture.valid()) { + mHintSession = mHintSessionFuture.get(); + } if (mHintSession) { gAPH_closeSessionFn(mHintSession); + mSessionValid = true; + mHintSession = nullptr; } } diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h index 24b8150dd489c7754a893fc8f65cd70b028422b6..bdb9959b1ca7e54d03d7e70fc984858820ef04db 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.h +++ b/libs/hwui/renderthread/HintSessionWrapper.h @@ -37,6 +37,7 @@ public: void sendLoadResetHint(); void sendLoadIncreaseHint(); bool init(); + void destroy(); private: APerformanceHintSession* mHintSession = nullptr; diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index d2b21ae19162866f7a09357287e08490023de645..2c18342af94d97f0127db0c885bf2184e5cf5e28 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -807,10 +807,10 @@ public class RingtoneManager { return ringtoneUri; } - + /** * Sets the {@link Uri} of the default sound for a given sound type. - * + * * @param context A context used for querying. * @param type The type whose default sound should be set. One of * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 4cb7f94f8e6d253e6fcfb87a6eaebc2b9e13a902..215a040c6a7237ca3b959489cbbaff69fbe862f4 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3229,6 +3229,15 @@ public class SettingsProvider extends ContentProvider { return settingsState.getSettingLocked(name); } + private static boolean shouldExcludeSettingFromReset(Setting setting, String prefix) { + // If a prefix was specified, exclude settings whose names don't start with it. + if (prefix != null && !setting.getName().startsWith(prefix)) { + return true; + } + // Never reset SECURE_FRP_MODE, as it could be abused to bypass FRP via RescueParty. + return Global.SECURE_FRP_MODE.equals(setting.getName()); + } + public void resetSettingsLocked(int type, int userId, String packageName, int mode, String tag) { resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/ @@ -3251,7 +3260,7 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (packageName.equals(setting.getPackageName())) { if ((tag != null && !tag.equals(setting.getTag())) - || (prefix != null && !setting.getName().startsWith(prefix))) { + || shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (settingsState.resetSettingLocked(name)) { @@ -3272,7 +3281,7 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName())) { - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (settingsState.resetSettingLocked(name)) { @@ -3293,7 +3302,7 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName())) { - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (setting.isDefaultFromSystem()) { @@ -3318,7 +3327,7 @@ public class SettingsProvider extends ContentProvider { for (String name : settingsState.getSettingNamesLocked()) { Setting setting = settingsState.getSettingLocked(name); boolean someSettingChanged = false; - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (setting.isDefaultFromSystem()) { diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java index eaf0dcb9b4e797a07e5c2c058c3bbfe260b5024a..a945c33bc20a1e3c4012c24be595e42d503e7ee5 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java @@ -464,6 +464,31 @@ public class SettingsProviderTest extends BaseSettingsProviderTest { } } + // To prevent FRP bypasses, the SECURE_FRP_MODE setting should not be reset when all other + // settings are reset. But it should still be possible to explicitly set its value. + @Test + public void testSecureFrpModeSettingCannotBeReset() throws Exception { + final String name = Settings.Global.SECURE_FRP_MODE; + final String origValue = getSetting(SETTING_TYPE_GLOBAL, name); + setSettingViaShell(SETTING_TYPE_GLOBAL, name, "1", false); + try { + assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name)); + for (int type : new int[] { SETTING_TYPE_GLOBAL, SETTING_TYPE_SECURE }) { + resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_CHANGES); + resetSettingsViaShell(type, Settings.RESET_MODE_TRUSTED_DEFAULTS); + } + // The value should still be "1". It should not have been reset to null. + assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name)); + // It should still be possible to explicitly set the value to "0". + setSettingViaShell(SETTING_TYPE_GLOBAL, name, "0", false); + assertEquals("0", getSetting(SETTING_TYPE_GLOBAL, name)); + } finally { + setSettingViaShell(SETTING_TYPE_GLOBAL, name, origValue, false); + assertEquals(origValue, getSetting(SETTING_TYPE_GLOBAL, name)); + } + } + private void doTestQueryStringInBracketsViaProviderApiForType(int type) { // Make sure we have a clean slate. deleteStringViaProviderApi(type, FAKE_SETTING_NAME); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 56e0643b1e20790a1142fd5c2b8d3e6bf64e44de..ee9883b0b0afb6e19f74bb1a6b4c80aa890ce1b2 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -320,6 +320,7 @@ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> + <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" /> <uses-permission android:name="android.permission.SUSPEND_APPS" /> <uses-permission android:name="android.permission.OBSERVE_APP_USAGE" /> <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 885b53d7e5d607daca15420e35ea23965ad5d487..b4fb887016343ad544c6139ac127fc6ad18c46f2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -330,7 +330,6 @@ public class KeyguardClockSwitch extends RelativeLayout { } }); - in.setAlpha(0); in.setVisibility(View.VISIBLE); mClockInAnim = new AnimatorSet(); mClockInAnim.setDuration(CLOCK_IN_MILLIS); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 2b71728f3391cc2d02100198155713ee69b507a2..f446e523afa964e94ad0a0876be181a4acc5b424 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -786,8 +786,6 @@ public class KeyguardSecurityContainer extends ConstraintLayout { void reloadColors() { mViewMode.reloadColors(); - setBackgroundColor(Utils.getColorAttrDefaultColor(getContext(), - com.android.internal.R.attr.materialColorSurface)); } /** Handles density or font scale changes. */ diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 3eb58bba1ca42a8f6c7db049406926d32120c0e4..ec76f433b23b1fcb45fd128f771156fd8ad35aaf 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -38,6 +38,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R import com.android.systemui.controls.ControlInterface +import com.android.systemui.controls.ui.CanUseIconPredicate import com.android.systemui.controls.ui.RenderInfo private typealias ModelFavoriteChanger = (String, Boolean) -> Unit @@ -51,7 +52,8 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit * @property elevation elevation of each control view */ class ControlAdapter( - private val elevation: Float + private val elevation: Float, + private val currentUserId: Int, ) : RecyclerView.Adapter<Holder>() { companion object { @@ -107,7 +109,8 @@ class ControlAdapter( background = parent.context.getDrawable( R.drawable.control_background_ripple) }, - model?.moveHelper // Indicates that position information is needed + currentUserId, + model?.moveHelper, // Indicates that position information is needed ) { id, favorite -> model?.changeFavoriteStatus(id, favorite) } @@ -212,8 +215,9 @@ private class ZoneHolder(view: View) : Holder(view) { */ internal class ControlHolder( view: View, + currentUserId: Int, val moveHelper: ControlsModel.MoveHelper?, - val favoriteCallback: ModelFavoriteChanger + val favoriteCallback: ModelFavoriteChanger, ) : Holder(view) { private val favoriteStateDescription = itemView.context.getString(R.string.accessibility_control_favorite) @@ -228,6 +232,7 @@ internal class ControlHolder( visibility = View.VISIBLE } + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val accessibilityDelegate = ControlHolderAccessibilityDelegate( this::stateDescription, this::getLayoutPosition, @@ -287,7 +292,9 @@ internal class ControlHolder( val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) icon.imageTintList = null - ci.customIcon?.let { + ci.customIcon + ?.takeIf(canUseIconPredicate) + ?.let { icon.setImageIcon(it) } ?: run { icon.setImageDrawable(ri.icon) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index 8e41974b9accafbcd34d13c23a0f25b7bc1b3e00..b387e4a66bd2b1a0771fb3e8beb6e281e3bcb22c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -250,7 +250,7 @@ open class ControlsEditingActivity @Inject constructor( val elevation = resources.getFloat(R.dimen.control_card_elevation) val recyclerView = requireViewById<RecyclerView>(R.id.list) recyclerView.alpha = 0.0f - val adapter = ControlAdapter(elevation).apply { + val adapter = ControlAdapter(elevation, userTracker.userId).apply { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { var hasAnimated = false override fun onChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 21051e7abe1f622e1be47bef566800ff27d46b3c..da571b3af086bdfafcd81ff0e3208b20d83246bd 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -197,7 +197,7 @@ open class ControlsFavoritingActivity @Inject constructor( } executor.execute { - structurePager.adapter = StructureAdapter(listOfStructures) + structurePager.adapter = StructureAdapter(listOfStructures, userTracker.userId) structurePager.setCurrentItem(structureIndex) if (error) { statusText.text = resources.getString(R.string.controls_favorite_load_error, @@ -243,7 +243,7 @@ open class ControlsFavoritingActivity @Inject constructor( structurePager.alpha = 0.0f pageIndicator.alpha = 0.0f structurePager.apply { - adapter = StructureAdapter(emptyList()) + adapter = StructureAdapter(emptyList(), userTracker.userId) registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt index 747bcbe1c2297c4b0e0542f6a6acb2983abb8876..5977d379acde26b66fbb2976e59887fbaba563df 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt @@ -24,13 +24,15 @@ import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R class StructureAdapter( - private val models: List<StructureContainer> + private val models: List<StructureContainer>, + private val currentUserId: Int, ) : RecyclerView.Adapter<StructureAdapter.StructureHolder>() { override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder { val layoutInflater = LayoutInflater.from(parent.context) return StructureHolder( - layoutInflater.inflate(R.layout.controls_structure_page, parent, false) + layoutInflater.inflate(R.layout.controls_structure_page, parent, false), + currentUserId, ) } @@ -40,7 +42,8 @@ class StructureAdapter( holder.bind(models[index].model) } - class StructureHolder(view: View) : RecyclerView.ViewHolder(view) { + class StructureHolder(view: View, currentUserId: Int) : + RecyclerView.ViewHolder(view) { private val recyclerView: RecyclerView private val controlAdapter: ControlAdapter @@ -48,7 +51,7 @@ class StructureAdapter( init { recyclerView = itemView.requireViewById<RecyclerView>(R.id.listAll) val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation) - controlAdapter = ControlAdapter(elevation) + controlAdapter = ControlAdapter(elevation, currentUserId) setUpRecyclerView() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt new file mode 100644 index 0000000000000000000000000000000000000000..61c21237144d5c410f040f1402ba450b5c9d83f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.systemui.controls.ui + +import android.content.ContentProvider +import android.graphics.drawable.Icon + +class CanUseIconPredicate(private val currentUserId: Int) : (Icon) -> Boolean { + + override fun invoke(icon: Icon): Boolean = + if (icon.type == Icon.TYPE_URI || icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + ContentProvider.getUserIdFromUri(icon.uri, currentUserId) == currentUserId + } else { + true + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 992c894b6b21b550f8cf290593bae7fac81dd83a..abe342320858251d257ab11107d9d46622caf55f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -68,7 +68,8 @@ class ControlViewHolder( val bgExecutor: DelayableExecutor, val controlActionCoordinator: ControlActionCoordinator, val controlsMetricsLogger: ControlsMetricsLogger, - val uid: Int + val uid: Int, + val currentUserId: Int, ) { companion object { @@ -85,29 +86,9 @@ class ControlViewHolder( private val ATTR_DISABLED = intArrayOf(-android.R.attr.state_enabled) const val MIN_LEVEL = 0 const val MAX_LEVEL = 10000 - - fun findBehaviorClass( - status: Int, - template: ControlTemplate, - deviceType: Int - ): Supplier<out Behavior> { - return when { - status != Control.STATUS_OK -> Supplier { StatusBehavior() } - template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() } - template is ThumbnailTemplate -> Supplier { ThumbnailBehavior() } - - // Required for legacy support, or where cameras do not use the new template - deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() } - template is ToggleTemplate -> Supplier { ToggleBehavior() } - template is StatelessTemplate -> Supplier { TouchBehavior() } - template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() } - template is RangeTemplate -> Supplier { ToggleRangeBehavior() } - template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() } - else -> Supplier { DefaultBehavior() } - } - } } + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val toggleBackgroundIntensity: Float = layout.context.resources .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1) private var stateAnimator: ValueAnimator? = null @@ -147,6 +128,27 @@ class ControlViewHolder( status.setSelected(true) } + fun findBehaviorClass( + status: Int, + template: ControlTemplate, + deviceType: Int + ): Supplier<out Behavior> { + return when { + status != Control.STATUS_OK -> Supplier { StatusBehavior() } + template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() } + template is ThumbnailTemplate -> Supplier { ThumbnailBehavior(currentUserId) } + + // Required for legacy support, or where cameras do not use the new template + deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() } + template is ToggleTemplate -> Supplier { ToggleBehavior() } + template is StatelessTemplate -> Supplier { TouchBehavior() } + template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() } + template is RangeTemplate -> Supplier { ToggleRangeBehavior() } + template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() } + else -> Supplier { DefaultBehavior() } + } + } + fun bindData(cws: ControlWithState, isLocked: Boolean) { // If an interaction is in progress, the update may visually interfere with the action the // action the user wants to make. Don't apply the update, and instead assume a new update @@ -473,7 +475,9 @@ class ControlViewHolder( status.setTextColor(color) - control?.getCustomIcon()?.let { + control?.customIcon + ?.takeIf(canUseIconPredicate) + ?.let { icon.setImageIcon(it) icon.imageTintList = it.tintList } ?: run { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 776b336e7b61ea942b621b9dd452421bbe194bcb..631ed3c26496c3571fa87bf5b1cfd6b11ae69461 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -690,7 +690,8 @@ class ControlsUiControllerImpl @Inject constructor ( bgExecutor, controlActionCoordinator, controlsMetricsLogger, - selected.uid + selected.uid, + controlsController.get().currentUserId, ) cvh.bindData(it, false /* isLocked, will be ignored on initial load */) controlViewsById.put(key, cvh) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt index a7dc09bb17e567cce208fd2dbc670d84676594a5..39d69704d8171e03869f52c342dc730bac0204f1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt @@ -63,7 +63,7 @@ class TemperatureControlBehavior : Behavior { // interactions (touch, range) subBehavior = cvh.bindBehavior( subBehavior, - ControlViewHolder.findBehaviorClass( + cvh.findBehaviorClass( control.status, subTemplate, control.deviceType diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt index 86f093d1aaf4fc9f2b0a86726ef179b15a79422e..0b57e792f9f76d09beeb634c223afeaedc986cdd 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt @@ -33,7 +33,7 @@ import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL * Supports display of static images on the background of the tile. When marked active, the title * and subtitle will not be visible. To be used with {@link Thumbnailtemplate} only. */ -class ThumbnailBehavior : Behavior { +class ThumbnailBehavior(currentUserId: Int) : Behavior { lateinit var template: ThumbnailTemplate lateinit var control: Control lateinit var cvh: ControlViewHolder @@ -42,6 +42,7 @@ class ThumbnailBehavior : Behavior { private var shadowRadius: Float = 0f private var shadowColor: Int = 0 + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val enabled: Boolean get() = template.isActive() @@ -80,7 +81,9 @@ class ThumbnailBehavior : Behavior { cvh.status.setShadowLayer(shadowOffsetX, shadowOffsetY, shadowRadius, shadowColor) cvh.bgExecutor.execute { - val drawable = template.getThumbnail().loadDrawable(cvh.context) + val drawable = template.thumbnail + ?.takeIf(canUseIconPredicate) + ?.loadDrawable(cvh.context) cvh.uiExecutor.execute { val radius = cvh.context.getResources() .getDimensionPixelSize(R.dimen.control_corner_radius).toFloat() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 25ecf1a424e04002ad844e0c27b49c848f28ae85..c42a6dc5b6f2cd520d68e38c11014b8a790617b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -1509,6 +1509,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mColors.setSupportsDarkText( ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5); } + + int surface = Utils.getColorAttr(mScrimBehind.getContext(), + com.android.internal.R.attr.materialColorSurface).getDefaultColor(); + for (ScrimState state : ScrimState.values()) { + state.setSurfaceColor(surface); + } + mNeedsDrawableColorUpdate = true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 7b2028310a84ed292ae6e798404093db36698002..e3b65ab27f4852708f7f88abff711a3c305d2972 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -122,11 +122,19 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; - mBehindTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT; + mBehindTint = mClipQsScrim ? Color.BLACK : mSurfaceColor; mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0; mNotifTint = Color.TRANSPARENT; mFrontAlpha = 0f; } + + @Override + public void setSurfaceColor(int surfaceColor) { + super.setSurfaceColor(surfaceColor); + if (!mClipQsScrim) { + mBehindTint = mSurfaceColor; + } + } }, /** @@ -295,6 +303,7 @@ public enum ScrimState { int mFrontTint = Color.TRANSPARENT; int mBehindTint = Color.TRANSPARENT; int mNotifTint = Color.TRANSPARENT; + int mSurfaceColor = Color.TRANSPARENT; boolean mAnimateChange = true; float mAodFrontScrimAlpha; @@ -409,6 +418,10 @@ public enum ScrimState { mDefaultScrimAlpha = defaultScrimAlpha; } + public void setSurfaceColor(int surfaceColor) { + mSurfaceColor = surfaceColor; + } + public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) { mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index bcf3b0cbfc867161f6e5ec49b8e10da5204745d9..24987abd7a8521f119fb7c8211c1b05573a61c02 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -240,8 +240,10 @@ public class StatusBarWindowController { Insets.of(0, safeTouchRegionHeight, 0, 0)); } lp.providedInsets = new InsetsFrameProvider[] { - new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()), - new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()), + new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()) + .setInsetsSize(getInsets(height)), + new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()) + .setInsetsSize(getInsets(height)), gestureInsetsProvider }; return lp; @@ -306,12 +308,37 @@ public class StatusBarWindowController { mLpChanged.height = state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight; for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { + int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rot); mLpChanged.paramsForRotation[rot].height = - state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : - SystemBarUtils.getStatusBarHeightForRotation(mContext, rot); + state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : height; + // The status bar height could change at runtime if one display has a cutout while + // another doesn't (like some foldables). It could also change when using debug cutouts. + // So, we need to re-fetch the height and re-apply it to the insets each time to avoid + // bugs like b/290300359. + InsetsFrameProvider[] providers = mLpChanged.paramsForRotation[rot].providedInsets; + if (providers != null) { + for (InsetsFrameProvider provider : providers) { + provider.setInsetsSize(getInsets(height)); + } + } } } + /** + * Get the insets that should be applied to the status bar window given the current status bar + * height. + * + * The status bar window height can sometimes be full-screen (see {@link #applyHeight(State)}. + * However, the status bar *insets* should *not* be full-screen, because this would prevent apps + * from drawing any content and can cause animations to be cancelled (see b/283958440). Instead, + * the status bar insets should always be equal to the space occupied by the actual status bar + * content -- setting the insets correctly will prevent window manager from unnecessarily + * re-drawing this window and other windows. This method provides the correct insets. + */ + private Insets getInsets(int height) { + return Insets.of(0, height, 0, 0); + } + private void apply(State state) { if (!mIsAttached) { return; diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..bfdb9231a9f8ce004e9da52895d4cf90ab66aed5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 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.systemui.controls.ui + +import android.content.ContentProvider +import android.graphics.Bitmap +import android.graphics.drawable.Icon +import android.net.Uri +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class CanUseIconPredicateTest : SysuiTestCase() { + + private companion object { + const val USER_ID_1 = 1 + const val USER_ID_2 = 2 + } + + val underTest: CanUseIconPredicate = CanUseIconPredicate(USER_ID_1) + + @Test + fun testReturnsFalseForDifferentUser() { + val user2Icon = + Icon.createWithContentUri( + ContentProvider.createContentUriForUser( + Uri.parse("content://test"), + UserHandle.of(USER_ID_2) + ) + ) + + assertThat(underTest.invoke(user2Icon)).isFalse() + } + + @Test + fun testReturnsTrueForCorrectUser() { + val user1Icon = + Icon.createWithContentUri( + ContentProvider.createContentUriForUser( + Uri.parse("content://test"), + UserHandle.of(USER_ID_1) + ) + ) + + assertThat(underTest.invoke(user1Icon)).isTrue() + } + + @Test + fun testReturnsTrueForUriWithoutUser() { + val uriIcon = Icon.createWithContentUri(Uri.parse("content://test")) + + assertThat(underTest.invoke(uriIcon)).isTrue() + } + + @Test + fun testReturnsTrueForNonUriIcon() { + val bitmapIcon = Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) + + assertThat(underTest.invoke(bitmapIcon)).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt index 1461a3fbeeb69a0d65bd4c5f4fbaee82de012eb1..2ae342a5cfa50253da4cc4d3a7ec5d38501fa850 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt @@ -66,7 +66,8 @@ class ControlViewHolderTest : SysuiTestCase() { FakeExecutor(clock), mock(ControlActionCoordinator::class.java), mock(ControlsMetricsLogger::class.java), - uid = 100 + uid = 100, + 0, ) val cws = ControlWithState( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index a9ed17531926590c6bdc5bd64a96ce0fbb66c34c..7fc02280354f3b0d2d845b624a248666384ed09c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -44,6 +44,9 @@ import static kotlinx.coroutines.flow.FlowKt.emptyFlow; import android.animation.Animator; import android.app.AlarmManager; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; import android.graphics.Color; import android.os.Handler; import android.testing.AndroidTestingRunner; @@ -121,6 +124,7 @@ public class ScrimControllerTest extends SysuiTestCase { private int mScrimVisibility; private boolean mAlwaysOnEnabled; private TestableLooper mLooper; + private Context mContext; @Mock private AlarmManager mAlarmManager; @Mock private DozeParameters mDozeParameters; @Mock private LightBarController mLightBarController; @@ -134,6 +138,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock private CoroutineDispatcher mMainDispatcher; + @Mock private TypedArray mMockTypedArray; // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The // event-dispatch-on-registration pattern caused some of these unit tests to fail.) @@ -182,10 +187,11 @@ public class ScrimControllerTest extends SysuiTestCase { mNumEnds = 0; mNumCancels = 0; } - }; + } private AnimatorListener mAnimatorListener = new AnimatorListener(); + private int mSurfaceColor = 0x112233; private void finishAnimationsImmediately() { // Execute code that will trigger animations. @@ -214,10 +220,17 @@ public class ScrimControllerTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); + mContext = spy(getContext()); + when(mContext.obtainStyledAttributes( + new int[]{com.android.internal.R.attr.materialColorSurface})) + .thenReturn(mMockTypedArray); + + when(mMockTypedArray.getColorStateList(anyInt())) + .thenAnswer((invocation) -> ColorStateList.valueOf(mSurfaceColor)); - mScrimBehind = spy(new ScrimView(getContext())); - mScrimInFront = new ScrimView(getContext()); - mNotificationsScrim = new ScrimView(getContext()); + mScrimBehind = spy(new ScrimView(mContext)); + mScrimInFront = new ScrimView(mContext); + mNotificationsScrim = new ScrimView(mContext); mAlwaysOnEnabled = true; mLooper = TestableLooper.get(this); DejankUtils.setImmediate(true); @@ -577,7 +590,7 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.transitionTo(BOUNCER); finishAnimationsImmediately(); // Front scrim should be transparent - // Back scrim should be visible without tint + // Back scrim should be visible and tinted to the surface color assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, mNotificationsScrim, TRANSPARENT, @@ -585,9 +598,31 @@ public class ScrimControllerTest extends SysuiTestCase { assertScrimTinted(Map.of( mScrimInFront, false, - mScrimBehind, false, + mScrimBehind, true, mNotificationsScrim, false )); + + assertScrimTint(mScrimBehind, mSurfaceColor); + } + + @Test + public void onThemeChange_bouncerBehindTint_isUpdatedToSurfaceColor() { + assertEquals(BOUNCER.getBehindTint(), 0x112233); + mSurfaceColor = 0x223344; + mConfigurationController.notifyThemeChanged(); + assertEquals(BOUNCER.getBehindTint(), 0x223344); + } + + @Test + public void onThemeChangeWhileClipQsScrim_bouncerBehindTint_remainsBlack() { + mScrimController.setClipsQsScrim(true); + mScrimController.transitionTo(BOUNCER); + finishAnimationsImmediately(); + + assertEquals(BOUNCER.getBehindTint(), Color.BLACK); + mSurfaceColor = 0x223344; + mConfigurationController.notifyThemeChanged(); + assertEquals(BOUNCER.getBehindTint(), Color.BLACK); } @Test @@ -619,16 +654,17 @@ public class ScrimControllerTest extends SysuiTestCase { finishAnimationsImmediately(); // Front scrim should be transparent - // Back scrim should be visible without tint + // Back scrim should be visible and has a tint of surfaceColor assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, mNotificationsScrim, TRANSPARENT, mScrimBehind, OPAQUE)); assertScrimTinted(Map.of( mScrimInFront, false, - mScrimBehind, false, + mScrimBehind, true, mNotificationsScrim, false )); + assertScrimTint(mScrimBehind, mSurfaceColor); } @Test @@ -1809,6 +1845,13 @@ public class ScrimControllerTest extends SysuiTestCase { assertEquals(message, hasTint, scrim.getTint() != Color.TRANSPARENT); } + private void assertScrimTint(ScrimView scrim, int expectedTint) { + String message = "Tint test failed with expected scrim tint: " + + Integer.toHexString(expectedTint) + " and actual tint: " + + Integer.toHexString(scrim.getTint()) + " for scrim: " + getScrimName(scrim); + assertEquals(message, expectedTint, scrim.getTint(), 0.1); + } + private String getScrimName(ScrimView scrim) { if (scrim == mScrimInFront) { return "front"; diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 82af38200166954f4ac87f6d68d49569f33c7c53..7557071d0d4b8645c4035e15724dc61a173c4695 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -20,6 +20,8 @@ import static com.android.server.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.assist.AssistStructure; import android.app.assist.AssistStructure.ViewNode; import android.app.assist.AssistStructure.WindowNode; @@ -40,6 +42,7 @@ import android.view.View; import android.view.WindowManager; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import android.widget.RemoteViews; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; @@ -50,6 +53,8 @@ import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + public final class Helper { @@ -83,6 +88,44 @@ public final class Helper { throw new UnsupportedOperationException("contains static members only"); } + private static boolean checkRemoteViewUriPermissions( + @UserIdInt int userId, @NonNull RemoteViews rView) { + final AtomicBoolean permissionsOk = new AtomicBoolean(true); + + rView.visitUris(uri -> { + int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri); + boolean allowed = uriOwnerId == userId; + permissionsOk.set(allowed && permissionsOk.get()); + }); + + return permissionsOk.get(); + } + + /** + * Checks the URI permissions of the remote view, + * to see if the current userId is able to access it. + * + * Returns the RemoteView that is passed if user is able, null otherwise. + * + * TODO: instead of returning a null remoteview when + * the current userId cannot access an URI, + * return a new RemoteView with the URI removed. + */ + public static @Nullable RemoteViews sanitizeRemoteView(RemoteViews rView) { + if (rView == null) return null; + + int userId = ActivityManager.getCurrentUser(); + + boolean ok = checkRemoteViewUriPermissions(userId, rView); + if (!ok) { + Slog.w(TAG, + "sanitizeRemoteView() user: " + userId + + " tried accessing resource that does not belong to them"); + } + return (ok ? rView : null); + } + + @Nullable static AutofillId[] toArray(@Nullable ArraySet<AutofillId> set) { if (set == null) return null; diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java index dbeb624bd202c2d684c9cd44ace1afbfd721c387..fa414e3b172b330f85608e85b78a9f977b89a791 100644 --- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java @@ -53,6 +53,7 @@ import android.widget.TextView; import com.android.internal.R; import com.android.server.autofill.AutofillManagerService; +import com.android.server.autofill.Helper; import java.io.PrintWriter; import java.util.ArrayList; @@ -208,7 +209,8 @@ final class DialogFillUi { } private void setHeader(View decor, FillResponse response) { - final RemoteViews presentation = response.getDialogHeader(); + final RemoteViews presentation = + Helper.sanitizeRemoteView(response.getDialogHeader()); if (presentation == null) { return; } @@ -243,9 +245,10 @@ final class DialogFillUi { } private void initialAuthenticationLayout(View decor, FillResponse response) { - RemoteViews presentation = response.getDialogPresentation(); + RemoteViews presentation = Helper.sanitizeRemoteView( + response.getDialogPresentation()); if (presentation == null) { - presentation = response.getPresentation(); + presentation = Helper.sanitizeRemoteView(response.getPresentation()); } if (presentation == null) { throw new RuntimeException("No presentation for fill dialog authentication"); @@ -289,7 +292,8 @@ final class DialogFillUi { final Dataset dataset = response.getDatasets().get(i); final int index = dataset.getFieldIds().indexOf(focusedViewId); if (index >= 0) { - RemoteViews presentation = dataset.getFieldDialogPresentation(index); + RemoteViews presentation = Helper.sanitizeRemoteView( + dataset.getFieldDialogPresentation(index)); if (presentation == null) { if (sDebug) { Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because " diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 129ce72e037dfa6ba9bee9577729e0b9469177a2..cdfe7bb4f4a78126309f6aba600ea8ba4655d572 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -148,8 +148,9 @@ final class FillUi { final LayoutInflater inflater = LayoutInflater.from(mContext); - final RemoteViews headerPresentation = response.getHeader(); - final RemoteViews footerPresentation = response.getFooter(); + final RemoteViews headerPresentation = Helper.sanitizeRemoteView(response.getHeader()); + final RemoteViews footerPresentation = Helper.sanitizeRemoteView(response.getFooter()); + final ViewGroup decor; if (mFullScreen) { decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_fullscreen, null); @@ -227,6 +228,9 @@ final class FillUi { ViewGroup container = decor.findViewById(R.id.autofill_dataset_picker); final View content; try { + if (Helper.sanitizeRemoteView(response.getPresentation()) == null) { + throw new RuntimeException("Permission error accessing RemoteView"); + } content = response.getPresentation().applyWithTheme( mContext, decor, interceptionHandler, mThemeId); container.addView(content); @@ -306,7 +310,8 @@ final class FillUi { final Dataset dataset = response.getDatasets().get(i); final int index = dataset.getFieldIds().indexOf(focusedViewId); if (index >= 0) { - final RemoteViews presentation = dataset.getFieldPresentation(index); + final RemoteViews presentation = Helper.sanitizeRemoteView( + dataset.getFieldPresentation(index)); if (presentation == null) { Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because " + "service didn't provide a presentation for it on " + dataset); diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index f035d076427908284c22331c4c66d7e1c964b315..70382f1d5274b6fd298581eff981f0b55be921a9 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -384,8 +384,7 @@ final class SaveUi { return false; } writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION); - - final RemoteViews template = customDescription.getPresentation(); + final RemoteViews template = Helper.sanitizeRemoteView(customDescription.getPresentation()); if (template == null) { Slog.w(TAG, "No remote view on custom description"); return false; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d5f41a1090422d92026a868e5a8bd9ffeaf4571a..a7107161777cdf7728e27fdeafb637dfd0f36900 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2414,7 +2414,7 @@ public final class ActiveServices { // Even if the service is already a FGS, we need to update the notification, // so we need to call it again. signalForegroundServiceObserversLocked(r); - r.postNotification(); + r.postNotification(true); if (r.app != null) { updateServiceForegroundLocked(psr, true); } @@ -2472,7 +2472,7 @@ public final class ActiveServices { } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { // if it's been deferred, force to visibility if (!r.mFgsNotificationShown) { - r.postNotification(); + r.postNotification(false); } dropFgsNotificationStateLocked(r); if ((flags & Service.STOP_FOREGROUND_DETACH) != 0) { @@ -2916,7 +2916,7 @@ public final class ActiveServices { // in the interval, so we lazy check whether we still need to show // the notification. if (r.isForeground && r.app != null) { - r.postNotification(); + r.postNotification(true); r.mFgsNotificationShown = true; } else { if (DEBUG_FOREGROUND_SERVICE) { @@ -5339,7 +5339,7 @@ public final class ActiveServices { thread.scheduleCreateService(r, r.serviceInfo, null /* compatInfo (unused but need to keep method signature) */, app.mState.getReportedProcState()); - r.postNotification(); + r.postNotification(false); created = true; } catch (DeadObjectException e) { Slog.w(TAG, "Application dead when creating service " + r); diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 50fe6d71d26e0ef781ff32db4ea3783d7a64cdb9..b9914faa469aadba5b121b8d0b3b055562d766dd 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -1212,7 +1212,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN }); } - public void postNotification() { + public void postNotification(boolean byForegroundService) { if (isForeground && foregroundNoti != null && app != null) { final int appUid = appInfo.uid; final int appPid = app.getPid(); @@ -1320,7 +1320,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } nm.enqueueNotification(localPackageName, localPackageName, appUid, appPid, null, localForegroundId, localForegroundNoti, - userId); + userId, byForegroundService /* byForegroundService */); foregroundNoti = localForegroundNoti; // save it for amending next time diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index fc3d7c8114b02fe9049def4c631d4eb391e8b973..745222873698bcbd7283f953499d253feb822bff 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -216,6 +216,10 @@ public final class BiometricContextProvider implements BiometricContext { public void subscribe(@NonNull OperationContextExt context, @NonNull Consumer<OperationContext> consumer) { mSubscribers.put(context, consumer); + // TODO(b/294161627) Combine the getContext/subscribe APIs to avoid race + if (context.getDisplayState() != getDisplayState()) { + consumer.accept(context.update(this, context.isCrypto()).toAidlContext()); + } } @Override diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 65e34e6827249e7404dec3b6dd0ad90a9877e20c..38aac4648e3474240d645e341e3e476e0ae8e764 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -117,6 +117,10 @@ public final class MediaProjectionManagerService extends SystemService // WindowManagerService -> MediaProjectionManagerService -> DisplayManagerService // See mediaprojection.md private final Object mLock = new Object(); + // A handler for posting tasks that must interact with a service holding another lock, + // especially for services that will eventually acquire the WindowManager lock. + @NonNull private final Handler mHandler; + private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters; private final CallbackDelegate mCallbackDelegate; @@ -145,6 +149,8 @@ public final class MediaProjectionManagerService extends SystemService super(context); mContext = context; mInjector = injector; + // Post messages on the main thread; no need for a separate thread. + mHandler = new Handler(Looper.getMainLooper()); mClock = injector.createClock(); mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>(); mCallbackDelegate = new CallbackDelegate(); @@ -238,15 +244,20 @@ public final class MediaProjectionManagerService extends SystemService if (!mProjectionGrant.requiresForegroundService()) { return; } + } - if (mActivityManagerInternal.hasRunningForegroundService( - uid, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)) { - // If there is any process within this UID running a FGS - // with the mediaProjection type, that's Okay. - return; - } + // Run outside the lock when calling into ActivityManagerService. + if (mActivityManagerInternal.hasRunningForegroundService( + uid, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)) { + // If there is any process within this UID running a FGS + // with the mediaProjection type, that's Okay. + return; + } - mProjectionGrant.stop(); + synchronized (mLock) { + if (mProjectionGrant != null) { + mProjectionGrant.stop(); + } } } @@ -854,7 +865,6 @@ public final class MediaProjectionManagerService extends SystemService mTargetSdkVersion = targetSdkVersion; mIsPrivileged = isPrivileged; mCreateTimeMs = mClock.uptimeMillis(); - // TODO(b/267740338): Add unit test. mActivityManagerInternal.notifyMediaProjectionEvent(uid, asBinder(), MEDIA_PROJECTION_TOKEN_EVENT_CREATED); } @@ -911,6 +921,10 @@ public final class MediaProjectionManagerService extends SystemService if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } + // Cache result of calling into ActivityManagerService outside of the lock, to prevent + // deadlock with WindowManagerService. + final boolean hasFGS = mActivityManagerInternal.hasRunningForegroundService( + uid, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION); synchronized (mLock) { if (isCurrentProjection(asBinder())) { Slog.w(TAG, "UID " + Binder.getCallingUid() @@ -922,9 +936,7 @@ public final class MediaProjectionManagerService extends SystemService } if (REQUIRE_FG_SERVICE_FOR_PROJECTION - && requiresForegroundService() - && !mActivityManagerInternal.hasRunningForegroundService( - uid, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)) { + && requiresForegroundService() && !hasFGS) { throw new SecurityException("Media projections require a foreground service" + " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION"); } @@ -1013,10 +1025,11 @@ public final class MediaProjectionManagerService extends SystemService mToken = null; unregisterCallback(mCallback); mCallback = null; - // TODO(b/267740338): Add unit test. - mActivityManagerInternal.notifyMediaProjectionEvent(uid, asBinder(), - MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED); } + // Run on a separate thread, to ensure no lock is held when calling into + // ActivityManagerService. + mHandler.post(() -> mActivityManagerInternal.notifyMediaProjectionEvent(uid, asBinder(), + MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED)); } @Override // Binder call diff --git a/services/core/java/com/android/server/media/projection/mediaprojection.md b/services/core/java/com/android/server/media/projection/mediaprojection.md index bccdf34119037d34e9e86225a34507b7956cb9d2..34e7ecc6c6c511178cb52639532379434e0a9395 100644 --- a/services/core/java/com/android/server/media/projection/mediaprojection.md +++ b/services/core/java/com/android/server/media/projection/mediaprojection.md @@ -11,6 +11,11 @@ Calls must follow the below invocation order while holding locks: `WindowManagerService -> MediaProjectionManagerService -> DisplayManagerService` +`MediaProjectionManagerService` should never lock when calling into a service that may acquire +the `WindowManagerService` global lock (for example, +`MediaProjectionManagerService -> ActivityManagerService` may result in deadlock, since +`ActivityManagerService -> WindowManagerService`). + ### Justification `MediaProjectionManagerService` calls into `WindowManagerService` in the below cases. While handling diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index 919fc712c4097795d0b1cea900d33772936b2ce6..c240bcbce91190cd31aa768e01e0613151c7806c 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -27,6 +27,9 @@ public interface NotificationManagerInternal { NotificationChannelGroup getNotificationChannelGroup(String pkg, int uid, String channelId); void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid, String tag, int id, Notification notification, int userId); + void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid, + String tag, int id, Notification notification, int userId, + boolean byForegroundService); void cancelNotification(String pkg, String basePkg, int callingUid, int callingPid, String tag, int id, int userId); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7369e5ed59320c5a1a8ce7054d0014a04f94360d..79559fdc34d657fe1a87273a9c0cd3b7f0471bda 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2528,7 +2528,8 @@ public class NotificationManagerService extends SystemService { } enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(), r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(), - r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn); + r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn, + false /* byForegroundService */); } catch (Exception e) { Slog.e(TAG, "Cannot un-snooze notification", e); } @@ -3518,7 +3519,8 @@ public class NotificationManagerService extends SystemService { public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, Notification notification, int userId) throws RemoteException { enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(), - Binder.getCallingPid(), tag, id, notification, userId); + Binder.getCallingPid(), tag, id, notification, userId, + false /* byForegroundService */); } @Override @@ -6074,7 +6076,7 @@ public class NotificationManagerService extends SystemService { } if (summaryRecord != null && checkDisqualifyingFeatures(userId, uid, summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord, - true)) { + true, false)) { return summaryRecord; } } @@ -6403,7 +6405,15 @@ public class NotificationManagerService extends SystemService { public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid, String tag, int id, Notification notification, int userId) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, - userId); + userId, false /* byForegroundService */); + } + + @Override + public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid, + String tag, int id, Notification notification, int userId, + boolean byForegroundService) { + enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, + userId, byForegroundService); } @Override @@ -6581,19 +6591,19 @@ public class NotificationManagerService extends SystemService { void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, - int incomingUserId) { + int incomingUserId, boolean byForegroundService) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, - incomingUserId, false); + incomingUserId, false /* postSilently */, byForegroundService); } void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, - int incomingUserId, boolean postSilently) { + int incomingUserId, boolean postSilently, boolean byForegroundService) { PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid); boolean enqueued = false; try { enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, - notification, incomingUserId, postSilently, tracker); + notification, incomingUserId, postSilently, tracker, byForegroundService); } finally { if (!enqueued) { tracker.cancel(); @@ -6624,10 +6634,10 @@ public class NotificationManagerService extends SystemService { * @return True if we successfully processed the notification and handed off the task of * enqueueing it to a background thread; false otherwise. */ - private boolean enqueueNotificationInternal(final String pkg, final String opPkg, + private boolean enqueueNotificationInternal(final String pkg, final String opPkg, //HUI final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId, boolean postSilently, - PostNotificationTracker tracker) { + PostNotificationTracker tracker, boolean byForegroundService) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); @@ -6773,7 +6783,7 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid)); if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r, - r.getSbn().getOverrideGroupKey() != null)) { + r.getSbn().getOverrideGroupKey() != null, byForegroundService)) { return false; } @@ -7193,7 +7203,7 @@ public class NotificationManagerService extends SystemService { * Has side effects. */ boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag, - NotificationRecord r, boolean isAutogroup) { + NotificationRecord r, boolean isAutogroup, boolean byForegroundService) { Notification n = r.getNotification(); final String pkg = r.getSbn().getPackageName(); final boolean isSystemNotification = @@ -7283,7 +7293,8 @@ public class NotificationManagerService extends SystemService { if (n.isStyle(Notification.CallStyle.class)) { boolean hasFullScreenIntent = n.fullScreenIntent != null; boolean requestedFullScreenIntent = (n.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0; - if (!n.isFgsOrUij() && !hasFullScreenIntent && !requestedFullScreenIntent) { + if (!n.isFgsOrUij() && !hasFullScreenIntent && !requestedFullScreenIntent + && !byForegroundService) { throw new IllegalArgumentException(r.getKey() + " Not posted." + " CallStyle notifications must be for a foreground service or" + " user initated job or use a fullScreenIntent."); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 3f1a077740ccfc6439b3c8adab56e2c78afac346..4c0c4b5add44a8184da0a6e0695844c9e9cd8a6d 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -176,7 +176,7 @@ public class DisplayPolicy { // TODO(b/266197298): Remove this by a more general protocol from the insets providers. private static final boolean USE_CACHED_INSETS_FOR_DISPLAY_SWITCH = - SystemProperties.getBoolean("persist.wm.debug.cached_insets_switch", false); + SystemProperties.getBoolean("persist.wm.debug.cached_insets_switch", true); private final WindowManagerService mService; private final Context mContext; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index a4423038a072a65852527272bc49c354c36cb0e9..437510595ecb5787c1667024428aefc81e6f07ca 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -251,6 +251,14 @@ public class BiometricContextProviderTest { assertThat(actual).containsExactly(true, false, true, true, false, false).inOrder(); } + @Test + public void testSubscribesWithDifferentState() throws RemoteException { + final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class); + mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD); + mProvider.subscribe(mOpContext, nonEmptyConsumer); + verify(nonEmptyConsumer).accept(same(mOpContext.toAidlContext())); + } + @Test public void testUnsubscribes() throws RemoteException { final Consumer<OperationContext> emptyConsumer = mock(Consumer.class); @@ -259,6 +267,9 @@ public class BiometricContextProviderTest { mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD); + //reset to unknown to avoid trigger accept when subscribe + mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_UNKNOWN); + final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class); mProvider.subscribe(mOpContext, nonEmptyConsumer); mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 4849665b0c4445dcca57363b0896d39f3b98c82d..72c9791a3ceb25f363292eab87fffd74867070db 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -10104,7 +10104,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { try { mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), - r.getSbn().getTag(), r,false); + r.getSbn().getTag(), r, false, false); fail("Allowed a contextual direct reply with an immutable intent to be posted"); } catch (IllegalArgumentException e) { // good @@ -10135,7 +10135,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.applyAdjustments(); mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), - r.getSbn().getTag(), r,false); + r.getSbn().getTag(), r, false, false); } @Test @@ -10169,7 +10169,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.applyAdjustments(); mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), - r.getSbn().getTag(), r,false); + r.getSbn().getTag(), r, false, false); } @Test @@ -10382,7 +10382,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // normal blocked notifications - blocked assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // just using the style - blocked nb.setStyle(new Notification.MediaStyle()); @@ -10391,7 +10391,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // using the style, but incorrect type in session - blocked nb.setStyle(new Notification.MediaStyle()); @@ -10403,7 +10403,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // style + media session - bypasses block nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); @@ -10412,7 +10412,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } @Test @@ -10495,7 +10495,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // normal blocked notifications - blocked assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // just using the style - blocked Person person = new Person.Builder() @@ -10509,36 +10509,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // style + managed call - bypasses block when(mTelecomManager.isInManagedCall()).thenReturn(true); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); // style + self managed call - bypasses block when(mTelecomManager.isInSelfManagedCall( r.getSbn().getPackageName(), r.getUser())).thenReturn(true); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); // set telecom manager to null - blocked mService.setTelecomManager(null); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)) + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)) .isFalse(); // set telecom feature to false - blocked when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(false); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)) + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)) .isFalse(); // telecom manager is not ready - blocked mService.setTelecomManager(mTelecomManager); when(mTelecomManager.isInCall()).thenThrow(new IllegalStateException("not ready")); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)) + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)) .isFalse(); } @@ -11058,7 +11058,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { try { mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false); assertFalse("CallStyle should not be allowed without a valid use case", true); } catch (IllegalArgumentException error) { assertThat(error.getMessage()).contains("CallStyle"); @@ -11078,7 +11078,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); + } + + @Test + public void checkCallStyleNotification_allowedForByForegroundService() throws Exception { + Person person = new Person.Builder().setName("caller").build(); + Notification n = new Notification.Builder(mContext, "test") + // Without FLAG_FOREGROUND_SERVICE. + //.setFlag(FLAG_FOREGROUND_SERVICE, true) + .setStyle(Notification.CallStyle.forOngoingCall( + person, mock(PendingIntent.class))) + .build(); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false, + true /* byForegroundService */)).isTrue(); } @Test @@ -11094,7 +11112,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } @Test @@ -11110,7 +11128,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } @Test @@ -11126,7 +11144,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } @Test