From aa68a4f19e6a122b80ca1bcff57228dc795081e5 Mon Sep 17 00:00:00 2001 From: Jin Seok Park Date: Tue, 19 Nov 2019 19:40:35 +0900 Subject: [PATCH 1/7] [DO NOT MERGE] Return correct offsets for getAttributeRange() Cherry-picked from ag/9745767 Currently, the offset value returned from getAttributeRange() does not take into account the offset to the EXIF data within the image file. This CL fixes the code to return the correct offsets. The existing test code could not detect this issue because it does not explicitly seek to the given offset and retrieve/compare the data. This CL also adds test code to do this. Make tag information is also added because all test files have this information, while only a select few have thumbnail/GPS tag information. This CL also fixes a bug where the thumbnail offset was not being updated if the data was passed with StripOffsets tag. Bug: 143118731, Bug: 143663411 Test: atest ExifInterfaceTest Change-Id: I94c3438020d1091a8829a96e1b0a5ef4193d9dc9 (cherry picked from commit e6e97865f3f3965915c5fd45554e207ffad6111f) --- media/java/android/media/ExifInterface.java | 81 +++++++++++++-------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 5645ba5d7dacd..6c723b3a70060 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1345,7 +1345,9 @@ public class ExifInterface { private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private boolean mHasThumbnail; - // The following values used for indicating a thumbnail position. + private boolean mHasThumbnailStrips; + private boolean mAreThumbnailStripsConsecutive; + // Used to indicate the position of the thumbnail (includes offset to EXIF data segment). private int mThumbnailOffset; private int mThumbnailLength; private byte[] mThumbnailBytes; @@ -2043,10 +2045,12 @@ public class ExifInterface { /** * Returns the offset and length of thumbnail inside the image file, or - * {@code null} if there is no thumbnail. + * {@code null} if either there is no thumbnail or the thumbnail bytes are stored + * non-consecutively. * * @return two-element array, the offset in the first value, and length in - * the second, or {@code null} if no thumbnail was found. + * the second, or {@code null} if no thumbnail was found or the thumbnail strips are + * not placed consecutively. * @throws IllegalStateException if {@link #saveAttributes()} has been * called since the underlying file was initially parsed, since * that means offsets may have changed. @@ -2058,10 +2062,12 @@ public class ExifInterface { } if (mHasThumbnail) { + if (mHasThumbnailStrips && !mAreThumbnailStripsConsecutive) { + return null; + } return new long[] { mThumbnailOffset, mThumbnailLength }; - } else { - return null; } + return null; } /** @@ -2536,10 +2542,9 @@ public class ExifInterface { final byte[] value = Arrays.copyOfRange(bytes, IDENTIFIER_EXIF_APP1.length, bytes.length); - readExifSegment(value, imageType); - // Save offset values for createJpegThumbnailBitmap() function mExifOffset = (int) offset; + readExifSegment(value, imageType); } else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) { // See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6 final long offset = start + IDENTIFIER_XMP_APP1.length; @@ -2843,6 +2848,8 @@ public class ExifInterface { if (in.read(bytes) != length) { throw new IOException("Can't read exif"); } + // Save offset values for handling thumbnail and attribute offsets. + mExifOffset = offset; readExifSegment(bytes, IFD_TYPE_PRIMARY); } @@ -2988,7 +2995,7 @@ public class ExifInterface { // Write EXIF APP1 segment dataOutputStream.writeByte(MARKER); dataOutputStream.writeByte(MARKER_APP1); - writeExifSegment(dataOutputStream, 6); + writeExifSegment(dataOutputStream); byte[] bytes = new byte[4096]; @@ -3319,7 +3326,7 @@ public class ExifInterface { continue; } - final int bytesOffset = dataInputStream.peek(); + final int bytesOffset = dataInputStream.peek() + mExifOffset; final byte[] bytes = new byte[(int) byteCount]; dataInputStream.readFully(bytes); ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents, @@ -3451,31 +3458,28 @@ public class ExifInterface { // The following code limits the size of thumbnail size not to overflow EXIF data area. thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset); - if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF - || mMimeType == IMAGE_TYPE_RW2) { - thumbnailOffset += mExifOffset; - } else if (mMimeType == IMAGE_TYPE_ORF) { + if (mMimeType == IMAGE_TYPE_ORF) { // Update offset value since RAF files have IFD data preceding MakerNote data. thumbnailOffset += mOrfMakerNoteOffset; } - if (DEBUG) { - Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset - + ", length: " + thumbnailLength); - } if (thumbnailOffset > 0 && thumbnailLength > 0) { mHasThumbnail = true; - mThumbnailOffset = thumbnailOffset; + mThumbnailOffset = thumbnailOffset + mExifOffset; mThumbnailLength = thumbnailLength; mThumbnailCompression = DATA_JPEG; if (mFilename == null && mAssetInputStream == null && mSeekableFileDescriptor == null) { // Save the thumbnail in memory if the input doesn't support reading again. - byte[] thumbnailBytes = new byte[thumbnailLength]; - in.seek(thumbnailOffset); + byte[] thumbnailBytes = new byte[mThumbnailLength]; + in.seek(mThumbnailOffset); in.readFully(thumbnailBytes); mThumbnailBytes = thumbnailBytes; } + if (DEBUG) { + Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset + + ", length: " + thumbnailLength); + } } } } @@ -3494,12 +3498,16 @@ public class ExifInterface { long[] stripByteCounts = convertToLongArray(stripByteCountsAttribute.getValue(mExifByteOrder)); - if (stripOffsets == null) { - Log.w(TAG, "stripOffsets should not be null."); + if (stripOffsets == null || stripOffsets.length == 0) { + Log.w(TAG, "stripOffsets should not be null or have zero length."); return; } - if (stripByteCounts == null) { - Log.w(TAG, "stripByteCounts should not be null."); + if (stripByteCounts == null || stripByteCounts.length == 0) { + Log.w(TAG, "stripByteCounts should not be null or have zero length."); + return; + } + if (stripOffsets.length != stripByteCounts.length) { + Log.w(TAG, "stripOffsets and stripByteCounts should have same length."); return; } @@ -3509,10 +3517,18 @@ public class ExifInterface { int bytesRead = 0; int bytesAdded = 0; + mHasThumbnail = mHasThumbnailStrips = mAreThumbnailStripsConsecutive = true; for (int i = 0; i < stripOffsets.length; i++) { int stripOffset = (int) stripOffsets[i]; int stripByteCount = (int) stripByteCounts[i]; + // Check if strips are consecutive + // TODO: Add test for non-consecutive thumbnail image + if (i < stripOffsets.length - 1 + && stripOffset + stripByteCount != stripOffsets[i + 1]) { + mAreThumbnailStripsConsecutive = false; + } + // Skip to offset int skipBytes = stripOffset - bytesRead; if (skipBytes < 0) { @@ -3531,10 +3547,13 @@ public class ExifInterface { stripBytes.length); bytesAdded += stripBytes.length; } - - mHasThumbnail = true; mThumbnailBytes = totalStripBytes; - mThumbnailLength = totalStripBytes.length; + + if (mAreThumbnailStripsConsecutive) { + // Need to add mExifOffset, which is the offset to the EXIF data segment + mThumbnailOffset = (int) stripOffsets[0] + mExifOffset; + mThumbnailLength = totalStripBytes.length; + } } } @@ -3691,8 +3710,7 @@ public class ExifInterface { } // Writes an Exif segment into the given output stream. - private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream, - int exifOffsetFromBeginning) throws IOException { + private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throws IOException { // The following variables are for calculating each IFD tag group size in bytes. int[] ifdOffsets = new int[EXIF_TAGS.length]; int[] ifdDataSizes = new int[EXIF_TAGS.length]; @@ -3751,6 +3769,8 @@ public class ExifInterface { } // Calculate IFD offsets. + // 8 bytes are for TIFF headers: 2 bytes (byte order) + 2 bytes (identifier) + 4 bytes + // (offset of IFDs) int position = 8; for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) { if (!mAttributes[ifdType].isEmpty()) { @@ -3762,7 +3782,8 @@ public class ExifInterface { int thumbnailOffset = position; mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name, ExifAttribute.createULong(thumbnailOffset, mExifByteOrder)); - mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset; + // Need to add mExifOffset, which is the offset to the EXIF data segment + mThumbnailOffset = thumbnailOffset + mExifOffset; position += mThumbnailLength; } From aa2ffea8baea65c13ac2b841b3d581f28261dd2b Mon Sep 17 00:00:00 2001 From: "Philip P. Moltmann" Date: Mon, 28 Oct 2019 11:49:11 -0700 Subject: [PATCH 2/7] Revoke granted permission when the permission defining app is removed. Bug: 67319274 Test: atest android.permission.cts.RemovePermissionTest Change-Id: I22df546f5cd19e10045131d36dc3f5033f727baa Merged-In: I20c4c975a1dd41a0a6c3e068988fe60be51dd1b4 (cherry picked from commit bde381848d0d07780710ce36e0c974646ba8f995) --- .../permission/PermissionManagerService.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 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 4c33bec71a143..b844362e69ed3 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -48,6 +48,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.PermissionWhitelistFlags; import android.content.pm.PackageManagerInternal; @@ -2601,7 +2602,7 @@ public class PermissionManagerService { // Make sure all dynamic permissions have been assigned to a package, // and make sure there are no dangling permissions. - flags = updatePermissions(changingPkgName, changingPkg, flags); + flags = updatePermissions(changingPkgName, changingPkg, flags, callback); synchronized (mLock) { if (mBackgroundPermissions == null) { @@ -2651,7 +2652,8 @@ public class PermissionManagerService { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - private int updatePermissions(String packageName, PackageParser.Package pkg, int flags) { + private int updatePermissions(String packageName, PackageParser.Package pkg, int flags, + @Nullable PermissionCallback callback) { Set needsUpdate = null; synchronized (mLock) { final Iterator it = mSettings.mPermissions.values().iterator(); @@ -2665,6 +2667,44 @@ public class PermissionManagerService { && (pkg == null || !hasPermission(pkg, bp.getName()))) { Slog.i(TAG, "Removing old permission tree: " + bp.getName() + " from package " + bp.getSourcePackageName()); + if (bp.isRuntime()) { + final int[] userIds = mUserManagerInt.getUserIds(); + final int numUserIds = userIds.length; + for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) { + final int userId = userIds[userIdNum]; + + mPackageManagerInt.forEachPackage((Package p) -> { + final String pName = p.packageName; + final ApplicationInfo appInfo = + mPackageManagerInt.getApplicationInfo(pName, 0, + Process.SYSTEM_UID, UserHandle.USER_SYSTEM); + if (appInfo != null + && appInfo.targetSdkVersion < Build.VERSION_CODES.M) { + return; + } + + final String permissionName = bp.getName(); + if (checkPermission(permissionName, pName, Process.SYSTEM_UID, + userId) == PackageManager.PERMISSION_GRANTED) { + try { + revokeRuntimePermission( + permissionName, + pName, + false, + userId, + callback); + } catch (IllegalArgumentException e) { + Slog.e(TAG, + "Failed to revoke " + + permissionName + + " from " + + pName, + e); + } + } + }); + } + } flags |= UPDATE_PERMISSIONS_ALL; it.remove(); } From 87670f362090b4790d85e1347be9849a7461c2a1 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Tue, 10 Dec 2019 17:15:03 -0500 Subject: [PATCH 3/7] DO NOT MERGE: Don't let NotificationEntryManager keep around old RankingMaps When a notification becomes lifetime-extended, NotificationEntryManager was holding onto the RankingMap that was passed at the time of removal of _that_ notification, and using it again in the NotificationSafeToRemoveCallback. The problem here is that when onSafeToRemove gets called, it was passing that same stale ranking map to removeNotification, which caused any notification that arrived in the intervening time to get improperly ranked. This fixes an issue where any notification that arrives while another is lifetime-extended can get the wrong ranking applied to it, causing trouble later in time such as mis-ranking and mis-sorting until the next update from system server. Bug: 146046016 Bug: 119041698 Test: atest SystemUITests Test: manual - Post a FGS notification and immediately cancel, then post a regular notification and wait for the FGS notification to dismiss. Note that the regular notification keeps showing in the status bar. Change-Id: I3df1279f13c424fcedd878bae2095fadc75d61b4 (cherry picked from commit 323ce6205704cc1b3e13b61286069451643392b5) --- .../statusbar/notification/NotificationEntryManager.java | 5 +---- .../statusbar/notification/collection/NotificationData.java | 3 +++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index a37367e4bb255..da0f83da5a71f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -89,7 +89,6 @@ public class NotificationEntryManager implements private NotificationRowBinder mNotificationRowBinder; private NotificationPresenter mPresenter; - private NotificationListenerService.RankingMap mLatestRankingMap; @VisibleForTesting protected NotificationData mNotificationData; @@ -168,8 +167,7 @@ public class NotificationEntryManager implements /** Adds a {@link NotificationLifetimeExtender}. */ public void addNotificationLifetimeExtender(NotificationLifetimeExtender extender) { mNotificationLifetimeExtenders.add(extender); - extender.setCallback(key -> removeNotification(key, mLatestRankingMap, - UNDEFINED_DISMISS_REASON)); + extender.setCallback(key -> removeNotification(key, null, UNDEFINED_DISMISS_REASON)); } public NotificationData getNotificationData() { @@ -307,7 +305,6 @@ public class NotificationEntryManager implements if (!forceRemove && !entryDismissed) { for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { if (extender.shouldExtendLifetime(entry)) { - mLatestRankingMap = ranking; extendLifetime(entry, extender); lifetimeExtended = true; break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java index 00092929fd49c..4ad7487091e45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java @@ -201,6 +201,9 @@ public class NotificationData { removed = mEntries.remove(key); } if (removed == null) return null; + // NEM may pass us a null ranking map if removing a lifetime-extended notification, + // so use the most recent ranking + if (ranking == null) ranking = mRankingMap; mGroupManager.onEntryRemoved(removed); updateRankingAndSort(ranking); return removed; From d885c3279f3fecb2c08e382c733a440113dae644 Mon Sep 17 00:00:00 2001 From: Bernardo Rufino Date: Tue, 22 Oct 2019 11:53:42 +0100 Subject: [PATCH 4/7] RESTRICT AUTOMERGE Make toasts non-clickable Since enforcement was only on client-side, in Toast class, an app could use reflection (or other means) to make the Toast clickable. This is a security vulnerability since it allows tapjacking, that is, intercept touch events and do stuff like steal PINs and passwords. This CL brings the enforcement to the system by applying flag FLAG_NOT_TOUCHABLE. Test: atest CtsWindowManagerDeviceTestCases:ToastTest Test: Construct app that uses reflection to remove flag FLAG_NOT_TOUCHABLE and log click events. Then: 1) Observe click events are logged without this CL. 2) Observer click events are not logged with this CL. Bug: 128674520 Change-Id: Ic36585bc4f186e0224f5b687c49c0b3d9266838c (cherry picked from commit b81f269ae2afb446b9d4a909fc2bcf038af00c41) --- services/core/java/com/android/server/wm/DisplayPolicy.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 99a9db316c63a..d4a462822ff37 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -865,6 +865,8 @@ public class DisplayPolicy { if (canToastShowWhenLocked(callingPid)) { attrs.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; } + // Toasts can't be clickable + attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; break; } From f5a3d382a97ef26fefbd15cd02c9993e77f7b813 Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Fri, 23 Aug 2019 13:50:23 -0700 Subject: [PATCH 5/7] Handles null outInfo in deleteSystemPackageLI This change adds null checks before accessing outInfo in deleteSystemPackageLI. Bug: 142083996 Test: manual; remove static dependency on eng build and reboot Change-Id: If0fd48343e89cbb77ccd25826656194195d5b0cd (cherry picked from commit 17471016508bb9c9ffb8c3946dda0b4897d722f1) (cherry picked from commit 5a3888c613bbc8f70a5d9e4d53526a5ae1491bb5) --- .../server/pm/PackageManagerService.java | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index dd8cb4dba0a71..52f569f2d802b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -19046,10 +19046,11 @@ public class PackageManagerService extends IPackageManager.Stub * Tries to delete system package. */ private void deleteSystemPackageLIF(DeletePackageAction action, PackageSetting deletedPs, - int[] allUserHandles, int flags, PackageRemovedInfo outInfo, boolean writeSettings) + int[] allUserHandles, int flags, @Nullable PackageRemovedInfo outInfo, + boolean writeSettings) throws SystemDeleteException { - final boolean applyUserRestrictions - = (allUserHandles != null) && (outInfo.origUsers != null); + final boolean applyUserRestrictions = + (allUserHandles != null) && outInfo != null && (outInfo.origUsers != null); final PackageParser.Package deletedPkg = deletedPs.pkg; // Confirm if the system package has been updated // An updated system app can be deleted. This will also have to restore @@ -19070,19 +19071,21 @@ public class PackageManagerService extends IPackageManager.Stub } } - // Delete the updated package - outInfo.isRemovedPackageSystemUpdate = true; - if (outInfo.removedChildPackages != null) { - final int childCount = (deletedPs.childPackageNames != null) - ? deletedPs.childPackageNames.size() : 0; - for (int i = 0; i < childCount; i++) { - String childPackageName = deletedPs.childPackageNames.get(i); - if (disabledPs.childPackageNames != null && disabledPs.childPackageNames - .contains(childPackageName)) { - PackageRemovedInfo childInfo = outInfo.removedChildPackages.get( - childPackageName); - if (childInfo != null) { - childInfo.isRemovedPackageSystemUpdate = true; + if (outInfo != null) { + // Delete the updated package + outInfo.isRemovedPackageSystemUpdate = true; + if (outInfo.removedChildPackages != null) { + final int childCount = (deletedPs.childPackageNames != null) + ? deletedPs.childPackageNames.size() : 0; + for (int i = 0; i < childCount; i++) { + String childPackageName = deletedPs.childPackageNames.get(i); + if (disabledPs.childPackageNames != null && disabledPs.childPackageNames + .contains(childPackageName)) { + PackageRemovedInfo childInfo = outInfo.removedChildPackages.get( + childPackageName); + if (childInfo != null) { + childInfo.isRemovedPackageSystemUpdate = true; + } } } } @@ -19115,7 +19118,8 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs); try { installPackageFromSystemLIF(disabledPs.codePathString, allUserHandles, - outInfo.origUsers, deletedPs.getPermissionsState(), writeSettings); + outInfo == null ? null : outInfo.origUsers, deletedPs.getPermissionsState(), + writeSettings); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": " + e.getMessage()); From 721e4d085ca3d1dc5826c0ba71615529f544d7f7 Mon Sep 17 00:00:00 2001 From: Abodunrinwa Toki Date: Mon, 1 Jul 2019 19:41:44 +0100 Subject: [PATCH 6/7] RESTRICT AUTOMERGE TextClassifier cross-user vulnerability in direct-reply Sys UI runs on user 0. This can lead to the TextClassifier (TC) running for the wrong user. Consequencies are user A can launch apps in user B via the TC's predicted actions and selected text being unintentionally shared from user A to an app running in user B. This fix ensures that the correct user id is passed and verified for every TC request going across process boundaries (i.e. via SystemTC). - Sys UI sets the appropriate user id in the TextView - TextClassificationManager (TCM) system service is constructed using a context generated from this user id - SystemTC sets this user id before querying the TCMService - TCMService validates the user id before forwarding the request to the TCService belonging to that user id. Bug: 136483597 Bug: 123232892 Test: atest android.view.textclassifier atest android.widget.TextViewActivityTest (manual) See I2fdffd8eb4221782cb1f34d2ddbe41dd3d36595c Change-Id: Ibe68bc9e257521de97cbb014176b2b8ba23547d1 (cherry picked from commit 34e380cdd64230db81a5754b7b6e2654509af180) --- .../ActionsModelParamsSupplier.java | 4 +- .../textclassifier/ConversationActions.java | 25 ++++ .../view/textclassifier/SelectionEvent.java | 31 ++++- .../textclassifier/SystemTextClassifier.java | 20 ++++ .../textclassifier/TextClassification.java | 25 ++++ .../TextClassificationContext.java | 28 ++++- .../textclassifier/TextClassifierEvent.java | 11 +- .../view/textclassifier/TextLanguage.java | 25 ++++ .../view/textclassifier/TextLinks.java | 25 ++++ .../view/textclassifier/TextSelection.java | 25 ++++ core/java/android/widget/TextView.java | 12 +- .../TextClassificationManagerService.java | 108 +++++++++++------- 12 files changed, 289 insertions(+), 50 deletions(-) diff --git a/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java b/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java index 6b90588f8d250..31645672f0499 100644 --- a/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java +++ b/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java @@ -60,7 +60,9 @@ public final class ActionsModelParamsSupplier implements private boolean mParsed = true; public ActionsModelParamsSupplier(Context context, @Nullable Runnable onChangedListener) { - mAppContext = Preconditions.checkNotNull(context).getApplicationContext(); + final Context appContext = Preconditions.checkNotNull(context).getApplicationContext(); + // Some contexts don't have an app context. + mAppContext = appContext != null ? appContext : context; mOnChangedListener = onChangedListener == null ? () -> {} : onChangedListener; mSettingsObserver = new SettingsObserver(mAppContext, () -> { synchronized (mLock) { diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index aeb99b896b110..7c527bae475d5 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -21,10 +21,12 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; +import android.annotation.UserIdInt; import android.app.Person; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.text.SpannedString; import com.android.internal.annotations.VisibleForTesting; @@ -316,6 +318,8 @@ public final class ConversationActions implements Parcelable { private final List mHints; @Nullable private String mCallingPackageName; + @UserIdInt + private int mUserId = UserHandle.USER_NULL; @NonNull private Bundle mExtras; @@ -340,6 +344,7 @@ public final class ConversationActions implements Parcelable { List hints = new ArrayList<>(); in.readStringList(hints); String callingPackageName = in.readString(); + int userId = in.readInt(); Bundle extras = in.readBundle(); Request request = new Request( conversation, @@ -348,6 +353,7 @@ public final class ConversationActions implements Parcelable { hints, extras); request.setCallingPackageName(callingPackageName); + request.setUserId(userId); return request; } @@ -358,6 +364,7 @@ public final class ConversationActions implements Parcelable { parcel.writeInt(mMaxSuggestions); parcel.writeStringList(mHints); parcel.writeString(mCallingPackageName); + parcel.writeInt(mUserId); parcel.writeBundle(mExtras); } @@ -427,6 +434,24 @@ public final class ConversationActions implements Parcelable { return mCallingPackageName; } + /** + * Sets the id of the user that sent this request. + *

+ * Package-private for SystemTextClassifier's use. + */ + void setUserId(@UserIdInt int userId) { + mUserId = userId; + } + + /** + * Returns the id of the user that sent this request. + * @hide + */ + @UserIdInt + public int getUserId() { + return mUserId; + } + /** * Returns the extended data related to this request. * diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java index 9ae0c65e0cff3..5c81664425e61 100644 --- a/core/java/android/view/textclassifier/SelectionEvent.java +++ b/core/java/android/view/textclassifier/SelectionEvent.java @@ -19,8 +19,10 @@ package android.view.textclassifier; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.view.textclassifier.TextClassifier.EntityType; import android.view.textclassifier.TextClassifier.WidgetType; @@ -127,6 +129,7 @@ public final class SelectionEvent implements Parcelable { private String mWidgetType = TextClassifier.WIDGET_TYPE_UNKNOWN; private @InvocationMethod int mInvocationMethod; @Nullable private String mWidgetVersion; + private @UserIdInt int mUserId = UserHandle.USER_NULL; @Nullable private String mResultId; private long mEventTime; private long mDurationSinceSessionStart; @@ -158,6 +161,7 @@ public final class SelectionEvent implements Parcelable { mEntityType = in.readString(); mWidgetVersion = in.readInt() > 0 ? in.readString() : null; mPackageName = in.readString(); + mUserId = in.readInt(); mWidgetType = in.readString(); mInvocationMethod = in.readInt(); mResultId = in.readString(); @@ -184,6 +188,7 @@ public final class SelectionEvent implements Parcelable { dest.writeString(mWidgetVersion); } dest.writeString(mPackageName); + dest.writeInt(mUserId); dest.writeString(mWidgetType); dest.writeInt(mInvocationMethod); dest.writeString(mResultId); @@ -400,6 +405,24 @@ public final class SelectionEvent implements Parcelable { return mPackageName; } + /** + * Sets the id of this event's user. + *

+ * Package-private for SystemTextClassifier's use. + */ + void setUserId(@UserIdInt int userId) { + mUserId = userId; + } + + /** + * Returns the id of this event's user. + * @hide + */ + @UserIdInt + public int getUserId() { + return mUserId; + } + /** * Returns the type of widget that was involved in triggering this event. */ @@ -426,6 +449,7 @@ public final class SelectionEvent implements Parcelable { mPackageName = context.getPackageName(); mWidgetType = context.getWidgetType(); mWidgetVersion = context.getWidgetVersion(); + mUserId = context.getUserId(); } /** @@ -612,7 +636,7 @@ public final class SelectionEvent implements Parcelable { @Override public int hashCode() { return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, - mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mResultId, + mWidgetVersion, mPackageName, mUserId, mWidgetType, mInvocationMethod, mResultId, mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); } @@ -633,6 +657,7 @@ public final class SelectionEvent implements Parcelable { && Objects.equals(mEntityType, other.mEntityType) && Objects.equals(mWidgetVersion, other.mWidgetVersion) && Objects.equals(mPackageName, other.mPackageName) + && mUserId == other.mUserId && Objects.equals(mWidgetType, other.mWidgetType) && mInvocationMethod == other.mInvocationMethod && Objects.equals(mResultId, other.mResultId) @@ -652,12 +677,12 @@ public final class SelectionEvent implements Parcelable { return String.format(Locale.US, "SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, " + "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, " - + "resultId=%s, eventTime=%d, durationSinceSessionStart=%d, " + + "userId=%d, resultId=%s, eventTime=%d, durationSinceSessionStart=%d, " + "durationSincePreviousEvent=%d, eventIndex=%d," + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}", mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, - mResultId, mEventTime, mDurationSinceSessionStart, + mUserId, mResultId, mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); } diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java index 8f8766e3f783d..a97c3305208af 100644 --- a/core/java/android/view/textclassifier/SystemTextClassifier.java +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -18,6 +18,7 @@ package android.view.textclassifier; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.content.Context; import android.os.Bundle; @@ -50,6 +51,10 @@ public final class SystemTextClassifier implements TextClassifier { private final TextClassificationConstants mSettings; private final TextClassifier mFallback; private final String mPackageName; + // NOTE: Always set this before sending a request to the manager service otherwise the manager + // service will throw a remote exception. + @UserIdInt + private final int mUserId; private TextClassificationSessionId mSessionId; public SystemTextClassifier(Context context, TextClassificationConstants settings) @@ -60,6 +65,7 @@ public final class SystemTextClassifier implements TextClassifier { mFallback = context.getSystemService(TextClassificationManager.class) .getTextClassifier(TextClassifier.LOCAL); mPackageName = Preconditions.checkNotNull(context.getOpPackageName()); + mUserId = context.getUserId(); } /** @@ -72,6 +78,7 @@ public final class SystemTextClassifier implements TextClassifier { Utils.checkMainThread(); try { request.setCallingPackageName(mPackageName); + request.setUserId(mUserId); final BlockingCallback callback = new BlockingCallback<>("textselection"); mManagerService.onSuggestSelection(mSessionId, request, callback); @@ -95,6 +102,7 @@ public final class SystemTextClassifier implements TextClassifier { Utils.checkMainThread(); try { request.setCallingPackageName(mPackageName); + request.setUserId(mUserId); final BlockingCallback callback = new BlockingCallback<>("textclassification"); mManagerService.onClassifyText(mSessionId, request, callback); @@ -123,6 +131,7 @@ public final class SystemTextClassifier implements TextClassifier { try { request.setCallingPackageName(mPackageName); + request.setUserId(mUserId); final BlockingCallback callback = new BlockingCallback<>("textlinks"); mManagerService.onGenerateLinks(mSessionId, request, callback); @@ -142,6 +151,7 @@ public final class SystemTextClassifier implements TextClassifier { Utils.checkMainThread(); try { + event.setUserId(mUserId); mManagerService.onSelectionEvent(mSessionId, event); } catch (RemoteException e) { Log.e(LOG_TAG, "Error reporting selection event.", e); @@ -154,6 +164,12 @@ public final class SystemTextClassifier implements TextClassifier { Utils.checkMainThread(); try { + final TextClassificationContext tcContext = event.getEventContext() == null + ? new TextClassificationContext.Builder(mPackageName, WIDGET_TYPE_UNKNOWN) + .build() + : event.getEventContext(); + tcContext.setUserId(mUserId); + event.setEventContext(tcContext); mManagerService.onTextClassifierEvent(mSessionId, event); } catch (RemoteException e) { Log.e(LOG_TAG, "Error reporting textclassifier event.", e); @@ -167,6 +183,7 @@ public final class SystemTextClassifier implements TextClassifier { try { request.setCallingPackageName(mPackageName); + request.setUserId(mUserId); final BlockingCallback callback = new BlockingCallback<>("textlanguage"); mManagerService.onDetectLanguage(mSessionId, request, callback); @@ -187,6 +204,7 @@ public final class SystemTextClassifier implements TextClassifier { try { request.setCallingPackageName(mPackageName); + request.setUserId(mUserId); final BlockingCallback callback = new BlockingCallback<>("conversation-actions"); mManagerService.onSuggestConversationActions(mSessionId, request, callback); @@ -228,6 +246,7 @@ public final class SystemTextClassifier implements TextClassifier { printWriter.printPair("mFallback", mFallback); printWriter.printPair("mPackageName", mPackageName); printWriter.printPair("mSessionId", mSessionId); + printWriter.printPair("mUserId", mUserId); printWriter.decreaseIndent(); printWriter.println(); } @@ -243,6 +262,7 @@ public final class SystemTextClassifier implements TextClassifier { @NonNull TextClassificationSessionId sessionId) { mSessionId = Preconditions.checkNotNull(sessionId); try { + classificationContext.setUserId(mUserId); mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId); } catch (RemoteException e) { Log.e(LOG_TAG, "Error starting a new classification session.", e); diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 63210516b3e16..975f3ba0763a6 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.PendingIntent; import android.app.RemoteAction; import android.content.Context; @@ -35,6 +36,7 @@ import android.os.Bundle; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.text.SpannedString; import android.util.ArrayMap; import android.view.View.OnClickListener; @@ -551,6 +553,8 @@ public final class TextClassification implements Parcelable { @Nullable private final ZonedDateTime mReferenceTime; @NonNull private final Bundle mExtras; @Nullable private String mCallingPackageName; + @UserIdInt + private int mUserId = UserHandle.USER_NULL; private Request( CharSequence text, @@ -630,6 +634,24 @@ public final class TextClassification implements Parcelable { return mCallingPackageName; } + /** + * Sets the id of the user that sent this request. + *

+ * Package-private for SystemTextClassifier's use. + */ + void setUserId(@UserIdInt int userId) { + mUserId = userId; + } + + /** + * Returns the id of the user that sent this request. + * @hide + */ + @UserIdInt + public int getUserId() { + return mUserId; + } + /** * Returns the extended data. * @@ -730,6 +752,7 @@ public final class TextClassification implements Parcelable { dest.writeParcelable(mDefaultLocales, flags); dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString()); dest.writeString(mCallingPackageName); + dest.writeInt(mUserId); dest.writeBundle(mExtras); } @@ -742,11 +765,13 @@ public final class TextClassification implements Parcelable { final ZonedDateTime referenceTime = referenceTimeString == null ? null : ZonedDateTime.parse(referenceTimeString); final String callingPackageName = in.readString(); + final int userId = in.readInt(); final Bundle extras = in.readBundle(); final Request request = new Request(text, startIndex, endIndex, defaultLocales, referenceTime, extras); request.setCallingPackageName(callingPackageName); + request.setUserId(userId); return request; } diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java index 3bf8e9bd2bf0b..db07685fe3ae7 100644 --- a/core/java/android/view/textclassifier/TextClassificationContext.java +++ b/core/java/android/view/textclassifier/TextClassificationContext.java @@ -18,8 +18,10 @@ package android.view.textclassifier; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.view.textclassifier.TextClassifier.WidgetType; import com.android.internal.util.Preconditions; @@ -35,6 +37,8 @@ public final class TextClassificationContext implements Parcelable { private final String mPackageName; private final String mWidgetType; @Nullable private final String mWidgetVersion; + @UserIdInt + private int mUserId = UserHandle.USER_NULL; private TextClassificationContext( String packageName, @@ -53,6 +57,24 @@ public final class TextClassificationContext implements Parcelable { return mPackageName; } + /** + * Sets the id of this context's user. + *

+ * Package-private for SystemTextClassifier's use. + */ + void setUserId(@UserIdInt int userId) { + mUserId = userId; + } + + /** + * Returns the id of this context's user. + * @hide + */ + @UserIdInt + public int getUserId() { + return mUserId; + } + /** * Returns the widget type for this classification context. */ @@ -75,8 +97,8 @@ public final class TextClassificationContext implements Parcelable { @Override public String toString() { return String.format(Locale.US, "TextClassificationContext{" - + "packageName=%s, widgetType=%s, widgetVersion=%s}", - mPackageName, mWidgetType, mWidgetVersion); + + "packageName=%s, widgetType=%s, widgetVersion=%s, userId=%d}", + mPackageName, mWidgetType, mWidgetVersion, mUserId); } /** @@ -133,12 +155,14 @@ public final class TextClassificationContext implements Parcelable { parcel.writeString(mPackageName); parcel.writeString(mWidgetType); parcel.writeString(mWidgetVersion); + parcel.writeInt(mUserId); } private TextClassificationContext(Parcel in) { mPackageName = in.readString(); mWidgetType = in.readString(); mWidgetVersion = in.readString(); + mUserId = in.readInt(); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java index 57da829b3f446..a041296f97db4 100644 --- a/core/java/android/view/textclassifier/TextClassifierEvent.java +++ b/core/java/android/view/textclassifier/TextClassifierEvent.java @@ -139,7 +139,7 @@ public abstract class TextClassifierEvent implements Parcelable { @Nullable private final String[] mEntityTypes; @Nullable - private final TextClassificationContext mEventContext; + private TextClassificationContext mEventContext; @Nullable private final String mResultId; private final int mEventIndex; @@ -288,6 +288,15 @@ public abstract class TextClassifierEvent implements Parcelable { return mEventContext; } + /** + * Sets the event context. + *

+ * Package-private for SystemTextClassifier's use. + */ + void setEventContext(@Nullable TextClassificationContext eventContext) { + mEventContext = eventContext; + } + /** * Returns the id of the text classifier result related to this event. */ diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java index 6c75ffbea0cda..3e3dc72bd6779 100644 --- a/core/java/android/view/textclassifier/TextLanguage.java +++ b/core/java/android/view/textclassifier/TextLanguage.java @@ -20,10 +20,12 @@ import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.icu.util.ULocale; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; @@ -226,6 +228,8 @@ public final class TextLanguage implements Parcelable { private final CharSequence mText; private final Bundle mExtra; @Nullable private String mCallingPackageName; + @UserIdInt + private int mUserId = UserHandle.USER_NULL; private Request(CharSequence text, Bundle bundle) { mText = text; @@ -259,6 +263,24 @@ public final class TextLanguage implements Parcelable { return mCallingPackageName; } + /** + * Sets the id of the user that sent this request. + *

+ * Package-private for SystemTextClassifier's use. + */ + void setUserId(@UserIdInt int userId) { + mUserId = userId; + } + + /** + * Returns the id of the user that sent this request. + * @hide + */ + @UserIdInt + public int getUserId() { + return mUserId; + } + /** * Returns a bundle containing non-structured extra information about this request. * @@ -278,16 +300,19 @@ public final class TextLanguage implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeCharSequence(mText); dest.writeString(mCallingPackageName); + dest.writeInt(mUserId); dest.writeBundle(mExtra); } private static Request readFromParcel(Parcel in) { final CharSequence text = in.readCharSequence(); final String callingPackageName = in.readString(); + final int userId = in.readInt(); final Bundle extra = in.readBundle(); final Request request = new Request(text, extra); request.setCallingPackageName(callingPackageName); + request.setUserId(userId); return request; } diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index f3e0dc1ed843e..d7ac52414923b 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -20,11 +20,13 @@ import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.content.Context; import android.os.Bundle; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.text.Spannable; import android.text.method.MovementMethod; import android.text.style.ClickableSpan; @@ -339,6 +341,8 @@ public final class TextLinks implements Parcelable { private final boolean mLegacyFallback; @Nullable private String mCallingPackageName; private final Bundle mExtras; + @UserIdInt + private int mUserId = UserHandle.USER_NULL; private Request( CharSequence text, @@ -409,6 +413,24 @@ public final class TextLinks implements Parcelable { return mCallingPackageName; } + /** + * Sets the id of the user that sent this request. + *

+ * Package-private for SystemTextClassifier's use. + */ + void setUserId(@UserIdInt int userId) { + mUserId = userId; + } + + /** + * Returns the id of the user that sent this request. + * @hide + */ + @UserIdInt + public int getUserId() { + return mUserId; + } + /** * Returns the extended data. * @@ -509,6 +531,7 @@ public final class TextLinks implements Parcelable { dest.writeParcelable(mDefaultLocales, flags); dest.writeParcelable(mEntityConfig, flags); dest.writeString(mCallingPackageName); + dest.writeInt(mUserId); dest.writeBundle(mExtras); } @@ -517,11 +540,13 @@ public final class TextLinks implements Parcelable { final LocaleList defaultLocales = in.readParcelable(null); final EntityConfig entityConfig = in.readParcelable(null); final String callingPackageName = in.readString(); + final int userId = in.readInt(); final Bundle extras = in.readBundle(); final Request request = new Request(text, defaultLocales, entityConfig, /* legacyFallback= */ true, extras); request.setCallingPackageName(callingPackageName); + request.setUserId(userId); return request; } diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index 75c27bdbc1d51..94e0bc3dcd670 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -20,10 +20,12 @@ import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.os.Bundle; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.text.SpannedString; import android.util.ArrayMap; import android.view.textclassifier.TextClassifier.EntityType; @@ -211,6 +213,8 @@ public final class TextSelection implements Parcelable { private final boolean mDarkLaunchAllowed; private final Bundle mExtras; @Nullable private String mCallingPackageName; + @UserIdInt + private int mUserId = UserHandle.USER_NULL; private Request( CharSequence text, @@ -291,6 +295,24 @@ public final class TextSelection implements Parcelable { return mCallingPackageName; } + /** + * Sets the id of the user that sent this request. + *

+ * Package-private for SystemTextClassifier's use. + */ + void setUserId(@UserIdInt int userId) { + mUserId = userId; + } + + /** + * Returns the id of the user that sent this request. + * @hide + */ + @UserIdInt + public int getUserId() { + return mUserId; + } + /** * Returns the extended data. * @@ -394,6 +416,7 @@ public final class TextSelection implements Parcelable { dest.writeInt(mEndIndex); dest.writeParcelable(mDefaultLocales, flags); dest.writeString(mCallingPackageName); + dest.writeInt(mUserId); dest.writeBundle(mExtras); } @@ -403,11 +426,13 @@ public final class TextSelection implements Parcelable { final int endIndex = in.readInt(); final LocaleList defaultLocales = in.readParcelable(null); final String callingPackageName = in.readString(); + final int userId = in.readInt(); final Bundle extras = in.readBundle(); final Request request = new Request(text, startIndex, endIndex, defaultLocales, /* darkLaunchAllowed= */ false, extras); request.setCallingPackageName(callingPackageName); + request.setUserId(userId); return request; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0918c5fdefa88..944bdcde08219 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -11282,6 +11282,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); } + @Nullable + final TextClassificationManager getTextClassificationManagerForUser() { + return getServiceManagerForUser( + getContext().getPackageName(), TextClassificationManager.class); + } + @Nullable final T getServiceManagerForUser(String packageName, Class managerClazz) { if (mTextOperationUser == null) { @@ -12383,8 +12389,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @NonNull public TextClassifier getTextClassifier() { if (mTextClassifier == null) { - final TextClassificationManager tcm = - mContext.getSystemService(TextClassificationManager.class); + final TextClassificationManager tcm = getTextClassificationManagerForUser(); if (tcm != null) { return tcm.getTextClassifier(); } @@ -12400,8 +12405,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @NonNull TextClassifier getTextClassificationSession() { if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { - final TextClassificationManager tcm = - mContext.getSystemService(TextClassificationManager.class); + final TextClassificationManager tcm = getTextClassificationManagerForUser(); if (tcm != null) { final String widgetType; if (isTextEditable()) { diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 5f00148335a7c..89a5305142639 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -30,6 +30,7 @@ import android.os.UserHandle; import android.service.textclassifier.ITextClassifierCallback; import android.service.textclassifier.ITextClassifierService; import android.service.textclassifier.TextClassifierService; +import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.view.textclassifier.ConversationActions; @@ -54,6 +55,7 @@ import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayDeque; +import java.util.Map; import java.util.Queue; /** @@ -119,6 +121,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi private final Object mLock; @GuardedBy("mLock") final SparseArray mUserStates = new SparseArray<>(); + @GuardedBy("mLock") + private final Map mSessionUserIds = new ArrayMap<>(); private TextClassificationManagerService(Context context) { mContext = Preconditions.checkNotNull(context); @@ -127,15 +131,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Override public void onSuggestSelection( - TextClassificationSessionId sessionId, + @Nullable TextClassificationSessionId sessionId, TextSelection.Request request, ITextClassifierCallback callback) throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); - validateInput(mContext, request.getCallingPackageName()); + final int userId = request.getUserId(); + validateInput(mContext, request.getCallingPackageName(), userId); synchronized (mLock) { - UserState userState = getCallingUserStateLocked(); + UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { callback.onFailure(); } else if (userState.isBoundLocked()) { @@ -150,15 +155,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Override public void onClassifyText( - TextClassificationSessionId sessionId, + @Nullable TextClassificationSessionId sessionId, TextClassification.Request request, ITextClassifierCallback callback) throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); - validateInput(mContext, request.getCallingPackageName()); + final int userId = request.getUserId(); + validateInput(mContext, request.getCallingPackageName(), userId); synchronized (mLock) { - UserState userState = getCallingUserStateLocked(); + UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { callback.onFailure(); } else if (userState.isBoundLocked()) { @@ -173,15 +179,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Override public void onGenerateLinks( - TextClassificationSessionId sessionId, + @Nullable TextClassificationSessionId sessionId, TextLinks.Request request, ITextClassifierCallback callback) throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); - validateInput(mContext, request.getCallingPackageName()); + final int userId = request.getUserId(); + validateInput(mContext, request.getCallingPackageName(), userId); synchronized (mLock) { - UserState userState = getCallingUserStateLocked(); + UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { callback.onFailure(); } else if (userState.isBoundLocked()) { @@ -196,12 +203,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Override public void onSelectionEvent( - TextClassificationSessionId sessionId, SelectionEvent event) throws RemoteException { + @Nullable TextClassificationSessionId sessionId, SelectionEvent event) + throws RemoteException { Preconditions.checkNotNull(event); - validateInput(mContext, event.getPackageName()); + final int userId = event.getUserId(); + validateInput(mContext, event.getPackageName(), userId); synchronized (mLock) { - UserState userState = getCallingUserStateLocked(); + UserState userState = getUserStateLocked(userId); if (userState.isBoundLocked()) { userState.mService.onSelectionEvent(sessionId, event); } else { @@ -213,16 +222,19 @@ public final class TextClassificationManagerService extends ITextClassifierServi } @Override public void onTextClassifierEvent( - TextClassificationSessionId sessionId, + @Nullable TextClassificationSessionId sessionId, TextClassifierEvent event) throws RemoteException { Preconditions.checkNotNull(event); final String packageName = event.getEventContext() == null ? null : event.getEventContext().getPackageName(); - validateInput(mContext, packageName); + final int userId = event.getEventContext() == null + ? UserHandle.getCallingUserId() + : event.getEventContext().getUserId(); + validateInput(mContext, packageName, userId); synchronized (mLock) { - UserState userState = getCallingUserStateLocked(); + UserState userState = getUserStateLocked(userId); if (userState.isBoundLocked()) { userState.mService.onTextClassifierEvent(sessionId, event); } else { @@ -235,15 +247,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Override public void onDetectLanguage( - TextClassificationSessionId sessionId, + @Nullable TextClassificationSessionId sessionId, TextLanguage.Request request, ITextClassifierCallback callback) throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); - validateInput(mContext, request.getCallingPackageName()); + final int userId = request.getUserId(); + validateInput(mContext, request.getCallingPackageName(), userId); synchronized (mLock) { - UserState userState = getCallingUserStateLocked(); + UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { callback.onFailure(); } else if (userState.isBoundLocked()) { @@ -258,15 +271,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Override public void onSuggestConversationActions( - TextClassificationSessionId sessionId, + @Nullable TextClassificationSessionId sessionId, ConversationActions.Request request, ITextClassifierCallback callback) throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); - validateInput(mContext, request.getCallingPackageName()); + final int userId = request.getUserId(); + validateInput(mContext, request.getCallingPackageName(), userId); synchronized (mLock) { - UserState userState = getCallingUserStateLocked(); + UserState userState = getUserStateLocked(userId); if (!userState.bindLocked()) { callback.onFailure(); } else if (userState.isBoundLocked()) { @@ -285,13 +299,15 @@ public final class TextClassificationManagerService extends ITextClassifierServi throws RemoteException { Preconditions.checkNotNull(sessionId); Preconditions.checkNotNull(classificationContext); - validateInput(mContext, classificationContext.getPackageName()); + final int userId = classificationContext.getUserId(); + validateInput(mContext, classificationContext.getPackageName(), userId); synchronized (mLock) { - UserState userState = getCallingUserStateLocked(); + UserState userState = getUserStateLocked(userId); if (userState.isBoundLocked()) { userState.mService.onCreateTextClassificationSession( classificationContext, sessionId); + mSessionUserIds.put(sessionId, userId); } else { userState.mPendingRequests.add(new PendingRequest( () -> onCreateTextClassificationSession(classificationContext, sessionId), @@ -306,9 +322,15 @@ public final class TextClassificationManagerService extends ITextClassifierServi Preconditions.checkNotNull(sessionId); synchronized (mLock) { - UserState userState = getCallingUserStateLocked(); + final int userId = mSessionUserIds.containsKey(sessionId) + ? mSessionUserIds.get(sessionId) + : UserHandle.getCallingUserId(); + validateInput(mContext, null /* packageName */, userId); + + UserState userState = getUserStateLocked(userId); if (userState.isBoundLocked()) { userState.mService.onDestroyTextClassificationSession(sessionId); + mSessionUserIds.remove(sessionId); } else { userState.mPendingRequests.add(new PendingRequest( () -> onDestroyTextClassificationSession(sessionId), @@ -317,11 +339,6 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } - @GuardedBy("mLock") - private UserState getCallingUserStateLocked() { - return getUserStateLocked(UserHandle.getCallingUserId()); - } - @GuardedBy("mLock") private UserState getUserStateLocked(int userId) { UserState result = mUserStates.get(userId); @@ -356,6 +373,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.decreaseIndent(); } } + pw.println("Number of active sessions: " + mSessionUserIds.size()); } } @@ -420,20 +438,32 @@ public final class TextClassificationManagerService extends ITextClassifierServi e -> Slog.d(LOG_TAG, "Error " + opDesc + ": " + e.getMessage())); } - private static void validateInput(Context context, @Nullable String packageName) + private static void validateInput( + Context context, @Nullable String packageName, @UserIdInt int userId) throws RemoteException { - if (packageName == null) return; try { - final int packageUid = context.getPackageManager() - .getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); - final int callingUid = Binder.getCallingUid(); - Preconditions.checkArgument(callingUid == packageUid - // Trust the system process: - || callingUid == android.os.Process.SYSTEM_UID); + if (packageName != null) { + final int packageUid = context.getPackageManager() + .getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); + final int callingUid = Binder.getCallingUid(); + Preconditions.checkArgument(callingUid == packageUid + // Trust the system process: + || callingUid == android.os.Process.SYSTEM_UID, + "Invalid package name. Package=" + packageName + + ", CallingUid=" + callingUid); + } + + Preconditions.checkArgument(userId != UserHandle.USER_NULL, "Null userId"); + final int callingUserId = UserHandle.getCallingUserId(); + if (callingUserId != userId) { + context.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "Invalid userId. UserId=" + userId + ", CallingUserId=" + callingUserId); + } } catch (Exception e) { - throw new RemoteException( - String.format("Invalid package: name=%s, error=%s", packageName, e)); + throw new RemoteException("Invalid request: " + e.getMessage(), e, + /* enableSuppression */ true, /* writableStackTrace */ true); } } From ea4bce75cfebd7cef90b1e483d752b252f09333c Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Tue, 10 Dec 2019 07:22:23 +0000 Subject: [PATCH 7/7] Put userId at the end of parcel userId is a new field added in ag/8350816, and it is written in between some old fields when we parcelize it. That breaks AiAi because some of its code rely on the implementation details of SelectionEvent.writeToParcel. In the long run, we may want to update AiAi such that it works without this assumption, but before that, let's fix that in platform for now. Bug: 145962464 Bug: 123232892 Test: atest FrameworksCoreTests:android.view.textclassifier.SelectionEventTest Merged-In: I98137a095cddf0fb4c600e1e09eef507bb750528 Change-Id: I66f65ac4c81ca964f710404fd081496355276435 (cherry picked from commit 642c22b223e40459d74332b629692a94de99ec9a) --- core/java/android/view/textclassifier/SelectionEvent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java index 5c81664425e61..ae9d5c6520f77 100644 --- a/core/java/android/view/textclassifier/SelectionEvent.java +++ b/core/java/android/view/textclassifier/SelectionEvent.java @@ -161,7 +161,6 @@ public final class SelectionEvent implements Parcelable { mEntityType = in.readString(); mWidgetVersion = in.readInt() > 0 ? in.readString() : null; mPackageName = in.readString(); - mUserId = in.readInt(); mWidgetType = in.readString(); mInvocationMethod = in.readInt(); mResultId = in.readString(); @@ -175,6 +174,7 @@ public final class SelectionEvent implements Parcelable { mEnd = in.readInt(); mSmartStart = in.readInt(); mSmartEnd = in.readInt(); + mUserId = in.readInt(); } @Override @@ -188,7 +188,6 @@ public final class SelectionEvent implements Parcelable { dest.writeString(mWidgetVersion); } dest.writeString(mPackageName); - dest.writeInt(mUserId); dest.writeString(mWidgetType); dest.writeInt(mInvocationMethod); dest.writeString(mResultId); @@ -204,6 +203,7 @@ public final class SelectionEvent implements Parcelable { dest.writeInt(mEnd); dest.writeInt(mSmartStart); dest.writeInt(mSmartEnd); + dest.writeInt(mUserId); } @Override