From bbbdc5ecb3e71be660c93981dc2041f434fcf09d Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 11 Jan 2023 18:58:41 +0000 Subject: Revert "Ensure that only SysUI can override pending intent launch flags" This reverts commit c4d3106e347922610f8c554de3ae238175ed393e. Reason for revert: b/264884187, b/264885689 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48acfb0f1d71912e757cadd505901471c1df4d4c) Merged-In: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba Change-Id: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba --- .../core/java/com/android/server/am/PendingIntentRecord.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index d5981bb60215..7e8dcd8db7b1 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -369,16 +369,11 @@ public final class PendingIntentRecord extends IIntentSender.Stub { resolvedType = key.requestResolvedType; } - // Apply any launch flags from the ActivityOptions. This is used only by SystemUI - // to ensure that we can launch the pending intent with a consistent launch mode even - // if the provided PendingIntent is immutable (ie. to force an activity to launch into - // a new task, or to launch multiple instances if supported by the app) + // Apply any launch flags from the ActivityOptions. This is to ensure that the caller + // can specify a consistent launch mode even if the PendingIntent is immutable final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { - // TODO(b/254490217): Move this check into SafeActivityOptions - if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) { - finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); - } + finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); } // Extract options before clearing calling identity -- cgit v1.2.3 From 6285b40ca767b9efebbf09f49026f1806f1e4d9a Mon Sep 17 00:00:00 2001 From: Lucas Lin Date: Fri, 3 Mar 2023 08:13:50 +0000 Subject: Sanitize VPN label to prevent HTML injection This commit will try to sanitize the content of VpnDialog. This commit creates a function which will try to sanitize the VPN label, if the sanitized VPN label is different from the original one, which means the VPN label might contain HTML tag or the VPN label violates the words restriction(may contain some wording which will mislead the user). For this kind of case, show the package name instead of the VPN label to prevent misleading the user. The malicious VPN app might be able to add a large number of line breaks with HTML in order to hide the system-displayed text from the user in the connection request dialog. Thus, sanitizing the content of the dialog is needed. Bug: 204554636 Test: N/A (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2178216b98bf9865edee198f45192f0b883624ab) Merged-In: I8eb890fd2e5797d8d6ab5b12f9c628bc9616081d Change-Id: I8eb890fd2e5797d8d6ab5b12f9c628bc9616081d --- packages/VpnDialogs/res/values/strings.xml | 28 ++++++++++++ .../src/com/android/vpndialogs/ConfirmDialog.java | 53 ++++++++++++++++++++-- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml index f971a0916837..a85b8e4ff553 100644 --- a/packages/VpnDialogs/res/values/strings.xml +++ b/packages/VpnDialogs/res/values/strings.xml @@ -100,4 +100,32 @@ without any consequences. [CHAR LIMIT=20] --> Dismiss + + + %1$s… ( + %2$s) + + + + + %1$s ( + %2$s) + diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index fb2367843fc1..2b3202e0a982 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -40,12 +40,18 @@ public class ConfirmDialog extends AlertActivity implements DialogInterface.OnClickListener, ImageGetter { private static final String TAG = "VpnConfirm"; + // Usually the label represents the app name, 150 code points might be enough to display the app + // name, and 150 code points won't cover the warning message from VpnDialog. + static final int MAX_VPN_LABEL_LENGTH = 150; + @VpnManager.VpnType private final int mVpnType; private String mPackage; private VpnManager mVm; + private View mView; + public ConfirmDialog() { this(VpnManager.TYPE_VPN_SERVICE); } @@ -54,6 +60,42 @@ public class ConfirmDialog extends AlertActivity mVpnType = vpnType; } + /** + * This function will use the string resource to combine the VPN label and the package name. + * + * If the VPN label violates the length restriction, the first 30 code points of VPN label and + * the package name will be returned. Or return the VPN label and the package name directly if + * the VPN label doesn't violate the length restriction. + * + * The result will be something like, + * - ThisIsAVeryLongVpnAppNameWhich... (com.vpn.app) + * if the VPN label violates the length restriction. + * or + * - VpnLabelWith<br>HtmlTag (com.vpn.app) + * if the VPN label doesn't violate the length restriction. + * + */ + private String getSimplifiedLabel(String vpnLabel, String packageName) { + if (vpnLabel.codePointCount(0, vpnLabel.length()) > 30) { + return getString(R.string.sanitized_vpn_label_with_ellipsis, + vpnLabel.substring(0, vpnLabel.offsetByCodePoints(0, 30)), + packageName); + } + + return getString(R.string.sanitized_vpn_label, vpnLabel, packageName); + } + + protected String getSanitizedVpnLabel(String vpnLabel, String packageName) { + final String sanitizedVpnLabel = Html.escapeHtml(vpnLabel); + final boolean exceedMaxVpnLabelLength = sanitizedVpnLabel.codePointCount(0, + sanitizedVpnLabel.length()) > MAX_VPN_LABEL_LENGTH; + if (exceedMaxVpnLabelLength || !vpnLabel.equals(sanitizedVpnLabel)) { + return getSimplifiedLabel(sanitizedVpnLabel, packageName); + } + + return sanitizedVpnLabel; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -75,15 +117,16 @@ public class ConfirmDialog extends AlertActivity finish(); return; } - View view = View.inflate(this, R.layout.confirm, null); - ((TextView) view.findViewById(R.id.warning)).setText( - Html.fromHtml(getString(R.string.warning, getVpnLabel()), - this, null /* tagHandler */)); + mView = View.inflate(this, R.layout.confirm, null); + ((TextView) mView.findViewById(R.id.warning)).setText( + Html.fromHtml(getString(R.string.warning, getSanitizedVpnLabel( + getVpnLabel().toString(), mPackage)), + this /* imageGetter */, null /* tagHandler */)); mAlertParams.mTitle = getText(R.string.prompt); mAlertParams.mPositiveButtonText = getText(android.R.string.ok); mAlertParams.mPositiveButtonListener = this; mAlertParams.mNegativeButtonText = getText(android.R.string.cancel); - mAlertParams.mView = view; + mAlertParams.mView = mView; setupAlert(); getWindow().setCloseOnTouchOutside(false); -- cgit v1.2.3 From 61bb656f3b19cfb7acec7c2a41206e9ce91283fc Mon Sep 17 00:00:00 2001 From: Michael Groover Date: Fri, 31 Mar 2023 21:31:22 +0000 Subject: Limit the number of supported v1 and v2 signers The v1 and v2 APK Signature Schemes support multiple signers; this was intended to allow multiple entities to sign an APK. Previously, the platform had no limits placed on the number of signers supported in an APK, but this commit sets a hard limit of 10 supported signers for these signature schemes to ensure a large number of signers does not place undue burden on the platform. Bug: 266580022 Test: Manually verified the platform only allowed an APK with the maximum number of supported signers. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6f6ee8a55f37c2b8c0df041b2bd53ec928764597) Merged-In: I6aa86b615b203cdc69d58a593ccf8f18474ca091 Change-Id: I6aa86b615b203cdc69d58a593ccf8f18474ca091 --- core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java | 10 ++++++++++ core/java/android/util/jar/StrictJarVerifier.java | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index f74990a82327..0f1ab7fd1c34 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -74,6 +74,11 @@ public class ApkSignatureSchemeV2Verifier { private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; + /** + * The maximum number of signers supported by the v2 APK signature scheme. + */ + private static final int MAX_V2_SIGNERS = 10; + /** * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature. * @@ -182,6 +187,11 @@ public class ApkSignatureSchemeV2Verifier { } while (signers.hasRemaining()) { signerCount++; + if (signerCount > MAX_V2_SIGNERS) { + throw new SecurityException( + "APK Signature Scheme v2 only supports a maximum of " + MAX_V2_SIGNERS + + " signers"); + } try { ByteBuffer signer = getLengthPrefixedSlice(signers); X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory); diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java index 45254908c5c9..a6aca330d323 100644 --- a/core/java/android/util/jar/StrictJarVerifier.java +++ b/core/java/android/util/jar/StrictJarVerifier.java @@ -78,6 +78,11 @@ class StrictJarVerifier { "SHA1", }; + /** + * The maximum number of signers supported by the JAR signature scheme. + */ + private static final int MAX_JAR_SIGNERS = 10; + private final String jarName; private final StrictJarManifest manifest; private final HashMap metaEntries; @@ -293,10 +298,16 @@ class StrictJarVerifier { return false; } + int signerCount = 0; Iterator it = metaEntries.keySet().iterator(); while (it.hasNext()) { String key = it.next(); if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) { + if (++signerCount > MAX_JAR_SIGNERS) { + throw new SecurityException( + "APK Signature Scheme v1 only supports a maximum of " + MAX_JAR_SIGNERS + + " signers"); + } verifyCertificate(key); it.remove(); } -- cgit v1.2.3 From 10c796526928c0d15c34a6b02615dfffce9b46d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Thu, 13 Apr 2023 17:58:22 +0200 Subject: Grant URI permissions to the CallStyle-related ones This will also verify that the caller app can actually grant them. Fix: 274592467 Test: atest NotificationManagerServiceTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4dee5aab12e95cd8b4d663ad050f07b0f2433596) Merged-In: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc Change-Id: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc --- core/java/android/app/Notification.java | 8 ++++++++ .../NotificationManagerServiceTest.java | 23 ++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2c02be7dc6b9..c32bbed27886 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2837,6 +2837,14 @@ public class Notification implements Parcelable } } + if (isStyle(CallStyle.class) & extras != null) { + Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); + if (callPerson != null) { + visitor.accept(callPerson.getIconUri()); + } + visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); + } + if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } 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 61afa9cba433..a6e116a6a70a 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4467,6 +4467,29 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(backgroundImage)); } + @Test + public void testVisitUris_callStyle() { + Icon personIcon = Icon.createWithContentUri("content://media/person"); + Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); + Person callingPerson = new Person.Builder().setName("Someone") + .setIcon(personIcon) + .build(); + PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent) + .setVerificationIcon(verificationIcon)) + .setContentTitle("Calling...") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(personIcon.getUri())); + verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); + } + @Test public void testVisitUris_audioContentsString() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); -- cgit v1.2.3 From 5f678fce5ddefe30ffe58355a304bff27a04d6f1 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 8 Feb 2023 01:04:46 +0000 Subject: Only allow NEW_TASK flag when adjusting pending intents Bug: 243794108 Test: atest CtsSecurityBulletinHostTestCases:android.security.cts.CVE_2023_20918 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c62d2e1021a030f4f0ae5fcfc8fe8e0875fa669f) Merged-In: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b Change-Id: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b --- core/java/android/app/ActivityOptions.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 3a813224f6d8..6715754b41ad 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -20,6 +20,8 @@ import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIO import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.view.Display.INVALID_DISPLAY; import android.annotation.IntDef; @@ -1553,7 +1555,9 @@ public class ActivityOptions extends ComponentOptions { * @hide */ public int getPendingIntentLaunchFlags() { - return mPendingIntentLaunchFlags; + // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217 + return mPendingIntentLaunchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND); } /** -- cgit v1.2.3 From d76a992efaada33e0acff57fe2d01849fedd5930 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Tue, 28 Mar 2023 13:15:04 -0700 Subject: Dismiss keyguard when simpin auth'd and... security method is none. This is mostly to fix the case where we auth sim pin in the set up wizard and it goes straight to keyguard instead of the setup wizard activity. This works with the prevent bypass keyguard flag because the device should be noe secure in this case. Fixes: 222446076 Test: turn locked sim on, which opens the sim pin screen. Auth the screen and observe that keyguard is not shown. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48fa9bef3451e4a358c941af5b230f99881c5cb6) Cherry-picking this CL as a security fix Bug: 222446076 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:65ea56f54c059584eb27ec53d486dba8161316ab) Merged-In: Id302c41f63028bc6dd58ba686e23d73565de9675 Change-Id: Id302c41f63028bc6dd58ba686e23d73565de9675 --- .../src/com/android/keyguard/KeyguardSecurityContainerController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index d501ccdd5103..97ed829380f3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -426,7 +426,7 @@ public class KeyguardSecurityContainerController extends ViewController Date: Fri, 21 Apr 2023 15:39:22 +0000 Subject: Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS. Also added the person URIs in the test, since they weren't being checked. Test: atest NotificationManagerServiceTest & tested with POC from bug Bug: 276729064 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:43b1711332763788c7abf05c3baa931296c45bbb) Merged-In: I848545f7aee202495c515f47a32871a2cb6ae707 Change-Id: I848545f7aee202495c515f47a32871a2cb6ae707 --- core/java/android/app/Notification.java | 11 ++++++++ .../NotificationManagerServiceTest.java | 32 ++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index c32bbed27886..4a2c2d0bc261 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2807,6 +2807,17 @@ public class Notification implements Parcelable if (person != null) { visitor.accept(person.getIconUri()); } + + final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) + extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + if (history != null) { + for (int i = 0; i < history.length; i++) { + RemoteInputHistoryItem item = history[i]; + if (item.getUri() != null) { + visitor.accept(item.getUri()); + } + } + } } if (isStyle(MessagingStyle.class) && extras != null) { 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 a6e116a6a70a..330583d23a69 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -119,6 +119,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -4450,10 +4451,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("People List Person 1") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("People List Person 2") + .setIcon(personIcon3) + .build(); + final Uri historyUri1 = Uri.parse("content://com.example/history1"); + final Uri historyUri2 = Uri.parse("content://com.example/history2"); + final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1, + "a"); + final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2, + "b"); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString()); + extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); + extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, + new ArrayList<>(Arrays.asList(person2, person3))); + extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + new RemoteInputHistoryItem[]{historyItem1, historyItem2}); Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") @@ -4465,6 +4492,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + verify(visitor, times(1)).accept(eq(historyUri1)); + verify(visitor, times(1)).accept(eq(historyUri2)); } @Test -- cgit v1.2.3 From 339f9f61729a45ab4bbe0e26baf3f3f5f6cea351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Kurucz?= Date: Fri, 21 Apr 2023 09:45:07 +0000 Subject: Truncate ShortcutInfo Id Creating Conversation with a ShortcutId longer than 65_535 (max unsigned short), we did not save the conversation settings into the notification_policy.xml due to a restriction in FastDataOutput. This put us to a state where the user changing the importance or turning off the notifications for the given conversation had no effect on notification behavior. Fixes: 273729476 Test: atest ShortcutManagerTest2 Test: Create a test app which creates a Conversation with a long shortcutId. Go to the Conversation Settings and turn off Notifications. Post a new Notification to this Conversation and see if it is displayed. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:fee62a33da3e9a15d4ab5e4c8f730b50eae67cbe) Merged-In: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 Change-Id: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 --- core/java/android/content/pm/ShortcutInfo.java | 20 +++++++++++++++++--- .../com/android/server/pm/ShortcutManagerTest2.java | 10 ++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index a264bebb5d88..3308d73d1d79 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -276,6 +276,12 @@ public final class ShortcutInfo implements Parcelable { */ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; + /** + * The maximum length of Shortcut ID. IDs will be truncated at this limit. + * @hide + */ + public static final int MAX_ID_LENGTH = 1000; + /** @hide */ @IntDef(prefix = { "DISABLED_REASON_" }, value = { DISABLED_REASON_NOT_DISABLED, @@ -453,8 +459,7 @@ public final class ShortcutInfo implements Parcelable { private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); - - mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); + mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided")); // Note we can't do other null checks here because SM.updateShortcuts() takes partial // information. @@ -558,6 +563,14 @@ public final class ShortcutInfo implements Parcelable { return ret; } + @NonNull + private static String getSafeId(@NonNull String id) { + if (id.length() > MAX_ID_LENGTH) { + return id.substring(0, MAX_ID_LENGTH); + } + return id; + } + /** * Throws if any of the mandatory fields is not set. * @@ -2141,7 +2154,8 @@ public final class ShortcutInfo implements Parcelable { final ClassLoader cl = getClass().getClassLoader(); mUserId = source.readInt(); - mId = source.readString8(); + mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(), + "Shortcut ID must be provided")); mPackageName = source.readString8(); mActivity = source.readParcelable(cl); mFlags = source.readInt(); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 90a127701505..27091b7d546a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -53,6 +53,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; +import java.util.Collections; import java.util.Locale; /** @@ -223,6 +224,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { }); } + public void testShortcutIdTruncated() { + ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), + String.join("", Collections.nCopies(Short.MAX_VALUE, "s"))).build(); + + assertTrue( + "id must be truncated to MAX_ID_LENGTH", + si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH); + } + public void testShortcutInfoParcel() { setCaller(CALLING_PACKAGE_1, USER_10); ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext) -- cgit v1.2.3 From e76bfa708f84ab8b59ffdb6405d46d8ba69aa1d4 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 27 Apr 2023 12:36:05 +0000 Subject: Visit URIs in landscape/portrait custom remote views. Bug: 277740848 Test: atest RemoteViewsTest NotificationManagerServiceTest & tested with POC from bug (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b4692946c10d11c1e935869e11dc709a9cdcba69) Merged-In: I7d3d35df0ec38945019f71755bed8797b7af4517 Change-Id: I7d3d35df0ec38945019f71755bed8797b7af4517 --- core/java/android/widget/RemoteViews.java | 6 ++ .../src/android/widget/RemoteViewsTest.java | 64 ++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 1784dcfc505f..fe6eb32f0158 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -709,6 +709,12 @@ public class RemoteViews implements Parcelable, Filter { mActions.get(i).visitUris(visitor); } } + if (mLandscape != null) { + mLandscape.visitUris(visitor); + } + if (mPortrait != null) { + mPortrait.visitUris(visitor); + } } private static void visitIconUri(Icon icon, @NonNull Consumer visitor) { diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 059c764213bc..6cdf72071194 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -20,6 +20,10 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -29,6 +33,8 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Parcel; @@ -50,6 +56,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * Tests for RemoteViews. @@ -499,4 +506,61 @@ public class RemoteViewsTest { return null; } } + + @Test + public void visitUris() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + + final Uri imageUri = Uri.parse("content://media/image"); + final Icon icon1 = Icon.createWithContentUri("content://media/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://media/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://media/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://media/icon4"); + views.setImageViewUri(R.id.image, imageUri); + views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUri)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_separateOrientation() { + final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://landscape/image"); + final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4"); + landscape.setImageViewUri(R.id.image, imageUriL); + landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews portrait = new RemoteViews(mPackage, 33); + final Uri imageUriP = Uri.parse("content://portrait/image"); + final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1"); + final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2"); + final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3"); + final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4"); + portrait.setImageViewUri(R.id.image, imageUriP); + portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P); + + RemoteViews views = new RemoteViews(landscape, portrait); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriP)); + verify(visitor, times(1)).accept(eq(icon1P.getUri())); + verify(visitor, times(1)).accept(eq(icon2P.getUri())); + verify(visitor, times(1)).accept(eq(icon3P.getUri())); + verify(visitor, times(1)).accept(eq(icon4P.getUri())); + } } -- cgit v1.2.3 From 8250d82860cf523a02024b42ec84e206771330d6 Mon Sep 17 00:00:00 2001 From: Jing Ji Date: Tue, 25 Oct 2022 22:39:52 -0700 Subject: DO NOT MERGE: ActivityManager#killBackgroundProcesses can kill caller's own app only unless it's a system app. Bug: 239423414 Bug: 223376078 Test: atest CtsAppTestCases:ActivityManagerTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f5943a364164f5d88cac42df7a7749a205932e7c) Merged-In: Iac6baa889965b8ffecd9a43179a4c96632ad1d02 Change-Id: Iac6baa889965b8ffecd9a43179a4c96632ad1d02 --- core/java/android/app/ActivityManager.java | 3 ++ core/res/AndroidManifest.xml | 6 +++- .../android/server/am/ActivityManagerService.java | 32 ++++++++++++++++++++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 9d59225f4344..a9772e033849 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3666,6 +3666,9 @@ public class ActivityManager { * processes to reclaim memory; the system will take care of restarting * these processes in the future as needed. * + *

Third party applications can only use this API to kill their own processes. + *

+ * * @param packageName The name of the package whose processes are to * be killed. */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index dd541f2cb3e0..7b3fb562a70f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2815,7 +2815,11 @@ android:protectionLevel="normal" /> = FIRST_APPLICATION_UID + && (proc == null || !proc.info.isSystemApp())) { + final String msg = "Permission Denial: killAllBackgroundProcesses() from pid=" + + callingPid + ", uid=" + callingUid + " is not allowed"; + Slog.w(TAG, msg); + // Silently return to avoid existing apps from crashing. + return; + } + final long callingId = Binder.clearCallingIdentity(); try { synchronized (this) { -- cgit v1.2.3 From d88ac0cfcc8ec4d993b8f6e4c83d68881a23cc59 Mon Sep 17 00:00:00 2001 From: Austin Borger Date: Sat, 18 Mar 2023 12:56:12 -0700 Subject: ActivityManagerService: Allow openContentUri from vendor/system/product. Apps should not have direct access to this entry point. Check that the caller is a vendor, system, or product package. Test: Ran PoC app and CtsMediaPlayerTestCases. Bug: 236688380 (cherry picked from commit d0ba7467c2cb2815f94f6651cbb1c2f405e8e9c7) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e37820e47c383aecf9d1173a0676c27e6a59ce4f) Merged-In: I0335496d28fa5fc3bfe1fecd4be90040b0b3687f Change-Id: I0335496d28fa5fc3bfe1fecd4be90040b0b3687f --- .../java/com/android/server/am/ActivityManagerService.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5de1b3e97db4..77a6e9d6b658 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6313,7 +6313,7 @@ public class ActivityManagerService extends IActivityManager.Stub mActivityTaskManager.unhandledBack(); } - // TODO: Move to ContentProviderHelper? + // TODO: Replace this method with one that returns a bound IContentProvider. public ParcelFileDescriptor openContentUri(String uriString) throws RemoteException { enforceNotIsolatedCaller("openContentUri"); final int userId = UserHandle.getCallingUserId(); @@ -6342,6 +6342,16 @@ public class ActivityManagerService extends IActivityManager.Stub Log.e(TAG, "Cannot find package for uid: " + uid); return null; } + + final ApplicationInfo appInfo = mPackageManagerInt.getApplicationInfo( + androidPackage.getPackageName(), /*flags*/0, Process.SYSTEM_UID, + UserHandle.USER_SYSTEM); + if (!appInfo.isVendor() && !appInfo.isSystemApp() && !appInfo.isSystemExt() + && !appInfo.isProduct()) { + Log.e(TAG, "openContentUri may only be used by vendor/system/product."); + return null; + } + final AttributionSource attributionSource = new AttributionSource( Binder.getCallingUid(), androidPackage.getPackageName(), null); pfd = cph.provider.openFile(attributionSource, uri, "r", null); -- cgit v1.2.3 From 0c1c9f29935166ea59b30d5e37c70ba82dc6b632 Mon Sep 17 00:00:00 2001 From: Silin Huang Date: Wed, 12 Apr 2023 17:22:11 +0000 Subject: Do not load drawable for wallet card if the card image icon iscreated with content URI. This prevents the primary user from accessing the secondary user's photos for QAW card images. Test: manually, atest Bug: 272020068 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ff753ae693065685d85bbda6af2953905fdf434c) Merged-In: I6932c5131b3c795bac4ea9b537938e7ef4f3ea4e Change-Id: I6932c5131b3c795bac4ea9b537938e7ef4f3ea4e --- .../systemui/qs/tiles/QuickAccessWalletTile.java | 8 +++++- .../systemui/wallet/ui/WalletScreenController.java | 7 ++++- .../qs/tiles/QuickAccessWalletTileTest.java | 31 +++++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index d9919bdac889..b5484b952c9f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import static android.graphics.drawable.Icon.TYPE_URI; import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; @@ -240,7 +241,12 @@ public class QuickAccessWalletTile extends QSTileImpl { return; } mSelectedCard = cards.get(selectedIndex); - mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage(); + if (cardImageIcon.getType() == TYPE_URI) { + mCardViewDrawable = null; + } else { + mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + } refreshState(); } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index ba9b638fac99..2aafc14be551 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -318,7 +318,12 @@ public class WalletScreenController implements */ QAWalletCardViewInfo(Context context, WalletCard walletCard) { mWalletCard = walletCard; - mCardDrawable = mWalletCard.getCardImage().loadDrawable(context); + Icon cardImageIcon = mWalletCard.getCardImage(); + if (cardImageIcon.getType() == Icon.TYPE_URI) { + mCardDrawable = null; + } else { + mCardDrawable = mWalletCard.getCardImage().loadDrawable(context); + } Icon icon = mWalletCard.getCardIcon(); mIconDrawable = icon == null ? null : icon.loadDrawable(context); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index 8922b43b7447..1ff7cf7b8356 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -91,8 +91,11 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private static final String CARD_ID = "card_id"; private static final String LABEL = "QAW"; + private static final String CARD_DESCRIPTION = "•••• 1234"; private static final Icon CARD_IMAGE = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)); + private static final int PRIMARY_USER_ID = 0; + private static final int SECONDARY_USER_ID = 10; private final Drawable mTileIcon = mContext.getDrawable(R.drawable.ic_qs_wallet); private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET) @@ -119,6 +122,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private SecureSettings mSecureSettings; @Mock private QuickAccessWalletController mController; + @Mock + private Icon mCardImage; @Captor ArgumentCaptor mIntentCaptor; @Captor @@ -144,6 +149,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); when(mController.getWalletClient()).thenReturn(mQuickAccessWalletClient); + when(mCardImage.getType()).thenReturn(Icon.TYPE_URI); + when(mCardImage.loadDrawableAsUser(any(), eq(SECONDARY_USER_ID))).thenReturn(null); mTile = new QuickAccessWalletTile( mHost, @@ -418,6 +425,28 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { assertNotNull(mTile.getState().sideViewCustomDrawable); } + @Test + public void testQueryCards_notCurrentUser_hasCards_noSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + WalletCard walletCard = + new WalletCard.Builder( + CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build(); + GetWalletCardsResponse response = + new GetWalletCardsResponse(Collections.singletonList(walletCard), 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + @Test public void testQueryCards_noCards_notUpdateSideViewDrawable() { setUpWalletCard(/* hasCard= */ false); @@ -465,6 +494,6 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private WalletCard createWalletCard(Context context) { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); - return new WalletCard.Builder(CARD_ID, CARD_IMAGE, "•••• 1234", pendingIntent).build(); + return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build(); } } -- cgit v1.2.3 From b359f1d3981a88863a72bcc94c899c8250738589 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 27 Apr 2023 14:55:28 +0000 Subject: Verify URI permissions for notification shortcutIcon. Bug: 277593270 Test: atest NotificationManagerServiceTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8378f404de96927b1051765e93ad242860f5442c) Merged-In: Iaf2a9a82f18e018e60e6cdc020da6ebf7267e8b1 Change-Id: Iaf2a9a82f18e018e60e6cdc020da6ebf7267e8b1 --- core/java/android/app/Notification.java | 2 + .../NotificationManagerServiceTest.java | 87 +++++++++++++++++----- 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4a2c2d0bc261..13d9b5ca8863 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2846,6 +2846,8 @@ public class Notification implements Parcelable } } } + + visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON)); } if (isStyle(CallStyle.class) & extras != null) { 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 330583d23a69..e70a22d74b26 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4451,6 +4451,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon smallIcon = Icon.createWithContentUri("content://media/small/icon"); + final Icon largeIcon = Icon.createWithContentUri("content://media/large/icon"); final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); @@ -4484,7 +4486,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") - .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setSmallIcon(smallIcon) + .setLargeIcon(largeIcon) .addExtras(extras) .build(); @@ -4492,6 +4495,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(smallIcon.getUri())); + verify(visitor, times(1)).accept(eq(largeIcon.getUri())); verify(visitor, times(1)).accept(eq(personIcon1.getUri())); verify(visitor, times(1)).accept(eq(personIcon2.getUri())); verify(visitor, times(1)).accept(eq(personIcon3.getUri())); @@ -4499,6 +4504,68 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(historyUri2)); } + @Test + public void testVisitUris_audioContentsString() throws Exception { + final Uri audioContents = Uri.parse("content://com.example/audio"); + + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString()); + + Notification n = new Notification.Builder(mContext, "a") + .setContentTitle("notification with uris") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addExtras(extras) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + verify(visitor, times(1)).accept(eq(audioContents)); + } + + @Test + public void testVisitUris_messagingStyle() { + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person 1") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("Messaging Person 2") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("Messaging Person 3") + .setIcon(personIcon3) + .build(); + Icon shortcutIcon = Icon.createWithContentUri("content://media/shortcut"); + + Notification.Builder builder = new Notification.Builder(mContext, "a") + .setCategory(Notification.CATEGORY_MESSAGE) + .setContentTitle("new message!") + .setContentText("Conversation Notification") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + Notification.MessagingStyle.Message message1 = new Notification.MessagingStyle.Message( + "Marco?", System.currentTimeMillis(), person2); + Notification.MessagingStyle.Message message2 = new Notification.MessagingStyle.Message( + "Polo!", System.currentTimeMillis(), person3); + Notification.MessagingStyle style = new Notification.MessagingStyle(person1) + .addMessage(message1) + .addMessage(message2) + .setShortcutIcon(shortcutIcon); + builder.setStyle(style); + Notification n = builder.build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(shortcutIcon.getUri())); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + } + @Test public void testVisitUris_callStyle() { Icon personIcon = Icon.createWithContentUri("content://media/person"); @@ -4522,24 +4589,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); } - @Test - public void testVisitUris_audioContentsString() throws Exception { - final Uri audioContents = Uri.parse("content://com.example/audio"); - - Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString()); - - Notification n = new Notification.Builder(mContext, "a") - .setContentTitle("notification with uris") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .addExtras(extras) - .build(); - - Consumer visitor = (Consumer) spy(Consumer.class); - n.visitUris(visitor); - verify(visitor, times(1)).accept(eq(audioContents)); - } - @Test public void testSetNotificationPolicy_preP_setOldFields() { ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class); -- cgit v1.2.3 From 8620454d5457a2a2bd5a247973b9686ca34b9e49 Mon Sep 17 00:00:00 2001 From: Beverly Date: Mon, 8 May 2023 16:33:12 +0000 Subject: On device lockdown, always show the keyguard Manual test steps: 1. Enable app pinning and disable "Ask for PIN before unpinning" setting 2. Pin an app (ie: Settings) 3. Lockdown from the power menu Observe: user is brought to the keyguard, primary auth is required to enter the device. After entering credential, the device is still in app pinning mode. Test: atest KeyguardViewMediatorTest Test: manual steps outlined above Bug: 218495634 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:100ae42365d7fc8ba7d241e8c9a7ef6aa0cdb961) Merged-In: I9a7c5e1acadabd4484e58573331f98dba895f2a2 Change-Id: I9a7c5e1acadabd4484e58573331f98dba895f2a2 --- .../com/android/systemui/keyguard/KeyguardViewMediator.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b7916f9f09e1..5f40e8c923e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -677,6 +677,13 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, } } } + + @Override + public void onStrongAuthStateChanged(int userId) { + if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + doKeyguardLocked(null); + } + } }; ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { @@ -1535,7 +1542,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, } // if another app is disabling us, don't show - if (!mExternallyEnabled) { + if (!mExternallyEnabled + && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); mNeedToReshowWhenReenabled = true; -- cgit v1.2.3 From a2cbf2fb713c8e4d208ba01f9c5f7a0458129287 Mon Sep 17 00:00:00 2001 From: Pavel Grafov Date: Wed, 5 Apr 2023 15:15:41 +0000 Subject: Ensure policy has no absurdly long strings The following APIs now enforce limits and throw IllegalArgumentException when limits are violated: * DPM.setTrustAgentConfiguration() limits agent packgage name, component name, and strings within configuration bundle. * DPM.setPermittedAccessibilityServices() limits package names. * DPM.setPermittedInputMethods() limits package names. * DPM.setAccountManagementDisabled() limits account name. * DPM.setLockTaskPackages() limits package names. * DPM.setAffiliationIds() limits id. * DPM.transferOwnership() limits strings inside the bundle. Package names are limited at 223, because they become directory names and it is a filesystem restriction, see FrameworkParsingPackageUtils. All other strings are limited at 65535, because longer ones break binary XML serializer. The following APIs silently truncate strings that are long beyond reason: * DPM.setShortSupportMessage() truncates message at 200. * DPM.setLongSupportMessage() truncates message at 20000. * DPM.setOrganizationName() truncates org name at 200. Bug: 260729089 Test: atest com.android.server.devicepolicy (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:12c201509e911f4dddabf371bd22c93e097e5d99) Merged-In: Idcf54e408722f164d16bf2f24a00cd1f5b626d23 Change-Id: Idcf54e408722f164d16bf2f24a00cd1f5b626d23 --- .../android/app/admin/DevicePolicyManager.java | 3 +- .../devicepolicy/DevicePolicyManagerService.java | 93 +++++++++++++++++++++- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 91913848dc3e..41018b6f72d8 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -11411,7 +11411,8 @@ public class DevicePolicyManager { /** * Called by a device admin to set the long support message. This will be displayed to the user - * in the device administators settings screen. + * in the device administrators settings screen. If the message is longer than 20000 characters + * it may be truncated. *

* If the long support message needs to be localized, it is the responsibility of the * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 33096a20723b..00dd3a8e949d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -361,6 +361,7 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.DateFormat; import java.time.LocalDate; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -372,6 +373,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Queue; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -400,6 +402,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572; + // Binary XML serializer doesn't support longer strings + private static final int MAX_POLICY_STRING_LENGTH = 65535; + // FrameworkParsingPackageUtils#MAX_FILE_NAME_SIZE, Android packages are used in dir names. + private static final int MAX_PACKAGE_NAME_LENGTH = 223; + + private static final int MAX_LONG_SUPPORT_MESSAGE_LENGTH = 20000; + private static final int MAX_SHORT_SUPPORT_MESSAGE_LENGTH = 200; + private static final int MAX_ORG_NAME_LENGTH = 200; + private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1); private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms @@ -9956,6 +9967,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(admin, "admin is null"); Objects.requireNonNull(agent, "agent is null"); + enforceMaxPackageNameLength(agent.getPackageName()); + final String agentAsString = agent.flattenToString(); + enforceMaxStringLength(agentAsString, "agent name"); + if (args != null) { + enforceMaxStringLength(args, "args"); + } final int userHandle = UserHandle.getCallingUserId(); synchronized (getLockObject()) { ActiveAdmin ap = getActiveAdminForCallerLocked(admin, @@ -10194,6 +10211,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(who); if (packageList != null) { + for (String pkg : (List) packageList) { + enforceMaxPackageNameLength(pkg); + } + int userId = caller.getUserId(); final List enabledServices; long id = mInjector.binderClearCallingIdentity(); @@ -10360,6 +10381,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } if (packageList != null) { + for (String pkg : (List) packageList) { + enforceMaxPackageNameLength(pkg); + } + List enabledImes = mInjector.binderWithCleanCallingIdentity(() -> InputMethodManagerInternal.get().getEnabledInputMethodListAsUser(userId)); if (enabledImes != null) { @@ -11679,6 +11704,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Objects.requireNonNull(who, "ComponentName is null"); + enforceMaxStringLength(accountType, "account type"); + final CallerIdentity caller = getCallerIdentity(who); synchronized (getLockObject()) { /* @@ -12097,6 +12124,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throws SecurityException { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(packages, "packages is null"); + for (String pkg : packages) { + enforceMaxPackageNameLength(pkg); + } + final CallerIdentity caller = getCallerIdentity(who); synchronized (getLockObject()) { @@ -14147,6 +14178,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Objects.requireNonNull(who, "ComponentName is null"); + message = truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH); + final CallerIdentity caller = getCallerIdentity(who); synchronized (getLockObject()) { ActiveAdmin admin = getActiveAdminForUidLocked(who, caller.getUid()); @@ -14179,6 +14212,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return; } + + message = truncateIfLonger(message, MAX_LONG_SUPPORT_MESSAGE_LENGTH); + Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); synchronized (getLockObject()) { @@ -14328,6 +14364,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); + text = truncateIfLonger(text, MAX_ORG_NAME_LENGTH); + synchronized (getLockObject()) { ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); if (!TextUtils.equals(admin.organizationName, text)) { @@ -14577,9 +14615,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new IllegalArgumentException("ids must not be null"); } for (String id : ids) { - if (TextUtils.isEmpty(id)) { - throw new IllegalArgumentException("ids must not contain empty string"); - } + Preconditions.checkArgument(!TextUtils.isEmpty(id), "ids must not have empty string"); + enforceMaxStringLength(id, "affiliation id"); } final Set affiliationIds = new ArraySet<>(ids); @@ -15862,6 +15899,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "Provided administrator and target are the same object."); Preconditions.checkArgument(!admin.getPackageName().equals(target.getPackageName()), "Provided administrator and target have the same package name."); + if (bundle != null) { + enforceMaxStringLength(bundle, "bundle"); + } final CallerIdentity caller = getCallerIdentity(admin); Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); @@ -17971,4 +18011,51 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3 ); } + + /** + * Truncates char sequence to maximum length, nulls are ignored. + */ + private static CharSequence truncateIfLonger(CharSequence input, int maxLength) { + return input == null || input.length() <= maxLength + ? input + : input.subSequence(0, maxLength); + } + + /** + * Throw if string argument is too long to be serialized. + */ + private static void enforceMaxStringLength(String str, String argName) { + Preconditions.checkArgument( + str.length() <= MAX_POLICY_STRING_LENGTH, argName + " loo long"); + } + + private static void enforceMaxPackageNameLength(String pkg) { + Preconditions.checkArgument( + pkg.length() <= MAX_PACKAGE_NAME_LENGTH, "Package name too long"); + } + + /** + * Throw if persistable bundle contains any string that we can't serialize. + */ + private static void enforceMaxStringLength(PersistableBundle bundle, String argName) { + // Persistable bundles can have other persistable bundles as values, traverse with a queue. + Queue queue = new ArrayDeque<>(); + queue.add(bundle); + while (!queue.isEmpty()) { + PersistableBundle current = queue.remove(); + for (String key : current.keySet()) { + enforceMaxStringLength(key, "key in " + argName); + Object value = current.get(key); + if (value instanceof String) { + enforceMaxStringLength((String) value, "string value in " + argName); + } else if (value instanceof String[]) { + for (String str : (String[]) value) { + enforceMaxStringLength(str, "string value in " + argName); + } + } else if (value instanceof PersistableBundle) { + queue.add((PersistableBundle) value); + } + } + } + } } -- cgit v1.2.3 From 7cfac9ce8453911551a99637607d6e025074b5ff Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Wed, 17 May 2023 01:30:20 -0700 Subject: Preserve flags for non-runtime permissions upon package update. PermissionManagerServiceImpl.restorePermissionState() creates a new UID permission state for non-shared-UID packages that have been updated (i.e. replaced), however the existing logic for non-runtime permission never carried over the flags from the old state. This wasn't an issue for much older platforms because permission flags weren't used for non-runtime permissions, however since we are starting to use them for role protected permissions (ROLE_GRANTED) and app op permissions (USER_SET), we do need to preserver the permission flags. This change merges the logic for granting and revoking a non-runtime permission in restorePermissionState() into a single if branch, and appends the logic to copy the flag from the old state in that branch. Bug: 283006437 Test: PermissionFlagsTest#nonRuntimePermissionFlagsPreservedAfterReinstall (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0e1ebd84e27f5d4fa8bc6577705293251bcbac4f) Merged-In: Iea3c66710e7d28c6fc730b1939da64f1172b08db Change-Id: Iea3c66710e7d28c6fc730b1939da64f1172b08db --- .../pm/permission/PermissionManagerService.java | 88 ++++++++++++---------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 9d0b2543e5ef..dd8b96eab3d7 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -2882,29 +2882,55 @@ public class PermissionManagerService extends IPermissionManager.Stub { + pkg.getPackageName()); } - if ((bp.isNormal() && shouldGrantNormalPermission) - || (bp.isSignature() - && (!bp.isPrivileged() || CollectionUtils.contains( - isPrivilegedPermissionAllowlisted, permName)) - && (CollectionUtils.contains(shouldGrantSignaturePermission, - permName) - || (((bp.isPrivileged() && CollectionUtils.contains( - shouldGrantPrivilegedPermissionIfWasGranted, - permName)) || bp.isDevelopment() || bp.isRole()) - && origState.isPermissionGranted(permName)))) - || (bp.isInternal() - && (!bp.isPrivileged() || CollectionUtils.contains( - isPrivilegedPermissionAllowlisted, permName)) - && (CollectionUtils.contains(shouldGrantInternalPermission, - permName) - || (((bp.isPrivileged() && CollectionUtils.contains( - shouldGrantPrivilegedPermissionIfWasGranted, - permName)) || bp.isDevelopment() || bp.isRole()) - && origState.isPermissionGranted(permName))))) { - // Grant an install permission. - if (uidState.grantPermission(bp)) { - changedInstallPermission = true; + if (bp.isNormal() || bp.isSignature() || bp.isInternal()) { + if ((bp.isNormal() && shouldGrantNormalPermission) + || (bp.isSignature() + && (!bp.isPrivileged() || CollectionUtils.contains( + isPrivilegedPermissionAllowlisted, permName)) + && (CollectionUtils.contains(shouldGrantSignaturePermission, + permName) + || (((bp.isPrivileged() && CollectionUtils.contains( + shouldGrantPrivilegedPermissionIfWasGranted, + permName)) || bp.isDevelopment() + || bp.isRole()) + && origState.isPermissionGranted( + permName)))) + || (bp.isInternal() + && (!bp.isPrivileged() || CollectionUtils.contains( + isPrivilegedPermissionAllowlisted, permName)) + && (CollectionUtils.contains(shouldGrantInternalPermission, + permName) + || (((bp.isPrivileged() && CollectionUtils.contains( + shouldGrantPrivilegedPermissionIfWasGranted, + permName)) || bp.isDevelopment() + || bp.isRole()) + && origState.isPermissionGranted( + permName))))) { + // Grant an install permission. + if (uidState.grantPermission(bp)) { + changedInstallPermission = true; + } + } else { + if (DEBUG_PERMISSIONS) { + boolean wasGranted = uidState.isPermissionGranted(bp.getName()); + if (wasGranted || bp.isAppOp()) { + Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting") + + " permission " + perm + + " from package " + friendlyName + + " (protectionLevel=" + bp.getProtectionLevel() + + " flags=0x" + + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, + ps)) + + ")"); + } + } + if (uidState.revokePermission(bp)) { + changedInstallPermission = true; + } } + PermissionState origPermState = origState.getPermissionState(perm); + int flags = origPermState != null ? origPermState.getFlags() : 0; + uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, flags); } else if (bp.isRuntime()) { boolean hardRestricted = bp.isHardRestricted(); boolean softRestricted = bp.isSoftRestricted(); @@ -3018,22 +3044,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, flags); } else { - if (DEBUG_PERMISSIONS) { - boolean wasGranted = uidState.isPermissionGranted(bp.getName()); - if (wasGranted || bp.isAppOp()) { - Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting") - + " permission " + perm - + " from package " + friendlyName - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" - + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, - ps)) - + ")"); - } - } - if (uidState.removePermissionState(bp.getName())) { - changedInstallPermission = true; - } + Slog.wtf(LOG_TAG, "Unknown permission protection " + bp.getProtection() + + " for permission " + bp.getName()); } } -- cgit v1.2.3 From 5a9ef1c44d954805c564a34dec0a3ed61055066d Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Mon, 15 May 2023 16:15:55 +0000 Subject: Check URIs in notification public version. Bug: 276294099 Test: atest NotificationManagerServiceTest NotificationVisitUrisTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:67cd169d073486c7c047b80ab83843cdee69bf53) Merged-In: I670198b213abb2cb29a9865eb9d1e897700508b4 Change-Id: I670198b213abb2cb29a9865eb9d1e897700508b4 --- core/java/android/app/Notification.java | 4 ++++ .../notification/NotificationManagerServiceTest.java | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 13d9b5ca8863..1a4c8f0e9120 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2761,6 +2761,10 @@ public class Notification implements Parcelable * @hide */ public void visitUris(@NonNull Consumer visitor) { + if (publicVersion != null) { + publicVersion.visitUris(visitor); + } + visitor.accept(sound); if (tickerView != null) tickerView.visitUris(visitor); 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 e70a22d74b26..48ad44797cd7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4504,6 +4504,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(historyUri2)); } + @Test + public void testVisitUris_publicVersion() throws Exception { + final Icon smallIconPublic = Icon.createWithContentUri("content://media/small/icon"); + final Icon largeIconPrivate = Icon.createWithContentUri("content://media/large/icon"); + + Notification publicVersion = new Notification.Builder(mContext, "a") + .setContentTitle("notification with uris") + .setSmallIcon(smallIconPublic) + .build(); + Notification n = new Notification.Builder(mContext, "a") + .setLargeIcon(largeIconPrivate) + .setPublicVersion(publicVersion) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + verify(visitor, times(1)).accept(eq(smallIconPublic.getUri())); + verify(visitor, times(1)).accept(eq(largeIconPrivate.getUri())); + } + @Test public void testVisitUris_audioContentsString() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); -- cgit v1.2.3 From 4b0c724622a47bb57fc4fa48e66ace42a3f6dfb4 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Fri, 12 May 2023 15:41:09 +0000 Subject: Implement visitUris for RemoteViews ViewGroupActionAdd. This is to prevent a vulnerability where notifications can show resources belonging to other users, since the URI in the nested views was not being checked. Bug: 277740082 Test: atest RemoteViewsTest NotificationVisitUrisTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:850fd984e5f346645b5a941ed7307387c7e4c4de) Merged-In: I5c71f0bad0a6f6361eb5ceffe8d1e47e936d78f8 Change-Id: I5c71f0bad0a6f6361eb5ceffe8d1e47e936d78f8 --- core/java/android/widget/RemoteViews.java | 5 +++++ .../src/android/widget/RemoteViewsTest.java | 24 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index fe6eb32f0158..0c89a679101a 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -2560,6 +2560,11 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return VIEW_GROUP_ACTION_ADD_TAG; } + + @Override + public final void visitUris(@NonNull Consumer visitor) { + mNestedViews.visitUris(visitor); + } } /** diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 6cdf72071194..f0f9056cc5b7 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -528,6 +528,30 @@ public class RemoteViewsTest { verify(visitor, times(1)).accept(eq(icon4.getUri())); } + @Test + public void visitUris_nestedViews() { + final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test); + + final RemoteViews inner = new RemoteViews(mPackage, 33); + final Uri imageUriI = Uri.parse("content://inner/image"); + final Icon icon1 = Icon.createWithContentUri("content://inner/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://inner/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://inner/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://inner/icon4"); + inner.setImageViewUri(R.id.image, imageUriI); + inner.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + outer.addView(R.id.layout, inner); + + Consumer visitor = (Consumer) spy(Consumer.class); + outer.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriI)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + @Test public void visitUris_separateOrientation() { final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); -- cgit v1.2.3 From f58e6b45ee55390e0bfe2259b5ad35aa4c90520b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Budnik?= Date: Tue, 4 Apr 2023 17:58:26 +0000 Subject: Validate ComponentName for MediaButtonBroadcastReceiver This is a security fix for b/270049379. Bug: 270049379 Test: atest CtsMediaMiscTestCases (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c573c83a2aa36ca022302f675d705518dd723a3c) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ba546a306217389a8ff9e5e948612651fd496081) Merged-In: I05626f7abf1efef86c9e01ee3f077d7177d7f662 Change-Id: I05626f7abf1efef86c9e01ee3f077d7177d7f662 --- media/java/android/media/session/MediaSession.java | 8 ++++-- .../android/server/media/MediaSessionRecord.java | 33 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 24118b086c24..a14999df666c 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -297,9 +297,11 @@ public final class MediaSession { * class that should receive media buttons. This allows restarting playback after the session * has been stopped. If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON} * intent will be sent to the broadcast receiver. - *

- * Note: The given {@link android.content.BroadcastReceiver} should belong to the same package - * as the context that was given when creating {@link MediaSession}. + * + *

Note: The given {@link android.content.BroadcastReceiver} should belong to the same + * package as the context that was given when creating {@link MediaSession}. + * + *

Calls with invalid or non-existent receivers will be ignored. * * @param broadcastReceiver the component name of the BroadcastReceiver class */ diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 9bbcec98ef71..53592baee6b7 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -16,12 +16,17 @@ package com.android.server.media; +import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioSystem; @@ -52,6 +57,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; +import android.os.UserHandle; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -879,6 +885,22 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } }; + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + private static boolean componentNameExists( + @NonNull ComponentName componentName, @NonNull Context context, int userId) { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mediaButtonIntent.setComponent(componentName); + + UserHandle userHandle = UserHandle.of(userId); + PackageManager pm = context.getPackageManager(); + + List resolveInfos = + pm.queryBroadcastReceiversAsUser( + mediaButtonIntent, /* flags */ 0, userHandle); + return !resolveInfos.isEmpty(); + } + private final class SessionStub extends ISession.Stub { @Override public void destroySession() throws RemoteException { @@ -949,6 +971,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } @Override + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException { final long token = Binder.clearCallingIdentity(); try { @@ -964,6 +987,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR != 0) { return; } + + if (!componentNameExists(receiver, mContext, mUserId)) { + Log.w( + TAG, + "setMediaButtonBroadcastReceiver(): " + + "Ignoring invalid component name=" + + receiver); + return; + } + mMediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mUserId, receiver); mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); } finally { -- cgit v1.2.3 From ecd105bfcc227086a41d0ab95bd67bcdcbd3b8c1 Mon Sep 17 00:00:00 2001 From: Michael Mikhail Date: Fri, 28 Apr 2023 16:17:16 +0000 Subject: Verify URI permissions in MediaMetadata Add a check for URI permission to make sure that user can access the URI set in MediaMetadata. If permission is denied, clear the URI string set in metadata. Bug: 271851153 Test: atest MediaSessionTest Test: Verified by POC app attached in bug, image of second user is not the UMO background of the first user. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b8a7fd8e6f41ee54d27c1e7aaa15b4a3f5365a02) Merged-In: I384f8e230c909d8fc8e5f147e2fd3558fec44626 Change-Id: I384f8e230c909d8fc8e5f147e2fd3558fec44626 --- .../android/server/media/MediaSessionRecord.java | 53 ++++++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 53592baee6b7..82dd478aa028 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -22,6 +22,8 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.app.PendingIntent; import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -63,6 +65,9 @@ import android.util.EventLog; import android.util.Log; import android.view.KeyEvent; +import com.android.server.LocalServices; +import com.android.server.uri.UriGrantsManagerInternal; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -77,6 +82,10 @@ import java.util.concurrent.CopyOnWriteArrayList; // TODO(jaewan): Do not call service method directly -- introduce listener instead. public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl { private static final String TAG = "MediaSessionRecord"; + private static final String[] ART_URIS = new String[] { + MediaMetadata.METADATA_KEY_ALBUM_ART_URI, + MediaMetadata.METADATA_KEY_ART_URI, + MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI}; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); /** @@ -130,6 +139,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR private final SessionStub mSession; private final SessionCb mSessionCb; private final MediaSessionService mService; + private final UriGrantsManagerInternal mUgmInternal; private final Context mContext; private final boolean mVolumeAdjustmentForRemoteGroupSessions; @@ -191,6 +201,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mAudioAttrs = DEFAULT_ATTRIBUTES; mPolicies = policies; + mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class); mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean( com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); @@ -1013,21 +1024,45 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription) throws RemoteException { synchronized (mLock) { - MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata) - .build(); - // This is to guarantee that the underlying bundle is unparceled - // before we set it to prevent concurrent reads from throwing an - // exception - if (temp != null) { - temp.size(); - } - mMetadata = temp; mDuration = duration; mMetadataDescription = metadataDescription; + mMetadata = sanitizeMediaMetadata(metadata); } mHandler.post(MessageHandler.MSG_UPDATE_METADATA); } + private MediaMetadata sanitizeMediaMetadata(MediaMetadata metadata) { + if (metadata == null) { + return null; + } + MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(metadata); + for (String key: ART_URIS) { + String uriString = metadata.getString(key); + if (TextUtils.isEmpty(uriString)) { + continue; + } + Uri uri = Uri.parse(uriString); + if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + continue; + } + try { + mUgmInternal.checkGrantUriPermission(getUid(), + getPackageName(), + ContentProvider.getUriWithoutUserId(uri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(uri, getUserId())); + } catch (SecurityException e) { + metadataBuilder.putString(key, null); + } + } + MediaMetadata sanitizedMetadata = metadataBuilder.build(); + // sanitizedMetadata.size() guarantees that the underlying bundle is unparceled + // before we set it to prevent concurrent reads from throwing an + // exception + sanitizedMetadata.size(); + return sanitizedMetadata; + } + @Override public void setPlaybackState(PlaybackState state) throws RemoteException { int oldState = mPlaybackState == null -- cgit v1.2.3 From 397c5072a868d4b779f163d3536cd917711734e7 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Tue, 23 May 2023 16:26:41 +0000 Subject: Check URIs in sized remote views. Bug: 277741109 Test: atest RemoteViewsTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ae0d45137b0f8ea49a085bbce4d39f901685c4a5) Merged-In: Iceb33606da3a49b9638ab21aeae17a168c1b411a Change-Id: Iceb33606da3a49b9638ab21aeae17a168c1b411a --- core/java/android/widget/RemoteViews.java | 5 +++ .../src/android/widget/RemoteViewsTest.java | 41 ++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 0c89a679101a..81061343844f 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -709,6 +709,11 @@ public class RemoteViews implements Parcelable, Filter { mActions.get(i).visitUris(visitor); } } + if (mSizedRemoteViews != null) { + for (int i = 0; i < mSizedRemoteViews.size(); i++) { + mSizedRemoteViews.get(i).visitUris(visitor); + } + } if (mLandscape != null) { mLandscape.visitUris(visitor); } diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index f0f9056cc5b7..e33b7e69caa4 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -38,6 +38,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Parcel; +import android.util.SizeF; import android.view.View; import android.view.ViewGroup; @@ -55,6 +56,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; @@ -587,4 +589,43 @@ public class RemoteViewsTest { verify(visitor, times(1)).accept(eq(icon3P.getUri())); verify(visitor, times(1)).accept(eq(icon4P.getUri())); } + + @Test + public void visitUris_sizedViews() { + final RemoteViews large = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://large/image"); + final Icon icon1L = Icon.createWithContentUri("content://large/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://large/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://large/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://large/icon4"); + large.setImageViewUri(R.id.image, imageUriL); + large.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews small = new RemoteViews(mPackage, 33); + final Uri imageUriS = Uri.parse("content://small/image"); + final Icon icon1S = Icon.createWithContentUri("content://small/icon1"); + final Icon icon2S = Icon.createWithContentUri("content://small/icon2"); + final Icon icon3S = Icon.createWithContentUri("content://small/icon3"); + final Icon icon4S = Icon.createWithContentUri("content://small/icon4"); + small.setImageViewUri(R.id.image, imageUriS); + small.setTextViewCompoundDrawables(R.id.text, icon1S, icon2S, icon3S, icon4S); + + HashMap sizedViews = new HashMap<>(); + sizedViews.put(new SizeF(300, 300), large); + sizedViews.put(new SizeF(100, 100), small); + RemoteViews views = new RemoteViews(sizedViews); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriS)); + verify(visitor, times(1)).accept(eq(icon1S.getUri())); + verify(visitor, times(1)).accept(eq(icon2S.getUri())); + verify(visitor, times(1)).accept(eq(icon3S.getUri())); + verify(visitor, times(1)).accept(eq(icon4S.getUri())); + } } -- cgit v1.2.3 From 7ec6b7c8acb930d8861d29d99e1785e9aa177d74 Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Mon, 22 May 2023 10:21:02 +0200 Subject: Fix PrivacyChip not visible issue Bug: 281807669 Test: Manual, i.e. posting the following sequence of events (within few milliseconds) to the scheduler and observe the behaviour with and without the fix: Mic in use -> Mic not in use -> Mic in use (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a45e1d045770eaabfdbf0e1212c9eb84caf1d565) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:20ea049a4a52dbc8d4e5ed957a2b6b9aa02a2f34) Merged-In: I9851e6ed4cb956d0459ef56251eb0ef3210764b8 Change-Id: I9851e6ed4cb956d0459ef56251eb0ef3210764b8 --- .../SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt index d4d84c138b20..f610101631dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt @@ -86,9 +86,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { } override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean { - return other is PrivacyEvent && - (other.privacyItems != privacyItems || - other.contentDescription != contentDescription) + return other is PrivacyEvent } override fun updateFromEvent(other: StatusEvent?) { -- cgit v1.2.3 From c180865708f7387420a88f8da525dc755f58b835 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 25 May 2023 11:43:43 +0000 Subject: Visit URIs in themed remoteviews icons. Bug: 281018094 Test: atest RemoteViewsTest NotificationVisitUrisTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:634a69b7700017eac534f3f58cdcc2572f3cc659) Merged-In: I2014bf21cf90267f7f1b3f370bf00ab7001b064e Change-Id: I2014bf21cf90267f7f1b3f370bf00ab7001b064e --- core/java/android/widget/RemoteViews.java | 10 +++++++++- .../tests/coretests/src/android/widget/RemoteViewsTest.java | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 81061343844f..3b5ba30fe922 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1808,7 +1808,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public final void visitUris(@NonNull Consumer visitor) { + public void visitUris(@NonNull Consumer visitor) { switch (this.type) { case URI: final Uri uri = (Uri) getParameterValue(null); @@ -2271,6 +2271,14 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return NIGHT_MODE_REFLECTION_ACTION_TAG; } + + @Override + public void visitUris(@NonNull Consumer visitor) { + if (this.type == ICON) { + visitIconUri((Icon) mDarkValue, visitor); + visitIconUri((Icon) mLightValue, visitor); + } + } } /** diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index e33b7e69caa4..7925136dd615 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -530,6 +530,19 @@ public class RemoteViewsTest { verify(visitor, times(1)).accept(eq(icon4.getUri())); } + @Test + public void visitUris_themedIcons() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + final Icon iconLight = Icon.createWithContentUri("content://light/icon"); + final Icon iconDark = Icon.createWithContentUri("content://dark/icon"); + views.setIcon(R.id.layout, "setLargeIcon", iconLight, iconDark); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(iconLight.getUri())); + verify(visitor, times(1)).accept(eq(iconDark.getUri())); + } + @Test public void visitUris_nestedViews() { final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test); -- cgit v1.2.3 From 4197725ffd3352cf922145af9586e0ccb1d0db20 Mon Sep 17 00:00:00 2001 From: Pranav Madapurmath Date: Thu, 25 May 2023 21:58:19 +0000 Subject: Resolve StatusHints image exploit across user. Because of the INTERACT_ACROSS_USERS permission, an app that implements a ConnectionService can upload an image icon belonging to another user by setting it in the StatusHints. Validating the construction of the StatusHints on the calling user would prevent a malicious app from registering a connection service with the embedded image icon from a different user. From additional feedback, this CL also addresses potential vulnerabilities in an app being able to directly invoke the binder for a means to manipulate the contents of the bundle that are passed with it. The targeted points of entry are in ConnectionServiceWrapper for the following APIs: handleCreateConnectionComplete, setStatusHints, addConferenceCall, and addExistingConnection. Fixes: 280797684 Test: Manual (verified that original exploit is no longer an issue). Test: Unit test for validating image in StatusHints constructor. Test: Unit tests to address vulnerabilities via the binder. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c51386a033634a64b6d2d5823880b32a86da9e7e) Merged-In: I6e70e238b3a5ace1cab41ec5796a6bb4d79769f2 Change-Id: I6e70e238b3a5ace1cab41ec5796a6bb4d79769f2 --- .../java/android/telecom/ParcelableConference.java | 12 ++++- telecomm/java/android/telecom/StatusHints.java | 53 +++++++++++++++++++++- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java index 1f8aafbca476..77034041f1fd 100644 --- a/telecomm/java/android/telecom/ParcelableConference.java +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -21,12 +21,12 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telecom.IVideoProvider; + import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.android.internal.telecom.IVideoProvider; - /** * A parcelable representation of a conference connection. * @hide @@ -287,6 +287,14 @@ public final class ParcelableConference implements Parcelable { return mCallDirection; } + public String getCallerDisplayName() { + return mCallerDisplayName; + } + + public int getCallerDisplayNamePresentation() { + return mCallerDisplayNamePresentation; + } + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator () { @Override diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java index 762c93a49022..b7346331dc60 100644 --- a/telecomm/java/android/telecom/StatusHints.java +++ b/telecomm/java/android/telecom/StatusHints.java @@ -16,14 +16,19 @@ package android.telecom; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.os.Binder; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; + +import com.android.internal.annotations.VisibleForTesting; import java.util.Objects; @@ -33,7 +38,7 @@ import java.util.Objects; public final class StatusHints implements Parcelable { private final CharSequence mLabel; - private final Icon mIcon; + private Icon mIcon; private final Bundle mExtras; /** @@ -48,10 +53,30 @@ public final class StatusHints implements Parcelable { public StatusHints(CharSequence label, Icon icon, Bundle extras) { mLabel = label; - mIcon = icon; + mIcon = validateAccountIconUserBoundary(icon, Binder.getCallingUserHandle()); mExtras = extras; } + /** + * @param icon + * @hide + */ + @VisibleForTesting + public StatusHints(@Nullable Icon icon) { + mLabel = null; + mExtras = null; + mIcon = icon; + } + + /** + * + * @param icon + * @hide + */ + public void setIcon(@Nullable Icon icon) { + mIcon = icon; + } + /** * @return A package used to load the icon. * @@ -112,6 +137,30 @@ public final class StatusHints implements Parcelable { return 0; } + /** + * Validates the StatusHints image icon to see if it's not in the calling user space. + * Invalidates the icon if so, otherwise returns back the original icon. + * + * @param icon + * @return icon (validated) + * @hide + */ + public static Icon validateAccountIconUserBoundary(Icon icon, UserHandle callingUserHandle) { + // Refer to Icon#getUriString for context. The URI string is invalid for icons of + // incompatible types. + if (icon != null && (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { + String encodedUser = icon.getUri().getEncodedUserInfo(); + // If there is no encoded user, the URI is calling into the calling user space + if (encodedUser != null) { + int userId = Integer.parseInt(encodedUser); + // Do not try to save the icon if the user id isn't in the calling user space. + if (userId != callingUserHandle.getIdentifier()) return null; + } + } + return icon; + } + @Override public void writeToParcel(Parcel out, int flags) { out.writeCharSequence(mLabel); -- cgit v1.2.3 From 02d01501cf04e5428694a3508e6546caee089274 Mon Sep 17 00:00:00 2001 From: Chandru S Date: Tue, 16 May 2023 10:41:07 -0700 Subject: Use Settings.System.getIntForUser instead of getInt to make sure user specific settings are used Bug: 265431505 Test: atest KeyguardViewMediatorTest (cherry picked from commit 625e009fc195ba5d658ca2d78ebb23d2770cc6c4) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:67e0292d0cb4350165117e03893aaee5c144c18e) Merged-In: I66a660c091c90a957a0fd1e144c013840db3f47e Change-Id: I66a660c091c90a957a0fd1e144c013840db3f47e --- .../com/android/systemui/keyguard/KeyguardViewMediator.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 5f40e8c923e5..46e1cfa3ad4b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1145,9 +1145,9 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, final ContentResolver cr = mContext.getContentResolver(); // From SecuritySettings - final long lockAfterTimeout = Settings.Secure.getInt(cr, + final long lockAfterTimeout = Settings.Secure.getIntForUser(cr, Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, - KEYGUARD_LOCK_AFTER_DELAY_DEFAULT); + KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, userId); // From DevicePolicyAdmin final long policyTimeout = mLockPatternUtils.getDevicePolicyManager() @@ -1159,8 +1159,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, timeout = lockAfterTimeout; } else { // From DisplaySettings - long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT, - KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT); + long displayTimeout = Settings.System.getIntForUser(cr, SCREEN_OFF_TIMEOUT, + KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT, userId); // policy in effect. Make sure we don't go beyond policy limit. displayTimeout = Math.max(displayTimeout, 0); // ignore negative values @@ -2031,7 +2031,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private void playSound(int soundId) { if (soundId == 0) return; final ContentResolver cr = mContext.getContentResolver(); - if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) { + int lockscreenSoundsEnabled = Settings.System.getIntForUser(cr, + Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1, + KeyguardUpdateMonitor.getCurrentUser()); + if (lockscreenSoundsEnabled == 1) { mLockSounds.stop(mLockSoundStreamId); // Init mAudioManager -- cgit v1.2.3 From d6683cb75720cdcdafad379986726b1d3836ab10 Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Fri, 19 May 2023 15:52:00 -0700 Subject: Remove unnecessary padding code Bug: 213170822 Remove the code that CursorWindow::writeToParcel() uses to ensure slot data is 4-byte aligned. Because mAllocOffset and mSlotsOffset are already 4-byte aligned, the alignment step here is unnecessary. CursorWindow::spaceInUse() returns the total space used. The tests verify that the total space used is always a multiple of 4 bytes. Test: atest * libandroidfw_tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5d4afa0986cbc440f458b4b8db05fd176ef3e6d2) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3af48e4421c86c4ba5a4c68bd21a93df7b31574f) Merged-In: I720699093d5c5a584283e5b76851938f449ffa21 Change-Id: I720699093d5c5a584283e5b76851938f449ffa21 --- libs/androidfw/CursorWindow.cpp | 10 ++++---- libs/androidfw/include/androidfw/CursorWindow.h | 3 +++ libs/androidfw/tests/CursorWindow_test.cpp | 31 ++++++++++++++++++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 3527eeead1d5..2a6dc7b95c07 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -108,7 +108,7 @@ status_t CursorWindow::maybeInflate() { { // Migrate existing contents into new ashmem region - uint32_t slotsSize = mSize - mSlotsOffset; + uint32_t slotsSize = sizeOfSlots(); uint32_t newSlotsOffset = mInflatedSize - slotsSize; memcpy(static_cast(newData), static_cast(mData), mAllocOffset); @@ -216,11 +216,9 @@ status_t CursorWindow::writeToParcel(Parcel* parcel) { if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail; } else { // Since we know we're going to be read-only on the remote side, - // we can compact ourselves on the wire, with just enough padding - // to ensure our slots stay aligned - size_t slotsSize = mSize - mSlotsOffset; - size_t compactedSize = mAllocOffset + slotsSize; - compactedSize = (compactedSize + 3) & ~3; + // we can compact ourselves on the wire. + size_t slotsSize = sizeOfSlots(); + size_t compactedSize = sizeInUse(); if (parcel->writeUint32(compactedSize)) goto fail; if (parcel->writeBool(false)) goto fail; void* dest = parcel->writeInplace(compactedSize); diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h index 6e55a9a0eb8b..9ec026a19c4c 100644 --- a/libs/androidfw/include/androidfw/CursorWindow.h +++ b/libs/androidfw/include/androidfw/CursorWindow.h @@ -90,6 +90,9 @@ public: inline uint32_t getNumRows() { return mNumRows; } inline uint32_t getNumColumns() { return mNumColumns; } + inline size_t sizeOfSlots() const { return mSize - mSlotsOffset; } + inline size_t sizeInUse() const { return mAllocOffset + sizeOfSlots(); } + status_t clear(); status_t setNumColumns(uint32_t numColumns); diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp index 15be80c48192..9ac427b66cb3 100644 --- a/libs/androidfw/tests/CursorWindow_test.cpp +++ b/libs/androidfw/tests/CursorWindow_test.cpp @@ -20,9 +20,16 @@ #include "TestHelpers.h" +// Verify that the memory in use is a multiple of 4 bytes +#define ASSERT_ALIGNED(w) \ + ASSERT_EQ(((w)->sizeInUse() & 3), 0); \ + ASSERT_EQ(((w)->freeSpace() & 3), 0); \ + ASSERT_EQ(((w)->sizeOfSlots() & 3), 0) + #define CREATE_WINDOW_1K \ CursorWindow* w; \ - CursorWindow::create(String8("test"), 1 << 10, &w); + CursorWindow::create(String8("test"), 1 << 10, &w); \ + ASSERT_ALIGNED(w); #define CREATE_WINDOW_1K_3X3 \ CursorWindow* w; \ @@ -30,11 +37,13 @@ ASSERT_EQ(w->setNumColumns(3), OK); \ ASSERT_EQ(w->allocRow(), OK); \ ASSERT_EQ(w->allocRow(), OK); \ - ASSERT_EQ(w->allocRow(), OK); + ASSERT_EQ(w->allocRow(), OK); \ + ASSERT_ALIGNED(w); #define CREATE_WINDOW_2M \ CursorWindow* w; \ - CursorWindow::create(String8("test"), 1 << 21, &w); + CursorWindow::create(String8("test"), 1 << 21, &w); \ + ASSERT_ALIGNED(w); static constexpr const size_t kHalfInlineSize = 8192; static constexpr const size_t kGiantSize = 1048576; @@ -48,6 +57,7 @@ TEST(CursorWindowTest, Empty) { ASSERT_EQ(w->getNumColumns(), 0); ASSERT_EQ(w->size(), 1 << 10); ASSERT_EQ(w->freeSpace(), 1 << 10); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, SetNumColumns) { @@ -59,6 +69,7 @@ TEST(CursorWindowTest, SetNumColumns) { ASSERT_NE(w->setNumColumns(5), OK); ASSERT_NE(w->setNumColumns(3), OK); ASSERT_EQ(w->getNumColumns(), 4); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, SetNumColumnsAfterRow) { @@ -69,6 +80,7 @@ TEST(CursorWindowTest, SetNumColumnsAfterRow) { ASSERT_EQ(w->allocRow(), OK); ASSERT_NE(w->setNumColumns(4), OK); ASSERT_EQ(w->getNumColumns(), 0); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, AllocRow) { @@ -82,14 +94,17 @@ TEST(CursorWindowTest, AllocRow) { ASSERT_EQ(w->allocRow(), OK); ASSERT_LT(w->freeSpace(), before); ASSERT_EQ(w->getNumRows(), 1); + ASSERT_ALIGNED(w); // Verify we can unwind ASSERT_EQ(w->freeLastRow(), OK); ASSERT_EQ(w->freeSpace(), before); ASSERT_EQ(w->getNumRows(), 0); + ASSERT_ALIGNED(w); // Can't unwind when no rows left ASSERT_NE(w->freeLastRow(), OK); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, AllocRowBounds) { @@ -99,6 +114,7 @@ TEST(CursorWindowTest, AllocRowBounds) { ASSERT_EQ(w->setNumColumns(60), OK); ASSERT_EQ(w->allocRow(), OK); ASSERT_NE(w->allocRow(), OK); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, StoreNull) { @@ -115,6 +131,7 @@ TEST(CursorWindowTest, StoreNull) { auto field = w->getFieldSlot(0, 0); ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, StoreLong) { @@ -133,6 +150,7 @@ TEST(CursorWindowTest, StoreLong) { ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, StoreString) { @@ -154,6 +172,7 @@ TEST(CursorWindowTest, StoreString) { auto actual = w->getFieldSlotValueString(field, &size); ASSERT_EQ(std::string(actual), "cafe"); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, StoreBounds) { @@ -174,6 +193,7 @@ TEST(CursorWindowTest, StoreBounds) { ASSERT_EQ(w->getFieldSlot(-1, 0), nullptr); ASSERT_EQ(w->getFieldSlot(0, -1), nullptr); ASSERT_EQ(w->getFieldSlot(-1, -1), nullptr); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, Inflate) { @@ -233,6 +253,7 @@ TEST(CursorWindowTest, Inflate) { ASSERT_NE(actual, buf); ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, ParcelEmpty) { @@ -248,10 +269,12 @@ TEST(CursorWindowTest, ParcelEmpty) { ASSERT_EQ(w->getNumColumns(), 0); ASSERT_EQ(w->size(), 0); ASSERT_EQ(w->freeSpace(), 0); + ASSERT_ALIGNED(w); // We can't mutate the window after parceling ASSERT_NE(w->setNumColumns(4), OK); ASSERT_NE(w->allocRow(), OK); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, ParcelSmall) { @@ -310,6 +333,7 @@ TEST(CursorWindowTest, ParcelSmall) { ASSERT_EQ(actualSize, 0); ASSERT_NE(actual, nullptr); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, ParcelLarge) { @@ -362,6 +386,7 @@ TEST(CursorWindowTest, ParcelLarge) { ASSERT_EQ(actualSize, 0); ASSERT_NE(actual, nullptr); } + ASSERT_ALIGNED(w); } } // android -- cgit v1.2.3 From 4e43556d65354ce7c67d2ee7df731bc2c90954fd Mon Sep 17 00:00:00 2001 From: Arvind Kumar Date: Fri, 21 Apr 2023 12:03:10 +0530 Subject: QSPA: Disable Launcher3QuickStepGo Disable Launcher3QuickStepGo for QSPA enabled targets CRs-Fixed: 3414044 Change-Id: I99d7d9181688c0ac72cfc7c77328c488acbd9c99 --- services/core/java/com/android/server/pm/PackageManagerService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5acd8cdec2cc..805f55546aa9 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7410,7 +7410,7 @@ public class PackageManagerService extends IPackageManager.Stub if (mQspaEnabled) { mPackagesPathToBeDisabledForQSPA.add("/system_ext/priv-app/SystemUI"); - mPackagesPathToBeDisabledForQSPA.add("/system_ext/priv-app/Launcher3QuickStep"); + mPackagesPathToBeDisabledForQSPA.add("/system_ext/priv-app/Launcher3QuickStepGo"); mPackagesPathToBeDisabledForQSPA.add("/system_ext/priv-app/Launcher3Go"); mPackagesPathToBeDisabledForQSPA.add("/system/app/PrintSpooler"); mPackagesPathToBeDisabledForQSPA.add("/system/priv-app/StatementService"); -- cgit v1.2.3 From 73167873b5c94a27243db599361cbc38fa979929 Mon Sep 17 00:00:00 2001 From: Aishwarya Mallampati Date: Tue, 1 Nov 2022 17:04:35 +0000 Subject: DO NOT MERGE Grant carrier privileges if package has carrier config access. TelephonyManager#hasCarrierPrivileges internally uses SubscriptionManager#canManageSubscription to decide whether to grant carrier privilege status to an app or not. SubscriptionManager#canManageSubscription returns true if caller APK's certificate matches with one of the mNativeAccessRules or mCarrierConfigAccessRules. This over-grants carrier privilege status to apps that only has mNativeAccessRules. Carrier privilege status should be granted to the caller APK only if it's certificate matches with one of mCarrierConfigAccessRules. Replaced SubscriptionManager#canManageSubscription with PhoneInterfaceManager#hasCarrierConfigAccess which returns true only if caller APK certificates matches with one of mCarrierConfigAccessRules of the given subscription. Bug: 226593252 Test: Manual Testing as explained in b/226593252#comment51 atest CtsTelephonyTestCases Flashed build on raven-userdebug and performed basic funtionality tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8b9fcf30d5c2aa2d3d41f5128094e111e151003d) Merged-In: I662064529d2a9348f395fe3b541366de8bc2fe7d Change-Id: I662064529d2a9348f395fe3b541366de8bc2fe7d --- telephony/java/android/telephony/SubscriptionInfo.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 2d50e08ab922..4719d7374b1b 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -18,6 +18,7 @@ package android.telephony; import static android.text.TextUtils.formatSimple; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -696,6 +697,15 @@ public class SubscriptionInfo implements Parcelable { return merged.isEmpty() ? null : merged; } + /** + * @hide + * @return mCarrierConfigAccessRules associated with this subscription. + */ + public @NonNull List getCarrierConfigAccessRules() { + return mCarrierConfigAccessRules == null ? Collections.emptyList() : + Arrays.asList(mCarrierConfigAccessRules); + } + /** * Returns the card string of the SIM card which contains the subscription. * -- cgit v1.2.3 From ef9bc3e20a858056a9e3cbb64312da398789d426 Mon Sep 17 00:00:00 2001 From: Miranda Kephart Date: Fri, 28 Apr 2023 10:58:46 -0400 Subject: [DO NOT MERGE] Update quickshare intent rather than recreating Currently, we extract the quickshare intent and re-wrap it as a new PendingIntent once we get the screenshot URI. This is insecure as it leads to executing the original with SysUI's permissions, which the app may not have. This change switches to using Intent.fillin to add the URI, keeping the original PendingIntent and original permission set. Bug: 278720336 Fix: 278720336 Test: manual (to test successful quickshare), atest SaveImageInBackgroundTaskTest (to verify original pending intent unchanged) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1d3643956eb8a04366e23dd1a51bb78da2445ba2) Merged-In: Icad3d5f939fcfb894e2038948954bc2735dbe326 Change-Id: Icad3d5f939fcfb894e2038948954bc2735dbe326 --- .../screenshot/SaveImageInBackgroundTask.java | 113 +++++---- .../systemui/screenshot/ScreenshotController.java | 8 +- .../systemui/screenshot/SmartActionsReceiver.java | 4 +- .../screenshot/SaveImageInBackgroundTaskTest.kt | 274 +++++++++++++++++++++ .../ScreenshotNotificationSmartActionsTest.java | 6 +- 5 files changed, 353 insertions(+), 52 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index e9dea65c2078..fe761c3e8d2e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -89,7 +89,8 @@ class SaveImageInBackgroundTask extends AsyncTask { SaveImageInBackgroundTask(Context context, ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data, - Supplier sharedElementTransition) { + Supplier sharedElementTransition, + boolean smartActionsEnabled) { mContext = context; mScreenshotSmartActions = screenshotSmartActions; mImageData = new ScreenshotController.SavedImageData(); @@ -101,8 +102,7 @@ class SaveImageInBackgroundTask extends AsyncTask { mParams = data; // Initialize screenshot notification smart actions provider. - mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true); + mSmartActionsEnabled = smartActionsEnabled; if (mSmartActionsEnabled) { mSmartActionsProvider = SystemUIFactory.getInstance() @@ -135,7 +135,12 @@ class SaveImageInBackgroundTask extends AsyncTask { // Since Quick Share target recommendation does not rely on image URL, it is // queried and surfaced before image compress/export. Action intent would not be // used, because it does not contain image URL. - queryQuickShareAction(image, user); + Notification.Action quickShare = + queryQuickShareAction(mScreenshotId, image, user, null); + if (quickShare != null) { + mQuickShareData.quickShareAction = quickShare; + mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData); + } } // Call synchronously here since already on a background thread. @@ -168,8 +173,9 @@ class SaveImageInBackgroundTask extends AsyncTask { mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri); mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri); mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); - mImageData.quickShareAction = createQuickShareAction(mContext, - mQuickShareData.quickShareAction, uri); + mImageData.quickShareAction = createQuickShareAction( + mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image, + user); mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { @@ -407,60 +413,73 @@ class SaveImageInBackgroundTask extends AsyncTask { } /** - * Populate image uri into intent of Quick Share action. + * Wrap the quickshare intent and populate the fillin intent with the URI */ @VisibleForTesting - private Notification.Action createQuickShareAction(Context context, Notification.Action action, - Uri uri) { - if (action == null) { + Notification.Action createQuickShareAction( + Notification.Action quickShare, String screenshotId, Uri uri, long imageTime, + Bitmap image, UserHandle user) { + if (quickShare == null) { return null; + } else if (quickShare.actionIntent.isImmutable()) { + Notification.Action quickShareWithUri = + queryQuickShareAction(screenshotId, image, user, uri); + if (quickShareWithUri == null + || !quickShareWithUri.title.toString().contentEquals(quickShare.title)) { + return null; + } + quickShare = quickShareWithUri; } - // Populate image URI into Quick Share chip intent - Intent sharingIntent = action.actionIntent.getIntent(); - sharingIntent.setType("image/png"); - sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); - String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime)); + + Intent wrappedIntent = new Intent(mContext, SmartActionsReceiver.class) + .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent) + .putExtra(ScreenshotController.EXTRA_ACTION_INTENT_FILLIN, + createFillInIntent(uri, imageTime)) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + Bundle extras = quickShare.getExtras(); + String actionType = extras.getString( + ScreenshotNotificationSmartActionsProvider.ACTION_TYPE, + ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE); + addIntentExtras(screenshotId, wrappedIntent, actionType, mSmartActionsEnabled); + PendingIntent broadcastIntent = + PendingIntent.getBroadcast(mContext, mRandom.nextInt(), wrappedIntent, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); + return new Notification.Action.Builder(quickShare.getIcon(), quickShare.title, + broadcastIntent) + .setContextual(true) + .addExtras(extras) + .build(); + } + + private Intent createFillInIntent(Uri uri, long imageTime) { + Intent fillIn = new Intent(); + fillIn.setType("image/png"); + fillIn.putExtra(Intent.EXTRA_STREAM, uri); + String subjectDate = DateFormat.getDateTimeInstance().format(new Date(imageTime)); String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate); - sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + fillIn.putExtra(Intent.EXTRA_SUBJECT, subject); // Include URI in ClipData also, so that grantPermission picks it up. // We don't use setData here because some apps interpret this as "to:". - ClipData clipdata = new ClipData(new ClipDescription("content", - new String[]{"image/png"}), + ClipData clipData = new ClipData( + new ClipDescription("content", new String[]{"image/png"}), new ClipData.Item(uri)); - sharingIntent.setClipData(clipdata); - sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - PendingIntent updatedPendingIntent = PendingIntent.getActivity( - context, 0, sharingIntent, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); - - // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver} - // for logging smart actions. - Bundle extras = action.getExtras(); - String actionType = extras.getString( - ScreenshotNotificationSmartActionsProvider.ACTION_TYPE, - ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE); - Intent intent = new Intent(context, SmartActionsReceiver.class) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled); - PendingIntent broadcastIntent = PendingIntent.getBroadcast(context, - mRandom.nextInt(), - intent, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); - return new Notification.Action.Builder(action.getIcon(), action.title, - broadcastIntent).setContextual(true).addExtras(extras).build(); + fillIn.setClipData(clipData); + fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + return fillIn; } /** * Query and surface Quick Share chip if it is available. Action intent would not be used, * because it does not contain image URL which would be populated in {@link - * #createQuickShareAction(Context, Notification.Action, Uri)} + * #createQuickShareAction(Notification.Action, String, Uri, long, Bitmap, UserHandle)} */ - private void queryQuickShareAction(Bitmap image, UserHandle user) { + + @VisibleForTesting + Notification.Action queryQuickShareAction( + String screenshotId, Bitmap image, UserHandle user, Uri uri) { CompletableFuture> quickShareActionsFuture = mScreenshotSmartActions.getSmartActionsFuture( - mScreenshotId, null, image, mSmartActionsProvider, - QUICK_SHARE_ACTION, + screenshotId, uri, image, mSmartActionsProvider, QUICK_SHARE_ACTION, mSmartActionsEnabled, user); int timeoutMs = DeviceConfig.getInt( DeviceConfig.NAMESPACE_SYSTEMUI, @@ -468,11 +487,11 @@ class SaveImageInBackgroundTask extends AsyncTask { 500); List quickShareActions = mScreenshotSmartActions.getSmartActions( - mScreenshotId, quickShareActionsFuture, timeoutMs, + screenshotId, quickShareActionsFuture, timeoutMs, mSmartActionsProvider, QUICK_SHARE_ACTION); if (!quickShareActions.isEmpty()) { - mQuickShareData.quickShareAction = quickShareActions.get(0); - mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData); + return quickShareActions.get(0); } + return null; } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 44b45401ad77..8143647204d8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -57,6 +57,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.provider.DeviceConfig; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; @@ -83,6 +84,7 @@ import android.widget.Toast; import android.window.WindowContext; import com.android.internal.app.ChooserActivity; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; @@ -227,6 +229,7 @@ public class ScreenshotController { static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition"; static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; + static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin"; static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id"; static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification"; @@ -867,8 +870,11 @@ public class ScreenshotController { mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); } + boolean smartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true); + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter, - mScreenshotSmartActions, data, getActionTransitionSupplier()); + mScreenshotSmartActions, data, getActionTransitionSupplier(), smartActionsEnabled); mSaveInBgTask.execute(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java index f703058f4a0f..6152f940d12d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java @@ -18,6 +18,7 @@ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT; +import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT_FILLIN; import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE; import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID; @@ -47,6 +48,7 @@ public class SmartActionsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); + Intent fillIn = intent.getParcelableExtra(EXTRA_ACTION_INTENT_FILLIN); String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE); if (DEBUG_ACTIONS) { Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent()); @@ -54,7 +56,7 @@ public class SmartActionsReceiver extends BroadcastReceiver { ActivityOptions opts = ActivityOptions.makeBasic(); try { - pendingIntent.send(context, 0, null, null, null, null, opts.toBundle()); + pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle()); } catch (PendingIntent.CanceledException e) { Log.e(TAG, "Pending intent canceled", e); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt new file mode 100644 index 000000000000..f1c2169b87f8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt @@ -0,0 +1,274 @@ +/* + * 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.screenshot + +import android.app.Notification +import android.app.PendingIntent +import android.content.ComponentName +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Icon +import android.net.Uri +import android.os.UserHandle +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.screenshot.ScreenshotController.SaveImageInBackgroundData +import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType +import java.util.concurrent.CompletableFuture +import java.util.function.Supplier +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito + +@SmallTest +class SaveImageInBackgroundTaskTest : SysuiTestCase() { + private val imageExporter = mock() + private val smartActions = mock() + private val saveImageData = SaveImageInBackgroundData() + private val sharedTransitionSupplier = + mock>() + private val testScreenshotId: String = "testScreenshotId" + private val testBitmap = mock() + private val testUser = UserHandle.getUserHandleForUid(0) + private val testIcon = mock() + private val testImageTime = 1234.toLong() + + private val smartActionsUriFuture = mock>>() + private val smartActionsFuture = mock>>() + + private val testUri: Uri = Uri.parse("testUri") + private val intent = + Intent(Intent.ACTION_SEND) + .setComponent( + ComponentName.unflattenFromString( + "com.google.android.test/com.google.android.test.TestActivity" + ) + ) + private val immutablePendingIntent = + PendingIntent.getBroadcast( + mContext, + 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + private val mutablePendingIntent = + PendingIntent.getBroadcast( + mContext, + 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE + ) + + private val saveImageTask = + SaveImageInBackgroundTask( + mContext, + imageExporter, + smartActions, + saveImageData, + sharedTransitionSupplier, + false, // forces a no-op implementation; we're mocking out the behavior anyway + ) + + @Before + fun setup() { + Mockito.`when`( + smartActions.getSmartActionsFuture( + Mockito.eq(testScreenshotId), + Mockito.any(Uri::class.java), + Mockito.eq(testBitmap), + Mockito.any(ScreenshotNotificationSmartActionsProvider::class.java), + Mockito.any(ScreenshotSmartActionType::class.java), + Mockito.any(Boolean::class.java), + Mockito.eq(testUser) + ) + ) + .thenReturn(smartActionsUriFuture) + Mockito.`when`( + smartActions.getSmartActionsFuture( + Mockito.eq(testScreenshotId), + Mockito.eq(null), + Mockito.eq(testBitmap), + Mockito.any(ScreenshotNotificationSmartActionsProvider::class.java), + Mockito.any(ScreenshotSmartActionType::class.java), + Mockito.any(Boolean::class.java), + Mockito.eq(testUser) + ) + ) + .thenReturn(smartActionsFuture) + } + + @Test + fun testQueryQuickShare_noAction() { + Mockito.`when`( + smartActions.getSmartActions( + Mockito.eq(testScreenshotId), + Mockito.eq(smartActionsFuture), + Mockito.any(Int::class.java), + Mockito.any(ScreenshotNotificationSmartActionsProvider::class.java), + Mockito.eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION) + ) + ) + .thenReturn(ArrayList()) + + val quickShareAction = + saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri) + + assertNull(quickShareAction) + } + + @Test + fun testQueryQuickShare_withActions() { + val actions = ArrayList() + actions.add(constructAction("Action One", mutablePendingIntent)) + actions.add(constructAction("Action Two", mutablePendingIntent)) + Mockito.`when`( + smartActions.getSmartActions( + Mockito.eq(testScreenshotId), + Mockito.eq(smartActionsUriFuture), + Mockito.any(Int::class.java), + Mockito.any(ScreenshotNotificationSmartActionsProvider::class.java), + Mockito.eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION) + ) + ) + .thenReturn(actions) + + val quickShareAction = + saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)!! + + assertEquals("Action One", quickShareAction.title) + assertEquals(mutablePendingIntent, quickShareAction.actionIntent) + } + + @Test + fun testCreateQuickShareAction_originalWasNull_returnsNull() { + val quickShareAction = + saveImageTask.createQuickShareAction( + null, + testScreenshotId, + testUri, + testImageTime, + testBitmap, + testUser + ) + + assertNull(quickShareAction) + } + + @Test + fun testCreateQuickShareAction_immutableIntentDifferentAction_returnsNull() { + val actions = ArrayList() + actions.add(constructAction("New Test Action", immutablePendingIntent)) + Mockito.`when`( + smartActions.getSmartActions( + Mockito.eq(testScreenshotId), + Mockito.eq(smartActionsUriFuture), + Mockito.any(Int::class.java), + Mockito.any(ScreenshotNotificationSmartActionsProvider::class.java), + Mockito.eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION) + ) + ) + .thenReturn(actions) + val origAction = constructAction("Old Test Action", immutablePendingIntent) + + val quickShareAction = + saveImageTask.createQuickShareAction( + origAction, + testScreenshotId, + testUri, + testImageTime, + testBitmap, + testUser, + ) + + assertNull(quickShareAction) + } + + @Test + fun testCreateQuickShareAction_mutableIntent_returnsSafeIntent() { + val actions = ArrayList() + val action = constructAction("Action One", mutablePendingIntent) + actions.add(action) + Mockito.`when`( + smartActions.getSmartActions( + Mockito.eq(testScreenshotId), + Mockito.eq(smartActionsUriFuture), + Mockito.any(Int::class.java), + Mockito.any(ScreenshotNotificationSmartActionsProvider::class.java), + Mockito.eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION) + ) + ) + .thenReturn(actions) + + val quickShareAction = + saveImageTask.createQuickShareAction( + constructAction("Test Action", mutablePendingIntent), + testScreenshotId, + testUri, + testImageTime, + testBitmap, + testUser + ) + val quickSharePendingIntent : PendingIntent = + quickShareAction.actionIntent.intent.extras!!.getParcelable( + ScreenshotController.EXTRA_ACTION_INTENT)!! + + assertEquals("Test Action", quickShareAction.title) + assertEquals(mutablePendingIntent, quickSharePendingIntent) + } + + @Test + fun testCreateQuickShareAction_immutableIntent_returnsSafeIntent() { + val actions = ArrayList() + val action = constructAction("Test Action", immutablePendingIntent) + actions.add(action) + Mockito.`when`( + smartActions.getSmartActions( + Mockito.eq(testScreenshotId), + Mockito.eq(smartActionsUriFuture), + Mockito.any(Int::class.java), + Mockito.any(ScreenshotNotificationSmartActionsProvider::class.java), + Mockito.eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION) + ) + ) + .thenReturn(actions) + + val quickShareAction = + saveImageTask.createQuickShareAction( + constructAction("Test Action", immutablePendingIntent), + testScreenshotId, + testUri, + testImageTime, + testBitmap, + testUser, + )!! + val quickSharePendingIntent : PendingIntent = + quickShareAction.actionIntent.intent.extras!!.getParcelable( + ScreenshotController.EXTRA_ACTION_INTENT)!! + + assertEquals("Test Action", quickShareAction.title) + assertEquals(immutablePendingIntent, quickSharePendingIntent) + } + + private fun constructAction(title: String, intent: PendingIntent): Notification.Action { + return Notification.Action.Builder(testIcon, title, intent).build() + } + + inline fun mock(apply: T.() -> Unit = {}): T = + Mockito.mock(T::class.java).apply(apply) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index 3d658ec8e811..98bde2c86b40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -183,7 +183,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data, - ActionTransition::new); + ActionTransition::new, false); Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")).get().action; @@ -211,7 +211,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data, - ActionTransition::new); + ActionTransition::new, false); Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")).get().action; @@ -239,7 +239,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data, - ActionTransition::new); + ActionTransition::new, false); Notification.Action deleteAction = task.createDeleteAction(mContext, mContext.getResources(), -- cgit v1.2.3 From 74052146a4424a92c4d00b3d21ea649f8fa08b5f Mon Sep 17 00:00:00 2001 From: Achim Thesmann Date: Tue, 23 May 2023 00:26:33 +0000 Subject: Ignore virtual presentation windows - RESTRICT AUTOMERGE Windows of TYPE_PRESENTATION on virtual displays should not be counted as visible windows to determine if BAL is allowed. Test: manual test, atest BackgroundActivityLaunchTest Bug: 264029851, 205130886 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:42557cb5710527a3ed1a6683d2dad82777ba34de) Merged-In: I08b16ba1c155e951286ddc22019180cbd6334dfa Change-Id: I08b16ba1c155e951286ddc22019180cbd6334dfa --- services/core/java/com/android/server/wm/WindowState.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index a16d9c196b93..ee3916c19374 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3616,8 +3616,12 @@ class WindowState extends WindowContainer implements WindowManagerP // apps won't always be considered as foreground state. // Exclude private presentations as they can only be shown on private virtual displays and // shouldn't be the cause of an app be considered foreground. - if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST - && mAttrs.type != TYPE_PRIVATE_PRESENTATION) { + // Exclude presentations on virtual displays as they are not actually visible. + if (mAttrs.type >= FIRST_SYSTEM_WINDOW + && mAttrs.type != TYPE_TOAST + && mAttrs.type != TYPE_PRIVATE_PRESENTATION + && !(mAttrs.type == TYPE_PRESENTATION && isOnVirtualDisplay()) + ) { mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown); } if (mIsImWindow && mWmService.mAccessibilityController.hasCallbacks()) { @@ -3625,6 +3629,10 @@ class WindowState extends WindowContainer implements WindowManagerP } } + private boolean isOnVirtualDisplay() { + return getDisplayContent().mDisplay.getType() == Display.TYPE_VIRTUAL; + } + private void logExclusionRestrictions(int side) { if (!logsGestureExclusionRestrictions(this) || SystemClock.uptimeMillis() < mLastExclusionLogUptimeMillis[side] -- cgit v1.2.3 From ae1cd018de4c8d2af0a3541e88dd63e19c8d66b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Thu, 15 Jun 2023 18:31:34 +0200 Subject: Forbid granting access to NLSes with too-long component names This makes the limitation, which was previously only checked on the Settings UI, enforced everywhere. Fixes: 260570119 Fixes: 286043036 Test: atest + manually (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8a40b0b3a17658af16922b4ba99ccc4258af89f5) Merged-In: I4c25d80978cb37a8fa1531f5045259d25ac64692 Change-Id: I4c25d80978cb37a8fa1531f5045259d25ac64692 --- core/java/android/app/NotificationManager.java | 6 ++++++ .../notification/NotificationManagerService.java | 5 +++++ .../com/android/server/vr/VrManagerService.java | 6 +++++- .../NotificationManagerServiceTest.java | 25 ++++++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index ccf1edb3fecc..d6835e31bab1 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -561,6 +561,12 @@ public class NotificationManager { */ public static final int BUBBLE_PREFERENCE_SELECTED = 2; + /** + * Maximum length of the component name of a registered NotificationListenerService. + * @hide + */ + public static int MAX_SERVICE_COMPONENT_NAME_LENGTH = 500; + @UnsupportedAppUsage private static INotificationManager sService; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 40b5eba3d3c4..0e537cc1af31 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5258,6 +5258,11 @@ public class NotificationManagerService extends SystemService { boolean granted, boolean userSet) { Objects.requireNonNull(listener); checkNotificationListenerAccess(); + if (granted && listener.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { + throw new IllegalArgumentException( + "Component name too long: " + listener.flattenToString()); + } if (!userSet && isNotificationListenerAccessUserSet(listener)) { // Don't override user's choice return; diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index b296ef2a1443..1ff01a6c70bf 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -1049,7 +1049,11 @@ public class VrManagerService extends SystemService for (ComponentName c : possibleServices) { if (Objects.equals(c.getPackageName(), pkg)) { - nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + try { + nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + } catch (Exception e) { + Slog.w(TAG, "Could not grant NLS access to package " + pkg, e); + } } } } 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 48ad44797cd7..ec2f00fb97a9 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -78,6 +78,7 @@ import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyLong; @@ -3171,6 +3172,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); } + @Test + public void testSetListenerAccessForUser_grantWithNameTooLong_throws() { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + assertThrows(IllegalArgumentException.class, + () -> mBinderService.setNotificationListenerAccessGrantedForUser( + c, user.getIdentifier(), /* enabled= */ true, true)); + } + + @Test + public void testSetListenerAccessForUser_revokeWithNameTooLong_okay() throws Exception { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + mBinderService.setNotificationListenerAccessGrantedForUser( + c, user.getIdentifier(), /* enabled= */ false, true); + + verify(mListeners).setPackageOrComponentEnabled( + c.flattenToString(), user.getIdentifier(), true, /* enabled= */ false, true); + } + @Test public void testSetAssistantAccessForUser() throws Exception { UserInfo ui = new UserInfo(); -- cgit v1.2.3 From c8fb204b65718d693bc42054cc035d8c48760678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Wed, 5 Jul 2023 13:52:21 +0200 Subject: Visit Uris added by WearableExtender Bug: 283962802 Test: atest + manual (POC app now crashes on notify() as expected) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3d36966ea2aeebc3501a69a8ef7afce5ef593cee) Merged-In: I0da18c631eb5e4844a48760c7aaedab715a0bfed Change-Id: I0da18c631eb5e4844a48760c7aaedab715a0bfed --- core/java/android/app/Notification.java | 17 ++++++++++++++++- .../notification/NotificationManagerServiceTest.java | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 1a4c8f0e9120..95506c42826d 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2092,6 +2092,10 @@ public class Notification implements Parcelable } } + private void visitUris(@NonNull Consumer visitor) { + visitIconUri(visitor, getIcon()); + } + @Override public Action clone() { return new Action( @@ -2777,7 +2781,7 @@ public class Notification implements Parcelable if (actions != null) { for (Action action : actions) { - visitIconUri(visitor, action.getIcon()); + action.visitUris(visitor); } } @@ -2865,6 +2869,11 @@ public class Notification implements Parcelable if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } + + if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { + WearableExtender extender = new WearableExtender(this); + extender.visitUris(visitor); + } } /** @@ -11416,6 +11425,12 @@ public class Notification implements Parcelable mFlags &= ~mask; } } + + private void visitUris(@NonNull Consumer visitor) { + for (Action action : mActions) { + action.visitUris(visitor); + } + } } /** 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 ec2f00fb97a9..7cede43f31e7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4634,6 +4634,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); } + @Test + public void testVisitUris_wearableExtender() { + Icon actionIcon = Icon.createWithContentUri("content://media/action"); + Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction"); + PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build()) + .extend(new Notification.WearableExtender().addAction( + new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build())) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor).accept(eq(actionIcon.getUri())); + verify(visitor).accept(eq(wearActionIcon.getUri())); + } + @Test public void testSetNotificationPolicy_preP_setOldFields() { ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class); -- cgit v1.2.3 From 00dde09ac847574fc16177c284f76c99959610bb Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Fri, 30 Jun 2023 14:36:44 -0700 Subject: Update AccountManagerService checkKeyIntentParceledCorrectly. Bug: 265798288 Test: manual (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b117b506ec0504ff9eb2fa523e82f1879ecb8cc1) Merged-In: Iad33851af32a11c99d11bc2b5c76d124c3e97ebb Change-Id: Iad33851af32a11c99d11bc2b5c76d124c3e97ebb --- .../core/java/com/android/server/accounts/AccountManagerService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index c0aa36a0fb77..215bd2b02cc7 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4923,6 +4923,9 @@ public class AccountManagerService Bundle simulateBundle = p.readBundle(); p.recycle(); Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); + if (intent != null && intent.getClass() != Intent.class) { + return false; + } Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT); if (intent == null) { return (simulateIntent == null); -- cgit v1.2.3 From 6a62bd350a190bd9b8a5cef35a25a063d8313a77 Mon Sep 17 00:00:00 2001 From: Beth Thibodeau Date: Thu, 22 Jun 2023 18:26:44 -0500 Subject: Improve user handling when querying for resumable media - Before trying to query recent media from a saved component, check whether the current user actually has that component installed - Track user when creating the MediaBrowser, in case the user changes before the MBS returns a result Test: atest MediaResumeListenerTest Bug: 284297711 (cherry picked from commit e566a250ad61e269119b475c7ebdae6ca962c4a7) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b7e77454e8889395b6f998c40e1ce12f994caca5) Merged-In: I838ff0e125acadabc8436a00dbff707cc4be6249 Change-Id: I838ff0e125acadabc8436a00dbff707cc4be6249 --- .../android/systemui/media/MediaResumeListener.kt | 46 +++++++-- .../android/systemui/media/ResumeMediaBrowser.java | 21 ++++- .../systemui/media/ResumeMediaBrowserFactory.java | 7 +- .../systemui/media/MediaResumeListenerTest.kt | 103 +++++++++++++++++---- .../systemui/media/ResumeMediaBrowserTest.kt | 15 ++- 5 files changed, 155 insertions(+), 37 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index 61d0b41e9bb6..f2deebf46771 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -97,9 +97,16 @@ class MediaResumeListener @Inject constructor( Log.e(TAG, "Error getting package information", e) } - Log.d(TAG, "Adding resume controls $desc") - mediaDataManager.addResumptionControls(currentUserId, desc, resumeAction, token, - appName.toString(), appIntent, component.packageName) + Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc") + mediaDataManager.addResumptionControls( + browser.userId, + desc, + resumeAction, + token, + appName.toString(), + appIntent, + component.packageName + ) } } @@ -154,7 +161,11 @@ class MediaResumeListener @Inject constructor( } resumeComponents.add(component to lastPlayed) } - Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}") + Log.d( + TAG, + "loaded resume components for $currentUserId: " + + "${resumeComponents.toArray().contentToString()}" + ) if (needsUpdate) { // Save any missing times that we had to fill in @@ -170,11 +181,21 @@ class MediaResumeListener @Inject constructor( return } + val pm = context.packageManager val now = systemClock.currentTimeMillis() resumeComponents.forEach { if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) { - val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first) - browser.findRecentMedia() + // Verify that the service exists for this user + val intent = Intent(MediaBrowserService.SERVICE_INTERFACE) + intent.component = it.first + val inf = pm.resolveServiceAsUser(intent, 0, currentUserId) + if (inf != null) { + val browser = + mediaBrowserFactory.create(mediaBrowserCallback, it.first, currentUserId) + browser.findRecentMedia() + } else { + Log.d(TAG, "User $currentUserId does not have component ${it.first}") + } } } } @@ -199,7 +220,7 @@ class MediaResumeListener @Inject constructor( Log.d(TAG, "Checking for service component for " + data.packageName) val pm = context.packageManager val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE) - val resumeInfo = pm.queryIntentServices(serviceIntent, 0) + val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId) val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName @@ -242,13 +263,18 @@ class MediaResumeListener @Inject constructor( browser: ResumeMediaBrowser ) { // Since this is a test, just save the component for later - Log.d(TAG, "Can get resumable media from $componentName") + Log.d( + TAG, + "Can get resumable media for ${browser.userId} from $componentName" + ) mediaDataManager.setResumeAction(key, getResumeAction(componentName)) updateResumptionList(componentName) mediaBrowser = null } }, - componentName) + componentName, + currentUserId + ) mediaBrowser?.testConnection() } @@ -288,7 +314,7 @@ class MediaResumeListener @Inject constructor( */ private fun getResumeAction(componentName: ComponentName): Runnable { return Runnable { - mediaBrowser = mediaBrowserFactory.create(null, componentName) + mediaBrowser = mediaBrowserFactory.create(null, componentName, currentUserId) mediaBrowser?.restart() } } diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java index fecc903326f5..bcd597814354 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java @@ -17,6 +17,7 @@ package com.android.systemui.media; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -50,6 +51,8 @@ public class ResumeMediaBrowser { private final Context mContext; @Nullable private final Callback mCallback; private MediaBrowserFactory mBrowserFactory; + @UserIdInt private final int mUserId; + private MediaBrowser mMediaBrowser; private ComponentName mComponentName; @@ -58,13 +61,19 @@ public class ResumeMediaBrowser { * @param context the context * @param callback used to report media items found * @param componentName Component name of the MediaBrowserService this browser will connect to + * @param userId ID of the current user */ - public ResumeMediaBrowser(Context context, @Nullable Callback callback, - ComponentName componentName, MediaBrowserFactory browserFactory) { + public ResumeMediaBrowser( + Context context, + @Nullable Callback callback, + ComponentName componentName, + MediaBrowserFactory browserFactory, + @UserIdInt int userId) { mContext = context; mCallback = callback; mComponentName = componentName; mBrowserFactory = browserFactory; + mUserId = userId; } /** @@ -259,6 +268,14 @@ public class ResumeMediaBrowser { return new MediaController(mContext, token); } + /** + * Get the ID of the user associated with this broswer + * @return the user ID + */ + public @UserIdInt int getUserId() { + return mUserId; + } + /** * Get the media session token * @return the token, or null if the MediaBrowser is null or disconnected diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java index 2261aa5ac265..3f4104906281 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java @@ -16,6 +16,7 @@ package com.android.systemui.media; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; @@ -39,10 +40,12 @@ public class ResumeMediaBrowserFactory { * * @param callback will be called on connection or error, and addTrack when media item found * @param componentName component to browse + * @param userId ID of the current user * @return */ public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback, - ComponentName componentName) { - return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory); + ComponentName componentName, @UserIdInt int userId) { + return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, + userId); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt index 54da1a4b5f65..7e3a5a6edc1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt @@ -92,6 +92,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Captor lateinit var callbackCaptor: ArgumentCaptor @Captor lateinit var actionCaptor: ArgumentCaptor @Captor lateinit var componentCaptor: ArgumentCaptor + @Captor lateinit var userIdCaptor: ArgumentCaptor private lateinit var executor: FakeExecutor private lateinit var data: MediaData @@ -112,7 +113,7 @@ class MediaResumeListenerTest : SysuiTestCase() { Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1) - whenever(resumeBrowserFactory.create(capture(callbackCaptor), any())) + whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), capture(userIdCaptor))) .thenReturn(resumeBrowser) // resume components are stored in sharedpreferences @@ -123,6 +124,7 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor) whenever(mockContext.packageManager).thenReturn(context.packageManager) whenever(mockContext.contentResolver).thenReturn(context.contentResolver) + whenever(mockContext.userId).thenReturn(context.userId) executor = FakeExecutor(clock) resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor, @@ -233,15 +235,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testOnLoad_checksForResume_hasService() { // Set up mocks to successfully find a MBS that returns valid media - val pm = mock(PackageManager::class.java) - whenever(mockContext.packageManager).thenReturn(pm) - val resolveInfo = ResolveInfo() - val serviceInfo = ServiceInfo() - serviceInfo.packageName = PACKAGE_NAME - resolveInfo.serviceInfo = serviceInfo - resolveInfo.serviceInfo.name = CLASS_NAME - val resumeInfo = listOf(resolveInfo) - whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo) + setUpMbsWithValidResolveInfo() val description = MediaDescription.Builder().setTitle(TITLE).build() val component = ComponentName(PACKAGE_NAME, CLASS_NAME) @@ -280,6 +274,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testOnUserUnlock_loadsTracks() { // Set up mock service to successfully find valid media + setUpMbsWithValidResolveInfo() val description = MediaDescription.Builder().setTitle(TITLE).build() val component = ComponentName(PACKAGE_NAME, CLASS_NAME) whenever(resumeBrowser.token).thenReturn(token) @@ -308,15 +303,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testGetResumeAction_restarts() { // Set up mocks to successfully find a MBS that returns valid media - val pm = mock(PackageManager::class.java) - whenever(mockContext.packageManager).thenReturn(pm) - val resolveInfo = ResolveInfo() - val serviceInfo = ServiceInfo() - serviceInfo.packageName = PACKAGE_NAME - resolveInfo.serviceInfo = serviceInfo - resolveInfo.serviceInfo.name = CLASS_NAME - val resumeInfo = listOf(resolveInfo) - whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo) + setUpMbsWithValidResolveInfo() val description = MediaDescription.Builder().setTitle(TITLE).build() val component = ComponentName(PACKAGE_NAME, CLASS_NAME) @@ -362,6 +349,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testLoadComponents_recentlyPlayed_adds() { // Set up browser to return successfully + setUpMbsWithValidResolveInfo() val description = MediaDescription.Builder().setTitle(TITLE).build() val component = ComponentName(PACKAGE_NAME, CLASS_NAME) whenever(resumeBrowser.token).thenReturn(token) @@ -445,4 +433,81 @@ class MediaResumeListenerTest : SysuiTestCase() { } verify(sharedPrefsEditor, times(1)).apply() } + + @Test + fun testUserUnlocked_userChangeWhileQuerying() { + val firstUserId = context.userId + val secondUserId = firstUserId + 1 + val description = MediaDescription.Builder().setTitle(TITLE).build() + val component = ComponentName(PACKAGE_NAME, CLASS_NAME) + + setUpMbsWithValidResolveInfo() + whenever(resumeBrowser.token).thenReturn(token) + whenever(resumeBrowser.appIntent).thenReturn(pendingIntent) + + val unlockIntent = + Intent(Intent.ACTION_USER_UNLOCKED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, firstUserId) + } + + // When the first user unlocks and we query their recent media + resumeListener.userChangeReceiver.onReceive(context, unlockIntent) + whenever(resumeBrowser.userId).thenReturn(userIdCaptor.value) + verify(resumeBrowser, times(3)).findRecentMedia() + + // And the user changes before the MBS response is received + val changeIntent = + Intent(Intent.ACTION_USER_SWITCHED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, secondUserId) + } + resumeListener.userChangeReceiver.onReceive(context, changeIntent) + callbackCaptor.value.addTrack(description, component, resumeBrowser) + + // Then the loaded media is correctly associated with the first user + verify(mediaDataManager) + .addResumptionControls( + eq(firstUserId), + eq(description), + any(), + eq(token), + eq(PACKAGE_NAME), + eq(pendingIntent), + eq(PACKAGE_NAME) + ) + } + + @Test + fun testUserUnlocked_noComponent_doesNotQuery() { + // Set up a valid MBS, but user does not have the service available + setUpMbsWithValidResolveInfo() + val pm = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(pm) + whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(null) + + val unlockIntent = + Intent(Intent.ACTION_USER_UNLOCKED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + } + + // When the user is unlocked, but does not have the component installed + resumeListener.userChangeReceiver.onReceive(context, unlockIntent) + + // Then we never attempt to connect to it + verify(resumeBrowser, never()).findRecentMedia() + } + + /** Sets up mocks to successfully find a MBS that returns valid media. */ + private fun setUpMbsWithValidResolveInfo() { + val pm = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(pm) + val resolveInfo = ResolveInfo() + val serviceInfo = ServiceInfo() + serviceInfo.packageName = PACKAGE_NAME + resolveInfo.serviceInfo = serviceInfo + resolveInfo.serviceInfo.name = CLASS_NAME + val resumeInfo = listOf(resolveInfo) + whenever(pm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(resumeInfo) + whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(resolveInfo) + whenever(pm.getApplicationLabel(any())).thenReturn(PACKAGE_NAME) + } } \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt index dfa7c66b38f9..5620467b2959 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt @@ -81,8 +81,14 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { whenever(mediaController.transportControls).thenReturn(transportControls) - resumeBrowser = TestableResumeMediaBrowser(context, callback, component, browserFactory, - mediaController) + resumeBrowser = TestableResumeMediaBrowser( + context, + callback, + component, + browserFactory, + mediaController, + context.userId + ) } @Test @@ -282,8 +288,9 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { callback: Callback, componentName: ComponentName, browserFactory: MediaBrowserFactory, - private val fakeController: MediaController - ) : ResumeMediaBrowser(context, callback, componentName, browserFactory) { + private val fakeController: MediaController, + userId: Int + ) : ResumeMediaBrowser(context, callback, componentName, browserFactory, userId) { override fun createMediaController(token: MediaSession.Token): MediaController { return fakeController -- cgit v1.2.3 From 2e6e4cc9910e1460397fc45226d18a5c873c8387 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Wed, 7 Dec 2022 04:36:46 +0000 Subject: RingtoneManager: verify default ringtone is audio When a ringtone picker tries to set a ringtone through RingtoneManager.setActualDefaultRingtoneUri (also called by com.android.settings.DefaultRingtonePreference), verify the mimeType can be obtained (not found when caller doesn't have access to it) and it is an audio resource. Bug: 205837340 Test: atest android.media.audio.cts.RingtoneManagerTest (cherry picked from commit 38618f9fb16d3b5617e2289354d47abe5af17dad) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0a4792b62ea86c153653b0663ffe920d90b7cc15) Merged-In: I3f2c487ded405c0c1a83ef0a2fe99cff7cc9328e Change-Id: I3f2c487ded405c0c1a83ef0a2fe99cff7cc9328e --- media/java/android/media/RingtoneManager.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 4ec79b7e085a..8aecf7f17026 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -802,10 +802,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 @@ -826,6 +826,21 @@ public class RingtoneManager { if(!isInternalRingtoneUri(ringtoneUri)) { ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId()); } + + if (ringtoneUri != null) { + final String mimeType = resolver.getType(ringtoneUri); + if (mimeType == null) { + Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri + + " ignored: failure to find mimeType (no access from this context?)"); + return; + } + if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) { + Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri + + " ignored: associated mimeType:" + mimeType + " is not an audio type"); + return; + } + } + Settings.System.putStringForUser(resolver, setting, ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId()); -- cgit v1.2.3 From e4780486026d0b0676810b1a66b36a50ec6c6907 Mon Sep 17 00:00:00 2001 From: Beth Thibodeau Date: Tue, 30 May 2023 18:45:47 -0500 Subject: Add placeholder when media control title is blank When an app posts a media control with no available title, show a placeholder string with the app name instead Bug: 274775190 Test: atest MediaDataManagerTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:df4686dc0a38b6027960dbe69b3fe18048f02b8f) Merged-In: Ie406c180af48653595e8e222a15b4dda27de2e0e Change-Id: Ie406c180af48653595e8e222a15b4dda27de2e0e --- packages/SystemUI/res/values/strings.xml | 2 + .../com/android/systemui/media/MediaDataManager.kt | 8 +- .../android/systemui/media/MediaDataManagerTest.kt | 97 +++++++++++++++++++++- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4a7d7089d712..48ff6d535874 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2173,6 +2173,8 @@ Play %1$s by %2$s from %3$s Play %1$s from %2$s + + %1$s is running Inactive, check app diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 5d2d556a5773..4bad1c5ebd9f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -580,12 +580,16 @@ class MediaDataManager( // Song name var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) - if (song == null) { + if (song.isNullOrBlank()) { song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE) } - if (song == null) { + if (song.isNullOrBlank()) { song = HybridGroupManager.resolveTitle(notif) } + if (song.isNullOrBlank()) { + // For apps that don't include a title, add a placeholder + song = context.getString(R.string.controls_media_empty_title, app) + } // Artist name var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 81e4182f2ad5..5762a87eed50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -17,6 +17,7 @@ import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager @@ -49,9 +50,11 @@ private const val KEY = "KEY" private const val KEY_2 = "KEY_2" private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" private const val PACKAGE_NAME = "com.android.systemui" -private const val APP_NAME = "SystemUI" +private const val APP_NAME = "com.android.systemui.tests" private const val SESSION_ARTIST = "artist" private const val SESSION_TITLE = "title" +private const val SESSION_BLANK_TITLE = " " +private const val SESSION_EMPTY_TITLE = "" private const val USER_ID = 0 private val DISMISS_INTENT = Intent().apply { action = "dismiss" } @@ -262,6 +265,98 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } + @Test + fun testOnNotificationAdded_emptyTitle_hasPlaceholder() { + // When the manager has a notification with an empty title + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) + .build() + ) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + // Then a media control is created with a placeholder title string + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) + assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) + } + + @Test + fun testOnNotificationAdded_blankTitle_hasPlaceholder() { + // GIVEN that the manager has a notification with a blank title + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) + .build() + ) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + // Then a media control is created with a placeholder title string + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) + assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) + } + + @Test + fun testOnNotificationAdded_emptyMetadata_usesNotificationTitle() { + // When the app sets the metadata title fields to empty strings, but does include a + // non-blank notification title + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) + .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE) + .build() + ) + mediaNotification = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setContentTitle(SESSION_TITLE) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + } + build() + } + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + // Then the media control is added using the notification's title + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE) + } + @Test fun testOnNotificationRemoved_withResumption() { // GIVEN that the manager has a notification with a resume action -- cgit v1.2.3 From b1bd83d7369c599c3fe970c4c0593e629f0e8f25 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 21 Jun 2023 13:29:29 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE BUG:286996125 Auto-generated-cl: translation import (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:204ea4a673cc47f154cbff66d664618f1942b6b9) Merged-In: I88f32886c5748d119bf37745060403a0e31d829d Change-Id: I88f32886c5748d119bf37745060403a0e31d829d --- packages/SystemUI/res/values-af/strings.xml | 1 + packages/SystemUI/res/values-am/strings.xml | 1 + packages/SystemUI/res/values-ar/strings.xml | 1 + packages/SystemUI/res/values-as/strings.xml | 1 + packages/SystemUI/res/values-az/strings.xml | 1 + packages/SystemUI/res/values-b+sr+Latn/strings.xml | 1 + packages/SystemUI/res/values-be/strings.xml | 1 + packages/SystemUI/res/values-bg/strings.xml | 1 + packages/SystemUI/res/values-bn/strings.xml | 1 + packages/SystemUI/res/values-bs/strings.xml | 1 + packages/SystemUI/res/values-ca/strings.xml | 1 + packages/SystemUI/res/values-cs/strings.xml | 1 + packages/SystemUI/res/values-da/strings.xml | 1 + packages/SystemUI/res/values-de/strings.xml | 1 + packages/SystemUI/res/values-el/strings.xml | 1 + packages/SystemUI/res/values-en-rAU/strings.xml | 1 + packages/SystemUI/res/values-en-rCA/strings.xml | 1 + packages/SystemUI/res/values-en-rGB/strings.xml | 1 + packages/SystemUI/res/values-en-rIN/strings.xml | 1 + packages/SystemUI/res/values-en-rXC/strings.xml | 1 + packages/SystemUI/res/values-es-rUS/strings.xml | 1 + packages/SystemUI/res/values-es/strings.xml | 1 + packages/SystemUI/res/values-et/strings.xml | 1 + packages/SystemUI/res/values-eu/strings.xml | 1 + packages/SystemUI/res/values-fa/strings.xml | 1 + packages/SystemUI/res/values-fi/strings.xml | 1 + packages/SystemUI/res/values-fr-rCA/strings.xml | 1 + packages/SystemUI/res/values-fr/strings.xml | 1 + packages/SystemUI/res/values-gl/strings.xml | 1 + packages/SystemUI/res/values-gu/strings.xml | 1 + packages/SystemUI/res/values-hi/strings.xml | 1 + packages/SystemUI/res/values-hr/strings.xml | 1 + packages/SystemUI/res/values-hu/strings.xml | 1 + packages/SystemUI/res/values-hy/strings.xml | 1 + packages/SystemUI/res/values-in/strings.xml | 1 + packages/SystemUI/res/values-is/strings.xml | 1 + packages/SystemUI/res/values-it/strings.xml | 1 + packages/SystemUI/res/values-iw/strings.xml | 1 + packages/SystemUI/res/values-ja/strings.xml | 1 + packages/SystemUI/res/values-ka/strings.xml | 1 + packages/SystemUI/res/values-kk/strings.xml | 1 + packages/SystemUI/res/values-km/strings.xml | 1 + packages/SystemUI/res/values-kn/strings.xml | 1 + packages/SystemUI/res/values-ko/strings.xml | 1 + packages/SystemUI/res/values-ky/strings.xml | 1 + packages/SystemUI/res/values-lo/strings.xml | 1 + packages/SystemUI/res/values-lt/strings.xml | 1 + packages/SystemUI/res/values-lv/strings.xml | 1 + packages/SystemUI/res/values-mk/strings.xml | 1 + packages/SystemUI/res/values-ml/strings.xml | 1 + packages/SystemUI/res/values-mn/strings.xml | 1 + packages/SystemUI/res/values-mr/strings.xml | 1 + packages/SystemUI/res/values-ms/strings.xml | 1 + packages/SystemUI/res/values-my/strings.xml | 1 + packages/SystemUI/res/values-nb/strings.xml | 1 + packages/SystemUI/res/values-ne/strings.xml | 1 + packages/SystemUI/res/values-nl/strings.xml | 1 + packages/SystemUI/res/values-or/strings.xml | 1 + packages/SystemUI/res/values-pa/strings.xml | 1 + packages/SystemUI/res/values-pl/strings.xml | 1 + packages/SystemUI/res/values-pt-rBR/strings.xml | 1 + packages/SystemUI/res/values-pt-rPT/strings.xml | 1 + packages/SystemUI/res/values-pt/strings.xml | 1 + packages/SystemUI/res/values-ro/strings.xml | 1 + packages/SystemUI/res/values-ru/strings.xml | 1 + packages/SystemUI/res/values-si/strings.xml | 1 + packages/SystemUI/res/values-sk/strings.xml | 1 + packages/SystemUI/res/values-sl/strings.xml | 1 + packages/SystemUI/res/values-sq/strings.xml | 1 + packages/SystemUI/res/values-sr/strings.xml | 1 + packages/SystemUI/res/values-sv/strings.xml | 1 + packages/SystemUI/res/values-sw/strings.xml | 1 + packages/SystemUI/res/values-ta/strings.xml | 1 + packages/SystemUI/res/values-te/strings.xml | 1 + packages/SystemUI/res/values-th/strings.xml | 1 + packages/SystemUI/res/values-tl/strings.xml | 1 + packages/SystemUI/res/values-tr/strings.xml | 1 + packages/SystemUI/res/values-uk/strings.xml | 1 + packages/SystemUI/res/values-ur/strings.xml | 1 + packages/SystemUI/res/values-uz/strings.xml | 1 + packages/SystemUI/res/values-vi/strings.xml | 1 + packages/SystemUI/res/values-zh-rCN/strings.xml | 1 + packages/SystemUI/res/values-zh-rHK/strings.xml | 1 + packages/SystemUI/res/values-zh-rTW/strings.xml | 1 + packages/SystemUI/res/values-zu/strings.xml | 1 + 85 files changed, 85 insertions(+) diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index df9ed67e074e..83822dafff33 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -802,6 +802,7 @@ "Maak %1$s oop" "Speel %1$s deur %2$s vanaf %3$s" "Speel %1$s vanaf %2$s" + "%1$s loop tans" "Onaktief, gaan program na" "Nie gekry nie" "Kontrole is nie beskikbaar nie" diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 9c0342abde9e..01f8b0e484b1 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -802,6 +802,7 @@ "%1$s ክፈት" "%1$s%2$s%3$s ያጫውቱ" "%1$s%2$s ያጫውቱ" + "%1$s እያሄደ ነው" "ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ" "አልተገኘም" "መቆጣጠሪያ አይገኝም" diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 3de031e97c04..906df453a823 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -826,6 +826,7 @@ "فتح %1$s" "تشغيل %1$s للفنان %2$s من تطبيق %3$s" "تشغيل %1$s من تطبيق %2$s" + "\"%1$s\" قيد التشغيل" "غير نشط، تحقّق من التطبيق." "لم يتم العثور عليه." "عنصر التحكّم غير متوفّر" diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index e6e0d728c8be..83b57dbfb580 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -802,6 +802,7 @@ "%1$s খোলক" "%3$s%2$s%1$s গীতটো প্লে’ কৰক" "%2$s%1$s গীতটো প্লে’ কৰক" + "%1$s চলি আছে" "সক্ৰিয় নহয়, এপ্‌টো পৰীক্ষা কৰক" "বিচাৰি পোৱা নগ’ল" "নিয়ন্ত্ৰণটো উপলব্ধ নহয়" diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index b4568742e155..662b1930621b 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -802,6 +802,7 @@ "%1$s tətbiqini açın" "%2$s tərəfindən %1$s mahnısını %3$s tətbiqindən oxudun" "%1$s mahnısını %2$s tətbiqindən oxudun" + "%1$s işləyir" "Aktiv deyil, tətbiqi yoxlayın" "Tapılmadı" "Nəzarət əlçatan deyil" diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index dd83cf5309a8..4d577a890d93 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -808,6 +808,7 @@ "Otvorite %1$s" "Pustite %1$s izvođača %2$s iz aplikacije %3$s" "Pustite %1$s iz aplikacije %2$s" + "Aplikacija %1$s je pokrenuta" "Neaktivno. Vidite aplikaciju" "Nije pronađeno" "Kontrola nije dostupna" diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index d4f823d3b418..95cceafefb09 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -814,6 +814,7 @@ "Адкрыйце праграму \"%1$s\"" "Прайграйце кампазіцыю \"%1$s\" (выканаўца – %2$s) з дапамогай праграмы \"%3$s\"" "Прайграйце кампазіцыю \"%1$s\" з дапамогай праграмы \"%2$s\"" + "%1$s працуе" "Неактыўна, праверце праграму" "Не знойдзена" "Кіраванне недаступнае" diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 6c8f728dc3d4..150b7a427180 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -802,6 +802,7 @@ "Отваряне на %1$s" "Пускане на %1$s на %2$s от %3$s" "Пускане на %1$s от %2$s" + "%1$s се изпълнява" "Неактивно, проверете прилож." "Не е намерено" "Контролата не е налице" diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 116807e59683..eb499bd33d0e 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -802,6 +802,7 @@ "%1$s অ্যাপ খুলুন" "%2$s-এর %1$s গানটি %3$s অ্যাপে চালান" "%1$s গানটি %2$s অ্যাপে চালান" + "%1$s চলছে" "বন্ধ আছে, অ্যাপ চেক করুন" "খুঁজে পাওয়া যায়নি" "কন্ট্রোল উপলভ্য নেই" diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index d05d7922e432..1a7fd0f7ddbf 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -808,6 +808,7 @@ "Otvorite aplikaciju %1$s" "Reproducirajte pjesmu %1$s izvođača %2$s pomoću aplikacije %3$s" "Reproducirajte pjesmu %1$s pomoću aplikacije %2$s" + "Aplikacija %1$s je pokrenuta" "Neaktivno, vidite aplikaciju" "Nije pronađeno" "Kontrola nije dostupna" diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 835b66cb7b62..90a1bbb7aab4 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -802,6 +802,7 @@ "Obre %1$s" "Reprodueix %1$s (%2$s) des de l\'aplicació %3$s" "Reprodueix %1$s des de l\'aplicació %2$s" + "%1$s s\'està executant" "Inactiu; comprova l\'aplicació" "No s\'ha trobat" "El control no està disponible" diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 0879532fab4d..9165537d8154 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -814,6 +814,7 @@ "Otevřít aplikaci %1$s" "Přehrát skladbu %1$s od interpreta %2$s z aplikace %3$s" "Přehrát skladbu %1$s z aplikace %2$s" + "Aplikace %1$s je spuštěna" "Neaktivní, zkontrolujte aplikaci" "Nenalezeno" "Ovládání není k dispozici" diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 5ad2d126b601..2364aab1347c 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -802,6 +802,7 @@ "Åbn %1$s" "Afspil %1$s af %2$s via %3$s" "Afspil %1$s via %2$s" + "%1$s kører" "Inaktiv. Tjek appen" "Ikke fundet" "Styringselement ikke tilgængeligt" diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index a1ce8d176489..bddb2a89c038 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -802,6 +802,7 @@ "%1$s öffnen" "%1$s von %2$s über %3$s wiedergeben" "%1$s über %2$s wiedergeben" + "%1$s wird ausgeführt" "Inaktiv – sieh in der App nach" "Nicht gefunden" "Steuerelement nicht verfügbar" diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index c7991eca0c21..796fa6c7924f 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -802,6 +802,7 @@ "Άνοιγμα της εφαρμογής %1$s" "Αναπαραγωγή του %1$s από %2$s στην εφαρμογή %3$s" "Αναπαραγωγή του %1$s στην εφαρμογή %2$s" + "Η εφαρμογή %1$s εκτελείται" "Ανενεργό, έλεγχος εφαρμογής" "Δεν βρέθηκε." "Μη διαθέσιμο στοιχείο ελέγχου" diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 2b82eb0fabb4..a49b7f021c11 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -802,6 +802,7 @@ "Open %1$s" "Play %1$s by %2$s from %3$s" "Play %1$s from %2$s" + "%1$s is running" "Inactive, check app" "Not found" "Control is unavailable" diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 561b9da20b83..aae8fc4ad6f6 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -802,6 +802,7 @@ "Open %1$s" "Play %1$s by %2$s from %3$s" "Play %1$s from %2$s" + "%1$s is running" "Inactive, check app" "Not found" "Control is unavailable" diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 2b82eb0fabb4..a49b7f021c11 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -802,6 +802,7 @@ "Open %1$s" "Play %1$s by %2$s from %3$s" "Play %1$s from %2$s" + "%1$s is running" "Inactive, check app" "Not found" "Control is unavailable" diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 2b82eb0fabb4..a49b7f021c11 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -802,6 +802,7 @@ "Open %1$s" "Play %1$s by %2$s from %3$s" "Play %1$s from %2$s" + "%1$s is running" "Inactive, check app" "Not found" "Control is unavailable" diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index b2e8494c9275..a2b04d954956 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -802,6 +802,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‎‏‏‎‎‎Open ‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎Play ‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ by ‎‏‎‎‏‏‎%2$s‎‏‎‎‏‏‏‎ from ‎‏‎‎‏‏‎%3$s‎‏‎‎‏‏‏‎‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‎Play ‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ from ‎‏‎‎‏‏‎%2$s‎‏‎‎‏‏‏‎‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ is running‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎Inactive, check app‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎Not found‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎Control is unavailable‎‏‎‎‏‎" diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 52887ba7ade5..608a957006dc 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -802,6 +802,7 @@ "Abre %1$s" "Reproduce %1$s, de %2$s, en %3$s" "Reproducir %1$s en %2$s" + "%1$s se está ejecutando" "Inactivo. Verifica la app" "No se encontró" "El control no está disponible" diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 9958af03b047..c6e0a8db8d4f 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -802,6 +802,7 @@ "Abrir %1$s" "Poner %1$s de %2$s en %3$s" "Poner %1$s en %2$s" + "%1$s se está ejecutando" "Inactivo, comprobar aplicación" "No se ha encontrado" "Control no disponible" diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index a709e2f53c93..3852e80e93dc 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -802,6 +802,7 @@ "Rakenduse %1$s avamine" "Esita lugu %1$s esitajalt %2$s rakenduses %3$s" "Esita lugu %1$s rakenduses %2$s" + "%1$s töötab" "Passiivne, vaadake rakendust" "Ei leitud" "Juhtelement pole saadaval" diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index d87f7b4a0b1d..bf8299d93f98 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -802,6 +802,7 @@ "Ireki %1$s" "Erreproduzitu %1$s (%2$s) %3$s bidez" "Erreproduzitu %1$s %2$s bidez" + "%1$s abian da" "Inaktibo; egiaztatu aplikazioa" "Ez da aurkitu" "Ez dago erabilgarri kontrolatzeko aukera" diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 8d9634de5176..8103899453a9 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -802,6 +802,7 @@ "باز کردن %1$s" "%1$s از %2$s را ازطریق %3$s پخش کنید" "%1$s را ازطریق %2$s پخش کنید" + "%1$s در حال اجرا است" "غیرفعال، برنامه را بررسی کنید" "پیدا نشد" "کنترل دردسترس نیست" diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index c3ca5d320fe7..14634a0b66c9 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -802,6 +802,7 @@ "Avaa %1$s" "Soita %1$s (%2$s) sovelluksessa %3$s" "Soita %1$s (%2$s)" + "%1$s on käynnissä" "Epäaktiivinen, tarkista sovellus" "Ei löydy" "Ohjain ei ole käytettävissä" diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index de5ac34afdb4..9f56ccac174b 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -802,6 +802,7 @@ "Ouvrez %1$s" "Lecture de %1$s par %2$s à partir de %3$s" "Lecture de %1$s à partir de %2$s" + "%1$s en cours d\'exécution" "Délai expiré, vérifiez l\'appli" "Introuvable" "La commande n\'est pas accessible" diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 13a992d5cbef..a9bd383abcbe 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -802,6 +802,7 @@ "Ouvre %1$s" "Mets %1$s par %2$s depuis %3$s" "Mets %1$s depuis %2$s" + "%1$s est en cours d\'exécution" "Délai expiré, vérifier l\'appli" "Introuvable" "Commande indisponible" diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index dcfbd710d0e3..2ba30c098e4d 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -802,6 +802,7 @@ "Abre %1$s" "Reproduce %1$s, de %2$s, en %3$s" "Reproduce %1$s en %2$s" + "%1$s estase executando" "Inactivo. Comproba a app" "Non se atopou" "O control non está dispoñible" diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 731b3767819f..daa45c7c667e 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -802,6 +802,7 @@ "%1$s ખોલો" "%3$s પર %2$sનું %1$s ગીત ચલાવો" "%2$s પર %1$s ગીત ચલાવો" + "%1$s ચાલી રહી છે" "નિષ્ક્રિય, ઍપને ચેક કરો" "મળ્યું નથી" "નિયંત્રણ ઉપલબ્ધ નથી" diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index c9a1cce59b72..5ab413be525b 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -802,6 +802,7 @@ "%1$s खोलें" "%3$s पर, %2$s का %1$s चलाएं" "%2$s पर, %1$s चलाएं" + "%1$s चालू है" "काम नहीं कर रहा, ऐप जांचें" "कंट्रोल नहीं है" "कंट्रोल मौजूद नहीं है" diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index a147fa0864d5..d0e041e5489e 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -808,6 +808,7 @@ "Otvori %1$s" "Pustite %1$s, %2$s putem aplikacije %3$s" "Pustite %1$s putem aplikacije %2$s" + "Aplikacija %1$s je pokrenuta" "Neaktivno, provjerite aplik." "Nije pronađeno" "Kontrola nije dostupna" diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index b3dc4a6b9722..bdb95f4694c5 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -802,6 +802,7 @@ "%1$s megnyitása" "%2$s %1$s című számának lejátszása innen: %3$s" "%1$s lejátszása innen: %2$s" + "A(z) %1$s jelenleg fut" "Inaktív, ellenőrizze az appot" "Nem található" "Nem hozzáférhető vezérlő" diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index b47aca4fa09a..a37c42a6b048 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -802,6 +802,7 @@ "Բացեք %1$s հավելվածը" "Նվագարկել %1$s երգը %2$s-ի կատարմամբ %3$s հավելվածից" "Նվագարկել %1$s երգը %2$s հավելվածից" + "%1$s հավելվածն աշխատում է" "Ակտիվ չէ, ստուգեք հավելվածը" "Չի գտնվել" "Կառավարման տարրը հասանելի չէ" diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 1ff99e70a805..f595b158f6e4 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -802,6 +802,7 @@ "Buka %1$s" "Putar %1$s oleh %2$s dari %3$s" "Putar %1$s dari %2$s" + "%1$s sedang berjalan" "Nonaktif, periksa aplikasi" "Tidak ditemukan" "Kontrol tidak tersedia" diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 1b709b7b6466..efce89d84d25 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -802,6 +802,7 @@ "Opna %1$s" "Spila %1$s með %2$s í %3$s" "Spila %1$s í %2$s" + "%1$s er í gangi" "Óvirkt, athugaðu forrit" "Fannst ekki" "Stýring er ekki tiltæk" diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index c203a566e522..2d67d70d6aca 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -802,6 +802,7 @@ "Apri %1$s" "Riproduci %1$s di %2$s da %3$s" "Riproduci %1$s da %2$s" + "%1$s è in esecuzione" "Inattivo, controlla l\'app" "Controllo non trovato" "Il controllo non è disponibile" diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index a7fab180f53a..5e7265ade5e2 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -814,6 +814,7 @@ "פתיחה של %1$s" "הפעלת %1$s של %2$s מ-%3$s" "הפעלת %1$s מ-%2$s" + "אפליקציית %1$s פועלת" "לא פעיל, יש לבדוק את האפליקציה" "לא נמצא" "הפקד לא זמין" diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index fdb9172d30db..39690e7e8492 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -802,6 +802,7 @@ "%1$s を開く" "%1$s(アーティスト名: %2$s)を %3$s で再生" "%1$s%2$s で再生" + "%1$s を実行しています" "無効: アプリをご確認ください" "見つかりませんでした" "コントロールを使用できません" diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 864af68b1b41..fe106b7fcb08 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -802,6 +802,7 @@ "გახსენით %1$s" "დაუკარით %1$s, %2$s, %3$s-დან" "დაუკარით %1$s %2$s-დან" + "%1$s გაშვებულია" "არააქტიურია, გადაამოწმეთ აპი" "ვერ მოიძებნა" "კონტროლი მიუწვდომელია" diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 90814466c849..0bf19821e2c0 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -802,6 +802,7 @@ "%1$s қолданбасын ашу" "%3$s қолданбасында %2$s орындайтын \"%1$s\" әнін ойнату" "%2$s қолданбасында \"%1$s\" әнін ойнату" + "%1$s қосулы тұр" "Өшірулі. Қолданба тексеріңіз." "Табылмады" "Басқару виджеті қолжетімсіз" diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 26083e79c1be..9b849b671a16 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -802,6 +802,7 @@ "បើក %1$s" "ចាក់ %1$s ច្រៀងដោយ %2$s ពី %3$s" "ចាក់ %1$s ពី %2$s" + "%1$s កំពុង​ដំណើរការ" "អសកម្ម ពិនិត្យមើល​កម្មវិធី" "រកមិន​ឃើញទេ" "មិនអាច​គ្រប់គ្រង​បានទេ" diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 2fd682fad66c..51cd2aea5463 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -802,6 +802,7 @@ "%1$s ಅನ್ನು ತೆರೆಯಿರಿ" "%2$s ಅವರ %1$s ಹಾಡನ್ನು %3$s ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ" "%1$s ಹಾಡನ್ನು %2$s ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ" + "%1$s ರನ್ ಆಗುತ್ತಿದೆ" "ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ" "ಕಂಡುಬಂದಿಲ್ಲ" "ನಿಯಂತ್ರಣ ಲಭ್ಯವಿಲ್ಲ" diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 75f48e549d55..afbddd3b8191 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -802,6 +802,7 @@ "%1$s 열기" "%3$s에서 %2$s%1$s 재생" "%2$s에서 %1$s 재생" + "%1$s 실행 중" "비활성. 앱을 확인하세요." "찾을 수 없음" "컨트롤을 사용할 수 없음" diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 211098988cdd..e6336fa83463 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -802,6 +802,7 @@ "%1$s колдонмосун ачуу" "%1$s ырын (аткаруучу: %2$s) %3$s колдонмосунан ойнотуу" "%1$s ырын %2$s колдонмосунан ойнотуу" + "%1$s иштеп жатат" "Жигерсиз. Колдонмону текшериңиз" "Табылган жок" "Башкара албайсыз" diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 3e70601245f1..9afb370b38fa 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -802,6 +802,7 @@ "ເປີດ %1$s" "ຫຼິ້ນ %1$s ໂດຍ %2$s ຈາກ %3$s" "ຫຼິ້ນ %1$s ຈາກ %2$s" + "%1$s ກຳລັງເຮັດວຽກຢູ່" "ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ" "ບໍ່ພົບ" "ບໍ່ສາມາດໃຊ້ການຄວບຄຸມໄດ້" diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 82fbebecf1d9..4efb90b215a9 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -814,6 +814,7 @@ "Atidaryti „%1$s“" "Leisti %2$s – „%1$s“ iš „%3$s“" "Leisti „%1$s“ iš „%2$s“" + "„%1$s“ vykdoma" "Neaktyvu, patikrinkite progr." "Nerasta" "Valdiklis nepasiekiamas" diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index fde5dba60ad8..54d7f29e3e57 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -808,6 +808,7 @@ "Atveriet lietotni %1$s." "Atskaņojiet failu “%1$s” (izpildītājs: %2$s) no lietotnes %3$s." "Atskaņojiet failu “%1$s” no lietotnes %2$s." + "Lietotne %1$s darbojas" "Neaktīva, pārbaudiet lietotni" "Netika atrasta" "Vadīkla nav pieejama" diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 988bf21fffc5..c1b618748e43 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -802,6 +802,7 @@ "Отворете %1$s" "Пуштете %1$s од %2$s на %3$s" "Пуштете %1$s на %2$s" + "%1$s работи" "Неактивна, провери апликација" "Не е најдено" "Контролата не е достапна" diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 0362d0e59d5b..6e1157e2f44a 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -802,6 +802,7 @@ "%1$s തുറക്കുക" "%2$s എന്ന ആർട്ടിസ്റ്റിന്റെ %1$s എന്ന ഗാനം %3$s ആപ്പിൽ പ്ലേ ചെയ്യുക" "%1$s എന്ന ഗാനം %2$s ആപ്പിൽ പ്ലേ ചെയ്യുക" + "%1$s റൺ ചെയ്യുന്നു" "നിഷ്‌ക്രിയം, ആപ്പ് പരിശോധിക്കൂ" "കണ്ടെത്തിയില്ല" "നിയന്ത്രണം ലഭ്യമല്ല" diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 3e517cc592ec..4e975fb4824b 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -802,6 +802,7 @@ "%1$s-г нээх" "%2$s%1$s%3$s дээр тоглуулах" "%1$s%2$s дээр тоглуулах" + "%1$s ажиллаж байна" "Идэвхгүй байна, аппыг шалгана уу" "Олдсонгүй" "Хяналт боломжгүй байна" diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 345b06be280c..63cad6a3a5e4 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -802,6 +802,7 @@ "%1$s उघडा" "%3$s मध्ये %2$s चे %1$s प्ले करा" "%2$s मध्ये %1$s प्ले करा" + "%1$s रन होत आहे" "निष्क्रिय, ॲप तपासा" "आढळले नाही" "नियंत्रण उपलब्ध नाही" diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 39d2825ae951..71d6d57ad312 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -802,6 +802,7 @@ "Buka %1$s" "Mainkan %1$s oleh %2$s daripada %3$s" "Mainkan %1$s daripada %2$s" + "%1$s sedang dijalankan" "Tidak aktif, semak apl" "Tidak ditemukan" "Kawalan tidak tersedia" diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 26169012bd5a..30e5450590ba 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -802,6 +802,7 @@ "%1$s ကို ဖွင့်ပါ" "%2$s%1$s ကို %3$s တွင် ဖွင့်ပါ" "%1$s ကို %2$s တွင် ဖွင့်ပါ" + "%1$s ပွင့်နေပါသည်" "ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ" "မတွေ့ပါ" "ထိန်းချုပ်မှု မရနိုင်ပါ" diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index ba6f8cf4fd77..3d450e14569e 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -802,6 +802,7 @@ "Åpne %1$s" "Spill av %1$s av %2$s fra %3$s" "Spill av %1$s fra %2$s" + "%1$s kjører" "Inaktiv. Sjekk appen" "Ikke funnet" "Kontrollen er utilgjengelig" diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 7024a77f5262..a3e5c47c826d 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -802,6 +802,7 @@ "%1$s खोल्नुहोस्" "%2$s को %1$s बोलको गीत %3$s मा बजाउनुहोस्" "%1$s बोलको गीत %2$s मा बजाउनुहोस्" + "%1$s चलिरहेको छ" "निष्क्रिय छ, एप जाँच गर्नु…" "फेला परेन" "नियन्त्रण उपलब्ध छैन" diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 9ee7d3691625..cac1701897da 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -802,6 +802,7 @@ "%1$s openen" "%1$s van %2$s afspelen via %3$s" "%1$s afspelen via %2$s" + "%1$s is actief" "Inactief, check de app" "Niet gevonden" "Beheeroptie niet beschikbaar" diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index e961716b982a..68621503f0c9 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -802,6 +802,7 @@ "%1$s ଖୋଲନ୍ତୁ" "%3$sରୁ %2$sଙ୍କ %1$s ଚଲାନ୍ତୁ" "%2$sରୁ %1$s ଚଲାନ୍ତୁ" + "%1$s ଚାଲୁଛି" "ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ" "ମିଳିଲା ନାହିଁ" "ନିୟନ୍ତ୍ରଣ ଉପଲବ୍ଧ ନାହିଁ" diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index cbe5aae69f6e..01a6b584e146 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -802,6 +802,7 @@ "%1$s ਖੋਲ੍ਹੋ" "%3$s ਤੋਂ %2$s ਦਾ %1$s ਚਲਾਓ" "%2$s ਤੋਂ %1$s ਚਲਾਓ" + "%1$s ਚੱਲ ਰਿਹਾ ਹੈ" "ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ" "ਨਹੀਂ ਮਿਲਿਆ" "ਕੰਟਰੋਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 055316793a88..62fa1edf0081 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -814,6 +814,7 @@ "Otwórz aplikację %1$s" "Odtwórz utwór %1$s (%2$s) w aplikacji %3$s" "Odtwórz utwór %1$s w aplikacji %2$s" + "Aplikacja %1$s jest uruchomiona" "Nieaktywny, sprawdź aplikację" "Nie znaleziono" "Element jest niedostępny" diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 3097e8f22b32..cfa071e8a5a5 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -802,6 +802,7 @@ "Abrir %1$s" "Tocar %1$s de %2$s no app %3$s" "Tocar %1$s no app %2$s" + "%1$s está em execução" "Inativo, verifique o app" "Não encontrado" "O controle está indisponível" diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 742b42aff669..051aac78464b 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -802,6 +802,7 @@ "Abrir %1$s" "Reproduzir %1$s de %2$s a partir da app %3$s" "Reproduzir %1$s a partir da app %2$s" + "%1$s em execução" "Inativa. Consulte a app." "Não encontrado." "O controlo está indisponível" diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 3097e8f22b32..cfa071e8a5a5 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -802,6 +802,7 @@ "Abrir %1$s" "Tocar %1$s de %2$s no app %3$s" "Tocar %1$s no app %2$s" + "%1$s está em execução" "Inativo, verifique o app" "Não encontrado" "O controle está indisponível" diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index cd03b08f6fed..898ffe8bd914 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -808,6 +808,7 @@ "Deschideți %1$s" "Redați %1$s de la %2$s în %3$s" "Redați %1$s în %2$s" + "%1$s rulează" "Inactiv, verificați aplicația" "Nu s-a găsit" "Comanda este indisponibilă" diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index d06151b3c0d6..ca14d410cc97 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -814,6 +814,7 @@ "Открыть приложение \"%1$s\"" "Воспроизвести медиафайл \"%1$s\" (исполнитель: %2$s) из приложения \"%3$s\"" "Воспроизвести медиафайл \"%1$s\" из приложения \"%2$s\"" + "Приложение \"%1$s\" запущено" "Нет ответа. Проверьте приложение." "Не найдено." "Управление недоступно" diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index be1448b6ff78..d5702d5e81a2 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -802,6 +802,7 @@ "%1$s විවෘත කරන්න" "%2$sගේ %1$s %3$s වෙතින් වාදනය කරන්න" "%1$s %2$s වෙතින් වාදනය කරන්න" + "%1$s ධාවනය වේ" "අක්‍රියයි, යෙදුම පරීක්ෂා කරන්න" "හමු නොවිණි" "පාලනය ලබා ගත නොහැකිය" diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 61b50fa3d8f3..0427f4e4a611 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -814,6 +814,7 @@ "Otvoriť %1$s" "Prehrať skladbu %1$s od interpreta %2$s z aplikácie %3$s" "Prehrať skladbu %1$s z aplikácie %2$s" + "Aplikácia %1$s je spustená" "Neaktívne, preverte aplikáciu" "Nenájdené" "Ovládač nie je k dispozícii" diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index ccad46df2f2f..14dc96669272 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -814,6 +814,7 @@ "Odpri aplikacijo %1$s." "Predvajaj skladbo %1$s izvajalca %2$s iz aplikacije %3$s." "Predvajaj skladbo %1$s iz aplikacije %2$s." + "%1$s se izvaja" "Neaktivno, poglejte aplikacijo" "Ni mogoče najti" "Kontrolnik ni na voljo" diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 0dc4826e9ed9..9a4ace0f61bf 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -802,6 +802,7 @@ "Hap %1$s" "Luaj %1$s nga %2$s nga %3$s" "Luaj %1$s nga %2$s" + "%1$s po ekzekutohet" "Joaktive, kontrollo aplikacionin" "Nuk u gjet" "Kontrolli është i padisponueshëm" diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 1f7b71617c66..8d95ac3984c5 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -808,6 +808,7 @@ "Отворите %1$s" "Пустите %1$s извођача %2$s из апликације %3$s" "Пустите %1$s из апликације %2$s" + "Апликација %1$s је покренута" "Неактивно. Видите апликацију" "Није пронађено" "Контрола није доступна" diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 0f403b252961..aced91b37539 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -802,6 +802,7 @@ "Öppna %1$s" "Spela upp %1$s med %2$s från %3$s" "Spela upp %1$s från %2$s" + "%1$s körs" "Inaktiv, kolla appen" "Hittades inte" "Styrning är inte tillgänglig" diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 361171c1d0b1..973c4bf2fba4 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -802,6 +802,7 @@ "Fungua %1$s" "Cheza %1$s ulioimbwa na %2$s katika %3$s" "Cheza %1$s katika %2$s" + "%1$s inatumika" "Haitumiki, angalia programu" "Hakipatikani" "Kidhibiti hakipatikani" diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 9e8ec2ddc3e3..f88b68832e1a 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -802,6 +802,7 @@ "%1$s ஆப்ஸைத் திறங்கள்" "%2$s இன் %1$s பாடலை %3$s ஆப்ஸில் பிளேசெய்" "%1$s பாடலை %2$s ஆப்ஸில் பிளேசெய்" + "%1$s இயங்கிக் கொண்டிருக்கிறது" "செயலில் இல்லை , சரிபார்க்கவும்" "இல்லை" "கட்டுப்பாடு இல்லை" diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index e1e872339028..0307ea7bf6ec 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -802,6 +802,7 @@ "%1$sను తెరవండి" "%3$s నుండి %2$s పాడిన %1$s‌ను ప్లే చేయండి" "%2$s నుండి %1$s‌ను ప్లే చేయండి" + "%1$s రన్ అవుతోంది" "ఇన్‌యాక్టివ్, యాప్ చెక్ చేయండి" "కనుగొనబడలేదు" "కంట్రోల్ అందుబాటులో లేదు" diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index a22112312928..bd38cef17e09 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -802,6 +802,7 @@ "เปิด %1$s" "เปิดเพลง %1$s ของ %2$s จาก %3$s" "เปิดเพลง %1$s จาก %2$s" + "%1$s กำลังทำงาน" "ไม่มีการใช้งาน โปรดตรวจสอบแอป" "ไม่พบ" "ใช้การควบคุมไม่ได้" diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index a67f90def41f..781786b60141 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -802,6 +802,7 @@ "Buksan ang %1$s" "I-play ang %1$s ni/ng %2$s mula sa %3$s" "I-play ang %1$s mula sa %2$s" + "Tumatakbo ang %1$s" "Hindi aktibo, tingnan ang app" "Hindi nahanap" "Hindi available ang kontrol" diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 63356de9aa36..c5fdffdc9471 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -802,6 +802,7 @@ "%1$s uygulamasını aç" "%3$s uygulamasından %2$s, %1$s şarkısını çal" "%2$s uygulamasından %1$s şarkısını çal" + "%1$s çalışıyor" "Devre dışı, uygulamaya bakın" "Bulunamadı" "Kontrol kullanılamıyor" diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 518845fd238d..51b8010d7dfd 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -814,6 +814,7 @@ "Відкрити додаток %1$s" "Увімкнути пісню \"%1$s\", яку виконує %2$s, у додатку %3$s" "Увімкнути пісню \"%1$s\" у додатку %2$s" + "%1$s працює" "Неактивно, перейдіть у додаток" "Не знайдено" "Елемент керування недоступний" diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 78b9a42d6a96..c866410ab678 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -802,6 +802,7 @@ "%1$s کھولیں" "%3$s سے %2$s کا %1$s چلائیں" "%2$s سے %1$s چلائیں" + "%1$s چل رہی ہے" "غیر فعال، ایپ چیک کریں" "نہیں ملا" "کنٹرول دستیاب نہیں ہے" diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index fef334e8759a..ed4ef99a2583 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -802,6 +802,7 @@ "%1$s ilovasini ochish" "%3$s ilovasida ijro etish: %1$s%2$s" "%2$s ilovasida ijro etilmoqda: %1$s" + "%1$s ishlamoqda" "Nofaol. Ilovani tekshiring" "Topilmadi" "Boshqarish imkonsiz" diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 157677cb553d..489cabd36bcf 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -802,6 +802,7 @@ "Mở %1$s" "Phát %1$s của %2$s trên %3$s" "Phát %1$s trên %2$s" + "%1$s đang chạy" "Không hoạt động, hãy kiểm tra ứng dụng" "Không tìm thấy" "Không có chức năng điều khiển" diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index be2e06a46c5e..e355cee649c8 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -802,6 +802,7 @@ "打开%1$s" "通过%3$s播放%2$s的《%1$s》" "通过%2$s播放《%1$s》" + "“%1$s”正在运行" "无效,请检查应用" "未找到" "控件不可用" diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index eb9527e8533c..4618e00bc3d6 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -802,6 +802,7 @@ "開啟 %1$s" "在 %3$s 播放 %2$s 的《%1$s》" "在 %2$s 播放《%1$s》" + "「%1$s」執行中" "已停用,請檢查應用程式" "找不到" "無法使用控制功能" diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index e40dec8e437c..19c7e3a7a0f8 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -802,6 +802,7 @@ "開啟「%1$s」" "透過「%3$s」播放%2$s的〈%1$s〉" "透過「%2$s」播放〈%1$s〉" + "「%1$s」執行中" "無效,請查看應用程式" "找不到控制項" "無法使用控制項" diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index b08eddfaa0b4..75c32d3c27cb 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -802,6 +802,7 @@ "Vula i-%1$s" "Dlala i-%1$s ka-%2$s kusuka ku-%3$s" "Dlala i-%1$s kusuka ku-%2$s" + "I-%1$s iyasebenza" "Akusebenzi, hlola uhlelo lokusebenza" "Ayitholakali" "Ukulawula akutholakali" -- cgit v1.2.3 From 36a69819b025ddc0c3e8fb5747e45531427280a4 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Mon, 3 Jul 2023 16:29:47 +0000 Subject: DO NOT MERGE Revert "Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS." This reverts commit 43b1711332763788c7abf05c3baa931296c45bbb. Reason for revert: regression reported at b/289223315 Bug: 289223315 Bug: 276729064 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:bdc9b977e376fb3b6047530a179d00fd77f2aec1) Merged-In: I101938fbc51592537023345ba1e642827510981b Change-Id: I101938fbc51592537023345ba1e642827510981b --- core/java/android/app/Notification.java | 11 ----------- .../server/notification/NotificationManagerServiceTest.java | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 95506c42826d..c9d7e8013a3d 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2815,17 +2815,6 @@ public class Notification implements Parcelable if (person != null) { visitor.accept(person.getIconUri()); } - - final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) - extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); - if (history != null) { - for (int i = 0; i < history.length; i++) { - RemoteInputHistoryItem item = history[i]; - if (item.getUri() != null) { - visitor.accept(item.getUri()); - } - } - } } if (isStyle(MessagingStyle.class) && extras != null) { 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 7cede43f31e7..e27f56930b07 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -120,7 +120,6 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; -import android.app.RemoteInputHistoryItem; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -4493,12 +4492,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setName("People List Person 2") .setIcon(personIcon3) .build(); - final Uri historyUri1 = Uri.parse("content://com.example/history1"); - final Uri historyUri2 = Uri.parse("content://com.example/history2"); - final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1, - "a"); - final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2, - "b"); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); @@ -4506,8 +4499,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, new ArrayList<>(Arrays.asList(person2, person3))); - extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, - new RemoteInputHistoryItem[]{historyItem1, historyItem2}); Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") @@ -4525,8 +4516,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(personIcon1.getUri())); verify(visitor, times(1)).accept(eq(personIcon2.getUri())); verify(visitor, times(1)).accept(eq(personIcon3.getUri())); - verify(visitor, times(1)).accept(eq(historyUri1)); - verify(visitor, times(1)).accept(eq(historyUri2)); } @Test -- cgit v1.2.3 From b620c220922799b58aa390c9cf3efce956c98802 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Mon, 8 May 2023 18:39:35 +0000 Subject: Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS. Also added a step to serialize & deserialize the notification in the test, to prevent exceptions about not being able to cast e.g. Parcelable[] to RemoteInputHistoryItem[]. Test: atest NotificationManagerServiceTest & tested with POC from bug Bug: 276729064 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:88e597d2b31d054ab5286b3a666accb08a8db5d5) Merged-In: I7053ca59f9c7f1df5226418594109cfb8b609b1e Change-Id: I7053ca59f9c7f1df5226418594109cfb8b609b1e --- core/java/android/app/Notification.java | 11 +++++++++++ .../notification/NotificationManagerServiceTest.java | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index c9d7e8013a3d..15a0fb81c138 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2815,6 +2815,17 @@ public class Notification implements Parcelable if (person != null) { visitor.accept(person.getIconUri()); } + + final RemoteInputHistoryItem[] history = getParcelableArrayFromBundle(extras, + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); + if (history != null) { + for (int i = 0; i < history.length; i++) { + RemoteInputHistoryItem item = history[i]; + if (item.getUri() != null) { + visitor.accept(item.getUri()); + } + } + } } if (isStyle(MessagingStyle.class) && extras != null) { 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 e27f56930b07..78caec02c2d5 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -120,6 +120,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -4492,6 +4493,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setName("People List Person 2") .setIcon(personIcon3) .build(); + final Uri historyUri1 = Uri.parse("content://com.example/history1"); + final Uri historyUri2 = Uri.parse("content://com.example/history2"); + final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1, + "a"); + final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2, + "b"); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); @@ -4499,6 +4506,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, new ArrayList<>(Arrays.asList(person2, person3))); + extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + new RemoteInputHistoryItem[]{historyItem1, historyItem2}); Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") @@ -4507,6 +4516,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .addExtras(extras) .build(); + // Serialize and deserialize the notification to make sure nothing breaks in the process, + // since that's what will usually happen before we get to call visitUris. + Parcel parcel = Parcel.obtain(); + n.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + n = new Notification(parcel); + Consumer visitor = (Consumer) spy(Consumer.class); n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); @@ -4516,6 +4532,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(personIcon1.getUri())); verify(visitor, times(1)).accept(eq(personIcon2.getUri())); verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + verify(visitor, times(1)).accept(eq(historyUri1)); + verify(visitor, times(1)).accept(eq(historyUri2)); } @Test -- cgit v1.2.3 From f4a144582ac66430900177d656b8ecb04a3d5b99 Mon Sep 17 00:00:00 2001 From: Josep del Rio Date: Mon, 26 Jun 2023 09:30:06 +0000 Subject: Do not share key mappings with JNI object The key mapping information between the native key mappings and the KeyCharacterMap object available in Java is currently shared, which means that a read can be attempted while it's being modified. Bug: 274058082 Test: Patch tested by Oppo (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3d993de0d1ada8065d1fe561f690c8f82b6a7d4b) Merged-In: I745008a0a8ea30830660c45dcebee917b3913d13 Change-Id: I745008a0a8ea30830660c45dcebee917b3913d13 --- core/jni/android_view_InputDevice.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp index 9cc72437a023..f7c770e0bffb 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 map = deviceInfo.getKeyCharacterMap(); + if (map != nullptr) { + map = std::make_shared(*map); + } + ScopedLocalRef descriptorObj(env, env->NewStringUTF(deviceInfo.getIdentifier().descriptor.c_str())); if (!descriptorObj.get()) { @@ -49,8 +56,8 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi } ScopedLocalRef kcmObj(env, - android_view_KeyCharacterMap_create(env, deviceInfo.getId(), - deviceInfo.getKeyCharacterMap())); + android_view_KeyCharacterMap_create(env, deviceInfo.getId(), + map)); if (!kcmObj.get()) { return NULL; } -- cgit v1.2.3 From f4e20cbacc299190335c52b2b5920b175401971a Mon Sep 17 00:00:00 2001 From: Tim Yu Date: Tue, 20 Jun 2023 21:24:36 +0000 Subject: [DO NOT MERGE] Verify URI Permissions in Autofill RemoteViews Check permissions of URI inside of FillResponse's RemoteViews. If the current user does not have the required permissions to view the URI, the RemoteView is dropped from displaying. This fixes a security spill in which a user can view content of another user through a malicious Autofill provider. Bug: 283137865 Fixes: b/283264674 b/281666022 b/281665050 b/281848557 b/281533566 b/281534749 b/283101289 Test: Verified by POC app attached in bugs Test: atest CtsAutoFillServiceTestCases (added new tests) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f7ca136c514dc975c3f46d95c53fd6b3752c577a) Merged-In: I6f4d2a35e89bbed7bd9e07bf5cd3e2d68b20af9a Change-Id: I6f4d2a35e89bbed7bd9e07bf5cd3e2d68b20af9a --- .../java/com/android/server/autofill/Helper.java | 43 ++++++++++++++++++++++ .../com/android/server/autofill/ui/FillUi.java | 11 ++++-- .../com/android/server/autofill/ui/SaveUi.java | 3 +- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index bc5d6457c945..48113a81cca5 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -18,6 +18,8 @@ package com.android.server.autofill; 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; @@ -34,6 +36,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; @@ -42,6 +45,8 @@ import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + public final class Helper { @@ -75,6 +80,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 set) { if (set == null) return null; 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 27ea3d618afa..af6e38b1d46c 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -141,8 +141,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); @@ -220,6 +221,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); @@ -299,7 +303,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 826a98afe2f8..49df4a8b66ed 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -368,8 +368,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; -- cgit v1.2.3 From 87a3f89ced8b805dedcfff212f6f1d2bd6d6cf95 Mon Sep 17 00:00:00 2001 From: Varun Shah Date: Fri, 19 May 2023 17:36:30 -0700 Subject: Update parcling logic for Uris. Implicitly convert all Uris to StringUris during parcel read/write. Bug: 231476072 Test: atest UriTest (cherry picked from commit 98bc5f99b14239aa871a998548ad80a076756318) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:18a2f95baeabdf23ecdb0475e62b8395825a26f6) Merged-In: Ic7688a00a07705301e5b06ee8783e801395e9f15 Change-Id: Ic7688a00a07705301e5b06ee8783e801395e9f15 --- core/java/android/net/Uri.java | 36 ++++--- core/tests/coretests/src/android/net/UriTest.java | 116 +++++++++++----------- 2 files changed, 77 insertions(+), 75 deletions(-) diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index d71faee4cc8d..c66891c8c0f4 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -885,10 +885,11 @@ public abstract class Uri implements Parcelable, Comparable { } 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() ); } @@ -898,9 +899,7 @@ public abstract class Uri implements Parcelable, Comparable { 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() { @@ -1199,22 +1198,25 @@ public abstract class Uri implements Parcelable, Comparable { 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() { @@ -1223,11 +1225,7 @@ public abstract class Uri implements Parcelable, Comparable { 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/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 3733bfa586d1..5275d39a6bc1 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"); -- cgit v1.2.3 From 3dc28011c79c58cc8e46bb3b5ccf90af76f664f5 Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Wed, 24 May 2023 20:19:43 -0700 Subject: Disallow loading icon from content URI to PipMenu Bug: 278246904 Test: manually, with the PoC app attached to the bug (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4bf71d74fc21cd9389dbe00fb750e2f9802eb789) Merged-In: Idbd4081bf464e2b3420d4c3fd22ca37867d26bc0 Change-Id: Idbd4081bf464e2b3420d4c3fd22ca37867d26bc0 --- .../com/android/wm/shell/pip/phone/PipMenuView.java | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index e1475efcdb57..d7e710d4de3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -43,6 +43,7 @@ import android.content.Intent; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -488,13 +489,19 @@ public class PipMenuView extends FrameLayout { final PipMenuActionView actionView = (PipMenuActionView) mActionsGroup.getChildAt(i); - // TODO: Check if the action drawable has changed before we reload it - action.getIcon().loadDrawableAsync(mContext, d -> { - if (d != null) { - d.setTint(Color.WHITE); - actionView.setImageDrawable(d); - } - }, mMainHandler); + final int iconType = action.getIcon().getType(); + if (iconType == Icon.TYPE_URI || iconType == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + // Disallow loading icon from content URI + actionView.setImageDrawable(null); + } else { + // TODO: Check if the action drawable has changed before we reload it + action.getIcon().loadDrawableAsync(mContext, d -> { + if (d != null) { + d.setTint(Color.WHITE); + actionView.setImageDrawable(d); + } + }, mMainHandler); + } actionView.setContentDescription(action.getContentDescription()); if (action.isEnabled()) { actionView.setOnClickListener(v -> { -- cgit v1.2.3 From d4e639b896a0c456f07856cf6825b03d13d54522 Mon Sep 17 00:00:00 2001 From: Kunal Malhotra Date: Fri, 2 Jun 2023 23:32:02 +0000 Subject: Fixing DatabaseUtils to detect malformed UTF-16 strings Test: tested with POC in bug, also using atest Bug: 224771621 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:fb4a72e3943d166088407e61aa4439ac349f3f12) Merged-In: Ide65205b83063801971c5778af3154bcf3f0e530 Change-Id: Ide65205b83063801971c5778af3154bcf3f0e530 --- core/java/android/database/DatabaseUtils.java | 32 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 6c8a8500e4e3..d41df4f49d48 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('\''); } -- cgit v1.2.3 From 5a4165a65c5e96d98465141eae33e1d95819d435 Mon Sep 17 00:00:00 2001 From: Anton Potapov Date: Fri, 7 Jul 2023 12:05:10 +0100 Subject: Add userId check before loading icon in Device Controls Test: manual with the steps from the bug Test: manual with a normal icon Test: atest CanUseIconPredicate Test: atest ControlViewHolderTest Bug: 272025416 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ffa97f42dd9496bb404e01727c923292d05a4466) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2be24a7804701f3dc3e185196a55e4b0add2b79b) Merged-In: Ibe4fb69a90904787b9f97a7cd90d318a047d1e11 Change-Id: Ibe4fb69a90904787b9f97a7cd90d318a047d1e11 --- .../systemui/controls/management/ControlAdapter.kt | 11 +++- .../controls/management/ControlsEditingActivity.kt | 2 +- .../management/ControlsFavoritingActivity.kt | 5 +- .../controls/management/StructureAdapter.kt | 11 ++-- .../systemui/controls/ui/CanUseIconPredicate.kt | 30 ++++++++++ .../systemui/controls/ui/ControlViewHolder.kt | 54 ++++++++--------- .../controls/ui/ControlsUiControllerImpl.kt | 3 +- .../controls/ui/TemperatureControlBehavior.kt | 2 +- .../systemui/controls/ui/ThumbnailBehavior.kt | 11 +++- .../controls/ui/CanUseIconPredicateTest.kt | 67 ++++++++++++++++++++++ .../systemui/controls/ui/ControlViewHolderTest.kt | 3 +- 11 files changed, 157 insertions(+), 42 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt 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 40662536e57e..45916d6e24ab 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -36,6 +36,7 @@ import androidx.recyclerview.widget.GridLayoutManager 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 @@ -49,7 +50,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() { companion object { @@ -84,6 +86,7 @@ class ControlAdapter( background = parent.context.getDrawable( R.drawable.control_background_ripple) }, + currentUserId, model?.moveHelper // Indicates that position information is needed ) { id, favorite -> model?.changeFavoriteStatus(id, favorite) @@ -189,6 +192,7 @@ private class ZoneHolder(view: View) : Holder(view) { */ internal class ControlHolder( view: View, + currentUserId: Int, val moveHelper: ControlsModel.MoveHelper?, val favoriteCallback: ModelFavoriteChanger ) : Holder(view) { @@ -205,6 +209,7 @@ internal class ControlHolder( visibility = View.VISIBLE } + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val accessibilityDelegate = ControlHolderAccessibilityDelegate( this::stateDescription, this::getLayoutPosition, @@ -264,7 +269,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 6f94943472b1..0ecfa3d1dd27 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -180,7 +180,7 @@ class ControlsEditingActivity @Inject constructor( val elevation = resources.getFloat(R.dimen.control_card_elevation) val recyclerView = requireViewById(R.id.list) recyclerView.alpha = 0.0f - val adapter = ControlAdapter(elevation).apply { + val adapter = ControlAdapter(elevation, currentUserTracker.currentUserId).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 dca52a9678b9..ab5bc7c707f4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -164,7 +164,8 @@ class ControlsFavoritingActivity @Inject constructor( } executor.execute { - structurePager.adapter = StructureAdapter(listOfStructures) + structurePager.adapter = StructureAdapter(listOfStructures, + currentUserTracker.currentUserId) structurePager.setCurrentItem(structureIndex) if (error) { statusText.text = resources.getString(R.string.controls_favorite_load_error, @@ -210,7 +211,7 @@ class ControlsFavoritingActivity @Inject constructor( structurePager.alpha = 0.0f pageIndicator.alpha = 0.0f structurePager.apply { - adapter = StructureAdapter(emptyList()) + adapter = StructureAdapter(emptyList(), currentUserTracker.currentUserId) 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 cb67454195ec..7524f1cc2226 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 + private val models: List, + private val currentUserId: Int ) : RecyclerView.Adapter() { 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(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 000000000000..61c21237144d --- /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 47e749cd1185..d79cb7ae8617 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -54,7 +54,6 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.controller.ControlsController import com.android.systemui.util.concurrency.DelayableExecutor -import kotlin.reflect.KClass /** * Wraps the widgets that make up the UI representation of a {@link Control}. Updates to the view @@ -68,7 +67,8 @@ class ControlViewHolder( val bgExecutor: DelayableExecutor, val controlActionCoordinator: ControlActionCoordinator, val controlsMetricsLogger: ControlsMetricsLogger, - val uid: Int + val uid: Int, + val currentUserId: Int ) { companion object { @@ -85,27 +85,6 @@ 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 - ): KClass { - return when { - status != Control.STATUS_OK -> StatusBehavior::class - template == ControlTemplate.NO_TEMPLATE -> TouchBehavior::class - template is ThumbnailTemplate -> ThumbnailBehavior::class - - // Required for legacy support, or where cameras do not use the new template - deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class - template is ToggleTemplate -> ToggleBehavior::class - template is StatelessTemplate -> TouchBehavior::class - template is ToggleRangeTemplate -> ToggleRangeBehavior::class - template is RangeTemplate -> ToggleRangeBehavior::class - template is TemperatureControlTemplate -> TemperatureControlBehavior::class - else -> DefaultBehavior::class - } - } } private val toggleBackgroundIntensity: Float = layout.context.resources @@ -146,6 +125,26 @@ class ControlViewHolder( status.setSelected(true) } + fun findBehavior( + status: Int, + template: ControlTemplate, + deviceType: Int + ): () -> Behavior { + return when { + status != Control.STATUS_OK -> { { StatusBehavior() } } + template == ControlTemplate.NO_TEMPLATE -> { { TouchBehavior() } } + template is ThumbnailTemplate -> { { ThumbnailBehavior(currentUserId) } } + // Required for legacy support, or where cameras do not use the new template + deviceType == DeviceTypes.TYPE_CAMERA -> { { TouchBehavior() } } + template is ToggleTemplate -> { { ToggleBehavior() } } + template is StatelessTemplate -> { { TouchBehavior() } } + template is ToggleRangeTemplate -> { { ToggleRangeBehavior() } } + template is RangeTemplate -> { { ToggleRangeBehavior() } } + template is TemperatureControlTemplate -> { { TemperatureControlBehavior() } } + else -> { { 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 @@ -179,7 +178,7 @@ class ControlViewHolder( val wasLoading = isLoading isLoading = false behavior = bindBehavior(behavior, - findBehaviorClass(controlStatus, controlTemplate, deviceType)) + findBehavior(controlStatus, controlTemplate, deviceType)) updateContentDescription() // Only log one event per control, at the moment we have determined that the control @@ -251,13 +250,14 @@ class ControlViewHolder( fun bindBehavior( existingBehavior: Behavior?, - clazz: KClass, + createBehaviour: () -> Behavior, offset: Int = 0 ): Behavior { - val behavior = if (existingBehavior == null || existingBehavior!!::class != clazz) { + val newBehavior = createBehaviour() + val behavior = if (existingBehavior == null || + existingBehavior::class != newBehavior::class) { // Behavior changes can signal a change in template from the app or // first time setup - val newBehavior = clazz.java.newInstance() newBehavior.initialize(this) // let behaviors define their own, if necessary, and clear any existing ones 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 5a52fd0516ea..267481f889a5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -412,7 +412,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 a7dc09bb17e5..42922441fa30 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.findBehavior( 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 c2168aa8d9d9..5360eea847ee 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,11 +81,15 @@ 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() - clipLayer.setDrawable(CornerDrawable(drawable, radius)) + drawable?.let { + clipLayer.drawable = CornerDrawable(it, radius) + } clipLayer.setColorFilter(BlendModeColorFilter(cvh.context.resources .getColor(R.color.control_thumbnail_tint), BlendMode.LUMINOSITY)) cvh.applyRenderInfo(enabled, colorOffset) 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 000000000000..ed17f179eeb0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt @@ -0,0 +1,67 @@ +/* + * 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.graphics.Bitmap +import android.graphics.drawable.Icon +import android.net.Uri +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("content://$USER_ID_2@test") + + assertThat(underTest.invoke(user2Icon)).isFalse() + } + + @Test + fun testReturnsTrueForCorrectUser() { + val user1Icon = Icon.createWithContentUri("content://$USER_ID_1@test") + + 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 47ab17dd7ed0..26095cda5248 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 @@ -63,7 +63,8 @@ class ControlViewHolderTest : SysuiTestCase() { FakeExecutor(clock), mock(ControlActionCoordinator::class.java), mock(ControlsMetricsLogger::class.java), - uid = 100 + uid = 100, + currentUserId = 0 ) val cws = ControlWithState( -- cgit v1.2.3 From 9c114782e6094b113c923111a77f6149d7efda10 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 28 Jul 2023 22:03:03 +0000 Subject: RESTRICT AUTOMERGE: SettingsProvider: exclude secure_frp_mode from resets When RescueParty detects that a system process is crashing frequently, it tries to recover in various ways, such as by resetting all settings. Unfortunately, this included resetting the secure_frp_mode setting, which is the means by which the system keeps track of whether the Factory Reset Protection (FRP) challenge has been passed yet. With this setting reset, some FRP restrictions went away and it became possible to bypass FRP by setting a new lockscreen credential. Fix this by excluding secure_frp_mode from resets. Note: currently this bug isn't reproducible on 'main' due to ag/23727749 disabling much of RescueParty, but that is a temporary change. Bug: 253043065 Test: With ag/23727749 reverted and with my fix to prevent com.android.settings from crashing *not* applied, tried repeatedly setting lockscreen credential while in FRP mode, using the smartlock setup activity launched by intent via adb. Verified that although RescueParty is still triggered after 5 attempts, secure_frp_mode is no longer reset (its value remains "1"). Test: Verified that secure_frp_mode still gets changed from 1 to 0 when FRP is passed legitimately. Test: atest com.android.providers.settings.SettingsProviderTest Test: atest android.provider.SettingsProviderTest (cherry picked from commit 9890dd7f15c091f7d1a09e4fddb9f85d32015955) (changed Global.SECURE_FRP_MODE to Secure.SECURE_FRP_MODE, needed because this setting was moved in U) (removed static keyword from shouldExcludeSettingFromReset(), needed for compatibility with Java 15 and earlier) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a7ea34724f1bdebde64d9e9a1391c92dc2e189b5) Merged-In: Id95ed43b9cc2208090064392bcd5dc012710af93 Change-Id: Id95ed43b9cc2208090064392bcd5dc012710af93 --- .../providers/settings/SettingsProvider.java | 17 +++++++++++---- .../providers/settings/SettingsProviderTest.java | 25 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 4c9500cf909a..8dd77a675d6e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3075,6 +3075,15 @@ public class SettingsProvider extends ContentProvider { return settingsState.getSettingLocked(name); } + private 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 Secure.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=*/ @@ -3097,7 +3106,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)) { @@ -3117,7 +3126,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)) { @@ -3137,7 +3146,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()) { @@ -3160,7 +3169,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 f49e209c09a1..74a61274ff8f 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java @@ -466,6 +466,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.Secure.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); -- cgit v1.2.3 From 684dbe033b73294584f53fdfcd2478c01667d44e Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Mon, 31 Jul 2023 16:47:45 -0700 Subject: [RESTRICT AUTOMERGE] Ignore small source rect hint Which may be abused by malicious app to create a non-visible PiP window that bypasses the background restriction. Bug: 270368476 Test: Manually, using the POC app (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:59ef2c19e559bfc3f29974d63735758185975074) Merged-In: I3531a64fc67a1b6c43997ee33b7a7d4ab4e2d985 Change-Id: I3531a64fc67a1b6c43997ee33b7a7d4ab4e2d985 --- .../src/com/android/wm/shell/pip/PipBoundsAlgorithm.java | 15 +++++++++++++++ .../src/com/android/wm/shell/pip/PipTaskOrganizer.java | 6 +++--- .../Shell/src/com/android/wm/shell/pip/PipTransition.java | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index a4b866aa3f5e..b23b43111281 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -182,6 +182,21 @@ public class PipBoundsAlgorithm { return null; } + /** + * Returns the source hint rect if it is valid (if provided and is contained by the current + * task bounds and not too small). + */ + public static Rect getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds, + @NonNull Rect destinationBounds) { + final Rect sourceHintRect = getValidSourceHintRect(params, sourceBounds); + if (sourceHintRect != null + && sourceHintRect.width() > destinationBounds.width() + && sourceHintRect.height() > destinationBounds.height()) { + return sourceHintRect; + } + return null; + } + public float getDefaultAspectRatio() { return mDefaultAspectRatio; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index a201616db208..3d11ef061661 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -553,7 +553,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { mPipMenuController.attach(mLeash); final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( - info.pictureInPictureParams, currentBounds); + info.pictureInPictureParams, currentBounds, destinationBounds); scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, null /* updateBoundsCallback */); @@ -579,9 +579,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); - final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( - mPictureInPictureParams, currentBounds); final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); + final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( + mPictureInPictureParams, currentBounds, destinationBounds); animateResizePip(currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, 0 /* startingAngle */); mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index b31e6e0750ce..94eaedfb4dba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -318,7 +318,7 @@ public class PipTransition extends PipTransitionController { if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( - taskInfo.pictureInPictureParams, currentBounds); + taskInfo.pictureInPictureParams, currentBounds, destinationBounds); animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, rotationDelta); -- cgit v1.2.3 From 005fa31aae197fd368ad6dc67e454f56a2097121 Mon Sep 17 00:00:00 2001 From: Nan Wu Date: Fri, 16 Jun 2023 14:42:24 +0000 Subject: DO NOT MERGE Fix BAL via notification.publicVersion We stripped the token that allows app to retrieve their own notification and fire their own PI to launch activities from background. But we forgot to strip the token from notification.publicVersion Bug: 278558814 Test: NotificationManagerTest#testActivityStartFromRetrievedNotification_isBlocked (cherry picked from commit cf851d81a954f0a6dd0c2fd7defa93932539e7f9) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8d839e4985d0acc662e1019390c88fab20bacbd6) Merged-In: I8f25d7a5e47890a0496af023149717e1df482f98 Change-Id: I8f25d7a5e47890a0496af023149717e1df482f98 --- core/java/android/app/Notification.java | 7 +++++-- .../android/server/notification/NotificationManagerService.java | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 15a0fb81c138..9004c3a3005a 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3366,8 +3366,11 @@ public class Notification implements Parcelable * * @hide */ - public void setAllowlistToken(@Nullable IBinder token) { - mAllowlistToken = token; + public void clearAllowlistToken() { + mAllowlistToken = null; + if (publicVersion != null) { + publicVersion.clearAllowlistToken(); + } } /** diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0e537cc1af31..5d22b4932d2d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4202,7 +4202,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // associated with notification interactions. - notification.setAllowlistToken(null); + notification.clearAllowlistToken(); return new StatusBarNotification( sbn.getPackageName(), sbn.getOpPkg(), -- cgit v1.2.3 From 997d1ce7a28475669d036fbd86a25ef0d0e46293 Mon Sep 17 00:00:00 2001 From: Piyush Mehrotra Date: Thu, 27 Jul 2023 19:35:14 +0000 Subject: [DO NOT MERGE] Check caller's uid in backupAgentCreated callback AM.backupAgentCreated() should enforce that caller belongs the package called in the API. Bug: 289549315 Test: atest android.security.cts.ActivityManagerTest#testActivityManager_backupAgentCreated_rejectIfCallerUidNotEqualsPackageUid (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4a82aa857eb738d3334a896dac525abf3b32c5bf) Merged-In: I9f3ae5ec0b8f00e020d471cc0eddf8bd8bdbb82d Change-Id: I9f3ae5ec0b8f00e020d471cc0eddf8bd8bdbb82d --- .../android/server/am/ActivityManagerService.java | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 77a6e9d6b658..884770bc5f3c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2807,6 +2807,22 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * Enforces that the uid of the caller matches the uid of the package. + * + * @param packageName the name of the package to match uid against. + * @param callingUid the uid of the caller. + * @throws SecurityException if the calling uid doesn't match uid of the package. + */ + private void enforceCallingPackage(String packageName, int callingUid) { + final int userId = UserHandle.getUserId(callingUid); + final int packageUid = getPackageManagerInternal().getPackageUid(packageName, + /*flags=*/ 0, userId); + if (packageUid != callingUid) { + throw new SecurityException(packageName + " does not belong to uid " + callingUid); + } + } + @Override public void setPackageScreenCompatMode(String packageName, int mode) { mActivityTaskManager.setPackageScreenCompatMode(packageName, mode); @@ -12267,13 +12283,16 @@ public class ActivityManagerService extends IActivityManager.Stub // A backup agent has just come up @Override public void backupAgentCreated(String agentPackageName, IBinder agent, int userId) { + final int callingUid = Binder.getCallingUid(); + enforceCallingPackage(agentPackageName, callingUid); + // Resolve the target user id and enforce permissions. - userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, /* allowAll */ false, ALLOW_FULL_ONLY, "backupAgentCreated", null); if (DEBUG_BACKUP) { Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName + " = " + agent + " callingUserId = " + UserHandle.getCallingUserId() + " userId = " + userId - + " callingUid = " + Binder.getCallingUid() + " uid = " + Process.myUid()); + + " callingUid = " + callingUid + " uid = " + Process.myUid()); } synchronized(this) { -- cgit v1.2.3 From 9da6ad7593088222ff490ca038a858916dd25238 Mon Sep 17 00:00:00 2001 From: kumarashishg Date: Thu, 3 Aug 2023 12:01:29 +0000 Subject: Use type safe API of readParcelableArray Bug: 291299076 Test: Build and flash the device and check if it throws exception for non UsbInterface object Test: atest CtsUsbManagerTestCases (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:85d7e6712a9eeeed3bdd68ea3c3862c7e88bfe70) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7491a49b633e4eaa4f01d9b12ea4bce15b8dce00) Merged-In: I2917c8331b6d56caaa9a6479bcd9a2d089f5f503 Change-Id: I2917c8331b6d56caaa9a6479bcd9a2d089f5f503 --- core/java/android/hardware/usb/UsbConfiguration.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java index 66269cb772f8..b25f47b11532 100644 --- a/core/java/android/hardware/usb/UsbConfiguration.java +++ b/core/java/android/hardware/usb/UsbConfiguration.java @@ -172,7 +172,8 @@ public class UsbConfiguration implements Parcelable { String name = in.readString(); int attributes = in.readInt(); int maxPower = in.readInt(); - Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader()); + Parcelable[] interfaces = in.readParcelableArray( + UsbInterface.class.getClassLoader(), UsbInterface.class); UsbConfiguration configuration = new UsbConfiguration(id, name, attributes, maxPower); configuration.setInterfaces(interfaces); return configuration; -- cgit v1.2.3 From f6fc3ec16785bfa4f71bee316f3fd4a05ad6be81 Mon Sep 17 00:00:00 2001 From: Songchun Fan Date: Mon, 14 Aug 2023 15:24:11 -0700 Subject: [SettingsProvider] verify ringtone URI before setting Similar to ag/24422287, but the same URI verification should be done in SettingsProvider as well, which can be called by apps via Settings.System API or ContentProvider APIs without using RingtoneManager. BUG: 227201030 Test: manual with a test app. Will add a CTS test. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1b234678ec122994ccbfc52ac48aafdad7fdb1ed) Merged-In: Ic0ffa1db14b5660d02880b632a7f2ad9e6e5d84b Change-Id: Ic0ffa1db14b5660d02880b632a7f2ad9e6e5d84b --- .../providers/settings/SettingsProvider.java | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 8dd77a675d6e..4df565045e82 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1906,6 +1906,9 @@ public class SettingsProvider extends ContentProvider { cacheName = Settings.System.ALARM_ALERT_CACHE; } if (cacheName != null) { + if (!isValidAudioUri(name, value)) { + return false; + } final File cacheFile = new File( getRingtoneCacheDir(owningUserId), cacheName); cacheFile.delete(); @@ -1938,6 +1941,34 @@ public class SettingsProvider extends ContentProvider { } } + private boolean isValidAudioUri(String name, String uri) { + if (uri != null) { + Uri audioUri = Uri.parse(uri); + if (Settings.AUTHORITY.equals( + ContentProvider.getAuthorityWithoutUserId(audioUri.getAuthority()))) { + // Don't accept setting the default uri to self-referential URIs like + // Settings.System.DEFAULT_RINGTONE_URI, which is an alias to the value of this + // setting. + return false; + } + final String mimeType = getContext().getContentResolver().getType(audioUri); + if (mimeType == null) { + Slog.e(LOG_TAG, + "mutateSystemSetting for setting: " + name + " URI: " + audioUri + + " ignored: failure to find mimeType (no access from this context?)"); + return false; + } + if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg") + || mimeType.equals("application/x-flac"))) { + Slog.e(LOG_TAG, + "mutateSystemSetting for setting: " + name + " URI: " + audioUri + + " ignored: associated mimeType: " + mimeType + " is not an audio type"); + return false; + } + } + return true; + } + private boolean hasWriteSecureSettingsPermission() { // Write secure settings is a more protected permission. If caller has it we are good. return getContext().checkCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) -- cgit v1.2.3 From f3385d197075d10659b388982b3d59c73adaa967 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 3 Aug 2023 06:09:51 +0000 Subject: Strips spans from AssistStructure text When a new view becomes visible on the screen, the view notifies AutofillManager about its visibility. AutofillManager then requests the activity to compile an AssistStructure object which will contain the view hierarchy of the activity as well as texts contained inside all the views. If there are Span text fields that contain custom implementation of ClickableSpan, these spans are also copied over during the construction of the AssistStructure. By keeping the references to the ClickableSpan, it causes memory leak when the AssistStructure object outlives the activity. Test: atest CtsAutoFillServiceTestCases, atest CtsAssistTestCases, atest android.widget.cts.TextViewTest Bug: 146100180 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e42d05e3d01f4c3b8aa7bce29086cd968970b4ec) Merged-In: I1fd97d9d6fdc569d14529347fcb85da409c3c1ff Change-Id: I1fd97d9d6fdc569d14529347fcb85da409c3c1ff --- core/java/android/app/assist/AssistStructure.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 6e49e956fe7e..27b8239edd4d 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -22,6 +22,7 @@ import android.os.PooledStringWriter; import android.os.RemoteException; import android.os.SystemClock; import android.service.autofill.FillRequest; +import android.text.Spanned; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -1556,6 +1557,10 @@ public class AssistStructure implements Parcelable { /** * Returns any text associated with the node that is displayed to the user, or null * if there is none. + * + *

The text will be stripped of any spans that could potentially contain reference to + * the activity context, to avoid memory leak. If the text contained a span, a plain + * string version of the text will be returned. */ @Nullable public CharSequence getText() { @@ -1995,14 +2000,16 @@ public class AssistStructure implements Parcelable { @Override public void setText(CharSequence text) { ViewNodeText t = getNodeText(); - t.mText = TextUtils.trimNoCopySpans(text); + // Strip spans from the text to avoid memory leak + t.mText = TextUtils.trimToParcelableSize(stripAllSpansFromText(text)); t.mTextSelectionStart = t.mTextSelectionEnd = -1; } @Override public void setText(CharSequence text, int selectionStart, int selectionEnd) { ViewNodeText t = getNodeText(); - t.mText = TextUtils.trimNoCopySpans(text); + // Strip spans from the text to avoid memory leak + t.mText = stripAllSpansFromText(text); t.mTextSelectionStart = selectionStart; t.mTextSelectionEnd = selectionEnd; } @@ -2221,6 +2228,13 @@ public class AssistStructure implements Parcelable { public void setHtmlInfo(@NonNull HtmlInfo htmlInfo) { mNode.mHtmlInfo = htmlInfo; } + + private CharSequence stripAllSpansFromText(CharSequence text) { + if (text instanceof Spanned) { + return text.toString(); + } + return text; + } } private static final class HtmlInfoNode extends HtmlInfo implements Parcelable { -- cgit v1.2.3 From 21c953d547d3c9332f781a291b388031dde0dae5 Mon Sep 17 00:00:00 2001 From: Amit Kumar Prasad Date: Fri, 18 Aug 2023 11:34:30 +0530 Subject: Frameworks: Add Bluetooth and connect permissions Add below permission to intent ACTION_REMOTE_ISSUE_OCCURRED RequiresBluetoothConnectPermission RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) CRs-Fixed: 3581934 Change-Id: I6331b99555240b7d86a316b4c26165ac61fdb34d --- core/java/android/bluetooth/BluetoothAdapter.java | 2 ++ core/java/android/bluetooth/BluetoothDevice.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 7e03c20eb9b3..368979035755 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -409,6 +409,8 @@ public final class BluetoothAdapter { * @hide **/ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_REMOTE_ISSUE_OCCURRED = "org.codeaurora.intent.bluetooth.action.REMOTE_ISSUE_OCCURRED"; diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index e37d64d99886..1ba514a77f99 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -300,6 +300,8 @@ public final class BluetoothDevice implements Parcelable, Attributable { * @hide **/ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_REMOTE_ISSUE_OCCURRED = "org.codeaurora.intent.bluetooth.action.REMOTE_ISSUE_OCCURRED"; -- cgit v1.2.3 From 04a8c299d1a73820c0859d7fab554a2faa4e5c33 Mon Sep 17 00:00:00 2001 From: LAVEENA FULWANI Date: Tue, 26 Sep 2023 16:01:20 +0530 Subject: Adding support for Bootanimation in vendor Change-Id: Iaa44c2f349f3752ea59a5c9fddcd6eb51a34a865 CRs-Fixed: 3625614 --- cmds/bootanimation/BootAnimation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 53a2ebcdb061..68abb85394ff 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -70,6 +70,7 @@ static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip"; static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip"; static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; +static const char VENDOR_BOOTANIMATION_FILE[] = "/vendor/etc/bootanimation.zip"; static const char APEX_BOOTANIMATION_FILE[] = "/apex/com.android.bootanimation/etc/bootanimation.zip"; static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip"; static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"; @@ -685,7 +686,7 @@ void BootAnimation::findBootAnimationFile() { const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1; static const std::vector bootFiles = { APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE, - OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE + OEM_BOOTANIMATION_FILE, VENDOR_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE }; static const std::vector shutdownFiles = { PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE, "" -- cgit v1.2.3 From f0f29bfad31ceaee242b046769860327fd33f710 Mon Sep 17 00:00:00 2001 From: Tingting Zhang Date: Tue, 10 Oct 2023 09:46:35 +0800 Subject: perf: add exit app animation boost for apps exit. CRs-Fixed: 3622453 Change-Id: Ia3799ea1a6452de81ab621997c8f708284e75bf7 --- core/java/android/util/BoostFramework.java | 2 ++ services/core/java/com/android/server/wm/TaskFragment.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/core/java/android/util/BoostFramework.java b/core/java/android/util/BoostFramework.java index 7bab65a86802..bcd50d9e9b36 100644 --- a/core/java/android/util/BoostFramework.java +++ b/core/java/android/util/BoostFramework.java @@ -107,6 +107,8 @@ public class BoostFramework { public static final int VENDOR_HINT_DRAG_END = 0x00001052; //Ime Launch Boost Hint public static final int VENDOR_HINT_IME_LAUNCH_EVENT = 0x0000109F; + //App exit animation boost + public static final int VENDOR_HINT_EXIT_ANIM_BOOST = 0x000010A9; //feedback hints public static final int VENDOR_FEEDBACK_WORKLOAD_TYPE = 0x00001601; diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index e1e5261071c7..d58e3eb0a17d 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1397,6 +1397,10 @@ class TaskFragment extends WindowContainer { dc.prepareAppTransition(TRANSIT_NONE); } else { dc.prepareAppTransition(TRANSIT_OPEN); + // Exit app animation boost + if (next != null && mPerf != null) { + mPerf.perfHint(BoostFramework.VENDOR_HINT_EXIT_ANIM_BOOST, next.packageName); + } } } -- cgit v1.2.3