diff options
221 files changed, 5657 insertions, 1469 deletions
diff --git a/Android.bp b/Android.bp index 53417e8e8042..8adf48dc49b6 100644 --- a/Android.bp +++ b/Android.bp @@ -693,6 +693,7 @@ filegroup { "core/java/android/annotation/CallbackExecutor.java", "core/java/android/annotation/CheckResult.java", "core/java/android/annotation/CurrentTimeMillisLong.java", + "core/java/android/annotation/Hide.java", "core/java/android/annotation/IntDef.java", "core/java/android/annotation/IntRange.java", "core/java/android/annotation/LongDef.java", @@ -752,6 +753,18 @@ filegroup { ], } +filegroup { + name: "framework-services-net-module-wifi-shared-srcs", + srcs: [ + "core/java/android/net/DhcpResults.java", + "core/java/android/net/shared/Inet4AddressUtils.java", + "core/java/android/net/shared/InetAddressUtils.java", + "core/java/android/net/util/IpUtils.java", + "core/java/android/util/LocalLog.java", + "core/java/com/android/internal/util/Preconditions.java", + ], +} + // keep these files in sync with the package/Tethering/jarjar-rules.txt for the tethering module. filegroup { name: "framework-tethering-shared-srcs", @@ -973,7 +986,6 @@ filegroup { srcs: [ "core/java/android/os/incremental/IIncrementalService.aidl", "core/java/android/os/incremental/IncrementalNewFileParams.aidl", - "core/java/android/os/incremental/IncrementalSignature.aidl", ], path: "core/java", } @@ -1240,7 +1252,6 @@ filegroup { "core/java/android/net/InterfaceConfiguration.java", "core/java/android/os/BasicShellCommandHandler.java", "core/java/android/util/BackupUtils.java", - "core/java/android/util/LocalLog.java", "core/java/android/util/Rational.java", "core/java/com/android/internal/util/FastXmlSerializer.java", "core/java/com/android/internal/util/HexDump.java", diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java index 02df5e2b6c31..23f025b0a759 100644 --- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java +++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java @@ -121,8 +121,9 @@ public class BlobStorePerfTests { } private DummyBlobData prepareDataBlob(int fileSizeInMb) throws Exception { - final DummyBlobData blobData = new DummyBlobData(mContext, - fileSizeInMb * 1024 * 1024 /* bytes */); + final DummyBlobData blobData = new DummyBlobData.Builder(mContext) + .setFileSize(fileSizeInMb * 1024 * 1024 /* bytes */) + .build(); blobData.prepare(); return blobData; } diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java index 9746dd023002..80062d5d245f 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java @@ -32,21 +32,21 @@ public final class BlobInfo implements Parcelable { private final long mId; private final long mExpiryTimeMs; private final CharSequence mLabel; - private final List<AccessorInfo> mAccessors; + private final List<LeaseInfo> mLeaseInfos; public BlobInfo(long id, long expiryTimeMs, CharSequence label, - List<AccessorInfo> accessors) { + List<LeaseInfo> leaseInfos) { mId = id; mExpiryTimeMs = expiryTimeMs; mLabel = label; - mAccessors = accessors; + mLeaseInfos = leaseInfos; } private BlobInfo(Parcel in) { mId = in.readLong(); mExpiryTimeMs = in.readLong(); mLabel = in.readCharSequence(); - mAccessors = in.readArrayList(null /* classloader */); + mLeaseInfos = in.readArrayList(null /* classloader */); } public long getId() { @@ -61,8 +61,8 @@ public final class BlobInfo implements Parcelable { return mLabel; } - public List<AccessorInfo> getAccessors() { - return Collections.unmodifiableList(mAccessors); + public List<LeaseInfo> getLeases() { + return Collections.unmodifiableList(mLeaseInfos); } @Override @@ -70,7 +70,7 @@ public final class BlobInfo implements Parcelable { dest.writeLong(mId); dest.writeLong(mExpiryTimeMs); dest.writeCharSequence(mLabel); - dest.writeList(mAccessors); + dest.writeList(mLeaseInfos); } @Override @@ -83,7 +83,7 @@ public final class BlobInfo implements Parcelable { + "id: " + mId + "," + "expiryMs: " + mExpiryTimeMs + "," + "label: " + mLabel + "," - + "accessors: " + AccessorInfo.toShortString(mAccessors) + "," + + "leases: " + LeaseInfo.toShortString(mLeaseInfos) + "," + "}"; } diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index 814ab6dbd7fd..c339351759cd 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -21,6 +21,7 @@ import android.annotation.CurrentTimeMillisLong; import android.annotation.IdRes; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; @@ -522,6 +523,50 @@ public class BlobStoreManager { } /** + * Return the {@link BlobHandle BlobHandles} corresponding to the data blobs that + * the calling app has acquired a lease on using {@link #acquireLease(BlobHandle, int)} or + * one of it's other variants. + * + * @hide + */ + @TestApi + @NonNull + public List<BlobHandle> getLeasedBlobs() throws IOException { + try { + return mService.getLeasedBlobs(mContext.getOpPackageName()); + } catch (ParcelableException e) { + e.maybeRethrow(IOException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return {@link LeaseInfo} representing a lease acquired using + * {@link #acquireLease(BlobHandle, int)} or one of it's other variants, + * or {@code null} if there is no lease acquired. + * + * @throws SecurityException when the blob represented by the {@code blobHandle} does not + * exist or the caller does not have access to it. + * @throws IllegalArgumentException when {@code blobHandle} is invalid. + * + * @hide + */ + @TestApi + @Nullable + public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle) throws IOException { + try { + return mService.getLeaseInfo(blobHandle, mContext.getOpPackageName()); + } catch (ParcelableException e) { + e.maybeRethrow(IOException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Represents an ongoing session of a blob's contribution to the blob store managed by the * system. * diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl index e78381359b41..20c15ab57496 100644 --- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl +++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl @@ -18,6 +18,7 @@ package android.app.blob; import android.app.blob.BlobHandle; import android.app.blob.BlobInfo; import android.app.blob.IBlobStoreSession; +import android.app.blob.LeaseInfo; import android.os.RemoteCallback; /** {@hide} */ @@ -35,4 +36,7 @@ interface IBlobStoreManager { List<BlobInfo> queryBlobsForUser(int userId); void deleteBlob(long blobId); + + List<BlobHandle> getLeasedBlobs(in String packageName); + LeaseInfo getLeaseInfo(in BlobHandle blobHandle, in String packageName); }
\ No newline at end of file diff --git a/apex/blobstore/framework/java/android/app/blob/LeaseInfo.aidl b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.aidl new file mode 100644 index 000000000000..908885731bb1 --- /dev/null +++ b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2020 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 android.app.blob; + +/** {@hide} */ +parcelable LeaseInfo;
\ No newline at end of file diff --git a/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.java index 3725ad4a6c09..fef50c9e8dba 100644 --- a/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java +++ b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.java @@ -16,50 +16,61 @@ package android.app.blob; +import android.annotation.CurrentTimeMillisLong; +import android.annotation.IdRes; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import java.util.List; /** - * Class to provide information about an accessor of a shared blob. + * Class to provide information about a lease (acquired using + * {@link BlobStoreManager#acquireLease(BlobHandle, int)} or one of it's variants) + * for a shared blob. * * @hide */ -public final class AccessorInfo implements Parcelable { +@TestApi +public final class LeaseInfo implements Parcelable { private final String mPackageName; - private final long mExpiryTimeMs; + private final long mExpiryTimeMillis; private final int mDescriptionResId; private final CharSequence mDescription; - public AccessorInfo(String packageName, long expiryTimeMs, - int descriptionResId, CharSequence description) { + public LeaseInfo(@NonNull String packageName, @CurrentTimeMillisLong long expiryTimeMs, + @IdRes int descriptionResId, @Nullable CharSequence description) { mPackageName = packageName; - mExpiryTimeMs = expiryTimeMs; + mExpiryTimeMillis = expiryTimeMs; mDescriptionResId = descriptionResId; mDescription = description; } - private AccessorInfo(Parcel in) { + private LeaseInfo(Parcel in) { mPackageName = in.readString(); - mExpiryTimeMs = in.readLong(); + mExpiryTimeMillis = in.readLong(); mDescriptionResId = in.readInt(); mDescription = in.readCharSequence(); } + @NonNull public String getPackageName() { return mPackageName; } - public long getExpiryTimeMs() { - return mExpiryTimeMs; + @CurrentTimeMillisLong + public long getExpiryTimeMillis() { + return mExpiryTimeMillis; } + @IdRes public int getDescriptionResId() { return mDescriptionResId; } + @Nullable public CharSequence getDescription() { return mDescription; } @@ -67,16 +78,16 @@ public final class AccessorInfo implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mPackageName); - dest.writeLong(mExpiryTimeMs); + dest.writeLong(mExpiryTimeMillis); dest.writeInt(mDescriptionResId); dest.writeCharSequence(mDescription); } @Override public String toString() { - return "AccessorInfo {" + return "LeaseInfo {" + "package: " + mPackageName + "," - + "expiryMs: " + mExpiryTimeMs + "," + + "expiryMs: " + mExpiryTimeMillis + "," + "descriptionResId: " + mDescriptionResId + "," + "description: " + mDescription + "," + "}"; @@ -86,11 +97,11 @@ public final class AccessorInfo implements Parcelable { return mPackageName; } - public static String toShortString(List<AccessorInfo> accessors) { + static String toShortString(List<LeaseInfo> leaseInfos) { final StringBuilder sb = new StringBuilder(); sb.append("["); - for (int i = 0, size = accessors.size(); i < size; ++i) { - sb.append(accessors.get(i).toShortString()); + for (int i = 0, size = leaseInfos.size(); i < size; ++i) { + sb.append(leaseInfos.get(i).toShortString()); sb.append(","); } sb.append("]"); @@ -103,17 +114,17 @@ public final class AccessorInfo implements Parcelable { } @NonNull - public static final Creator<AccessorInfo> CREATOR = new Creator<AccessorInfo>() { + public static final Creator<LeaseInfo> CREATOR = new Creator<LeaseInfo>() { @Override @NonNull - public AccessorInfo createFromParcel(Parcel source) { - return new AccessorInfo(source); + public LeaseInfo createFromParcel(Parcel source) { + return new LeaseInfo(source); } @Override @NonNull - public AccessorInfo[] newArray(int size) { - return new AccessorInfo[size]; + public LeaseInfo[] newArray(int size) { + return new LeaseInfo[size]; } }; } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index 970766d2c8a6..8b640ca75698 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -38,6 +38,7 @@ import static com.android.server.blob.BlobStoreUtils.getPackageResources; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.blob.BlobHandle; +import android.app.blob.LeaseInfo; import android.content.Context; import android.content.res.ResourceId; import android.content.res.Resources; @@ -281,6 +282,25 @@ class BlobMetadata { return false; } + @Nullable + LeaseInfo getLeaseInfo(@NonNull String packageName, int uid) { + synchronized (mMetadataLock) { + for (int i = 0, size = mLeasees.size(); i < size; ++i) { + final Leasee leasee = mLeasees.valueAt(i); + if (leasee.uid == uid && leasee.packageName.equals(packageName)) { + final int descriptionResId = leasee.descriptionResEntryName == null + ? Resources.ID_NULL + : BlobStoreUtils.getDescriptionResourceId( + mContext, leasee.descriptionResEntryName, leasee.packageName, + UserHandle.getUserId(leasee.uid)); + return new LeaseInfo(packageName, leasee.expiryTimeMillis, + descriptionResId, leasee.description); + } + } + } + return null; + } + void forEachLeasee(Consumer<Leasee> consumer) { mLeasees.forEach(consumer); } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 53a97cefa59b..f4b8f0f39e85 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -45,11 +45,11 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.blob.AccessorInfo; import android.app.blob.BlobHandle; import android.app.blob.BlobInfo; import android.app.blob.IBlobStoreManager; import android.app.blob.IBlobStoreSession; +import android.app.blob.LeaseInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -454,17 +454,17 @@ public class BlobStoreManagerService extends SystemService { return packageResources; }; getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> { - final ArrayList<AccessorInfo> accessorInfos = new ArrayList<>(); + final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>(); blobMetadata.forEachLeasee(leasee -> { final int descriptionResId = leasee.descriptionResEntryName == null ? Resources.ID_NULL : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName), leasee.descriptionResEntryName, leasee.packageName); - accessorInfos.add(new AccessorInfo(leasee.packageName, leasee.expiryTimeMillis, + leaseInfos.add(new LeaseInfo(leasee.packageName, leasee.expiryTimeMillis, descriptionResId, leasee.description)); }); blobInfos.add(new BlobInfo(blobMetadata.getBlobId(), - blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), accessorInfos)); + blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), leaseInfos)); }); } return blobInfos; @@ -482,6 +482,31 @@ public class BlobStoreManagerService extends SystemService { } } + private List<BlobHandle> getLeasedBlobsInternal(int callingUid, + @NonNull String callingPackage) { + final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>(); + forEachBlobInUser(blobMetadata -> { + if (blobMetadata.isALeasee(callingPackage, callingUid)) { + leasedBlobs.add(blobMetadata.getBlobHandle()); + } + }, UserHandle.getUserId(callingUid)); + return leasedBlobs; + } + + private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle, + int callingUid, @NonNull String callingPackage) { + synchronized (mBlobsLock) { + final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid)) + .get(blobHandle); + if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( + callingPackage, callingUid)) { + throw new SecurityException("Caller not allowed to access " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } + return blobMetadata.getLeaseInfo(callingPackage, callingUid); + } + } + private void verifyCallingPackage(int callingUid, String callingPackage) { if (mPackageManagerInternal.getPackageUid( callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) { @@ -1267,6 +1292,12 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); + if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( + packageName, UserHandle.getUserId(callingUid))) { + throw new SecurityException("Caller not allowed to open blob; " + + "callingUid=" + callingUid + ", callingPackage=" + packageName); + } + try { acquireLeaseInternal(blobHandle, descriptionResId, description, leaseExpiryTimeMillis, callingUid, packageName); @@ -1284,6 +1315,12 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); + if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( + packageName, UserHandle.getUserId(callingUid))) { + throw new SecurityException("Caller not allowed to open blob; " + + "callingUid=" + callingUid + ", callingPackage=" + packageName); + } + releaseLeaseInternal(blobHandle, callingUid, packageName); } @@ -1320,6 +1357,36 @@ public class BlobStoreManagerService extends SystemService { } @Override + @NonNull + public List<BlobHandle> getLeasedBlobs(@NonNull String packageName) { + Objects.requireNonNull(packageName, "packageName must not be null"); + + final int callingUid = Binder.getCallingUid(); + verifyCallingPackage(callingUid, packageName); + + return getLeasedBlobsInternal(callingUid, packageName); + } + + @Override + @Nullable + public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle, @NonNull String packageName) { + Objects.requireNonNull(blobHandle, "blobHandle must not be null"); + blobHandle.assertIsValid(); + Objects.requireNonNull(packageName, "packageName must not be null"); + + final int callingUid = Binder.getCallingUid(); + verifyCallingPackage(callingUid, packageName); + + if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( + packageName, UserHandle.getUserId(callingUid))) { + throw new SecurityException("Caller not allowed to open blob; " + + "callingUid=" + callingUid + ", callingPackage=" + packageName); + } + + return getLeaseInfoInternal(blobHandle, callingUid, packageName); + } + + @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { // TODO: add proto-based version of this. diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java index 6af540acd6a4..fabce766c237 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java @@ -47,4 +47,13 @@ class BlobStoreUtils { @NonNull String resourceEntryName, @NonNull String packageName) { return resources.getIdentifier(resourceEntryName, DESC_RES_TYPE_STRING, packageName); } + + @IdRes + static int getDescriptionResourceId(@NonNull Context context, + @NonNull String resourceEntryName, @NonNull String packageName, int userId) { + final Resources resources = getPackageResources(context, packageName, userId); + return resources == null + ? Resources.ID_NULL + : getDescriptionResourceId(resources, resourceEntryName, packageName); + } } diff --git a/api/current.txt b/api/current.txt index 86fb857817e5..3b714a2f23fc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -290,6 +290,7 @@ package android { field public static final int allowAudioPlaybackCapture = 16844289; // 0x1010601 field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 + field public static final int allowDontAutoRevokePermissions = 16844309; // 0x1010615 field public static final int allowEmbedded = 16843765; // 0x10103f5 field public static final int allowNativeHeapPointerTagging = 16844307; // 0x1010613 field public static final int allowParallelSyncs = 16843570; // 0x1010332 @@ -572,7 +573,7 @@ package android { field public static final int elevation = 16843840; // 0x1010440 field public static final int ellipsize = 16842923; // 0x10100ab field public static final int ems = 16843096; // 0x1010158 - field public static final int enableGwpAsan = 16844310; // 0x1010616 + field public static final int enableGwpAsan = 16844312; // 0x1010618 field public static final int enableVrMode = 16844069; // 0x1010525 field public static final int enabled = 16842766; // 0x101000e field public static final int end = 16843996; // 0x10104dc @@ -953,7 +954,7 @@ package android { field public static final int mediaRouteButtonStyle = 16843693; // 0x10103ad field public static final int mediaRouteTypes = 16843694; // 0x10103ae field public static final int menuCategory = 16843230; // 0x10101de - field public static final int mimeGroup = 16844309; // 0x1010615 + field public static final int mimeGroup = 16844311; // 0x1010617 field public static final int mimeType = 16842790; // 0x1010026 field public static final int min = 16844089; // 0x1010539 field public static final int minAspectRatio = 16844187; // 0x101059b @@ -1082,7 +1083,7 @@ package android { field public static final int preferenceScreenStyle = 16842891; // 0x101008b field public static final int preferenceStyle = 16842894; // 0x101008e field public static final int presentationTheme = 16843712; // 0x10103c0 - field public static final int preserveLegacyExternalStorage = 16844308; // 0x1010614 + field public static final int preserveLegacyExternalStorage = 16844310; // 0x1010616 field public static final int previewImage = 16843482; // 0x10102da field public static final int primaryContentAlpha = 16844114; // 0x1010552 field public static final int priority = 16842780; // 0x101001c @@ -1139,6 +1140,7 @@ package android { field public static final int reqKeyboardType = 16843304; // 0x1010228 field public static final int reqNavigation = 16843306; // 0x101022a field public static final int reqTouchScreen = 16843303; // 0x1010227 + field public static final int requestDontAutoRevokePermissions = 16844308; // 0x1010614 field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603 field public static final int requireDeviceUnlock = 16843756; // 0x10103ec field public static final int required = 16843406; // 0x101028e @@ -11894,6 +11896,7 @@ package android.content.pm { method public void setAppIcon(@Nullable android.graphics.Bitmap); method public void setAppLabel(@Nullable CharSequence); method public void setAppPackageName(@Nullable String); + method public void setAutoRevokePermissionsMode(boolean); method public void setInstallLocation(int); method public void setInstallReason(int); method public void setMultiPackage(); @@ -12023,6 +12026,8 @@ package android.content.pm { method public boolean hasSigningCertificate(int, @NonNull byte[], int); method public abstract boolean hasSystemFeature(@NonNull String); method public abstract boolean hasSystemFeature(@NonNull String, int); + method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean isAutoRevokeWhitelisted(@NonNull String); + method public boolean isAutoRevokeWhitelisted(); method public boolean isDefaultApplicationIcon(@NonNull android.graphics.drawable.Drawable); method public boolean isDeviceUpgrading(); method public abstract boolean isInstantApp(); @@ -12047,6 +12052,7 @@ package android.content.pm { method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int); method public abstract void setApplicationCategoryHint(@NonNull String, int); method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int); + method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean setAutoRevokeWhitelisted(@NonNull String, boolean); method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int); method public abstract void setInstallerPackageName(@NonNull String, @Nullable String); method public void setMimeGroup(@NonNull String, @NonNull java.util.Set<java.lang.String>); @@ -17353,7 +17359,7 @@ package android.hardware.camera2 { public final class CameraManager { method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String) throws android.hardware.camera2.CameraAccessException; method @NonNull public String[] getCameraIdList() throws android.hardware.camera2.CameraAccessException; - method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentStreamingCameraIds() throws android.hardware.camera2.CameraAccessException; + method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentCameraIds() throws android.hardware.camera2.CameraAccessException; method @RequiresPermission(android.Manifest.permission.CAMERA) public boolean isConcurrentSessionConfigurationSupported(@NonNull java.util.Map<java.lang.String,android.hardware.camera2.params.SessionConfiguration>) throws android.hardware.camera2.CameraAccessException; method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull android.hardware.camera2.CameraDevice.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException; @@ -20095,8 +20101,7 @@ package android.icu.number { method public T numberFormatterSecond(android.icu.number.UnlocalizedNumberFormatter); } - public abstract class Precision implements java.lang.Cloneable { - method public Object clone(); + public abstract class Precision { method public static android.icu.number.CurrencyPrecision currency(android.icu.util.Currency.CurrencyUsage); method public static android.icu.number.FractionPrecision fixedFraction(int); method public static android.icu.number.Precision fixedSignificantDigits(int); @@ -20119,8 +20124,7 @@ package android.icu.number { method public static android.icu.number.Scale powerOfTen(int); } - public class ScientificNotation extends android.icu.number.Notation implements java.lang.Cloneable { - method public Object clone(); + public class ScientificNotation extends android.icu.number.Notation { method public android.icu.number.ScientificNotation withExponentSignDisplay(android.icu.number.NumberFormatter.SignDisplay); method public android.icu.number.ScientificNotation withMinExponentDigits(int); } @@ -43016,12 +43020,12 @@ package android.service.autofill { } public static final class Dataset.Builder { - ctor public Dataset.Builder(@NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation); ctor public Dataset.Builder(@NonNull android.widget.RemoteViews); ctor public Dataset.Builder(); method @NonNull public android.service.autofill.Dataset build(); method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender); method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String); + method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern); @@ -56741,6 +56745,7 @@ package android.view.contentcapture { method public final void notifySessionResumed(); method public final void notifyViewAppeared(@NonNull android.view.ViewStructure); method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId); + method public final void notifyViewInsetsChanged(@NonNull android.graphics.Insets); method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence); method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull long[]); method public final void setContentCaptureContext(@Nullable android.view.contentcapture.ContentCaptureContext); diff --git a/api/system-current.txt b/api/system-current.txt index 0fb6919bac0a..4ce64178f09f 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -235,6 +235,7 @@ package android { field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY"; field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK"; + field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS"; field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS"; field public static final String WIFI_SET_DEVICE_MOBILITY_STATE = "android.permission.WIFI_SET_DEVICE_MOBILITY_STATE"; field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"; @@ -1371,7 +1372,6 @@ package android.app.role { method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); - method @Nullable public String getDefaultSmsPackage(int); method @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle); @@ -2133,6 +2133,7 @@ package android.content.pm { public static class PackageInstaller.SessionInfo implements android.os.Parcelable { method public boolean getAllocateAggressive(); method @Deprecated public boolean getAllowDowngrade(); + method public int getAutoRevokePermissionsMode(); method public boolean getDontKillApp(); method public boolean getEnableRollback(); method @Nullable public String[] getGrantedRuntimePermissions(); @@ -2216,7 +2217,7 @@ package android.content.pm { field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; - field public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = -268435456; // 0xf0000000 + field public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = -268435456; // 0xf0000000 field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000 field public static final int FLAG_PERMISSION_AUTO_REVOKED = 131072; // 0x20000 field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20 @@ -8943,11 +8944,13 @@ package android.permission { method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>); method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); - method @BinderThread public void onUpdateUserSensitivePermissionFlags(); + method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull Runnable); field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService"; } public final class PermissionManager { + method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages(); + method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages(); method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion(); method @NonNull public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions(); method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToEnabledCarrierApps(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); @@ -9427,9 +9430,7 @@ package android.provider { public static final class Telephony.Carriers implements android.provider.BaseColumns { field public static final String APN_SET_ID = "apn_set_id"; field public static final int CARRIER_EDITED = 4; // 0x4 - field @NonNull public static final android.net.Uri DPC_URI; field public static final String EDITED_STATUS = "edited"; - field public static final int INVALID_APN_ID = -1; // 0xffffffff field public static final String MAX_CONNECTIONS = "max_conns"; field public static final String MODEM_PERSIST = "modem_cognitive"; field public static final String MTU = "mtu"; @@ -9798,7 +9799,7 @@ package android.service.autofill { public static final class Dataset.Builder { ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation); - method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation); } public abstract class InlineSuggestionRenderService extends android.app.Service { @@ -11540,7 +11541,6 @@ package android.telephony { } public class TelephonyManager { - method public int addDevicePolicyOverrideApn(@NonNull android.content.Context, @NonNull android.telephony.data.ApnSetting); method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int changeIccLockPassword(@NonNull String, @NonNull String); method public int checkCarrierPrivilegesForPackage(String); @@ -11573,7 +11573,6 @@ package android.telephony { method @Deprecated public boolean getDataEnabled(); method @Deprecated public boolean getDataEnabled(int); method @Nullable public static android.content.ComponentName getDefaultRespondViaMessageApplication(@NonNull android.content.Context, boolean); - method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDeviceSoftwareVersion(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion(); @@ -11623,7 +11622,6 @@ package android.telephony { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled(); method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String); - method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting); method public boolean needsOtaServiceProvisioning(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyUserActivity(); @@ -12982,17 +12980,6 @@ package android.telephony.ims.stub { method public int updateClir(int); method public int updateColp(boolean); method public int updateColr(int); - field public static final int CALL_BARRING_ALL = 7; // 0x7 - field public static final int CALL_BARRING_ALL_INCOMING = 1; // 0x1 - field public static final int CALL_BARRING_ALL_OUTGOING = 2; // 0x2 - field public static final int CALL_BARRING_ANONYMOUS_INCOMING = 6; // 0x6 - field public static final int CALL_BARRING_INCOMING_ALL_SERVICES = 9; // 0x9 - field public static final int CALL_BARRING_OUTGOING_ALL_SERVICES = 8; // 0x8 - field public static final int CALL_BARRING_OUTGOING_INTL = 3; // 0x3 - field public static final int CALL_BARRING_OUTGOING_INTL_EXCL_HOME = 4; // 0x4 - field public static final int CALL_BARRING_SPECIFIC_INCOMING_CALLS = 10; // 0xa - field public static final int CALL_BLOCKING_INCOMING_WHEN_ROAMING = 5; // 0x5 - field public static final int INVALID_RESULT = -1; // 0xffffffff } } @@ -13197,6 +13184,7 @@ package android.view.contentcapture { method public long getEventTime(); method @Nullable public android.view.autofill.AutofillId getId(); method @Nullable public java.util.List<android.view.autofill.AutofillId> getIds(); + method @Nullable public android.graphics.Insets getInsets(); method @Nullable public CharSequence getText(); method public int getType(); method @Nullable public android.view.contentcapture.ViewNode getViewNode(); @@ -13207,6 +13195,7 @@ package android.view.contentcapture { field public static final int TYPE_SESSION_RESUMED = 7; // 0x7 field public static final int TYPE_VIEW_APPEARED = 1; // 0x1 field public static final int TYPE_VIEW_DISAPPEARED = 2; // 0x2 + field public static final int TYPE_VIEW_INSETS_CHANGED = 9; // 0x9 field public static final int TYPE_VIEW_TEXT_CHANGED = 3; // 0x3 field public static final int TYPE_VIEW_TREE_APPEARED = 5; // 0x5 field public static final int TYPE_VIEW_TREE_APPEARING = 4; // 0x4 diff --git a/api/test-current.txt b/api/test-current.txt index 9045646ad656..dc764782899c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -83,6 +83,7 @@ package android.app { method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle); + method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); field public static final int PROCESS_CAPABILITY_ALL = 7; // 0x7 field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1 field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6 @@ -596,9 +597,22 @@ package android.app.backup { package android.app.blob { public class BlobStoreManager { + method @Nullable public android.app.blob.LeaseInfo getLeaseInfo(@NonNull android.app.blob.BlobHandle) throws java.io.IOException; + method @NonNull public java.util.List<android.app.blob.BlobHandle> getLeasedBlobs() throws java.io.IOException; method public void waitForIdle(long) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException; } + public final class LeaseInfo implements android.os.Parcelable { + ctor public LeaseInfo(@NonNull String, long, @IdRes int, @Nullable CharSequence); + method public int describeContents(); + method @Nullable public CharSequence getDescription(); + method @IdRes public int getDescriptionResId(); + method public long getExpiryTimeMillis(); + method @NonNull public String getPackageName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.blob.LeaseInfo> CREATOR; + } + } package android.app.prediction { @@ -919,6 +933,7 @@ package android.content.pm { } public static class PackageInstaller.SessionInfo implements android.os.Parcelable { + method public int getAutoRevokePermissionsMode(); method public int getRollbackDataPolicy(); method @NonNull public java.util.Set<java.lang.String> getWhitelistedRestrictedPermissions(); } @@ -3134,7 +3149,7 @@ package android.service.autofill { public static final class Dataset.Builder { ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation); - method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation); } public final class DateTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation { @@ -4671,17 +4686,6 @@ package android.telephony.ims.stub { method public int updateClir(int); method public int updateColp(boolean); method public int updateColr(int); - field public static final int CALL_BARRING_ALL = 7; // 0x7 - field public static final int CALL_BARRING_ALL_INCOMING = 1; // 0x1 - field public static final int CALL_BARRING_ALL_OUTGOING = 2; // 0x2 - field public static final int CALL_BARRING_ANONYMOUS_INCOMING = 6; // 0x6 - field public static final int CALL_BARRING_INCOMING_ALL_SERVICES = 9; // 0x9 - field public static final int CALL_BARRING_OUTGOING_ALL_SERVICES = 8; // 0x8 - field public static final int CALL_BARRING_OUTGOING_INTL = 3; // 0x3 - field public static final int CALL_BARRING_OUTGOING_INTL_EXCL_HOME = 4; // 0x4 - field public static final int CALL_BARRING_SPECIFIC_INCOMING_CALLS = 10; // 0xa - field public static final int CALL_BLOCKING_INCOMING_WHEN_ROAMING = 5; // 0x5 - field public static final int INVALID_RESULT = -1; // 0xffffffff } } @@ -4970,7 +4974,7 @@ package android.view { method public void resetRtlProperties(); method public boolean restoreFocusInCluster(int); method public boolean restoreFocusNotInCluster(); - method public void setAutofilled(boolean); + method public void setAutofilled(boolean, boolean); method public final void setFocusedInCluster(); method public void setIsRootNamespace(boolean); method public final void setShowingLayoutBounds(boolean); @@ -5084,6 +5088,7 @@ package android.view.contentcapture { method public long getEventTime(); method @Nullable public android.view.autofill.AutofillId getId(); method @Nullable public java.util.List<android.view.autofill.AutofillId> getIds(); + method @Nullable public android.graphics.Insets getInsets(); method @Nullable public CharSequence getText(); method public int getType(); method @Nullable public android.view.contentcapture.ViewNode getViewNode(); @@ -5094,6 +5099,7 @@ package android.view.contentcapture { field public static final int TYPE_SESSION_RESUMED = 7; // 0x7 field public static final int TYPE_VIEW_APPEARED = 1; // 0x1 field public static final int TYPE_VIEW_DISAPPEARED = 2; // 0x2 + field public static final int TYPE_VIEW_INSETS_CHANGED = 9; // 0x9 field public static final int TYPE_VIEW_TEXT_CHANGED = 3; // 0x3 field public static final int TYPE_VIEW_TREE_APPEARED = 5; // 0x5 field public static final int TYPE_VIEW_TREE_APPEARING = 4; // 0x4 diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 258f84d0fb79..b515d0a5b72f 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -350,17 +350,19 @@ bool LogEvent::write(const AttributionNodeInternal& node) { return false; } -void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last) { +void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t value = readNextValue<int32_t>(); addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last) { +void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int64_t value = readNextValue<int64_t>(); addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last) { +void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t numBytes = readNextValue<int32_t>(); if ((uint32_t)numBytes > mRemainingLen) { mValid = false; @@ -371,20 +373,23 @@ void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last) { mBuf += numBytes; mRemainingLen -= numBytes; addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last) { +void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { float value = readNextValue<float>(); addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last) { +void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { // cast to int32_t because FieldValue does not support bools int32_t value = (int32_t)readNextValue<uint8_t>(); addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last) { +void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t numBytes = readNextValue<int32_t>(); if ((uint32_t)numBytes > mRemainingLen) { mValid = false; @@ -395,9 +400,10 @@ void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last) { mBuf += numBytes; mRemainingLen -= numBytes; addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last) { +void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t numPairs = readNextValue<uint8_t>(); for (pos[1] = 1; pos[1] <= numPairs; pos[1]++) { @@ -405,56 +411,79 @@ void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last) { // parse key pos[2] = 1; - parseInt32(pos, 2, last); + parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); // parse value last[2] = true; - uint8_t typeId = getTypeId(readNextValue<uint8_t>()); - switch (typeId) { + + uint8_t typeInfo = readNextValue<uint8_t>(); + switch (getTypeId(typeInfo)) { case INT32_TYPE: pos[2] = 2; // pos[2] determined by index of type in KeyValuePair in atoms.proto - parseInt32(pos, 2, last); + parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); break; case INT64_TYPE: pos[2] = 3; - parseInt64(pos, 2, last); + parseInt64(pos, /*depth=*/2, last, /*numAnnotations=*/0); break; case STRING_TYPE: pos[2] = 4; - parseString(pos, 2, last); + parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); break; case FLOAT_TYPE: pos[2] = 5; - parseFloat(pos, 2, last); + parseFloat(pos, /*depth=*/2, last, /*numAnnotations=*/0); break; default: mValid = false; } } + parseAnnotations(numAnnotations); + pos[1] = pos[2] = 1; last[1] = last[2] = false; } -void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last) { +void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, + uint8_t numAnnotations) { int32_t numNodes = readNextValue<uint8_t>(); for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) { last[1] = (pos[1] == numNodes); // parse uid pos[2] = 1; - parseInt32(pos, 2, last); + parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); // parse tag pos[2] = 2; last[2] = true; - parseString(pos, 2, last); + parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); } + parseAnnotations(numAnnotations); + pos[1] = pos[2] = 1; last[1] = last[2] = false; } +// TODO(b/151109630): store annotation information within LogEvent +void LogEvent::parseAnnotations(uint8_t numAnnotations) { + for (uint8_t i = 0; i < numAnnotations; i++) { + /*uint8_t annotationId = */ readNextValue<uint8_t>(); + uint8_t annotationType = readNextValue<uint8_t>(); + switch (annotationType) { + case BOOL_TYPE: + /*bool annotationValue = */ readNextValue<uint8_t>(); + break; + case INT32_TYPE: + /*int32_t annotationValue =*/ readNextValue<int32_t>(); + break; + default: + mValid = false; + } + } +} // This parsing logic is tied to the encoding scheme used in StatsEvent.java and // stats_event.c @@ -475,6 +504,7 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { typeInfo = readNextValue<uint8_t>(); if (getTypeId(typeInfo) != INT64_TYPE) mValid = false; mElapsedTimestampNs = readNextValue<int64_t>(); + parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations numElements--; typeInfo = readNextValue<uint8_t>(); @@ -484,37 +514,36 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) { + last[0] = (pos[0] == numElements); + typeInfo = readNextValue<uint8_t>(); uint8_t typeId = getTypeId(typeInfo); - last[0] = (pos[0] == numElements); - // TODO(b/144373276): handle errors passed to the socket - // TODO(b/144373257): parse annotations switch(typeId) { case BOOL_TYPE: - parseBool(pos, 0, last); + parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case INT32_TYPE: - parseInt32(pos, 0, last); + parseInt32(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case INT64_TYPE: - parseInt64(pos, 0, last); + parseInt64(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case FLOAT_TYPE: - parseFloat(pos, 0, last); + parseFloat(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case BYTE_ARRAY_TYPE: - parseByteArray(pos, 0, last); + parseByteArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case STRING_TYPE: - parseString(pos, 0, last); + parseString(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case KEY_VALUE_PAIRS_TYPE: - parseKeyValuePairs(pos, 0, last); + parseKeyValuePairs(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case ATTRIBUTION_CHAIN_TYPE: - parseAttributionChain(pos, 0, last); + parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; default: mValid = false; @@ -531,7 +560,7 @@ uint8_t LogEvent::getTypeId(uint8_t typeInfo) { } uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) { - return (typeInfo >> 4) & 0x0F; + return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes } int64_t LogEvent::GetLong(size_t key, status_t* err) const { diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index b68eeb8d6499..6537f13c4089 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -232,14 +232,15 @@ private: */ LogEvent(const LogEvent&); - void parseInt32(int32_t* pos, int32_t depth, bool* last); - void parseInt64(int32_t* pos, int32_t depth, bool* last); - void parseString(int32_t* pos, int32_t depth, bool* last); - void parseFloat(int32_t* pos, int32_t depth, bool* last); - void parseBool(int32_t* pos, int32_t depth, bool* last); - void parseByteArray(int32_t* pos, int32_t depth, bool* last); - void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last); - void parseAttributionChain(int32_t* pos, int32_t depth, bool* last); + void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseAnnotations(uint8_t numAnnotations); /** * The below three variables are only valid during the execution of diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index dd4788e1146a..1a92b758ab9f 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4243,6 +4243,7 @@ public class ActivityManager { * @hide */ @SystemApi + @TestApi @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String mcc, @NonNull String mnc) { if (mcc == null || mnc == null) { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index f6a79cd767da..cdf446467a5e 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1071,9 +1071,17 @@ public class AppOpsManager { /** @hide Auto-revoke app permissions if app is unused for an extended period */ public static final int OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = 97; + /** + * Whether {@link #OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED} is allowed to be changed by + * the installer + * + * @hide + */ + public static final int OP_AUTO_REVOKE_MANAGED_BY_INSTALLER = 98; + /** @hide */ @UnsupportedAppUsage - public static final int _NUM_OP = 98; + public static final int _NUM_OP = 99; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1463,6 +1471,7 @@ public class AppOpsManager { OP_LOADER_USAGE_STATS, OP_ACCESS_CALL_AUDIO, OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, + OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, }; /** @@ -1572,6 +1581,7 @@ public class AppOpsManager { OP_LOADER_USAGE_STATS, // LOADER_USAGE_STATS OP_ACCESS_CALL_AUDIO, // ACCESS_CALL_AUDIO OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, //AUTO_REVOKE_PERMISSIONS_IF_UNUSED + OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER }; /** @@ -1887,6 +1897,7 @@ public class AppOpsManager { android.Manifest.permission.LOADER_USAGE_STATS, Manifest.permission.ACCESS_CALL_AUDIO, null, // no permission for OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED + null, // no permission for OP_AUTO_REVOKE_MANAGED_BY_INSTALLER }; /** @@ -2202,6 +2213,7 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // LOADER_USAGE_STATS AppOpsManager.MODE_DEFAULT, // ACCESS_CALL_AUDIO AppOpsManager.MODE_DEFAULT, // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED + AppOpsManager.MODE_ALLOWED, // OP_AUTO_REVOKE_MANAGED_BY_INSTALLER }; /** diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 87d33a980438..18df401c9c8d 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -840,6 +840,27 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public boolean setAutoRevokeWhitelisted( + @NonNull String packageName, boolean whitelisted) { + try { + final int userId = getUserId(); + return mPermissionManager.setAutoRevokeWhitelisted(packageName, whitelisted, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean isAutoRevokeWhitelisted(@NonNull String packageName) { + try { + final int userId = getUserId(); + return mPermissionManager.isAutoRevokeWhitelisted(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override public boolean removeWhitelistedRestrictedPermission(@NonNull String packageName, @NonNull String permName, @PermissionWhitelistFlags int flags) { try { @@ -3337,6 +3358,15 @@ public class ApplicationPackageManager extends PackageManager { } } + @Override + public boolean isAutoRevokeWhitelisted() { + try { + return mPM.isAutoRevokeWhitelisted(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + public void setMimeGroup(String mimeGroup, Set<String> mimeTypes) { try { mPM.setMimeGroup(mContext.getPackageName(), mimeGroup, diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 32e7d84e6083..061e5ff35f55 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -21,6 +21,7 @@ import static android.graphics.drawable.Icon.TYPE_URI; import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast; +import static com.android.internal.widget.ConversationLayout.CONVERSATION_LAYOUT_ENABLED; import android.annotation.ColorInt; import android.annotation.DimenRes; @@ -389,6 +390,7 @@ public class Notification implements Parcelable STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text); STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox); STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation); STANDARD_LAYOUTS.add(R.layout.notification_template_material_media); STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media); STANDARD_LAYOUTS.add(R.layout.notification_template_header); @@ -5138,7 +5140,7 @@ public class Notification implements Parcelable int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p); contentView.setDrawableTint(R.id.expand_button, false, color, PorterDuff.Mode.SRC_ATOP); - contentView.setInt(R.id.notification_header, "setOriginalNotificationColor", + contentView.setInt(R.id.expand_button, "setOriginalNotificationColor", color); } @@ -6116,7 +6118,9 @@ public class Notification implements Parcelable } private int getMessagingLayoutResource() { - return R.layout.notification_template_material_messaging; + return CONVERSATION_LAYOUT_ENABLED + ? R.layout.notification_template_material_conversation + : R.layout.notification_template_material_messaging; } private int getActionLayoutResource() { @@ -6221,6 +6225,17 @@ public class Notification implements Parcelable } return loadHeaderAppName(); } + + /** + * @return if this builder uses a template + * + * @hide + */ + public boolean usesTemplate() { + return (mN.contentView == null && mN.headsUpContentView == null + && mN.bigContentView == null) + || (mStyle != null && mStyle.displayCustomViewInline()); + } } /** @@ -7379,7 +7394,7 @@ public class Notification implements Parcelable public RemoteViews makeContentView(boolean increasedHeight) { mBuilder.mOriginalActions = mBuilder.mActions; mBuilder.mActions = new ArrayList<>(); - RemoteViews remoteViews = makeMessagingView(true /* displayImagesAtEnd */, + RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */, false /* hideLargeIcon */); mBuilder.mActions = mBuilder.mOriginalActions; mBuilder.mOriginalActions = null; @@ -7469,19 +7484,18 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { - return makeMessagingView(false /* displayImagesAtEnd */, true /* hideLargeIcon */); + return makeMessagingView(false /* isCollapsed */, true /* hideLargeIcon */); } /** * Create a messaging layout. * - * @param displayImagesAtEnd should images be displayed at the end of the content instead - * of inline. + * @param isCollapsed Should this use the collapsed layout * @param hideRightIcons Should the reply affordance be shown at the end of the notification * @return the created remoteView. */ @NonNull - private RemoteViews makeMessagingView(boolean displayImagesAtEnd, boolean hideRightIcons) { + private RemoteViews makeMessagingView(boolean isCollapsed, boolean hideRightIcons) { CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; @@ -7522,14 +7536,21 @@ public class Notification implements Parcelable mBuilder.getPrimaryTextColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", mBuilder.getSecondaryTextColor(p)); - contentView.setBoolean(R.id.status_bar_latest_event_content, "setDisplayImagesAtEnd", - displayImagesAtEnd); + contentView.setInt(R.id.status_bar_latest_event_content, + "setNotificationBackgroundColor", + mBuilder.resolveBackgroundColor(p)); + contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", + isCollapsed); contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", avatarReplacement); contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", nameReplacement); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", isOneToOne); + contentView.setCharSequence(R.id.status_bar_latest_event_content, + "setConversationTitle", conversationTitle); + contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", + mBuilder.mN.mLargeIcon); contentView.setBundle(R.id.status_bar_latest_event_content, "setData", mBuilder.mN.extras); return contentView; @@ -7590,9 +7611,11 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - RemoteViews remoteViews = makeMessagingView(true /* displayImagesAtEnd */, + RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */, true /* hideLargeIcon */); - remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); + if (!CONVERSATION_LAYOUT_ENABLED) { + remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); + } return remoteViews; } diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index db4f1de1f743..917eeb84c7a5 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -636,7 +636,6 @@ public final class RoleManager { * @hide */ @Nullable - @SystemApi public String getDefaultSmsPackage(@UserIdInt int userId) { try { return mService.getDefaultSmsPackage(userId); diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java index 052c9209f6f7..b4340729a220 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -435,10 +435,11 @@ public final class PermissionChecker { final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); final int opMode = (forDataDelivery) ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message) - : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); + : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); switch (opMode) { - case AppOpsManager.MODE_ALLOWED: { + case AppOpsManager.MODE_ALLOWED: + case AppOpsManager.MODE_FOREGROUND: { return PERMISSION_GRANTED; } case AppOpsManager.MODE_DEFAULT: { @@ -467,12 +468,14 @@ public final class PermissionChecker { final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); final int opMode = (forDataDelivery) ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message) - : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); + : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); - if (opMode == AppOpsManager.MODE_ALLOWED) { - return PERMISSION_GRANTED; - } else { - return PERMISSION_SOFT_DENIED; + switch (opMode) { + case AppOpsManager.MODE_ALLOWED: + case AppOpsManager.MODE_FOREGROUND: + return PERMISSION_GRANTED; + default: + return PERMISSION_SOFT_DENIED; } } } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 8c3eef27dd58..0311120aebfb 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -748,4 +748,6 @@ interface IPackageManager { void clearMimeGroup(String packageName, String group); List<String> getMimeGroup(String packageName, String group); + + boolean isAutoRevokeWhitelisted(String packageName); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index ec3590fd23cf..50bee854c027 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -16,6 +16,10 @@ package android.content.pm; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_DEFAULT; +import static android.app.AppOpsManager.MODE_IGNORED; + import android.Manifest; import android.annotation.CurrentTimeMillisLong; import android.annotation.IntDef; @@ -1456,6 +1460,8 @@ public class PackageInstaller { /** {@hide} */ public List<String> whitelistedRestrictedPermissions; /** {@hide} */ + public int autoRevokePermissionsMode = MODE_DEFAULT; + /** {@hide} */ public String installerPackageName; /** {@hide} */ public boolean isMultiPackage; @@ -1498,6 +1504,7 @@ public class PackageInstaller { volumeUuid = source.readString(); grantedRuntimePermissions = source.readStringArray(); whitelistedRestrictedPermissions = source.createStringArrayList(); + autoRevokePermissionsMode = source.readInt(); installerPackageName = source.readString(); isMultiPackage = source.readBoolean(); isStaged = source.readBoolean(); @@ -1528,6 +1535,7 @@ public class PackageInstaller { ret.volumeUuid = volumeUuid; ret.grantedRuntimePermissions = grantedRuntimePermissions; ret.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions; + ret.autoRevokePermissionsMode = autoRevokePermissionsMode; ret.installerPackageName = installerPackageName; ret.isMultiPackage = isMultiPackage; ret.isStaged = isStaged; @@ -1691,6 +1699,22 @@ public class PackageInstaller { } /** + * Sets whether permissions should be auto-revoked if this package is unused for an + * extended periodd of time. + * + * It's disabled by default but generally the installer should enable it for most packages, + * excluding only those where doing so might cause breakage that cannot be easily addressed + * by simply re-requesting the permission(s). + * + * If user explicitly enabled or disabled it via settings, this call is ignored. + * + * @param shouldAutoRevoke whether permissions should be auto-revoked. + */ + public void setAutoRevokePermissionsMode(boolean shouldAutoRevoke) { + autoRevokePermissionsMode = shouldAutoRevoke ? MODE_ALLOWED : MODE_IGNORED; + } + + /** * Request that rollbacks be enabled or disabled for the given upgrade with rollback data * policy set to RESTORE. * @@ -1932,6 +1956,7 @@ public class PackageInstaller { pw.printPair("volumeUuid", volumeUuid); pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions); pw.printPair("whitelistedRestrictedPermissions", whitelistedRestrictedPermissions); + pw.printPair("autoRevokePermissions", autoRevokePermissionsMode); pw.printPair("installerPackageName", installerPackageName); pw.printPair("isMultiPackage", isMultiPackage); pw.printPair("isStaged", isStaged); @@ -1964,6 +1989,7 @@ public class PackageInstaller { dest.writeString(volumeUuid); dest.writeStringArray(grantedRuntimePermissions); dest.writeStringList(whitelistedRestrictedPermissions); + dest.writeInt(autoRevokePermissionsMode); dest.writeString(installerPackageName); dest.writeBoolean(isMultiPackage); dest.writeBoolean(isStaged); @@ -2085,6 +2111,8 @@ public class PackageInstaller { public String[] grantedRuntimePermissions; /** {@hide}*/ public List<String> whitelistedRestrictedPermissions; + /** {@hide}*/ + public int autoRevokePermissionsMode = MODE_DEFAULT; /** {@hide} */ public int installFlags; /** {@hide} */ @@ -2147,6 +2175,7 @@ public class PackageInstaller { referrerUri = source.readParcelable(null); grantedRuntimePermissions = source.readStringArray(); whitelistedRestrictedPermissions = source.createStringArrayList(); + autoRevokePermissionsMode = source.readInt(); installFlags = source.readInt(); isMultiPackage = source.readBoolean(); @@ -2374,6 +2403,24 @@ public class PackageInstaller { } /** + * Get the status of whether permission auto-revocation should be allowed, ignored, or + * deferred to manifest data. + * + * @see android.app.AppOpsManager#MODE_ALLOWED + * @see android.app.AppOpsManager#MODE_IGNORED + * @see android.app.AppOpsManager#MODE_DEFAULT + * + * @return the status of auto-revoke for this package + * + * @hide + */ + @TestApi + @SystemApi + public int getAutoRevokePermissionsMode() { + return autoRevokePermissionsMode; + } + + /** * Get the value set in {@link SessionParams#setAllowDowngrade(boolean)}. * * @deprecated use {@link #getRequestDowngrade()}. @@ -2660,6 +2707,7 @@ public class PackageInstaller { dest.writeParcelable(referrerUri, flags); dest.writeStringArray(grantedRuntimePermissions); dest.writeStringList(whitelistedRestrictedPermissions); + dest.writeInt(autoRevokePermissionsMode); dest.writeInt(installFlags); dest.writeBoolean(isMultiPackage); dest.writeBoolean(isStaged); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 7600a08a256c..ea941ccdfdb4 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1978,7 +1978,7 @@ public abstract class PackageManager { * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device's main front and back cameras can stream * concurrently as described in {@link - * android.hardware.camera2.CameraManager#getConcurrentStreamingCameraIds()} + * android.hardware.camera2.CameraManager#getConcurrentCameraIds()} */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_CAMERA_CONCURRENT = "android.hardware.camera.concurrent"; @@ -3411,12 +3411,13 @@ public abstract class PackageManager { public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 17; /** - * Permission flags: Reserved for use by the permission controller. - * + * Permission flags: Reserved for use by the permission controller. The platform and any + * packages besides the permission controller should not assume any definition about these + * flags. * @hide */ @SystemApi - public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = 1 << 28 | 1 << 29 + public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = 1 << 28 | 1 << 29 | 1 << 30 | 1 << 31; /** @@ -4575,6 +4576,53 @@ public abstract class PackageManager { } /** + * Marks an application exempt from having its permissions be automatically revoked when + * the app is unused for an extended period of time. + * + * Only the installer on record that installed the given package, or a holder of + * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this. + * + * Packages start in whitelisted state, and it is the installer's responsibility to + * un-whitelist the packages it installs, unless auto-revoking permissions from that package + * would cause breakages beyond having to re-request the permission(s). + * + * @param packageName The app for which to set exemption. + * @param whitelisted Whether the app should be whitelisted. + * + * @return whether any change took effect. + * + * @see #isAutoRevokeWhitelisted + * + * @throws SecurityException if you you have no access to modify this. + */ + @RequiresPermission(value = Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS, + conditional = true) + public boolean setAutoRevokeWhitelisted(@NonNull String packageName, boolean whitelisted) { + return false; + } + + /** + * Checks whether an application is exempt from having its permissions be automatically revoked + * when the app is unused for an extended period of time. + * + * Only the installer on record that installed the given package, or a holder of + * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this. + * @param packageName The app for which to set exemption. + * + * @return Whether the app is whitelisted. + * + * @see #setAutoRevokeWhitelisted + * + * @throws SecurityException if you you have no access to this. + */ + @RequiresPermission(value = Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS, + conditional = true) + public boolean isAutoRevokeWhitelisted(@NonNull String packageName) { + return false; + } + + + /** * Gets whether you should show UI with rationale for requesting a permission. * You should do this only if you do not have the permission and the context in * which the permission is requested does not clearly communicate to the user @@ -7834,6 +7882,15 @@ public abstract class PackageManager { } /** + * @return whether this package is whitelisted from having its runtime permission be + * auto-revoked if unused for an extended period of time. + */ + public boolean isAutoRevokeWhitelisted() { + throw new UnsupportedOperationException( + "isAutoRevokeWhitelisted not implemented in subclass"); + } + + /** * Returns if the provided drawable represents the default activity icon provided by the system. * * PackageManager silently returns a default application icon for any package/activity if the diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 1e02a7d0d3cf..1304ba8ef3a0 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -191,7 +191,11 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setRequestLegacyExternalStorage(boolean requestLegacyExternalStorage); ParsingPackage setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging); - + + ParsingPackage setDontAutoRevokePermissions(boolean dontAutoRevokePermissions); + + ParsingPackage setAllowDontAutoRevokePermissions(boolean allowDontAutoRevokePermissions); + ParsingPackage setPreserveLegacyExternalStorage(boolean preserveLegacyExternalStorage); ParsingPackage setRestoreAnyVersion(boolean restoreAnyVersion); diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index d7c0dfbc65e2..3390f1616134 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -405,6 +405,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { private boolean hasFragileUserData; private boolean cantSaveState; private boolean allowNativeHeapPointerTagging; + private boolean dontAutoRevokePermissions; + private boolean allowDontAutoRevokePermissions; private boolean preserveLegacyExternalStorage; @Nullable @@ -1089,6 +1091,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { dest.writeBoolean(this.hasFragileUserData); dest.writeBoolean(this.cantSaveState); dest.writeBoolean(this.allowNativeHeapPointerTagging); + dest.writeBoolean(this.dontAutoRevokePermissions); + dest.writeBoolean(this.allowDontAutoRevokePermissions); dest.writeBoolean(this.preserveLegacyExternalStorage); dest.writeArraySet(this.mimeGroups); sForBoolean.parcel(this.enableGwpAsan, dest, flags); @@ -1247,6 +1251,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { this.hasFragileUserData = in.readBoolean(); this.cantSaveState = in.readBoolean(); this.allowNativeHeapPointerTagging = in.readBoolean(); + this.dontAutoRevokePermissions = in.readBoolean(); + this.allowDontAutoRevokePermissions = in.readBoolean(); this.preserveLegacyExternalStorage = in.readBoolean(); this.mimeGroups = (ArraySet<String>) in.readArraySet(boot); this.enableGwpAsan = sForBoolean.unparcel(in); @@ -2023,6 +2029,16 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { } @Override + public boolean isDontAutoRevokePermmissions() { + return dontAutoRevokePermissions; + } + + @Override + public boolean isAllowDontAutoRevokePermmissions() { + return allowDontAutoRevokePermissions; + } + + @Override public boolean hasPreserveLegacyExternalStorage() { return preserveLegacyExternalStorage; } @@ -2493,6 +2509,18 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { } @Override + public ParsingPackageImpl setDontAutoRevokePermissions(boolean value) { + dontAutoRevokePermissions = value; + return this; + } + + @Override + public ParsingPackageImpl setAllowDontAutoRevokePermissions(boolean value) { + allowDontAutoRevokePermissions = value; + return this; + } + + @Override public ParsingPackageImpl setPreserveLegacyExternalStorage(boolean value) { preserveLegacyExternalStorage = value; return this; diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java index 0b673b5f89cd..9c13c85a041e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java @@ -771,6 +771,12 @@ public interface ParsingPackageRead extends Parcelable { /** @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING */ boolean isAllowNativeHeapPointerTagging(); + /** @see ApplicationInfo#PRIVATE_FLAG2_DONT_AUTO_REVOKE_PERMISSIONS */ + boolean isDontAutoRevokePermmissions(); + + /** @see ApplicationInfo#PRIVATE_FLAG2_ALLOW_DONT_AUTO_REVOKE_PERMISSIONS */ + boolean isAllowDontAutoRevokePermmissions(); + boolean hasPreserveLegacyExternalStorage(); /** diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 3ed0fd57975b..e41ed85d2438 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -1820,6 +1820,8 @@ public class ParsingPackageUtils { .setUseEmbeddedDex(bool(false, R.styleable.AndroidManifestApplication_useEmbeddedDex, sa)) .setUsesNonSdkApi(bool(false, R.styleable.AndroidManifestApplication_usesNonSdkApi, sa)) .setVmSafeMode(bool(false, R.styleable.AndroidManifestApplication_vmSafeMode, sa)) + .setDontAutoRevokePermissions(bool(false, R.styleable.AndroidManifestApplication_requestDontAutoRevokePermissions, sa)) + .setAllowDontAutoRevokePermissions(bool(false, R.styleable.AndroidManifestApplication_allowDontAutoRevokePermissions, sa)) // targetSdkVersion gated .setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa)) .setBaseHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa)) diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl index 61310f302fe4..8bcaf828be97 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl @@ -35,7 +35,7 @@ oneway interface IBiometricServiceReceiverInternal { // Notifies that a biometric has been acquired. void onAcquired(int acquiredInfo, String message); // Notifies that the SystemUI dialog has been dismissed. - void onDialogDismissed(int reason); + void onDialogDismissed(int reason, in byte[] credentialAttestation); // Notifies that the user has pressed the "try again" button on SystemUI void onTryAgainPressed(); // Notifies that the user has pressed the "use password" button on SystemUI diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index b3a1ee2f9b69..7e72b73db358 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -2884,12 +2884,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * generated according to the documented * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} for each device * which has its Id present in the set returned by - * {@link android.hardware.camera2.CameraManager#getConcurrentStreamingCameraIds}. + * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds}. * Clients can use the array as a quick reference to find an appropriate camera stream * combination. * The mandatory stream combination array will be {@code null} in case the device is not a part * of at least one set of combinations returned by - * {@link android.hardware.camera2.CameraManager#getConcurrentStreamingCameraIds}.</p> + * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds}.</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> */ @PublicKey diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index cc0c1a309038..30ee32604939 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -681,7 +681,7 @@ public abstract class CameraDevice implements AutoCloseable { * </p> * *<p>Devices capable of streaming concurrently with other devices as described by - * {@link android.hardware.camera2.CameraManager#getConcurrentStreamingCameraIds} have the + * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds} have the * following guaranteed streams (when streaming concurrently with other devices)</p> * * <table> diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 972b0f55acef..e81c649796a0 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -160,8 +160,8 @@ public final class CameraManager { * @throws CameraAccessException if the camera device has been disconnected. */ @NonNull - public Set<Set<String>> getConcurrentStreamingCameraIds() throws CameraAccessException { - return CameraManagerGlobal.get().getConcurrentStreamingCameraIds(); + public Set<Set<String>> getConcurrentCameraIds() throws CameraAccessException { + return CameraManagerGlobal.get().getConcurrentCameraIds(); } /** @@ -189,11 +189,11 @@ public final class CameraManager { * * @return {@code true} if the given combination of session configurations and corresponding * camera ids are concurrently supported by the camera sub-system, - * {@code false} otherwise. + * {@code false} otherwise OR if the set of camera devices provided is not a subset of + * those returned by {@link #getConcurrentCameraIds}. * - * @throws IllegalArgumentException if the set of camera devices provided is not a subset of - * those returned by getConcurrentStreamingCameraIds() * @throws CameraAccessException if one of the camera devices queried is no longer connected. + * */ @RequiresPermission(android.Manifest.permission.CAMERA) public boolean isConcurrentSessionConfigurationSupported( @@ -1183,7 +1183,7 @@ public final class CameraManager { try { ConcurrentCameraIdCombination[] cameraIdCombinations = - cameraService.getConcurrentStreamingCameraIds(); + cameraService.getConcurrentCameraIds(); for (ConcurrentCameraIdCombination comb : cameraIdCombinations) { mConcurrentCameraIdCombinations.add(comb.getConcurrentCameraIdCombination()); } @@ -1372,7 +1372,7 @@ public final class CameraManager { return cameraIds; } - public @NonNull Set<Set<String>> getConcurrentStreamingCameraIds() { + public @NonNull Set<Set<String>> getConcurrentCameraIds() { Set<Set<String>> concurrentStreamingCameraIds = null; synchronized (mLock) { // Try to make sure we have an up-to-date list of concurrent camera devices. @@ -1398,7 +1398,7 @@ public final class CameraManager { synchronized (mLock) { // Go through all the elements and check if the camera ids are valid at least / - // belong to one of the combinations returned by getConcurrentStreamingCameraIds() + // belong to one of the combinations returned by getConcurrentCameraIds() boolean subsetFound = false; for (Set<String> combination : mConcurrentCameraIdCombinations) { if (combination.containsAll(cameraIdsAndSessionConfigurations.keySet())) { @@ -1406,9 +1406,9 @@ public final class CameraManager { } } if (!subsetFound) { - throw new IllegalArgumentException( - "The set of camera ids provided is not a subset of" - + "getConcurrentStreamingCameraIds"); + Log.v(TAG, "isConcurrentSessionConfigurationSupported called with a subset of" + + "camera ids not returned by getConcurrentCameraIds"); + return false; } CameraIdAndSessionConfiguration [] cameraIdsAndConfigs = new CameraIdAndSessionConfiguration[size]; @@ -1436,10 +1436,10 @@ public final class CameraManager { /** * Helper function to find out if a camera id is in the set of combinations returned by - * getConcurrentStreamingCameraIds() + * getConcurrentCameraIds() * @param cameraId the unique identifier of the camera device to query * @return Whether the camera device was found in the set of combinations returned by - * getConcurrentStreamingCameraIds + * getConcurrentCameraIds */ public boolean cameraIdHasConcurrentStreamsLocked(String cameraId) { if (!mDeviceStatus.containsKey(cameraId)) { diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index f0fab6a99d14..20d9c30bb4cc 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -721,7 +721,7 @@ public final class MandatoryStreamCombination { /** * Retrieve a list of all available mandatory concurrent stream combinations. * This method should only be called for devices which are listed in combinations returned - * by CameraManager.getConcurrentStreamingCameraIds. + * by CameraManager.getConcurrentCameraIds. * * @return a non-modifiable list of supported mandatory concurrent stream combinations. */ diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java index 2b8b7e69dec9..6c0ba2f63a80 100644 --- a/core/java/android/net/KeepalivePacketData.java +++ b/core/java/android/net/KeepalivePacketData.java @@ -22,7 +22,6 @@ import static android.net.InvalidPacketException.ERROR_INVALID_PORT; import android.annotation.NonNull; import android.annotation.SystemApi; import android.net.util.IpUtils; -import android.os.Parcel; import android.util.Log; import java.net.InetAddress; @@ -30,7 +29,6 @@ import java.net.InetAddress; /** * Represents the actual packets that are sent by the * {@link android.net.SocketKeepalive} API. - * * @hide */ @SystemApi @@ -54,6 +52,9 @@ public class KeepalivePacketData { /** Packet data. A raw byte string of packet data, not including the link-layer header. */ private final byte[] mPacket; + // Note: If you add new fields, please modify the parcelling code in the child classes. + + // This should only be constructed via static factory methods, such as // nattKeepalivePacket. /** @@ -87,21 +88,4 @@ public class KeepalivePacketData { return mPacket.clone(); } - /** @hide */ - public void writeToParcel(Parcel out, int flags) { - out.writeString(srcAddress.getHostAddress()); - out.writeString(dstAddress.getHostAddress()); - out.writeInt(srcPort); - out.writeInt(dstPort); - out.writeByteArray(mPacket); - } - - /** @hide */ - protected KeepalivePacketData(Parcel in) { - srcAddress = NetworkUtils.numericToInetAddress(in.readString()); - dstAddress = NetworkUtils.numericToInetAddress(in.readString()); - srcPort = in.readInt(); - dstPort = in.readInt(); - mPacket = in.createByteArray(); - } } diff --git a/core/java/android/os/incremental/IncrementalNewFileParams.aidl b/core/java/android/os/incremental/IncrementalNewFileParams.aidl index 182732cebdf1..8faf158b72a0 100644 --- a/core/java/android/os/incremental/IncrementalNewFileParams.aidl +++ b/core/java/android/os/incremental/IncrementalNewFileParams.aidl @@ -16,8 +16,6 @@ package android.os.incremental; -import android.os.incremental.IncrementalSignature; - /** * All the parameters to create a new file on IncFS * FileId is a 16 byte-long identifier. @@ -27,5 +25,5 @@ parcelable IncrementalNewFileParams { long size; byte[] fileId; byte[] metadata; - @nullable IncrementalSignature signature; + @nullable byte[] signature; } diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index bf31bc206278..7092751c0d7e 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -20,8 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; @@ -180,11 +178,12 @@ public final class IncrementalStorage { if (id == null && metadata == null) { throw new IOException("File ID and metadata cannot both be null"); } + validateV4Signature(v4signatureBytes); final IncrementalNewFileParams params = new IncrementalNewFileParams(); params.size = size; params.metadata = (metadata == null ? new byte[0] : metadata); params.fileId = idToBytes(id); - params.signature = parseV4Signature(v4signatureBytes); + params.signature = v4signatureBytes; int res = mService.makeFile(mId, path, params); if (res != 0) { throw new IOException("makeFile() failed with errno " + -res); @@ -415,27 +414,23 @@ public final class IncrementalStorage { return new UUID(msb, lsb); } - private static final int INCFS_HASH_SHA256 = 1; private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256 private static final int INCFS_MAX_ADD_DATA_SIZE = 128; /** * Deserialize and validate v4 signature bytes. */ - private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes) + private static void validateV4Signature(@Nullable byte[] v4signatureBytes) throws IOException { if (v4signatureBytes == null || v4signatureBytes.length == 0) { - return null; + return; } final V4Signature signature; - try (DataInputStream input = new DataInputStream( - new ByteArrayInputStream(v4signatureBytes))) { - try { - signature = V4Signature.readFrom(input); - } catch (IOException e) { - throw new IOException("Failed to read v4 signature:", e); - } + try { + signature = V4Signature.readFrom(v4signatureBytes); + } catch (IOException e) { + throw new IOException("Failed to read v4 signature:", e); } if (!signature.isVersionSupported()) { @@ -443,25 +438,27 @@ public final class IncrementalStorage { + " is not supported"); } - final byte[] rootHash = signature.verityRootHash; - final byte[] additionalData = signature.v3Digest; - final byte[] pkcs7Signature = signature.pkcs7SignatureBlock; + final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray( + signature.hashingInfo); + final V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray( + signature.signingInfo); - if (rootHash.length != INCFS_MAX_HASH_SIZE) { - throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes"); + if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) { + throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm); + } + if (hashingInfo.log2BlockSize != V4Signature.LOG2_BLOCK_SIZE_4096_BYTES) { + throw new IOException("Unsupported log2BlockSize: " + hashingInfo.log2BlockSize); } - if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) { + if (hashingInfo.salt != null && hashingInfo.salt.length > 0) { + throw new IOException("Unsupported salt: " + hashingInfo.salt); + } + if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) { + throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes"); + } + if (signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) { throw new IOException( "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes"); } - - IncrementalSignature result = new IncrementalSignature(); - result.hashAlgorithm = INCFS_HASH_SHA256; - result.rootHash = rootHash; - result.additionalData = additionalData; - result.signature = pkcs7Signature; - - return result; } /** diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 6d334f539fc9..71f931da1a92 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -20,9 +20,12 @@ import android.os.ParcelFileDescriptor; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * V4 signature fields. @@ -31,30 +34,95 @@ import java.io.IOException; */ public class V4Signature { public static final String EXT = ".idsig"; - public static final int SUPPORTED_VERSION = 1; + public static final int SUPPORTED_VERSION = 2; - public final int version; - public final byte[] verityRootHash; - public final byte[] v3Digest; - public final byte[] pkcs7SignatureBlock; + public static final int HASHING_ALGORITHM_SHA256 = 1; + public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12; + + /** + * IncFS hashing data. + */ + public static class HashingInfo { + public final int hashAlgorithm; // only 1 == SHA256 supported + public final byte log2BlockSize; // only 12 (block size 4096) supported now + public final byte[] salt; // used exactly as in fs-verity, 32 bytes max + public final byte[] rawRootHash; // salted digest of the first Merkle tree page + + HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) { + this.hashAlgorithm = hashAlgorithm; + this.log2BlockSize = log2BlockSize; + this.salt = salt; + this.rawRootHash = rawRootHash; + } + + /** + * Constructs HashingInfo from byte array. + */ + public static HashingInfo fromByteArray(byte[] bytes) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); + final int hashAlgorithm = buffer.getInt(); + final byte log2BlockSize = buffer.get(); + byte[] salt = readBytes(buffer); + byte[] rawRootHash = readBytes(buffer); + return new HashingInfo(hashAlgorithm, log2BlockSize, salt, rawRootHash); + } + } + + /** + * V4 signature data. + */ + public static class SigningInfo { + public final byte[] v3Digest; // used to match with the corresponding APK + public final byte[] certificate; // ASN.1 DER form + public final byte[] additionalData; // a free-form binary data blob + public final byte[] publicKey; // ASN.1 DER, must match the certificate + public final int signatureAlgorithmId; // see the APK v2 doc for the list + public final byte[] signature; + + SigningInfo(byte[] v3Digest, byte[] certificate, byte[] additionalData, + byte[] publicKey, int signatureAlgorithmId, byte[] signature) { + this.v3Digest = v3Digest; + this.certificate = certificate; + this.additionalData = additionalData; + this.publicKey = publicKey; + this.signatureAlgorithmId = signatureAlgorithmId; + this.signature = signature; + } + + /** + * Constructs SigningInfo from byte array. + */ + public static SigningInfo fromByteArray(byte[] bytes) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); + byte[] v3Digest = readBytes(buffer); + byte[] certificate = readBytes(buffer); + byte[] additionalData = readBytes(buffer); + byte[] publicKey = readBytes(buffer); + int signatureAlgorithmId = buffer.getInt(); + byte[] signature = readBytes(buffer); + return new SigningInfo(v3Digest, certificate, additionalData, publicKey, + signatureAlgorithmId, signature); + } + } + + public final int version; // Always 2 for now. + public final byte[] hashingInfo; + public final byte[] signingInfo; // Passed as-is to the kernel. Can be retrieved later. /** * Construct a V4Signature from .idsig file. */ public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException { - final ParcelFileDescriptor dupedFd = pfd.dup(); - final ParcelFileDescriptor.AutoCloseInputStream fdInputStream = - new ParcelFileDescriptor.AutoCloseInputStream(dupedFd); - try (DataInputStream stream = new DataInputStream(fdInputStream)) { + try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd.dup())) { return readFrom(stream); } } /** - * Construct a V4Signature from .idsig file. + * Construct a V4Signature from a byte array. */ public static V4Signature readFrom(byte[] bytes) throws IOException { - try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) { + try (InputStream stream = new ByteArrayInputStream(bytes)) { return readFrom(stream); } } @@ -63,51 +131,131 @@ public class V4Signature { * Store the V4Signature to a byte-array. */ public byte[] toByteArray() { - try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { - try (DataOutputStream steam = new DataOutputStream(byteArrayOutputStream)) { - this.writeTo(steam); - steam.flush(); - } - return byteArrayOutputStream.toByteArray(); + try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + this.writeTo(stream); + return stream.toByteArray(); } catch (IOException e) { return null; } } - boolean isVersionSupported() { - return this.version == SUPPORTED_VERSION; + /** + * Combines necessary data to a signed data blob. + * The blob can be validated against signingInfo.signature. + * + * @param fileSize - size of the signed file (APK) + */ + public static byte[] getSigningData(long fileSize, HashingInfo hashingInfo, + SigningInfo signingInfo) { + final int size = + 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize( + hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize( + signingInfo.v3Digest) + bytesSize(signingInfo.certificate) + bytesSize( + signingInfo.additionalData); + ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(size); + buffer.putLong(fileSize); + buffer.putInt(hashingInfo.hashAlgorithm); + buffer.put(hashingInfo.log2BlockSize); + writeBytes(buffer, hashingInfo.salt); + writeBytes(buffer, hashingInfo.rawRootHash); + writeBytes(buffer, signingInfo.v3Digest); + writeBytes(buffer, signingInfo.certificate); + writeBytes(buffer, signingInfo.additionalData); + return buffer.array(); } - static V4Signature readFrom(DataInputStream stream) throws IOException { - final int version = stream.readInt(); - byte[] verityRootHash = readBytes(stream); - byte[] v3Digest = readBytes(stream); - byte[] pkcs7SignatureBlock = readBytes(stream); - return new V4Signature(version, verityRootHash, v3Digest, pkcs7SignatureBlock); + public boolean isVersionSupported() { + return this.version == SUPPORTED_VERSION; } - V4Signature(int version, byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) { + private V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) { this.version = version; - this.verityRootHash = verityRootHash; - this.v3Digest = v3Digest; - this.pkcs7SignatureBlock = pkcs7SignatureBlock; + this.hashingInfo = hashingInfo; + this.signingInfo = signingInfo; } - void writeTo(DataOutputStream stream) throws IOException { - stream.writeInt(this.version); - writeBytes(stream, this.verityRootHash); - writeBytes(stream, this.v3Digest); - writeBytes(stream, this.pkcs7SignatureBlock); + private static V4Signature readFrom(InputStream stream) throws IOException { + final int version = readIntLE(stream); + final byte[] hashingInfo = readBytes(stream); + final byte[] signingInfo = readBytes(stream); + return new V4Signature(version, hashingInfo, signingInfo); } - private static byte[] readBytes(DataInputStream stream) throws IOException { - byte[] result = new byte[stream.readInt()]; - stream.read(result); - return result; + private void writeTo(OutputStream stream) throws IOException { + writeIntLE(stream, this.version); + writeBytes(stream, this.hashingInfo); + writeBytes(stream, this.signingInfo); } - private static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException { - stream.writeInt(bytes.length); + // Utility methods. + private static int bytesSize(byte[] bytes) { + return 4/*length*/ + (bytes == null ? 0 : bytes.length); + } + + private static void readFully(InputStream stream, byte[] buffer) throws IOException { + int len = buffer.length; + int n = 0; + while (n < len) { + int count = stream.read(buffer, n, len - n); + if (count < 0) { + throw new EOFException(); + } + n += count; + } + } + + private static int readIntLE(InputStream stream) throws IOException { + final byte[] buffer = new byte[4]; + readFully(stream, buffer); + return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt(); + } + + private static void writeIntLE(OutputStream stream, int v) throws IOException { + final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt( + v).array(); + stream.write(buffer); + } + + private static byte[] readBytes(InputStream stream) throws IOException { + try { + final int size = readIntLE(stream); + final byte[] bytes = new byte[size]; + readFully(stream, bytes); + return bytes; + } catch (EOFException ignored) { + return null; + } + } + + private static byte[] readBytes(ByteBuffer buffer) throws IOException { + if (buffer.remaining() < 4) { + throw new EOFException(); + } + final int size = buffer.getInt(); + if (buffer.remaining() < size) { + throw new EOFException(); + } + final byte[] bytes = new byte[size]; + buffer.get(bytes); + return bytes; + } + + private static void writeBytes(OutputStream stream, byte[] bytes) throws IOException { + if (bytes == null) { + writeIntLE(stream, 0); + return; + } + writeIntLE(stream, bytes.length); stream.write(bytes); } + + private static void writeBytes(ByteBuffer buffer, byte[] bytes) { + if (bytes == null) { + buffer.putInt(0); + return; + } + buffer.putInt(bytes.length); + buffer.put(bytes); + } } diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 0483514e6297..f01139542541 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -42,6 +42,6 @@ oneway interface IPermissionController { void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName, String permission, int grantState, in AndroidFuture callback); void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback); - void updateUserSensitive(in AndroidFuture callback); void notifyOneTimePermissionSessionTimeout(String packageName); + void updateUserSensitiveForApp(int uid, in AndroidFuture callback); } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 2615c98a6d5e..09df72c286d0 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -106,4 +106,12 @@ interface IPermissionManager { int importanceToResetTimer, int importanceToKeepSessionAlive); void stopOneTimePermissionSession(String packageName, int userId); + + List<String> getAutoRevokeExemptionRequestedPackages(int userId); + + List<String> getAutoRevokeExemptionGrantedPackages(int userId); + + boolean setAutoRevokeWhitelisted(String packageName, boolean whitelisted, int userId); + + boolean isAutoRevokeWhitelisted(String packageName, int userId); } diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 2a1857fd0027..f08e3d25632b 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -46,6 +46,7 @@ import android.content.pm.ResolveInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.Process; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; @@ -626,14 +627,26 @@ public final class PermissionControllerManager { } /** - * @see PermissionControllerService#onUpdateUserSensitive() + * @see PermissionControllerManager#updateUserSensitiveForApp * @hide */ public void updateUserSensitive() { + updateUserSensitiveForApp(Process.INVALID_UID); + } + + /** + * @see PermissionControllerService#onUpdateUserSensitiveForApp + * @hide + */ + public void updateUserSensitiveForApp(int uid) { mRemoteService.postAsync(service -> { AndroidFuture<Void> future = new AndroidFuture<>(); - service.updateUserSensitive(future); + service.updateUserSensitiveForApp(uid, future); return future; + }).whenComplete((res, err) -> { + if (err != null) { + Log.e(TAG, "Error updating user_sensitive flags for uid " + uid, err); + } }); } diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 263b2c7a4ac7..4a42230ad15a 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -218,11 +218,14 @@ public abstract class PermissionControllerService extends Service { * Called by system to update the * {@link PackageManager}{@code .FLAG_PERMISSION_USER_SENSITIVE_WHEN_*} flags for permissions. * <p> - * This is typically when creating a new user or upgrading either system or - * permission controller package. + * + * If uid is -1, updates the permission flags for all packages. + * + * Typically called by the system when a new app is installed or updated or when creating a + * new user or upgrading either system or permission controller package. */ @BinderThread - public void onUpdateUserSensitivePermissionFlags() { + public void onUpdateUserSensitivePermissionFlags(int uid, @NonNull Runnable callback) { throw new AbstractMethodError("Must be overridden in implementing class"); } @@ -459,11 +462,14 @@ public abstract class PermissionControllerService extends Service { } @Override - public void updateUserSensitive(AndroidFuture callback) { + public void updateUserSensitiveForApp(int uid, @NonNull AndroidFuture callback) { Preconditions.checkNotNull(callback, "callback cannot be null"); - onUpdateUserSensitivePermissionFlags(); - callback.complete(null); + try { + onUpdateUserSensitivePermissionFlags(uid, () -> callback.complete(null)); + } catch (Exception e) { + callback.completeExceptionally(e); + } } @Override diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 0bd211d70e89..8308bb39d8c5 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -40,11 +40,13 @@ import android.os.UserHandle; import android.util.Slog; import com.android.internal.annotations.Immutable; +import com.android.internal.util.CollectionUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -299,6 +301,46 @@ public final class PermissionManager { } } + /** + * Gets the list of packages that have permissions that specified + * {@code requestDontAutoRevokePermissions=true} in their + * {@code application} manifest declaration. + * + * @return the list of packages for current user + * @hide + */ + @SystemApi + @NonNull + @RequiresPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) + public Set<String> getAutoRevokeExemptionRequestedPackages() { + try { + return CollectionUtils.toSet(mPermissionManager.getAutoRevokeExemptionRequestedPackages( + mContext.getUser().getIdentifier())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the list of packages that have permissions that specified + * {@code allowDontAutoRevokePermissions=true} in their + * {@code application} manifest declaration. + * + * @return the list of packages for current user + * @hide + */ + @SystemApi + @NonNull + @RequiresPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) + public Set<String> getAutoRevokeExemptionGrantedPackages() { + try { + return CollectionUtils.toSet(mPermissionManager.getAutoRevokeExemptionGrantedPackages( + mContext.getUser().getIdentifier())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private List<SplitPermissionInfo> splitPermissionInfoListToNonParcelableList( List<SplitPermissionInfoParcelable> parcelableList) { final int size = parcelableList.size(); diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index aa511cc46de9..fb81d675939c 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -399,6 +399,13 @@ public final class DeviceConfig { public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER = "connectivity_thermal_power_manager"; + /** + * Namespace for configuration related features. + * + * @hide + */ + public static final String NAMESPACE_CONFIGURATION = "configuration"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 03b38ab9b3ee..e7b360da47b8 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -3551,7 +3551,6 @@ public final class Telephony { * can manage DPC-owned APNs. * @hide */ - @SystemApi public static final @NonNull Uri DPC_URI = Uri.parse("content://telephony/carriers/dpc"); /** @@ -3864,7 +3863,6 @@ public final class Telephony { * Integer value denoting an invalid APN id * @hide */ - @SystemApi public static final int INVALID_APN_ID = -1; /** diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 886b433d4ade..08aa534be152 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -232,22 +232,6 @@ public final class Dataset implements Parcelable { * Creates a new builder. * * @param presentation The presentation used to visualize this dataset. - * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset - * as inline suggestions. If the dataset supports inline suggestions, - * this should not be null. - */ - public Builder(@NonNull RemoteViews presentation, - @NonNull InlinePresentation inlinePresentation) { - Preconditions.checkNotNull(presentation, "presentation must be non-null"); - Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); - mPresentation = presentation; - mInlinePresentation = inlinePresentation; - } - - /** - * Creates a new builder. - * - * @param presentation The presentation used to visualize this dataset. */ public Builder(@NonNull RemoteViews presentation) { Preconditions.checkNotNull(presentation, "presentation must be non-null"); @@ -282,6 +266,22 @@ public final class Dataset implements Parcelable { } /** + * Sets the {@link InlinePresentation} used to visualize this dataset as inline suggestions. + * If the dataset supports inline suggestions this should not be null. + * + * @throws IllegalStateException if {@link #build()} was already called. + * + * @return this builder. + */ + public @NonNull Builder setInlinePresentation( + @NonNull InlinePresentation inlinePresentation) { + throwIfDestroyed(); + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); + mInlinePresentation = inlinePresentation; + return this; + } + + /** * Triggers a custom UI before before autofilling the screen with the contents of this * dataset. * @@ -600,7 +600,7 @@ public final class Dataset implements Parcelable { */ @SystemApi @TestApi - public @NonNull Builder setInlinePresentation(@NonNull AutofillId id, + public @NonNull Builder setFieldInlinePresentation(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull InlinePresentation inlinePresentation) { throwIfDestroyed(); @@ -700,7 +700,7 @@ public final class Dataset implements Parcelable { final Builder builder = presentation != null ? inlinePresentation == null ? new Builder(presentation) - : new Builder(presentation, inlinePresentation) + : new Builder(presentation).setInlinePresentation(inlinePresentation) : inlinePresentation == null ? new Builder() : new Builder(inlinePresentation); diff --git a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl index 101165176293..1bcc76bfca44 100644 --- a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl +++ b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl @@ -25,7 +25,8 @@ import android.view.SurfaceControlViewHost; * @hide */ oneway interface IInlineSuggestionUiCallback { - void onAutofill(); + void onClick(); + void onLongClick(); void onContent(in SurfaceControlViewHost.SurfacePackage surface); void onError(); void onTransferTouchFocusToImeWindow(in IBinder sourceInputToken, int displayId); diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java index ee15283715ff..b6cc62dc213e 100644 --- a/core/java/android/service/autofill/InlineSuggestionRenderService.java +++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java @@ -97,12 +97,21 @@ public abstract class InlineSuggestionRenderService extends Service { host.addView(suggestionRoot, lp); suggestionRoot.setOnClickListener((v) -> { try { - callback.onAutofill(); + callback.onClick(); } catch (RemoteException e) { - Log.w(TAG, "RemoteException calling onAutofill()"); + Log.w(TAG, "RemoteException calling onClick()"); } }); + suggestionRoot.setOnLongClickListener((v) -> { + try { + callback.onLongClick(); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException calling onLongClick()"); + } + return true; + }); + sendResult(callback, host.getSurfacePackage()); } finally { updateDisplay(Display.DEFAULT_DISPLAY); diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index ed27dd568823..5b08ae20f071 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -488,7 +488,8 @@ public abstract class AugmentedAutofillService extends Service { ids.add(pair.first); values.add(pair.second); } - mClient.autofill(mSessionId, ids, values); + final boolean hideHighlight = size == 1 && ids.get(0).equals(mFocusedId); + mClient.autofill(mSessionId, ids, values, hideHighlight); } public void setFillWindow(@NonNull FillWindow fillWindow) { diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index abd04cc2b0e7..79eb9f6e749c 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -16,6 +16,8 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256; +import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; @@ -211,6 +213,12 @@ public class ApkSignatureSchemeV3Verifier { verityDigest, apk.length(), signatureInfo); } + if (contentDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) { + result.digest = contentDigests.get(CONTENT_DIGEST_CHUNKED_SHA512); + } else if (contentDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) { + result.digest = contentDigests.get(CONTENT_DIGEST_CHUNKED_SHA256); + } + return result; } @@ -568,6 +576,7 @@ public class ApkSignatureSchemeV3Verifier { public final VerifiedProofOfRotation por; public byte[] verityRootHash; + public byte[] digest; public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) { this.certs = certs; diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java index b6b8089b1743..8c240d99f590 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java @@ -16,13 +16,32 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; + import android.os.incremental.IncrementalManager; +import android.os.incremental.V4Signature; +import android.util.Pair; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; import java.security.cert.Certificate; - -import sun.security.pkcs.PKCS7; -import sun.security.pkcs.ParsingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; /** * APK Signature Scheme v4 verifier. @@ -30,24 +49,118 @@ import sun.security.pkcs.ParsingException; * @hide for internal use only. */ public class ApkSignatureSchemeV4Verifier { - /** - * Extracts APK Signature Scheme v4 signatures of the provided APK and returns the certificates - * associated with each signer. + * Extracts and verifies APK Signature Scheme v4 signatures of the provided APK and returns the + * certificates associated with each signer. */ - public static Certificate[] extractCertificates(String apkFile) + public static VerifiedSigner extractCertificates(String apkFile) throws SignatureNotFoundException, SecurityException { - final byte[] rawSignature = IncrementalManager.unsafeGetFileSignature( - new File(apkFile).getAbsolutePath()); - if (rawSignature == null || rawSignature.length == 0) { - throw new SignatureNotFoundException("Failed to obtain raw signature from IncFS."); + final File apk = new File(apkFile); + final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature( + apk.getAbsolutePath()); + if (signatureBytes == null || signatureBytes.length == 0) { + throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS."); + } + + final V4Signature signature; + final V4Signature.HashingInfo hashingInfo; + final V4Signature.SigningInfo signingInfo; + try { + signature = V4Signature.readFrom(signatureBytes); + + if (!signature.isVersionSupported()) { + throw new SecurityException( + "v4 signature version " + signature.version + " is not supported"); + } + + hashingInfo = V4Signature.HashingInfo.fromByteArray(signature.hashingInfo); + signingInfo = V4Signature.SigningInfo.fromByteArray(signature.signingInfo); + } catch (IOException e) { + throw new SignatureNotFoundException("Failed to read V4 signature.", e); } + final byte[] signedData = V4Signature.getSigningData(apk.length(), hashingInfo, + signingInfo); + + return verifySigner(signingInfo, signedData); + } + + private static VerifiedSigner verifySigner(V4Signature.SigningInfo signingInfo, + final byte[] signedData) throws SecurityException { + if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) { + throw new SecurityException("No supported signatures found"); + } + + final int signatureAlgorithmId = signingInfo.signatureAlgorithmId; + final byte[] signatureBytes = signingInfo.signature; + final byte[] publicKeyBytes = signingInfo.publicKey; + final byte[] encodedCert = signingInfo.certificate; + + String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(signatureAlgorithmId); + Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams = + getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithmId); + String jcaSignatureAlgorithm = signatureAlgorithmParams.first; + AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; + boolean sigVerified; try { - PKCS7 pkcs7 = new PKCS7(rawSignature); - return pkcs7.getCertificates(); - } catch (ParsingException e) { - throw new SecurityException("Failed to parse signature and extract certificates", e); + PublicKey publicKey = + KeyFactory.getInstance(keyAlgorithm) + .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); + Signature sig = Signature.getInstance(jcaSignatureAlgorithm); + sig.initVerify(publicKey); + if (jcaSignatureAlgorithmParams != null) { + sig.setParameter(jcaSignatureAlgorithmParams); + } + sig.update(signedData); + sigVerified = sig.verify(signatureBytes); + } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException + | InvalidAlgorithmParameterException | SignatureException e) { + throw new SecurityException( + "Failed to verify " + jcaSignatureAlgorithm + " signature", e); } + if (!sigVerified) { + throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); + } + + // Signature over signedData has verified. + CertificateFactory certFactory; + try { + certFactory = CertificateFactory.getInstance("X.509"); + } catch (CertificateException e) { + throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); + } + + X509Certificate certificate; + try { + certificate = (X509Certificate) + certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); + } catch (CertificateException e) { + throw new SecurityException("Failed to decode certificate", e); + } + certificate = new VerbatimX509Certificate(certificate, encodedCert); + + byte[] certificatePublicKeyBytes = certificate.getPublicKey().getEncoded(); + if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { + throw new SecurityException( + "Public key mismatch between certificate and signature record"); + } + + return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.v3Digest); + } + + /** + * Verified APK Signature Scheme v4 signer, including V3 digest. + * + * @hide for internal use only. + */ + public static class VerifiedSigner { + public final Certificate[] certs; + public byte[] v3Digest; + + public VerifiedSigner(Certificate[] certs, byte[] v3Digest) { + this.certs = certs; + this.v3Digest = v3Digest; + } + } } diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index f325c2171c23..c1cee48cc663 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -168,7 +168,7 @@ public class ApkSignatureVerifier { /** * Verifies the provided APK using V4 schema. * - * @param verifyFull whether to verify all contents of this APK or just collect certificates. + * @param verifyFull whether to verify (V4 vs V3) or just collect certificates. * @return the certificates associated with each signer. * @throws SignatureNotFoundException if there are no V4 signatures in the APK * @throws PackageParserException if there was a problem collecting certificates @@ -178,30 +178,34 @@ public class ApkSignatureVerifier { throws SignatureNotFoundException, PackageParserException { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4"); try { - Certificate[] certs = ApkSignatureSchemeV4Verifier.extractCertificates(apkPath); - Certificate[][] signerCerts = new Certificate[][]{certs}; + ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner = + ApkSignatureSchemeV4Verifier.extractCertificates(apkPath); + Certificate[][] signerCerts = new Certificate[][]{vSigner.certs}; Signature[] signerSigs = convertToSignatures(signerCerts); if (verifyFull) { - // v4 is an add-on and requires v2/v3 signature to validate against its certificates - final PackageParser.SigningDetails nonstreaming = verifyV3AndBelowSignatures( - apkPath, minSignatureSchemeVersion, false); - if (nonstreaming.signatureSchemeVersion <= SignatureSchemeVersion.JAR) { + // v4 is an add-on and requires v3 signature to validate against its certificates + ApkSignatureSchemeV3Verifier.VerifiedSigner nonstreaming = + ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); + Certificate[][] nonstreamingCerts = new Certificate[][]{nonstreaming.certs}; + Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); + + if (nonstreamingSigs.length != signerSigs.length) { throw new SecurityException( - "V4 signing block can only be verified along with V2 and above."); - } - if (nonstreaming.signatures.length == 0 - || nonstreaming.signatures.length != signerSigs.length) { - throw new SecurityException("Invalid number of signatures in " - + nonstreaming.signatureSchemeVersion); + "Invalid number of certificates: " + nonstreaming.certs.length); } for (int i = 0, size = signerSigs.length; i < size; ++i) { - if (!nonstreaming.signatures[i].equals(signerSigs[i])) { - throw new SecurityException("V4 signature certificate does not match " - + nonstreaming.signatureSchemeVersion); + if (!nonstreamingSigs[i].equals(signerSigs[i])) { + throw new SecurityException("V4 signature certificate does not match V3"); } } + + // TODO(b/151240006): add support for v2 digest and make it mandatory. + if (!ArrayUtils.isEmpty(vSigner.v3Digest) && !ArrayUtils.equals(vSigner.v3Digest, + nonstreaming.digest, vSigner.v3Digest.length)) { + throw new SecurityException("V3 digest in V4 signature does not match V3"); + } } return new PackageParser.SigningDetails(signerSigs, diff --git a/core/java/android/util/apk/SourceStampVerifier.java b/core/java/android/util/apk/SourceStampVerifier.java index 759c8649532b..c78baf5b7c53 100644 --- a/core/java/android/util/apk/SourceStampVerifier.java +++ b/core/java/android/util/apk/SourceStampVerifier.java @@ -43,6 +43,7 @@ import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -78,29 +79,56 @@ public abstract class SourceStampVerifier { /** Hidden constructor to prevent instantiation. */ private SourceStampVerifier() {} + /** Verifies SourceStamp present in a list of APKs. */ + public static SourceStampVerificationResult verify(List<String> apkFiles) { + Certificate stampCertificate = null; + for (String apkFile : apkFiles) { + SourceStampVerificationResult sourceStampVerificationResult = verify(apkFile); + if (!sourceStampVerificationResult.isPresent() + || !sourceStampVerificationResult.isVerified()) { + return sourceStampVerificationResult; + } + if (stampCertificate != null + && !stampCertificate.equals(sourceStampVerificationResult.getCertificate())) { + return SourceStampVerificationResult.notVerified(); + } + stampCertificate = sourceStampVerificationResult.getCertificate(); + } + return SourceStampVerificationResult.verified(stampCertificate); + } + /** Verifies SourceStamp present in the provided APK. */ public static SourceStampVerificationResult verify(String apkFile) { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { return verify(apk); - } catch (Exception e) { - // Any exception in the SourceStamp verification returns a non-verified SourceStamp - // outcome without affecting the outcome of any of the other signature schemes. - return SourceStampVerificationResult.notVerified(); + } catch (IOException e) { + // Any exception in reading the APK returns a non-present SourceStamp outcome + // without affecting the outcome of any of the other signature schemes. + return SourceStampVerificationResult.notPresent(); } } - private static SourceStampVerificationResult verify(RandomAccessFile apk) - throws IOException, SignatureNotFoundException { - byte[] sourceStampCertificateDigest = getSourceStampCertificateDigest(apk); - if (sourceStampCertificateDigest == null) { - // SourceStamp certificate hash file not found, which means that there is not - // SourceStamp present. + private static SourceStampVerificationResult verify(RandomAccessFile apk) { + byte[] sourceStampCertificateDigest; + try { + sourceStampCertificateDigest = getSourceStampCertificateDigest(apk); + if (sourceStampCertificateDigest == null) { + // SourceStamp certificate hash file not found, which means that there is not + // SourceStamp present. + return SourceStampVerificationResult.notPresent(); + } + } catch (IOException e) { return SourceStampVerificationResult.notPresent(); } - SignatureInfo signatureInfo = - ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID); - Map<Integer, byte[]> apkContentDigests = getApkContentDigests(apk); - return verify(signatureInfo, apkContentDigests, sourceStampCertificateDigest); + + try { + SignatureInfo signatureInfo = + ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID); + Map<Integer, byte[]> apkContentDigests = getApkContentDigests(apk); + return verify(signatureInfo, apkContentDigests, sourceStampCertificateDigest); + } catch (IOException | SignatureNotFoundException e) { + return SourceStampVerificationResult.notVerified(); + } } private static SourceStampVerificationResult verify( diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 74fac2b40472..6784cf7407fa 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -165,10 +165,16 @@ public final class ImeFocusController { if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) { return; } - if (mServedView == view || !view.hasImeFocus() || !view.hasWindowFocus()) { + if (!view.hasImeFocus() || !view.hasWindowFocus()) { return; } - mNextServedView = hasFocus ? view : null; + if (DEBUG) Log.d(TAG, "onViewFocusChanged, view=" + view + ", mServedView=" + mServedView); + + if (hasFocus) { + mNextServedView = view; + } else if (view == mServedView) { + mNextServedView = null; + } mViewRootImpl.dispatchCheckFocus(); } diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index a15d6c79417c..4227f78564a7 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -56,4 +56,10 @@ public interface InsetsAnimationControlCallbacks { * apply. */ void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params); + + /** + * Post a message to release the Surface, guaranteed to happen after all + * previous calls to applySurfaceParams. + */ + void releaseSurfaceControlFromRt(SurfaceControl sc); } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 2b30c2dd658e..baee4123ef47 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -180,10 +180,19 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mAnimation.setAlpha(mPendingAlpha); if (mFinished) { mController.notifyFinished(this, mShownOnFinish); + releaseLeashes(); } return mFinished; } + private void releaseLeashes() { + for (int i = mControls.size() - 1; i >= 0; i--) { + final InsetsSourceControl c = mControls.valueAt(i); + if (c == null) continue; + c.release(mController::releaseSurfaceControlFromRt); + } + } + @Override public void finish(boolean shown) { if (mCancelled || mFinished) { @@ -191,6 +200,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll } setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */); mFinished = true; + mShownOnFinish = shown; } @@ -207,6 +217,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll } mCancelled = true; mListener.onCancelled(); + + releaseLeashes(); } public boolean isCancelled() { diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 9c27802293b8..13b4cd83b4df 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -75,6 +75,12 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro t.apply(); t.close(); } + + @Override + public void releaseSurfaceControlFromRt(SurfaceControl sc) { + // Since we don't push the SurfaceParams to the RT we can release directly + sc.release(); + } }; @UiThread diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index c1763d62d829..88e7f2ed9cf1 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -704,7 +704,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } final InsetsSourceControl control = consumer.getControl(); if (control != null) { - controls.put(consumer.getType(), control); + controls.put(consumer.getType(), new InsetsSourceControl(control)); typesReady |= toPublicType(consumer.getType()); } else if (animationType == ANIMATION_TYPE_SHOW) { diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 8ec5df85dc7b..18e0132e2c4e 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -35,6 +35,7 @@ import android.widget.RemoteViews; import com.android.internal.R; import com.android.internal.widget.CachingIconView; +import com.android.internal.widget.NotificationExpandButton; import java.util.ArrayList; @@ -56,7 +57,7 @@ public class NotificationHeaderView extends ViewGroup { private OnClickListener mAppOpsListener; private HeaderTouchListener mTouchListener = new HeaderTouchListener(); private LinearLayout mTransferChip; - private ImageView mExpandButton; + private NotificationExpandButton mExpandButton; private CachingIconView mIcon; private View mProfileBadge; private View mOverlayIcon; @@ -65,7 +66,6 @@ public class NotificationHeaderView extends ViewGroup { private View mAppOps; private View mAudiblyAlertedIcon; private int mIconColor; - private int mOriginalNotificationColor; private boolean mExpanded; private boolean mShowExpandButtonAtEnd; private boolean mShowWorkBadgeAtEnd; @@ -324,13 +324,8 @@ public class NotificationHeaderView extends ViewGroup { return mIconColor; } - @RemotableViewMethod - public void setOriginalNotificationColor(int color) { - mOriginalNotificationColor = color; - } - public int getOriginalNotificationColor() { - return mOriginalNotificationColor; + return mExpandButton.getOriginalNotificationColor(); } @RemotableViewMethod @@ -371,7 +366,7 @@ public class NotificationHeaderView extends ViewGroup { contentDescriptionId = R.string.expand_button_content_description_collapsed; } mExpandButton.setImageDrawable(getContext().getDrawable(drawableId)); - mExpandButton.setColorFilter(mOriginalNotificationColor); + mExpandButton.setColorFilter(getOriginalNotificationColor()); mExpandButton.setContentDescription(mContext.getText(contentDescriptionId)); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 879f2840a1b3..708a09467247 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3318,7 +3318,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Flag indicating that the view is autofilled * * @see #isAutofilled() - * @see #setAutofilled(boolean) + * @see #setAutofilled(boolean, boolean) */ private static final int PFLAG3_IS_AUTOFILLED = 0x10000; @@ -3428,6 +3428,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE * 11 PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK * 1 PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS + * 1 PFLAG4_AUTOFILL_HIDE_HIGHLIGHT * |-------|-------|-------|-------| */ @@ -3470,6 +3471,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS = 0x000000100; + /** + * Flag indicating the field should not have yellow highlight when autofilled. + */ + private static final int PFLAG4_AUTOFILL_HIDE_HIGHLIGHT = 0x100; + /* End of masks for mPrivateFlags4 */ /** @hide */ @@ -9170,6 +9176,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @hide + */ + public boolean hideAutofillHighlight() { + return (mPrivateFlags4 & PFLAG4_AUTOFILL_HIDE_HIGHLIGHT) != 0; + } + + /** * Gets the {@link View}'s current autofill value. * * <p>By default returns {@code null}, but subclasses should override it and return an @@ -11750,7 +11763,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ @TestApi - public void setAutofilled(boolean isAutofilled) { + public void setAutofilled(boolean isAutofilled, boolean hideHighlight) { boolean wasChanged = isAutofilled != isAutofilled(); if (wasChanged) { @@ -11760,6 +11773,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags3 &= ~PFLAG3_IS_AUTOFILLED; } + if (hideHighlight) { + mPrivateFlags4 |= PFLAG4_AUTOFILL_HIDE_HIGHLIGHT; + } else { + mPrivateFlags4 &= ~PFLAG4_AUTOFILL_HIDE_HIGHLIGHT; + } + invalidate(); } } @@ -20578,6 +20597,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, state.mStartActivityRequestWhoSaved = mStartActivityRequestWho; state.mIsAutofilled = isAutofilled(); + state.mHideHighlight = hideAutofillHighlight(); state.mAutofillViewId = mAutofillViewId; return state; } @@ -20654,7 +20674,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mStartActivityRequestWho = baseState.mStartActivityRequestWhoSaved; } if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) { - setAutofilled(baseState.mIsAutofilled); + setAutofilled(baseState.mIsAutofilled, baseState.mHideHighlight); } if ((baseState.mSavedData & BaseSavedState.AUTOFILL_ID) != 0) { // It can happen that views have the same view id and the restoration path will not @@ -24087,12 +24107,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Draw {@link View#isAutofilled()} highlight over view if the view is autofilled. + * Draw {@link View#isAutofilled()} highlight over view if the view is autofilled, unless + * {@link #PFLAG4_AUTOFILL_HIDE_HIGHLIGHT} is enabled. * * @param canvas The canvas to draw on */ private void drawAutofilledHighlight(@NonNull Canvas canvas) { - if (isAutofilled()) { + if (isAutofilled() && !hideAutofillHighlight()) { Drawable autofilledHighlight = getAutofilledDrawable(); if (autofilledHighlight != null) { @@ -28535,6 +28556,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mSavedData; String mStartActivityRequestWhoSaved; boolean mIsAutofilled; + boolean mHideHighlight; int mAutofillViewId; /** @@ -28558,6 +28580,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mSavedData = source.readInt(); mStartActivityRequestWhoSaved = source.readString(); mIsAutofilled = source.readBoolean(); + mHideHighlight = source.readBoolean(); mAutofillViewId = source.readInt(); } @@ -28577,6 +28600,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, out.writeInt(mSavedData); out.writeString(mStartActivityRequestWhoSaved); out.writeBoolean(mIsAutofilled); + out.writeBoolean(mHideHighlight); out.writeInt(mAutofillViewId); } @@ -29075,8 +29099,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mTreeObserver = new ViewTreeObserver(context); } + @Nullable + ContentCaptureManager getContentCaptureManager(@NonNull Context context) { + if (mContentCaptureManager != null) { + return mContentCaptureManager; + } + mContentCaptureManager = context.getSystemService(ContentCaptureManager.class); + return mContentCaptureManager; + } + + void delayNotifyContentCaptureInsetsEvent(@NonNull Insets insets) { + if (mContentCaptureManager == null) { + return; + } + + ArrayList<Object> events = ensureEvents( + mContentCaptureManager.getMainContentCaptureSession()); + events.add(insets); + } + private void delayNotifyContentCaptureEvent(@NonNull ContentCaptureSession session, @NonNull View view, boolean appeared) { + ArrayList<Object> events = ensureEvents(session); + events.add(appeared ? view : view.getAutofillId()); + } + + @NonNull + private ArrayList<Object> ensureEvents(@NonNull ContentCaptureSession session) { if (mContentCaptureEvents == null) { // Most of the time there will be just one session, so intial capacity is 1 mContentCaptureEvents = new SparseArray<>(1); @@ -29088,16 +29137,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, events = new ArrayList<>(); mContentCaptureEvents.put(sessionId, events); } - events.add(appeared ? view : view.getAutofillId()); - } - @Nullable - ContentCaptureManager getContentCaptureManager(@NonNull Context context) { - if (mContentCaptureManager != null) { - return mContentCaptureManager; - } - mContentCaptureManager = context.getSystemService(ContentCaptureManager.class); - return mContentCaptureManager; + return events; } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 26ac4fc4ddc2..dd34bcb018b9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -81,6 +81,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.FrameInfo; import android.graphics.HardwareRenderer.FrameDrawingCallback; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; @@ -2254,6 +2255,7 @@ public final class ViewRootImpl implements ViewParent, insets = insets.consumeDisplayCutout(); } host.dispatchApplyWindowInsets(insets); + mAttachInfo.delayNotifyContentCaptureInsetsEvent(insets.getInsets(Type.all())); Trace.traceEnd(Trace.TRACE_TAG_VIEW); } @@ -3118,6 +3120,8 @@ public final class ViewRootImpl implements ViewParent, ViewStructure structure = session.newViewStructure(view); view.onProvideContentCaptureStructure(structure, /* flags= */ 0); session.notifyViewAppeared(structure); + } else if (event instanceof Insets) { + mainSession.notifyViewInsetsChanged(sessionId, (Insets) event); } else { Log.w(mTag, "invalid content capture event: " + event); } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index dda4e8b63a91..39a9ed4a82e7 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1236,7 +1236,7 @@ public final class AutofillManager { // If the session is gone some fields might still be highlighted, hence we have to // remove the isAutofilled property even if no sessions are active. if (mLastAutofilledData == null) { - view.setAutofilled(false); + view.setAutofilled(false, false); } else { id = view.getAutofillId(); if (mLastAutofilledData.containsKey(id)) { @@ -1244,13 +1244,13 @@ public final class AutofillManager { valueWasRead = true; if (Objects.equals(mLastAutofilledData.get(id), value)) { - view.setAutofilled(true); + view.setAutofilled(true, false); } else { - view.setAutofilled(false); + view.setAutofilled(false, false); mLastAutofilledData.remove(id); } } else { - view.setAutofilled(false); + view.setAutofilled(false, false); } } @@ -2166,7 +2166,8 @@ public final class AutofillManager { * @param view The view that is to be autofilled * @param targetValue The value we want to fill into view */ - private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) { + private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue, + boolean hideHighlight) { AutofillValue currentValue = view.getAutofillValue(); if (Objects.equals(currentValue, targetValue)) { synchronized (mLock) { @@ -2175,11 +2176,12 @@ public final class AutofillManager { } mLastAutofilledData.put(view.getAutofillId(), targetValue); } - view.setAutofilled(true); + view.setAutofilled(true, hideHighlight); } } - private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { + private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values, + boolean hideHighlight) { synchronized (mLock) { if (sessionId != mSessionId) { return; @@ -2238,7 +2240,7 @@ public final class AutofillManager { // synchronously. // If autofill happens async, the view is set to autofilled in // notifyValueChanged. - setAutofilledIfValuesIs(view, value); + setAutofilledIfValuesIs(view, value, hideHighlight); numApplied++; } @@ -3256,10 +3258,11 @@ public final class AutofillManager { } @Override - public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { + public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values, + boolean hideHighlight) { final AutofillManager afm = mAfm.get(); if (afm != null) { - afm.post(() -> afm.autofill(sessionId, ids, values)); + afm.post(() -> afm.autofill(sessionId, ids, values, hideHighlight)); } } @@ -3397,10 +3400,11 @@ public final class AutofillManager { } @Override - public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { + public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values, + boolean hideHighlight) { final AutofillManager afm = mAfm.get(); if (afm != null) { - afm.post(() -> afm.autofill(sessionId, ids, values)); + afm.post(() -> afm.autofill(sessionId, ids, values, hideHighlight)); } } diff --git a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl index 03054dfdc7ea..8526c1e443c8 100644 --- a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl +++ b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl @@ -38,7 +38,8 @@ interface IAugmentedAutofillManagerClient { /** * Autofills the activity with the contents of the values. */ - void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values); + void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values, + boolean hideHighlight); /** * Requests showing the fill UI. diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index 3903665f2cde..4371b3c54f94 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -44,7 +44,8 @@ oneway interface IAutoFillManagerClient { /** * Autofills the activity with the contents of a dataset. */ - void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values); + void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values, + boolean hideHighlight); /** * Authenticates a fill response or a data set. diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java index 7487ec4d921c..44b4353871a2 100644 --- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java @@ -17,6 +17,7 @@ package android.view.contentcapture; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Insets; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; @@ -84,6 +85,11 @@ final class ChildContentCaptureSession extends ContentCaptureSession { } @Override + void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) { + getMainCaptureSession().notifyViewInsetsChanged(mId, viewInsets); + } + + @Override public void internalNotifyViewTreeEvent(boolean started) { getMainCaptureSession().notifyViewTreeEvent(mId, started); } diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index c29d251e6535..ea34d948c91a 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.graphics.Insets; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -112,6 +113,11 @@ public final class ContentCaptureEvent implements Parcelable { */ public static final int TYPE_SESSION_PAUSED = 8; + /** + * Called when the view's insets are changed. The new insets associated with the + * event may then be retrieved by calling {@link #getInsets()} + */ + public static final int TYPE_VIEW_INSETS_CHANGED = 9; /** @hide */ @IntDef(prefix = { "TYPE_" }, value = { @@ -122,7 +128,8 @@ public final class ContentCaptureEvent implements Parcelable { TYPE_VIEW_TREE_APPEARED, TYPE_CONTEXT_UPDATED, TYPE_SESSION_PAUSED, - TYPE_SESSION_RESUMED + TYPE_SESSION_RESUMED, + TYPE_VIEW_INSETS_CHANGED }) @Retention(RetentionPolicy.SOURCE) public @interface EventType{} @@ -136,6 +143,7 @@ public final class ContentCaptureEvent implements Parcelable { private @Nullable CharSequence mText; private int mParentSessionId = NO_SESSION_ID; private @Nullable ContentCaptureContext mClientContext; + private @Nullable Insets mInsets; /** @hide */ public ContentCaptureEvent(int sessionId, int type, long eventTime) { @@ -242,6 +250,13 @@ public final class ContentCaptureEvent implements Parcelable { return this; } + /** @hide */ + @NonNull + public ContentCaptureEvent setInsets(@NonNull Insets insets) { + mInsets = insets; + return this; + } + /** * Gets the type of the event. * @@ -305,6 +320,16 @@ public final class ContentCaptureEvent implements Parcelable { } /** + * Gets the rectangle of the insets associated with the event. Valid insets will only be + * returned if the type of the event is {@link #TYPE_VIEW_INSETS_CHANGED}, otherwise they + * will be null. + */ + @Nullable + public Insets getInsets() { + return mInsets; + } + + /** * Merges event of the same type, either {@link #TYPE_VIEW_TEXT_CHANGED} * or {@link #TYPE_VIEW_DISAPPEARED}. * @@ -369,7 +394,9 @@ public final class ContentCaptureEvent implements Parcelable { } if (mClientContext != null) { pw.print(", context="); mClientContext.dump(pw); pw.println(); - + } + if (mInsets != null) { + pw.print(", insets="); pw.println(mInsets); } } @@ -401,6 +428,9 @@ public final class ContentCaptureEvent implements Parcelable { if (mClientContext != null) { string.append(", context=").append(mClientContext); } + if (mInsets != null) { + string.append(", insets=").append(mInsets); + } return string.append(']').toString(); } @@ -424,6 +454,9 @@ public final class ContentCaptureEvent implements Parcelable { if (mType == TYPE_SESSION_STARTED || mType == TYPE_CONTEXT_UPDATED) { parcel.writeParcelable(mClientContext, flags); } + if (mType == TYPE_VIEW_INSETS_CHANGED) { + parcel.writeParcelable(mInsets, flags); + } } public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureEvent> CREATOR = @@ -455,6 +488,9 @@ public final class ContentCaptureEvent implements Parcelable { if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) { event.setClientContext(parcel.readParcelable(null)); } + if (type == TYPE_VIEW_INSETS_CHANGED) { + event.setInsets(parcel.readParcelable(null)); + } return event; } @@ -488,6 +524,8 @@ public final class ContentCaptureEvent implements Parcelable { return "VIEW_TREE_APPEARED"; case TYPE_CONTEXT_UPDATED: return "CONTEXT_UPDATED"; + case TYPE_VIEW_INSETS_CHANGED: + return "VIEW_INSETS_CHANGED"; default: return "UKNOWN_TYPE: " + type; } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 2134dab7986b..012f5e6d3507 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -23,6 +23,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.graphics.Insets; import android.util.DebugUtils; import android.util.Log; import android.view.View; @@ -440,6 +441,19 @@ public abstract class ContentCaptureSession implements AutoCloseable { abstract void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text); + /** + * Notifies the Intelligence Service that the insets of a view have changed. + */ + public final void notifyViewInsetsChanged(@NonNull Insets viewInsets) { + Preconditions.checkNotNull(viewInsets); + + if (!isContentCaptureEnabled()) return; + + internalNotifyViewInsetsChanged(viewInsets); + } + + abstract void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets); + /** @hide */ public abstract void internalNotifyViewTreeEvent(boolean started); diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 96f224fef251..893d38dcfde7 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -22,6 +22,7 @@ import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUM import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_INSETS_CHANGED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING; @@ -36,6 +37,7 @@ import android.annotation.UiThread; import android.content.ComponentName; import android.content.Context; import android.content.pm.ParceledListSlice; +import android.graphics.Insets; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -578,6 +580,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } @Override + void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) { + notifyViewInsetsChanged(mId, viewInsets); + } + + @Override public void internalNotifyViewTreeEvent(boolean started) { notifyViewTreeEvent(mId, started); } @@ -642,6 +649,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } /** Public because is also used by ViewRootImpl */ + public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { + sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) + .setInsets(viewInsets)); + } + + /** Public because is also used by ViewRootImpl */ public void notifyViewTreeEvent(int sessionId, boolean started) { final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH); diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java index 650061396992..dd1738a5ff29 100644 --- a/core/java/android/view/inputmethod/InlineSuggestion.java +++ b/core/java/android/view/inputmethod/InlineSuggestion.java @@ -16,6 +16,7 @@ package android.view.inputmethod; +import android.annotation.BinderThread; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; @@ -94,19 +95,21 @@ public final class InlineSuggestion implements Parcelable { } - /** * Inflates a view with the content of this suggestion at a specific size. * The size must be between the {@link InlinePresentationSpec#getMinSize() min size} * and the {@link InlinePresentationSpec#getMaxSize() max size} of the presentation * spec returned by {@link InlineSuggestionInfo#getPresentationSpec()}. * - * @param context Context in which to inflate the view. - * @param size The size at which to inflate the suggestion. - * @param callback Callback for receiving the inflated view. + * <p> The caller can attach an {@link View.OnClickListener} and/or an + * {@link View.OnLongClickListener} to the view in the {@code callback} to receive click and + * long click events on the view. * + * @param context Context in which to inflate the view. + * @param size The size at which to inflate the suggestion. + * @param callback Callback for receiving the inflated view. * @throws IllegalArgumentException If an invalid argument is passed. - * @throws IllegalStateException if this method is already called. + * @throws IllegalStateException If this method is already called. */ public void inflate(@NonNull Context context, @NonNull Size size, @NonNull @CallbackExecutor Executor callbackExecutor, @@ -151,12 +154,31 @@ public final class InlineSuggestion implements Parcelable { } @Override + @BinderThread public void onContent(SurfaceControlViewHost.SurfacePackage content) { final InlineContentCallbackImpl callbackImpl = mCallbackImpl.get(); if (callbackImpl != null) { callbackImpl.onContent(content); } } + + @Override + @BinderThread + public void onClick() { + final InlineContentCallbackImpl callbackImpl = mCallbackImpl.get(); + if (callbackImpl != null) { + callbackImpl.onClick(); + } + } + + @Override + @BinderThread + public void onLongClick() { + final InlineContentCallbackImpl callbackImpl = mCallbackImpl.get(); + if (callbackImpl != null) { + callbackImpl.onLongClick(); + } + } } private static final class InlineContentCallbackImpl { @@ -164,6 +186,7 @@ public final class InlineSuggestion implements Parcelable { private final @NonNull Context mContext; private final @NonNull Executor mCallbackExecutor; private final @NonNull Consumer<View> mCallback; + private @Nullable View mView; InlineContentCallbackImpl(@NonNull Context context, @NonNull @CallbackExecutor Executor callbackExecutor, @@ -173,12 +196,27 @@ public final class InlineSuggestion implements Parcelable { mCallback = callback; } + @BinderThread public void onContent(SurfaceControlViewHost.SurfacePackage content) { if (content == null) { mCallbackExecutor.execute(() -> mCallback.accept(/* view */null)); } else { - mCallbackExecutor.execute( - () -> mCallback.accept(new InlineContentView(mContext, content))); + mView = new InlineContentView(mContext, content); + mCallbackExecutor.execute(() -> mCallback.accept(mView)); + } + } + + @BinderThread + public void onClick() { + if (mView != null && mView.hasOnClickListeners()) { + mView.callOnClick(); + } + } + + @BinderThread + public void onLongClick() { + if (mView != null && mView.hasOnLongClickListeners()) { + mView.performLongClick(); } } } @@ -201,7 +239,7 @@ public final class InlineSuggestion implements Parcelable { - // Code below generated by codegen v1.0.14. + // Code below generated by codegen v1.0.15. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -360,8 +398,8 @@ public final class InlineSuggestion implements Parcelable { }; @DataClass.Generated( - time = 1581929285156L, - codegenVersion = "1.0.14", + time = 1583889058241L, + codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java", inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") @Deprecated diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 6246b507ba59..842ba2975b3b 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -21,15 +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; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -317,13 +314,9 @@ public final class ConversationActions implements Parcelable { @NonNull @Hint private final List<String> mHints; - @Nullable - private String mCallingPackageName; - @UserIdInt - private int mUserId = UserHandle.USER_NULL; @NonNull private Bundle mExtras; - private boolean mUseDefaultTextClassifier; + @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; private Request( @NonNull List<Message> conversation, @@ -345,10 +338,8 @@ public final class ConversationActions implements Parcelable { int maxSuggestions = in.readInt(); List<String> hints = new ArrayList<>(); in.readStringList(hints); - String callingPackageName = in.readString(); - int userId = in.readInt(); Bundle extras = in.readBundle(); - boolean useDefaultTextClassifier = in.readBoolean(); + SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); Request request = new Request( conversation, @@ -356,9 +347,7 @@ public final class ConversationActions implements Parcelable { maxSuggestions, hints, extras); - request.setCallingPackageName(callingPackageName); - request.setUserId(userId); - request.setUseDefaultTextClassifier(useDefaultTextClassifier); + request.setSystemTextClassifierMetadata(systemTcMetadata); return request; } @@ -368,10 +357,8 @@ public final class ConversationActions implements Parcelable { parcel.writeParcelable(mTypeConfig, flags); parcel.writeInt(mMaxSuggestions); parcel.writeStringList(mHints); - parcel.writeString(mCallingPackageName); - parcel.writeInt(mUserId); parcel.writeBundle(mExtras); - parcel.writeBoolean(mUseDefaultTextClassifier); + parcel.writeParcelable(mSystemTcMetadata, flags); } @Override @@ -421,62 +408,31 @@ public final class ConversationActions implements Parcelable { } /** - * Sets the name of the package that is sending this request. - * <p> - * Package-private for SystemTextClassifier's use. - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public void setCallingPackageName(@Nullable String callingPackageName) { - mCallingPackageName = callingPackageName; - } - - /** * Returns the name of the package that sent this request. * This returns {@code null} if no calling package name is set. */ @Nullable public String getCallingPackageName() { - return mCallingPackageName; + return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; } /** - * Sets the id of the user that sent this request. - * <p> - * Package-private for SystemTextClassifier's use. - * @hide - */ - void setUserId(@UserIdInt int userId) { - mUserId = userId; - } - - /** - * Returns the id of the user that sent this request. - * @hide - */ - @UserIdInt - public int getUserId() { - return mUserId; - } - - /** - * Sets whether to use the default text classifier to handle this request. - * This will be ignored if it is not the system text classifier to handle this request. + * Sets the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { - mUseDefaultTextClassifier = useDefaultTextClassifier; + void setSystemTextClassifierMetadata(@Nullable SystemTextClassifierMetadata systemTcData) { + mSystemTcMetadata = systemTcData; } /** - * Returns whether to use the default text classifier to handle this request. This - * will be ignored if it is not the system text classifier to handle this request. + * Returns the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - public boolean getUseDefaultTextClassifier() { - return mUseDefaultTextClassifier; + @Nullable + public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { + return mSystemTcMetadata; } /** diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java index e0f29a981d89..9a5454472076 100644 --- a/core/java/android/view/textclassifier/SelectionEvent.java +++ b/core/java/android/view/textclassifier/SelectionEvent.java @@ -19,10 +19,8 @@ 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; @@ -129,7 +127,6 @@ 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; @@ -140,7 +137,7 @@ public final class SelectionEvent implements Parcelable { private int mEnd; private int mSmartStart; private int mSmartEnd; - private boolean mUseDefaultTextClassifier; + @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; SelectionEvent( int start, int end, @@ -161,6 +158,7 @@ public final class SelectionEvent implements Parcelable { mEventType = in.readInt(); mEntityType = in.readString(); mWidgetVersion = in.readInt() > 0 ? in.readString() : null; + // TODO: remove mPackageName once aiai does not need it mPackageName = in.readString(); mWidgetType = in.readString(); mInvocationMethod = in.readInt(); @@ -175,8 +173,7 @@ public final class SelectionEvent implements Parcelable { mEnd = in.readInt(); mSmartStart = in.readInt(); mSmartEnd = in.readInt(); - mUserId = in.readInt(); - mUseDefaultTextClassifier = in.readBoolean(); + mSystemTcMetadata = in.readParcelable(null); } @Override @@ -189,6 +186,7 @@ public final class SelectionEvent implements Parcelable { if (mWidgetVersion != null) { dest.writeString(mWidgetVersion); } + // TODO: remove mPackageName once aiai does not need it dest.writeString(mPackageName); dest.writeString(mWidgetType); dest.writeInt(mInvocationMethod); @@ -205,8 +203,7 @@ public final class SelectionEvent implements Parcelable { dest.writeInt(mEnd); dest.writeInt(mSmartStart); dest.writeInt(mSmartEnd); - dest.writeInt(mUserId); - dest.writeBoolean(mUseDefaultTextClassifier); + dest.writeParcelable(mSystemTcMetadata, flags); } @Override @@ -409,45 +406,26 @@ public final class SelectionEvent implements Parcelable { */ @NonNull public String getPackageName() { - return mPackageName; + return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : ""; } /** - * Sets the id of this event's user. - * <p> - * 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; - } - - /** - * Sets whether to use the default text classifier to handle this request. - * This will be ignored if it is not the system text classifier to handle this request. + * Sets the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { - mUseDefaultTextClassifier = useDefaultTextClassifier; + void setSystemTextClassifierMetadata(@Nullable SystemTextClassifierMetadata systemTcMetadata) { + mSystemTcMetadata = systemTcMetadata; } /** - * Returns whether to use the default text classifier to handle this request. This - * will be ignored if it is not the system text classifier to handle this request. + * Returns the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - public boolean getUseDefaultTextClassifier() { - return mUseDefaultTextClassifier; + @Nullable + public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { + return mSystemTcMetadata; } /** @@ -476,7 +454,7 @@ public final class SelectionEvent implements Parcelable { mPackageName = context.getPackageName(); mWidgetType = context.getWidgetType(); mWidgetVersion = context.getWidgetVersion(); - mUserId = context.getUserId(); + mSystemTcMetadata = context.getSystemTextClassifierMetadata(); } /** @@ -663,10 +641,9 @@ public final class SelectionEvent implements Parcelable { @Override public int hashCode() { return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, - mWidgetVersion, mPackageName, mUserId, mWidgetType, mInvocationMethod, mResultId, + mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mResultId, mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, - mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd, - mUseDefaultTextClassifier); + mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd, mSystemTcMetadata); } @Override @@ -685,7 +662,6 @@ 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) @@ -698,7 +674,7 @@ public final class SelectionEvent implements Parcelable { && mEnd == other.mEnd && mSmartStart == other.mSmartStart && mSmartEnd == other.mSmartEnd - && mUseDefaultTextClassifier == other.mUseDefaultTextClassifier; + && mSystemTcMetadata == other.mSystemTcMetadata; } @Override @@ -706,15 +682,14 @@ 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, " - + "userId=%d, resultId=%s, eventTime=%d, durationSinceSessionStart=%d, " + + "resultId=%s, eventTime=%d, durationSinceSessionStart=%d, " + "durationSincePreviousEvent=%d, eventIndex=%d," + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d, " - + "mUseDefaultTextClassifier=%b}", + + "systemTcMetadata=%s}", mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, - mUserId, mResultId, mEventTime, mDurationSinceSessionStart, - mDurationSincePreviousEvent, mEventIndex, - mSessionId, mStart, mEnd, mSmartStart, mSmartEnd, mUseDefaultTextClassifier); + mResultId, mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, + mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd, mSystemTcMetadata); } public static final @android.annotation.NonNull Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() { diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java index fe5e8d658dc3..86ef4e103990 100644 --- a/core/java/android/view/textclassifier/SystemTextClassifier.java +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -18,7 +18,6 @@ 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; @@ -39,7 +38,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** - * Proxy to the system's default TextClassifier. + * proxy to the request to TextClassifierService via the TextClassificationManagerService. + * * @hide */ @VisibleForTesting(visibility = Visibility.PACKAGE) @@ -50,14 +50,19 @@ public final class SystemTextClassifier implements TextClassifier { private final ITextClassifierService mManagerService; 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 final boolean mUseDefault; private TextClassificationSessionId mSessionId; + // NOTE: Always set this before sending a request to the manager service otherwise the + // manager service will throw a remote exception. + @NonNull + private final SystemTextClassifierMetadata mSystemTcMetadata; + /** + * Constructor of {@link SystemTextClassifier} + * + * @param context the context of the request. + * @param settings TextClassifier specific settings. + * @param useDefault whether to use the default text classifier to handle this request + */ public SystemTextClassifier( Context context, TextClassificationConstants settings, @@ -66,9 +71,11 @@ public final class SystemTextClassifier implements TextClassifier { ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE)); mSettings = Objects.requireNonNull(settings); mFallback = TextClassifier.NO_OP; - mPackageName = Objects.requireNonNull(context.getOpPackageName()); - mUserId = context.getUserId(); - mUseDefault = useDefault; + // NOTE: Always set this before sending a request to the manager service otherwise the + // manager service will throw a remote exception. + mSystemTcMetadata = new SystemTextClassifierMetadata( + Objects.requireNonNull(context.getOpPackageName()), context.getUserId(), + useDefault); } /** @@ -80,9 +87,7 @@ public final class SystemTextClassifier implements TextClassifier { Objects.requireNonNull(request); Utils.checkMainThread(); try { - request.setCallingPackageName(mPackageName); - request.setUserId(mUserId); - request.setUseDefaultTextClassifier(mUseDefault); + request.setSystemTextClassifierMetadata(mSystemTcMetadata); final BlockingCallback<TextSelection> callback = new BlockingCallback<>("textselection"); mManagerService.onSuggestSelection(mSessionId, request, callback); @@ -105,9 +110,7 @@ public final class SystemTextClassifier implements TextClassifier { Objects.requireNonNull(request); Utils.checkMainThread(); try { - request.setCallingPackageName(mPackageName); - request.setUserId(mUserId); - request.setUseDefaultTextClassifier(mUseDefault); + request.setSystemTextClassifierMetadata(mSystemTcMetadata); final BlockingCallback<TextClassification> callback = new BlockingCallback<>("textclassification"); mManagerService.onClassifyText(mSessionId, request, callback); @@ -137,9 +140,7 @@ public final class SystemTextClassifier implements TextClassifier { } try { - request.setCallingPackageName(mPackageName); - request.setUserId(mUserId); - request.setUseDefaultTextClassifier(mUseDefault); + request.setSystemTextClassifierMetadata(mSystemTcMetadata); final BlockingCallback<TextLinks> callback = new BlockingCallback<>("textlinks"); mManagerService.onGenerateLinks(mSessionId, request, callback); @@ -159,8 +160,7 @@ public final class SystemTextClassifier implements TextClassifier { Utils.checkMainThread(); try { - event.setUserId(mUserId); - event.setUseDefaultTextClassifier(mUseDefault); + event.setSystemTextClassifierMetadata(mSystemTcMetadata); mManagerService.onSelectionEvent(mSessionId, event); } catch (RemoteException e) { Log.e(LOG_TAG, "Error reporting selection event.", e); @@ -173,12 +173,11 @@ 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); - tcContext.setUseDefaultTextClassifier(mUseDefault); + final TextClassificationContext tcContext = + event.getEventContext() == null ? new TextClassificationContext.Builder( + mSystemTcMetadata.getCallingPackageName(), WIDGET_TYPE_UNKNOWN).build() + : event.getEventContext(); + tcContext.setSystemTextClassifierMetadata(mSystemTcMetadata); event.setEventContext(tcContext); mManagerService.onTextClassifierEvent(mSessionId, event); } catch (RemoteException e) { @@ -192,9 +191,7 @@ public final class SystemTextClassifier implements TextClassifier { Utils.checkMainThread(); try { - request.setCallingPackageName(mPackageName); - request.setUserId(mUserId); - request.setUseDefaultTextClassifier(mUseDefault); + request.setSystemTextClassifierMetadata(mSystemTcMetadata); final BlockingCallback<TextLanguage> callback = new BlockingCallback<>("textlanguage"); mManagerService.onDetectLanguage(mSessionId, request, callback); @@ -214,9 +211,7 @@ public final class SystemTextClassifier implements TextClassifier { Utils.checkMainThread(); try { - request.setCallingPackageName(mPackageName); - request.setUserId(mUserId); - request.setUseDefaultTextClassifier(mUseDefault); + request.setSystemTextClassifierMetadata(mSystemTcMetadata); final BlockingCallback<ConversationActions> callback = new BlockingCallback<>("conversation-actions"); mManagerService.onSuggestConversationActions(mSessionId, request, callback); @@ -256,10 +251,8 @@ public final class SystemTextClassifier implements TextClassifier { printWriter.println("SystemTextClassifier:"); printWriter.increaseIndent(); printWriter.printPair("mFallback", mFallback); - printWriter.printPair("mPackageName", mPackageName); printWriter.printPair("mSessionId", mSessionId); - printWriter.printPair("mUserId", mUserId); - printWriter.printPair("mUseDefault", mUseDefault); + printWriter.printPair("mSystemTcMetadata", mSystemTcMetadata); printWriter.decreaseIndent(); printWriter.println(); } @@ -275,7 +268,7 @@ public final class SystemTextClassifier implements TextClassifier { @NonNull TextClassificationSessionId sessionId) { mSessionId = Objects.requireNonNull(sessionId); try { - classificationContext.setUserId(mUserId); + classificationContext.setSystemTextClassifierMetadata(mSystemTcMetadata); mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId); } catch (RemoteException e) { Log.e(LOG_TAG, "Error starting a new classification session.", e); diff --git a/core/java/android/os/incremental/IncrementalSignature.aidl b/core/java/android/view/textclassifier/SystemTextClassifierMetadata.aidl index 729e8e5556a8..4d4e90a4f346 100644 --- a/core/java/android/os/incremental/IncrementalSignature.aidl +++ b/core/java/android/view/textclassifier/SystemTextClassifierMetadata.aidl @@ -14,18 +14,6 @@ * limitations under the License. */ -package android.os.incremental; +package android.view.textclassifier; -/** {@hide} */ -parcelable IncrementalSignature { - /* - * Stable AIDL doesn't support constants, but here's the possible values - * const int HASH_ALGO_NONE = 0; - * const int HASH_ALGO_SHA256 = 1; - */ - - int hashAlgorithm = 0; - byte[] rootHash; - byte[] additionalData; - byte[] signature; -} +parcelable SystemTextClassifierMetadata;
\ No newline at end of file diff --git a/core/java/android/view/textclassifier/SystemTextClassifierMetadata.java b/core/java/android/view/textclassifier/SystemTextClassifierMetadata.java new file mode 100644 index 000000000000..971e3e25c975 --- /dev/null +++ b/core/java/android/view/textclassifier/SystemTextClassifierMetadata.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 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 android.view.textclassifier; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + +import java.util.Locale; +import java.util.Objects; + +/** + * SystemTextClassifier specific information. + * <p> + * This contains information requires for the TextClassificationManagerService to process the + * requests from the application, e.g. user id, calling package name and etc. Centrialize the data + * into this class helps to extend the scalability if we want to add new fields. + * @hide + */ +@VisibleForTesting(visibility = Visibility.PACKAGE) +public final class SystemTextClassifierMetadata implements Parcelable { + + /* The name of the package that sent the TC request. */ + @NonNull + private final String mCallingPackageName; + /* The id of the user that sent the TC request. */ + @UserIdInt + private final int mUserId; + /* Whether to use the default text classifier to handle the request. */ + private final boolean mUseDefaultTextClassifier; + + public SystemTextClassifierMetadata(@NonNull String packageName, @UserIdInt int userId, + boolean useDefaultTextClassifier) { + Objects.requireNonNull(packageName); + mCallingPackageName = packageName; + mUserId = userId; + mUseDefaultTextClassifier = useDefaultTextClassifier; + } + + /** + * Returns the id of the user that sent the TC request. + */ + @UserIdInt + public int getUserId() { + return mUserId; + } + + /** + * Returns the name of the package that sent the TC request. + * This returns {@code null} if no calling package name is set. + */ + @NonNull + public String getCallingPackageName() { + return mCallingPackageName; + } + + /** + * Returns whether to use the default text classifier to handle TC request. + */ + public boolean useDefaultTextClassifier() { + return mUseDefaultTextClassifier; + } + + @Override + public String toString() { + return String.format(Locale.US, + "SystemTextClassifierMetadata {callingPackageName=%s, userId=%d, " + + "useDefaultTextClassifier=%b}", + mCallingPackageName, mUserId, mUseDefaultTextClassifier); + } + + private static SystemTextClassifierMetadata readFromParcel(Parcel in) { + final String packageName = in.readString(); + final int userId = in.readInt(); + final boolean useDefaultTextClassifier = in.readBoolean(); + return new SystemTextClassifierMetadata(packageName, userId, useDefaultTextClassifier); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mCallingPackageName); + dest.writeInt(mUserId); + dest.writeBoolean(mUseDefaultTextClassifier); + } + + public static final @NonNull Creator<SystemTextClassifierMetadata> CREATOR = + new Creator<SystemTextClassifierMetadata>() { + @Override + public SystemTextClassifierMetadata createFromParcel(Parcel in) { + return readFromParcel(in); + } + + @Override + public SystemTextClassifierMetadata[] newArray(int size) { + return new SystemTextClassifierMetadata[size]; + } + }; +} diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 00f762b44a07..8b9d12916595 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -21,7 +21,6 @@ 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; @@ -36,7 +35,6 @@ 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; @@ -552,10 +550,7 @@ public final class TextClassification implements Parcelable { @Nullable private final LocaleList mDefaultLocales; @Nullable private final ZonedDateTime mReferenceTime; @NonNull private final Bundle mExtras; - @Nullable private String mCallingPackageName; - @UserIdInt - private int mUserId = UserHandle.USER_NULL; - private boolean mUseDefaultTextClassifier; + @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; private Request( CharSequence text, @@ -616,62 +611,33 @@ public final class TextClassification implements Parcelable { } /** - * Sets the name of the package that is sending this request. - * <p> - * For SystemTextClassifier's use. - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public void setCallingPackageName(@Nullable String callingPackageName) { - mCallingPackageName = callingPackageName; - } - - /** * Returns the name of the package that sent this request. * This returns {@code null} if no calling package name is set. */ @Nullable public String getCallingPackageName() { - return mCallingPackageName; - } - - /** - * Sets the id of the user that sent this request. - * <p> - * Package-private for SystemTextClassifier's use. - * @hide - */ - void setUserId(@UserIdInt int userId) { - mUserId = userId; + return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; } /** - * Returns the id of the user that sent this request. - * @hide - */ - @UserIdInt - public int getUserId() { - return mUserId; - } - - /** - * Sets whether to use the default text classifier to handle this request. - * This will be ignored if it is not the system text classifier to handle this request. + * Sets the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { - mUseDefaultTextClassifier = useDefaultTextClassifier; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setSystemTextClassifierMetadata( + @Nullable SystemTextClassifierMetadata systemTcMetadata) { + mSystemTcMetadata = systemTcMetadata; } /** - * Returns whether to use the default text classifier to handle this request. This - * will be ignored if it is not the system text classifier to handle this request. + * Returns the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - public boolean getUseDefaultTextClassifier() { - return mUseDefaultTextClassifier; + @Nullable + public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { + return mSystemTcMetadata; } /** @@ -773,10 +739,8 @@ public final class TextClassification implements Parcelable { dest.writeInt(mEndIndex); dest.writeParcelable(mDefaultLocales, flags); dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString()); - dest.writeString(mCallingPackageName); - dest.writeInt(mUserId); dest.writeBundle(mExtras); - dest.writeBoolean(mUseDefaultTextClassifier); + dest.writeParcelable(mSystemTcMetadata, flags); } private static Request readFromParcel(Parcel in) { @@ -787,16 +751,12 @@ public final class TextClassification implements Parcelable { final String referenceTimeString = in.readString(); 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 boolean useDefaultTextClassifier = in.readBoolean(); + final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); final Request request = new Request(text, startIndex, endIndex, defaultLocales, referenceTime, extras); - request.setCallingPackageName(callingPackageName); - request.setUserId(userId); - request.setUseDefaultTextClassifier(useDefaultTextClassifier); + request.setSystemTextClassifierMetadata(systemTcMetadata); return request; } diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java index d58d175c9c93..f2323c625d15 100644 --- a/core/java/android/view/textclassifier/TextClassificationContext.java +++ b/core/java/android/view/textclassifier/TextClassificationContext.java @@ -18,10 +18,8 @@ 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 java.util.Locale; @@ -33,12 +31,11 @@ import java.util.Objects; */ public final class TextClassificationContext implements Parcelable { - private final String mPackageName; + // NOTE: Modify packageName only in the constructor or in setSystemTextClassifierMetadata() + private String mPackageName; private final String mWidgetType; @Nullable private final String mWidgetVersion; - @UserIdInt - private int mUserId = UserHandle.USER_NULL; - private boolean mUseDefaultTextClassifier; + private SystemTextClassifierMetadata mSystemTcMetadata; private TextClassificationContext( String packageName, @@ -58,42 +55,26 @@ public final class TextClassificationContext implements Parcelable { } /** - * Sets the id of this context's user. - * <p> - * Package-private for SystemTextClassifier's use. - * @hide - */ - void setUserId(@UserIdInt int userId) { - mUserId = userId; - } - - /** - * Returns the id of this context's user. - * @hide - */ - @UserIdInt - public int getUserId() { - return mUserId; - } - - /** - * Sets whether to use the default text classifier to handle this request. - * This will be ignored if it is not the system text classifier to handle this request. + * Sets the information about the {@link SystemTextClassifier} that sent this request. * + * <p><b>NOTE: </b>This will override the value returned in {@link getPackageName()}. * @hide */ - void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { - mUseDefaultTextClassifier = useDefaultTextClassifier; + void setSystemTextClassifierMetadata(@Nullable SystemTextClassifierMetadata systemTcMetadata) { + mSystemTcMetadata = systemTcMetadata; + if (mSystemTcMetadata != null) { + mPackageName = mSystemTcMetadata.getCallingPackageName(); + } } /** - * Returns whether to use the default text classifier to handle this request. This - * will be ignored if it is not the system text classifier to handle this request. + * Returns the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - public boolean getUseDefaultTextClassifier() { - return mUseDefaultTextClassifier; + @Nullable + public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { + return mSystemTcMetadata; } /** @@ -118,8 +99,8 @@ public final class TextClassificationContext implements Parcelable { @Override public String toString() { return String.format(Locale.US, "TextClassificationContext{" - + "packageName=%s, widgetType=%s, widgetVersion=%s, userId=%d}", - mPackageName, mWidgetType, mWidgetVersion, mUserId); + + "packageName=%s, widgetType=%s, widgetVersion=%s, systemTcMetadata=%s}", + mPackageName, mWidgetType, mWidgetVersion, mSystemTcMetadata); } /** @@ -176,16 +157,14 @@ public final class TextClassificationContext implements Parcelable { parcel.writeString(mPackageName); parcel.writeString(mWidgetType); parcel.writeString(mWidgetVersion); - parcel.writeInt(mUserId); - parcel.writeBoolean(mUseDefaultTextClassifier); + parcel.writeParcelable(mSystemTcMetadata, flags); } private TextClassificationContext(Parcel in) { mPackageName = in.readString(); mWidgetType = in.readString(); mWidgetVersion = in.readString(); - mUserId = in.readInt(); - mUseDefaultTextClassifier = in.readBoolean(); + mSystemTcMetadata = in.readParcelable(null); } public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR = diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java index 58024dcc09b9..1e8253db888a 100644 --- a/core/java/android/view/textclassifier/TextLanguage.java +++ b/core/java/android/view/textclassifier/TextLanguage.java @@ -20,12 +20,10 @@ 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; @@ -227,10 +225,7 @@ 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 boolean mUseDefaultTextClassifier; + @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; private Request(CharSequence text, Bundle bundle) { mText = text; @@ -246,61 +241,33 @@ public final class TextLanguage implements Parcelable { } /** - * Sets the name of the package that is sending this request. - * Package-private for SystemTextClassifier's use. - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public void setCallingPackageName(@Nullable String callingPackageName) { - mCallingPackageName = callingPackageName; - } - - /** * Returns the name of the package that sent this request. * This returns null if no calling package name is set. */ @Nullable public String getCallingPackageName() { - return mCallingPackageName; + return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; } /** - * Sets the id of the user that sent this request. - * <p> - * Package-private for SystemTextClassifier's use. - * @hide - */ - void setUserId(@UserIdInt int userId) { - mUserId = userId; - } - - /** - * Returns the id of the user that sent this request. - * @hide - */ - @UserIdInt - public int getUserId() { - return mUserId; - } - - /** - * Sets whether to use the default text classifier to handle this request. - * This will be ignored if it is not the system text classifier to handle this request. + * Sets the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { - mUseDefaultTextClassifier = useDefaultTextClassifier; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setSystemTextClassifierMetadata( + @Nullable SystemTextClassifierMetadata systemTcMetadata) { + mSystemTcMetadata = systemTcMetadata; } /** - * Returns whether to use the default text classifier to handle this request. This - * will be ignored if it is not the system text classifier to handle this request. + * Returns the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - public boolean getUseDefaultTextClassifier() { - return mUseDefaultTextClassifier; + @Nullable + public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { + return mSystemTcMetadata; } /** @@ -321,23 +288,17 @@ public final class TextLanguage implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeCharSequence(mText); - dest.writeString(mCallingPackageName); - dest.writeInt(mUserId); dest.writeBundle(mExtra); - dest.writeBoolean(mUseDefaultTextClassifier); + dest.writeParcelable(mSystemTcMetadata, flags); } 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 boolean useDefaultTextClassifier = in.readBoolean(); + final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); final Request request = new Request(text, extra); - request.setCallingPackageName(callingPackageName); - request.setUserId(userId); - request.setUseDefaultTextClassifier(useDefaultTextClassifier); + request.setSystemTextClassifierMetadata(systemTcMetadata); return request; } diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index 7430cb38b987..dea3a9010b18 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -20,13 +20,11 @@ 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; @@ -340,12 +338,9 @@ public final class TextLinks implements Parcelable { @Nullable private final LocaleList mDefaultLocales; @Nullable private final EntityConfig mEntityConfig; private final boolean mLegacyFallback; - @Nullable private String mCallingPackageName; private final Bundle mExtras; @Nullable private final ZonedDateTime mReferenceTime; - @UserIdInt - private int mUserId = UserHandle.USER_NULL; - private boolean mUseDefaultTextClassifier; + @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; private Request( CharSequence text, @@ -409,62 +404,33 @@ public final class TextLinks implements Parcelable { } /** - * Sets the name of the package that is sending this request. - * <p> - * Package-private for SystemTextClassifier's use. - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public void setCallingPackageName(@Nullable String callingPackageName) { - mCallingPackageName = callingPackageName; - } - - /** * Returns the name of the package that sent this request. * This returns {@code null} if no calling package name is set. */ @Nullable public String getCallingPackageName() { - return mCallingPackageName; - } - - /** - * Sets the id of the user that sent this request. - * <p> - * Package-private for SystemTextClassifier's use. - * @hide - */ - void setUserId(@UserIdInt int userId) { - mUserId = userId; + return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; } /** - * Returns the id of the user that sent this request. - * @hide - */ - @UserIdInt - public int getUserId() { - return mUserId; - } - - /** - * Sets whether to use the default text classifier to handle this request. - * This will be ignored if it is not the system text classifier to handle this request. + * Sets the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { - mUseDefaultTextClassifier = useDefaultTextClassifier; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setSystemTextClassifierMetadata( + @Nullable SystemTextClassifierMetadata systemTcMetadata) { + mSystemTcMetadata = systemTcMetadata; } /** - * Returns whether to use the default text classifier to handle this request. This - * will be ignored if it is not the system text classifier to handle this request. + * Returns the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - public boolean getUseDefaultTextClassifier() { - return mUseDefaultTextClassifier; + @Nullable + public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { + return mSystemTcMetadata; } /** @@ -585,30 +551,24 @@ public final class TextLinks implements Parcelable { dest.writeString(mText.toString()); dest.writeParcelable(mDefaultLocales, flags); dest.writeParcelable(mEntityConfig, flags); - dest.writeString(mCallingPackageName); - dest.writeInt(mUserId); dest.writeBundle(mExtras); dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString()); - dest.writeBoolean(mUseDefaultTextClassifier); + dest.writeParcelable(mSystemTcMetadata, flags); } private static Request readFromParcel(Parcel in) { final String text = in.readString(); 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 String referenceTimeString = in.readString(); final ZonedDateTime referenceTime = referenceTimeString == null ? null : ZonedDateTime.parse(referenceTimeString); - final boolean useDefaultTextClassifier = in.readBoolean(); + final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); final Request request = new Request(text, defaultLocales, entityConfig, /* legacyFallback= */ true, referenceTime, extras); - request.setCallingPackageName(callingPackageName); - request.setUserId(userId); - request.setUseDefaultTextClassifier(useDefaultTextClassifier); + request.setSystemTextClassifierMetadata(systemTcMetadata); return request; } diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index 575a072d7066..d8a632d10bc3 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -20,12 +20,10 @@ 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; @@ -213,10 +211,7 @@ public final class TextSelection implements Parcelable { @Nullable private final LocaleList mDefaultLocales; private final boolean mDarkLaunchAllowed; private final Bundle mExtras; - @Nullable private String mCallingPackageName; - @UserIdInt - private int mUserId = UserHandle.USER_NULL; - private boolean mUseDefaultTextClassifier; + @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; private Request( CharSequence text, @@ -278,62 +273,33 @@ public final class TextSelection implements Parcelable { } /** - * Sets the name of the package that is sending this request. - * <p> - * Package-private for SystemTextClassifier's use. - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public void setCallingPackageName(@Nullable String callingPackageName) { - mCallingPackageName = callingPackageName; - } - - /** * Returns the name of the package that sent this request. * This returns {@code null} if no calling package name is set. */ @Nullable public String getCallingPackageName() { - return mCallingPackageName; - } - - /** - * Sets the id of the user that sent this request. - * <p> - * Package-private for SystemTextClassifier's use. - * @hide - */ - void setUserId(@UserIdInt int userId) { - mUserId = userId; + return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; } /** - * Returns the id of the user that sent this request. - * @hide - */ - @UserIdInt - public int getUserId() { - return mUserId; - } - - /** - * Sets whether to use the default text classifier to handle this request. - * This will be ignored if it is not the system text classifier to handle this request. + * Sets the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { - mUseDefaultTextClassifier = useDefaultTextClassifier; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setSystemTextClassifierMetadata( + @Nullable SystemTextClassifierMetadata systemTcMetadata) { + mSystemTcMetadata = systemTcMetadata; } /** - * Returns whether to use the default text classifier to handle this request. This - * will be ignored if it is not the system text classifier to handle this request. + * Returns the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ - public boolean getUseDefaultTextClassifier() { - return mUseDefaultTextClassifier; + @Nullable + public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { + return mSystemTcMetadata; } /** @@ -438,10 +404,8 @@ public final class TextSelection implements Parcelable { dest.writeInt(mStartIndex); dest.writeInt(mEndIndex); dest.writeParcelable(mDefaultLocales, flags); - dest.writeString(mCallingPackageName); - dest.writeInt(mUserId); dest.writeBundle(mExtras); - dest.writeBoolean(mUseDefaultTextClassifier); + dest.writeParcelable(mSystemTcMetadata, flags); } private static Request readFromParcel(Parcel in) { @@ -449,16 +413,12 @@ public final class TextSelection implements Parcelable { final int startIndex = in.readInt(); 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 boolean systemTextClassifierType = in.readBoolean(); + final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); final Request request = new Request(text, startIndex, endIndex, defaultLocales, /* darkLaunchAllowed= */ false, extras); - request.setCallingPackageName(callingPackageName); - request.setUserId(userId); - request.setUseDefaultTextClassifier(systemTextClassifierType); + request.setSystemTextClassifierMetadata(systemTcMetadata); return request; } diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java index eb746127a351..e0bbc04515e0 100644 --- a/core/java/com/android/internal/app/AbstractResolverComparator.java +++ b/core/java/com/android/internal/app/AbstractResolverComparator.java @@ -221,6 +221,12 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC */ abstract float getScore(ComponentName name); + /** + * Returns the list of top K component names which have highest + * {@link #getScore(ComponentName)} + */ + abstract List<ComponentName> getTopComponentNames(int topK); + /** Handles result message sent to mHandler. */ abstract void handleResultMessage(Message message); diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java index 04ad7e9ee8a7..ba8c7b32ad02 100644 --- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java +++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java @@ -36,7 +36,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.Executors; +import java.util.stream.Collectors; /** * Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be @@ -160,6 +162,15 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator } @Override + List<ComponentName> getTopComponentNames(int topK) { + return mTargetRanks.entrySet().stream() + .sorted(Entry.comparingByValue()) + .limit(topK) + .map(Entry::getKey) + .collect(Collectors.toList()); + } + + @Override void updateModel(ComponentName componentName) { if (mResolverRankerService != null) { mResolverRankerService.updateModel(componentName); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 5620bff5142b..188041e75eac 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -223,6 +223,11 @@ public class ChooserActivity extends ResolverActivity implements SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS, DEFAULT_SALT_EXPIRATION_DAYS); + private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED, + false); + private Bundle mReplacementExtras; private IntentSender mChosenComponentSender; private IntentSender mRefinementIntentSender; @@ -409,6 +414,11 @@ public class ChooserActivity extends ResolverActivity implements private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000; private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000; + private static final int DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS = 1500; + private int mDirectShareTimeout = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.SHARE_SHEET_DIRECT_SHARE_TIMEOUT, + DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS); + private boolean mMinTimeoutPassed = false; private void removeAllMessages() { @@ -427,15 +437,14 @@ public class ChooserActivity extends ResolverActivity implements if (DEBUG) { Log.d(TAG, "queryTargets setting watchdog timer for " - + WATCHDOG_TIMEOUT_MIN_MILLIS + "-" + + mDirectShareTimeout + "-" + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms"); } sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT, WATCHDOG_TIMEOUT_MIN_MILLIS); sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT, - WATCHDOG_TIMEOUT_MAX_MILLIS); - + mAppendDirectShareEnabled ? mDirectShareTimeout : WATCHDOG_TIMEOUT_MAX_MILLIS); } private void maybeStopServiceRequestTimer() { @@ -463,6 +472,7 @@ public class ChooserActivity extends ResolverActivity implements final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; if (!mServiceConnections.contains(sri.connection)) { Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection + + sri.originalTarget.getResolveInfo().activityInfo.packageName + " returned after being removed from active connections." + " Have you considered returning results faster?"); break; @@ -474,7 +484,7 @@ public class ChooserActivity extends ResolverActivity implements if (adapterForUserHandle != null) { adapterForUserHandle.addServiceResults(sri.originalTarget, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET, - /* directShareShortcutInfoCache */ null); + /* directShareShortcutInfoCache */ null, mServiceConnections); } } unbindService(sri.connection); @@ -489,6 +499,7 @@ public class ChooserActivity extends ResolverActivity implements break; case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT: + mMinTimeoutPassed = true; unbindRemainingServices(); maybeStopServiceRequestTimer(); break; @@ -513,7 +524,7 @@ public class ChooserActivity extends ResolverActivity implements if (adapterForUserHandle != null) { adapterForUserHandle.addServiceResults( resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1, - mDirectShareShortcutInfoCache); + mDirectShareShortcutInfoCache, mServiceConnections); } } break; @@ -1481,7 +1492,7 @@ public class ChooserActivity extends ResolverActivity implements /* origTarget */ null, Lists.newArrayList(mCallerChooserTargets), TARGET_TYPE_DEFAULT, - /* directShareShortcutInfoCache */ null); + /* directShareShortcutInfoCache */ null, mServiceConnections); } } @@ -3584,6 +3595,10 @@ public class ChooserActivity extends ResolverActivity implements ? mOriginalTarget.getResolveInfo().activityInfo.toString() : "<connection destroyed>") + "}"; } + + public ComponentName getComponentName() { + return mOriginalTarget.getResolveInfo().activityInfo.getComponentName(); + } } static class ServiceResultInfo { diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index 74ae29117b59..0ea855a6b7a9 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -32,8 +32,10 @@ import android.content.pm.ShortcutInfo; import android.os.AsyncTask; import android.os.UserHandle; import android.os.UserManager; +import android.provider.DeviceConfig; import android.service.chooser.ChooserTarget; import android.util.Log; +import android.util.Pair; import android.view.View; import android.view.ViewGroup; @@ -44,17 +46,26 @@ import com.android.internal.app.chooser.DisplayResolveInfo; import com.android.internal.app.chooser.MultiDisplayResolveInfo; import com.android.internal.app.chooser.SelectableTargetInfo; import com.android.internal.app.chooser.TargetInfo; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; public class ChooserListAdapter extends ResolverListAdapter { private static final String TAG = "ChooserListAdapter"; private static final boolean DEBUG = false; + private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED, + false); + private boolean mEnableStackedApps = true; public static final int NO_POSITION = -1; @@ -84,6 +95,11 @@ public class ChooserListAdapter extends ResolverListAdapter { // Reserve spots for incoming direct share targets by adding placeholders private ChooserTargetInfo mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo(); + private int mValidServiceTargetsNum = 0; + private final Map<ComponentName, Pair<List<ChooserTargetInfo>, Integer>> + mParkingDirectShareTargets = new HashMap<>(); + private Set<ComponentName> mPendingChooserTargetService = new HashSet<>(); + private Set<ComponentName> mShortcutComponents = new HashSet<>(); private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); private final List<TargetInfo> mCallerTargets = new ArrayList<>(); @@ -189,6 +205,9 @@ public class ChooserListAdapter extends ResolverListAdapter { void refreshListView() { if (mListViewDataChanged) { + if (mAppendDirectShareEnabled) { + appendServiceTargetsWithQuota(); + } super.notifyDataSetChanged(); } mListViewDataChanged = false; @@ -198,6 +217,10 @@ public class ChooserListAdapter extends ResolverListAdapter { private void createPlaceHolders() { mNumShortcutResults = 0; mServiceTargets.clear(); + mValidServiceTargetsNum = 0; + mParkingDirectShareTargets.clear(); + mPendingChooserTargetService.clear(); + mShortcutComponents.clear(); for (int i = 0; i < MAX_SERVICE_TARGETS; i++) { mServiceTargets.add(mPlaceHolderTargetInfo); } @@ -393,12 +416,19 @@ public class ChooserListAdapter extends ResolverListAdapter { */ public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets, @ChooserActivity.ShareTargetType int targetType, - Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos) { + Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos, + List<ChooserActivity.ChooserTargetServiceConnection> + pendingChooserTargetServiceConnections) { if (DEBUG) { - Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() + Log.d(TAG, "addServiceResults " + origTarget.getResolvedComponentName() + ", " + + targets.size() + " targets"); } - + if (mAppendDirectShareEnabled) { + parkTargetIntoMemory(origTarget, targets, targetType, directShareToShortcutInfos, + pendingChooserTargetServiceConnections); + return; + } if (targets.size() == 0) { return; } @@ -449,6 +479,126 @@ public class ChooserListAdapter extends ResolverListAdapter { } } + /** + * Park {@code targets} into memory for the moment to surface them later when view is refreshed. + * Components pending on ChooserTargetService query are also recorded. + */ + private void parkTargetIntoMemory(DisplayResolveInfo origTarget, List<ChooserTarget> targets, + @ChooserActivity.ShareTargetType int targetType, + Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos, + List<ChooserActivity.ChooserTargetServiceConnection> + pendingChooserTargetServiceConnections) { + ComponentName origComponentName = origTarget.getResolvedComponentName(); + mPendingChooserTargetService = pendingChooserTargetServiceConnections.stream() + .map(ChooserActivity.ChooserTargetServiceConnection::getComponentName) + .filter(componentName -> !componentName.equals(origComponentName)) + .collect(Collectors.toSet()); + // Park targets in memory + if (!targets.isEmpty() && !mParkingDirectShareTargets.containsKey(origComponentName)) { + final boolean isShortcutResult = + (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER + || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE); + Context contextAsUser = mContext.createContextAsUser(getUserHandle(), + 0 /* flags */); + List<ChooserTargetInfo> parkingTargetInfos = targets.stream() + .map(target -> + new SelectableTargetInfo( + contextAsUser, origTarget, target, target.getScore(), + mSelectableTargetInfoComunicator, + (isShortcutResult ? directShareToShortcutInfos.get(target) + : null)) + ) + .collect(Collectors.toList()); + mParkingDirectShareTargets.put(origComponentName, + new Pair<>(parkingTargetInfos, 0)); + if (isShortcutResult) { + mShortcutComponents.add(origComponentName); + } + } + notifyDataSetChanged(); + } + + /** + * Append targets of top ranked share app into direct share row with quota limit. Remove + * appended ones from memory. + */ + private void appendServiceTargetsWithQuota() { + int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets(); + List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets); + int appRank = 0; + for (ComponentName component : topComponentNames) { + if (!mPendingChooserTargetService.contains(component) + && !mParkingDirectShareTargets.containsKey(component)) { + continue; + } + appRank++; + Pair<List<ChooserTargetInfo>, Integer> parkingTargetsItem = + mParkingDirectShareTargets.get(component); + if (parkingTargetsItem != null && parkingTargetsItem.second == 0) { + List<ChooserTargetInfo> parkingTargets = parkingTargetsItem.first; + int initTargetsQuota = appRank <= maxRankedTargets / 2 ? 2 : 1; + int insertedNum = 0; + while (insertedNum < initTargetsQuota && !parkingTargets.isEmpty()) { + if (!checkDuplicateTarget(parkingTargets.get(0))) { + mServiceTargets.add(mValidServiceTargetsNum, parkingTargets.get(0)); + mValidServiceTargetsNum++; + insertedNum++; + } + parkingTargets.remove(0); + } + mParkingDirectShareTargets.put(component, new Pair<>(parkingTargets, insertedNum)); + if (mShortcutComponents.contains(component)) { + mNumShortcutResults += insertedNum; + } + } + } + } + + /** + * Append all remaining targets (parking in memory) into direct share row as per their ranking. + */ + private void fillAllServiceTargets() { + int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets(); + List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets); + // Append all remaining targets of top recommended components into direct share row. + for (ComponentName component : topComponentNames) { + if (!mParkingDirectShareTargets.containsKey(component)) { + continue; + } + mParkingDirectShareTargets.get(component).first.stream() + .filter(target -> !checkDuplicateTarget(target)) + .forEach(target -> { + if (mShortcutComponents.contains(component)) { + mNumShortcutResults++; + } + mServiceTargets.add(target); + }); + mParkingDirectShareTargets.remove(component); + } + // Append all remaining shortcuts targets into direct share row. + List<ChooserTargetInfo> remainingTargets = new ArrayList<>(); + mParkingDirectShareTargets.entrySet().stream() + .filter(entry -> mShortcutComponents.contains(entry.getKey())) + .map(entry -> entry.getValue()) + .map(pair -> pair.first) + .forEach(remainingTargets::addAll); + remainingTargets.sort( + (t1, t2) -> -Float.compare(t1.getModifiedScore(), t2.getModifiedScore())); + mServiceTargets.addAll(remainingTargets); + mNumShortcutResults += remainingTargets.size(); + mParkingDirectShareTargets.clear(); + } + + private boolean checkDuplicateTarget(ChooserTargetInfo chooserTargetInfo) { + // Check for duplicates and abort if found + for (ChooserTargetInfo otherTargetInfo : mServiceTargets) { + if (chooserTargetInfo.isSimilar(otherTargetInfo)) { + return true; + } + } + return false; + } + int getNumShortcutResults() { return mNumShortcutResults; } @@ -487,7 +637,9 @@ public class ChooserListAdapter extends ResolverListAdapter { */ public void completeServiceTargetLoading() { mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo); - + if (mAppendDirectShareEnabled) { + fillAllServiceTargets(); + } if (mServiceTargets.isEmpty()) { mServiceTargets.add(new ChooserActivity.EmptyTargetInfo()); } diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 61a404e3bc5f..579abeecad13 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -153,6 +153,14 @@ public class ResolverListAdapter extends BaseAdapter { return mResolverListController.getScore(target); } + /** + * Returns the list of top K component names which have highest + * {@link #getScore(DisplayResolveInfo)} + */ + public List<ComponentName> getTopComponentNames(int topK) { + return mResolverListController.getTopComponentNames(topK); + } + public void updateModel(ComponentName componentName) { mResolverListController.updateModel(componentName); } diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 022aded188fa..51dce5547890 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -367,6 +367,14 @@ public class ResolverListController { return mResolverComparator.getScore(target.getResolvedComponentName()); } + /** + * Returns the list of top K component names which have highest + * {@link #getScore(DisplayResolveInfo)} + */ + public List<ComponentName> getTopComponentNames(int topK) { + return mResolverComparator.getTopComponentNames(topK); + } + public void updateModel(ComponentName componentName) { mResolverComparator.updateModel(componentName); } diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java index 01e0622063cd..286945037ab7 100644 --- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java @@ -48,6 +48,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * Ranks and compares packages based on usage stats and uses the {@link ResolverRankerService}. @@ -252,6 +253,15 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator return 0; } + @Override + List<ComponentName> getTopComponentNames(int topK) { + return mTargetsDict.entrySet().stream() + .sorted((o1, o2) -> -Float.compare(getScore(o1.getKey()), getScore(o2.getKey()))) + .limit(topK) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + // update ranking model when the connection to it is valid. @Override public void updateModel(ComponentName componentName) { diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 086b9d83fed5..837cc466a895 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -378,6 +378,17 @@ public final class SystemUiDeviceConfigFlags { "nav_bar_handle_show_over_lockscreen"; /** + * (int) Timeout threshold, in millisecond, that Sharesheet waits for direct share targets. + */ + public static final String SHARE_SHEET_DIRECT_SHARE_TIMEOUT = + "share_sheet_direct_share_timeout"; + + /** + * (boolean) Whether append direct share on Sharesheet is enabled. + */ + public static final String APPEND_DIRECT_SHARE_ENABLED = "append_direct_share_enabled"; + + /** * (boolean) Whether to enable user-drag resizing for PIP. */ public static final String PIP_USER_RESIZE = "pip_user_resize"; diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 0bfd65936b4a..ec1f516df5f3 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -761,6 +761,10 @@ public class ZygoteInit { * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */ parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + /* Enable gwp-asan on the system server with a small probability. This is the same + * policy as applied to native processes and system apps. */ + parsedArgs.mRuntimeFlags |= Zygote.GWP_ASAN_LEVEL_LOTTERY; + if (shouldProfileSystemServer()) { parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 380a20c88d56..5b79b184b6a4 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -136,7 +136,8 @@ oneway interface IStatusBar // Used to show the authentication dialog (Biometrics, Device Credential) void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, - int biometricModality, boolean requireConfirmation, int userId, String opPackageName); + int biometricModality, boolean requireConfirmation, int userId, String opPackageName, + long operationId); // Used to notify the authentication dialog that a biometric has been authenticated void onBiometricAuthenticated(); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 9907b9975afe..1dbd69c67831 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -105,7 +105,8 @@ interface IStatusBarService // Used to show the authentication dialog (Biometrics, Device Credential) void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, - int biometricModality, boolean requireConfirmation, int userId, String opPackageName); + int biometricModality, boolean requireConfirmation, int userId, String opPackageName, + long operationId); // Used to notify the authentication dialog that a biometric has been authenticated void onBiometricAuthenticated(); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index 9b2bcfbe89c7..488b18db66d6 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -16,6 +16,8 @@ package com.android.internal.util; +import static java.util.Collections.emptySet; + import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; @@ -67,7 +69,7 @@ public class CollectionUtils { */ public static @NonNull <T> Set<T> filter(@Nullable Set<T> set, java.util.function.Predicate<? super T> predicate) { - if (set == null || set.size() == 0) return Collections.emptySet(); + if (set == null || set.size() == 0) return emptySet(); ArraySet<T> result = null; if (set instanceof ArraySet) { ArraySet<T> arraySet = (ArraySet<T>) set; @@ -123,7 +125,7 @@ public class CollectionUtils { */ public static @NonNull <I, O> Set<O> map(@Nullable Set<I> cur, Function<? super I, ? extends O> f) { - if (isEmpty(cur)) return Collections.emptySet(); + if (isEmpty(cur)) return emptySet(); ArraySet<O> result = new ArraySet<>(); if (cur instanceof ArraySet) { ArraySet<I> arraySet = (ArraySet<I>) cur; @@ -182,7 +184,7 @@ public class CollectionUtils { * @see Collections#emptySet */ public static @NonNull <T> Set<T> emptyIfNull(@Nullable Set<T> cur) { - return cur == null ? Collections.emptySet() : cur; + return cur == null ? emptySet() : cur; } /** @@ -325,9 +327,9 @@ public class CollectionUtils { */ public static @NonNull <T> Set<T> addAll(@Nullable Set<T> cur, @Nullable Collection<T> val) { if (isEmpty(val)) { - return cur != null ? cur : Collections.emptySet(); + return cur != null ? cur : emptySet(); } - if (cur == null || cur == Collections.emptySet()) { + if (cur == null || cur == emptySet()) { cur = new ArraySet<>(); } cur.addAll(val); @@ -338,7 +340,7 @@ public class CollectionUtils { * @see #add(List, Object) */ public static @NonNull <T> Set<T> add(@Nullable Set<T> cur, T val) { - if (cur == null || cur == Collections.emptySet()) { + if (cur == null || cur == emptySet()) { cur = new ArraySet<>(); } cur.add(val); @@ -390,7 +392,14 @@ public class CollectionUtils { * @return a list that will not be affected by mutations to the given original list. */ public static @NonNull <T> Set<T> copyOf(@Nullable Set<T> cur) { - return isEmpty(cur) ? Collections.emptySet() : new ArraySet<>(cur); + return isEmpty(cur) ? emptySet() : new ArraySet<>(cur); + } + + /** + * @return a {@link Set} representing the given collection. + */ + public static @NonNull <T> Set<T> toSet(@Nullable Collection<T> cur) { + return isEmpty(cur) ? emptySet() : new ArraySet<>(cur); } /** diff --git a/core/java/com/android/internal/view/inline/IInlineContentCallback.aidl b/core/java/com/android/internal/view/inline/IInlineContentCallback.aidl index 29bdf5661eaf..feb3f026b806 100644 --- a/core/java/com/android/internal/view/inline/IInlineContentCallback.aidl +++ b/core/java/com/android/internal/view/inline/IInlineContentCallback.aidl @@ -24,4 +24,6 @@ import android.view.SurfaceControlViewHost; */ oneway interface IInlineContentCallback { void onContent(in SurfaceControlViewHost.SurfacePackage content); + void onClick(); + void onLongClick(); } diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java index 74ad81566ef4..bd0623e1144e 100644 --- a/core/java/com/android/internal/widget/CachingIconView.java +++ b/core/java/com/android/internal/widget/CachingIconView.java @@ -32,6 +32,7 @@ import android.widget.ImageView; import android.widget.RemoteViews; import java.util.Objects; +import java.util.function.Consumer; /** * An ImageView for displaying an Icon. Avoids reloading the Icon when possible. @@ -44,6 +45,7 @@ public class CachingIconView extends ImageView { private boolean mInternalSetDrawable; private boolean mForceHidden; private int mDesiredVisibility; + private Consumer<Integer> mOnVisibilityChangedListener; @UnsupportedAppUsage public CachingIconView(Context context, @Nullable AttributeSet attrs) { @@ -198,6 +200,13 @@ public class CachingIconView extends ImageView { private void updateVisibility() { int visibility = mDesiredVisibility == VISIBLE && mForceHidden ? INVISIBLE : mDesiredVisibility; + if (mOnVisibilityChangedListener != null) { + mOnVisibilityChangedListener.accept(visibility); + } super.setVisibility(visibility); } + + public void setOnVisibilityChangedListener(Consumer<Integer> listener) { + mOnVisibilityChangedListener = listener; + } } diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java new file mode 100644 index 000000000000..07be113d9a53 --- /dev/null +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -0,0 +1,866 @@ +/* + * Copyright (C) 2020 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.internal.widget; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StyleRes; +import android.app.Notification; +import android.app.Person; +import android.app.RemoteInputHistoryItem; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.RemotableViewMethod; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RemoteViews; +import android.widget.TextView; + +import com.android.internal.R; +import com.android.internal.graphics.ColorUtils; +import com.android.internal.util.ContrastColorUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +/** + * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal + * messages and adapts the layout accordingly. + */ +@RemoteViews.RemoteView +public class ConversationLayout extends FrameLayout + implements ImageMessageConsumer, IMessagingLayout { + + public static final boolean CONVERSATION_LAYOUT_ENABLED = true; + private static final float COLOR_SHIFT_AMOUNT = 60; + /** + * Pattren for filter some ingonable characters. + * p{Z} for any kind of whitespace or invisible separator. + * p{C} for any kind of punctuation character. + */ + private static final Pattern IGNORABLE_CHAR_PATTERN + = Pattern.compile("[\\p{C}\\p{Z}]"); + private static final Pattern SPECIAL_CHAR_PATTERN + = Pattern.compile ("[!@#$%&*()_+=|<>?{}\\[\\]~-]"); + private static final Consumer<MessagingMessage> REMOVE_MESSAGE + = MessagingMessage::removeMessage; + public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); + public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); + public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR + = new MessagingPropertyAnimator(); + private List<MessagingMessage> mMessages = new ArrayList<>(); + private List<MessagingMessage> mHistoricMessages = new ArrayList<>(); + private MessagingLinearLayout mMessagingLinearLayout; + private boolean mShowHistoricMessages; + private ArrayList<MessagingGroup> mGroups = new ArrayList<>(); + private TextView mTitleView; + private int mLayoutColor; + private int mSenderTextColor; + private int mMessageTextColor; + private int mAvatarSize; + private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint mTextPaint = new Paint(); + private Icon mAvatarReplacement; + private boolean mIsOneToOne; + private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>(); + private Person mUser; + private CharSequence mNameReplacement; + private boolean mIsCollapsed; + private ImageResolver mImageResolver; + private ImageView mConversationIcon; + private TextView mConversationText; + private View mConversationIconBadge; + private Icon mLargeIcon; + private View mExpandButtonContainer; + private ViewGroup mExpandButtonAndContentContainer; + private NotificationExpandButton mExpandButton; + private int mExpandButtonExpandedTopMargin; + private int mBadgedSideMargins; + private int mIconSizeBadged; + private int mIconSizeCentered; + private CachingIconView mIcon; + private int mExpandedGroupTopMargin; + private int mExpandButtonExpandedSize; + private View mConversationFacePile; + private int mNotificationBackgroundColor; + private CharSequence mFallbackChatName; + private CharSequence mFallbackGroupChatName; + private CharSequence mConversationTitle; + + public ConversationLayout(@NonNull Context context) { + super(context); + } + + public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mMessagingLinearLayout = findViewById(R.id.notification_messaging); + mMessagingLinearLayout.setMessagingLayout(this); + // We still want to clip, but only on the top, since views can temporarily out of bounds + // during transitions. + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + int size = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels); + Rect rect = new Rect(0, 0, size, size); + mMessagingLinearLayout.setClipBounds(rect); + mTitleView = findViewById(R.id.title); + mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size); + mTextPaint.setTextAlign(Paint.Align.CENTER); + mTextPaint.setAntiAlias(true); + mConversationIcon = findViewById(R.id.conversation_icon); + mIcon = findViewById(R.id.icon); + mConversationIconBadge = findViewById(R.id.conversation_icon_badge); + mIcon.setOnVisibilityChangedListener((visibility) -> { + // Always keep the badge visibility in sync with the icon. This is necessary in cases + // Where the icon is being hidden externally like in group children. + mConversationIconBadge.setVisibility(visibility); + }); + mConversationText = findViewById(R.id.conversation_text); + mExpandButtonContainer = findViewById(R.id.expand_button_container); + mExpandButtonAndContentContainer = findViewById(R.id.expand_button_and_content_container); + mExpandButton = findViewById(R.id.expand_button); + mExpandButtonExpandedTopMargin = getResources().getDimensionPixelSize( + R.dimen.conversation_expand_button_top_margin_expanded); + mExpandButtonExpandedSize = getResources().getDimensionPixelSize( + R.dimen.conversation_expand_button_expanded_size); + mBadgedSideMargins = getResources().getDimensionPixelSize( + R.dimen.conversation_badge_side_margin); + mIconSizeBadged = getResources().getDimensionPixelSize( + R.dimen.conversation_icon_size_badged); + mIconSizeCentered = getResources().getDimensionPixelSize( + R.dimen.conversation_icon_size_centered); + mExpandedGroupTopMargin = getResources().getDimensionPixelSize( + R.dimen.conversation_icon_margin_top_centered); + mConversationFacePile = findViewById(R.id.conversation_face_pile); + mFallbackChatName = getResources().getString( + R.string.conversation_title_fallback_one_to_one); + mFallbackGroupChatName = getResources().getString( + R.string.conversation_title_fallback_group_chat); + } + + @RemotableViewMethod + public void setAvatarReplacement(Icon icon) { + mAvatarReplacement = icon; + } + + @RemotableViewMethod + public void setNameReplacement(CharSequence nameReplacement) { + mNameReplacement = nameReplacement; + } + + /** + * Set this layout to show the collapsed representation. + * + * @param isCollapsed is it collapsed + */ + @RemotableViewMethod + public void setIsCollapsed(boolean isCollapsed) { + mIsCollapsed = isCollapsed; + mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE); + updateExpandButton(); + } + + @RemotableViewMethod + public void setData(Bundle extras) { + Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); + List<Notification.MessagingStyle.Message> newMessages + = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages); + Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES); + List<Notification.MessagingStyle.Message> newHistoricMessages + = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages); + + // mUser now set (would be nice to avoid the side effect but WHATEVER) + setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON)); + + + // Append remote input history to newMessages (again, side effect is lame but WHATEVS) + RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) + extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + addRemoteInputHistoryToMessages(newMessages, history); + + boolean showSpinner = + extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false); + + // bind it, baby + bind(newMessages, newHistoricMessages, showSpinner); + } + + @Override + public void setImageResolver(ImageResolver resolver) { + mImageResolver = resolver; + } + + private void addRemoteInputHistoryToMessages( + List<Notification.MessagingStyle.Message> newMessages, + RemoteInputHistoryItem[] remoteInputHistory) { + if (remoteInputHistory == null || remoteInputHistory.length == 0) { + return; + } + for (int i = remoteInputHistory.length - 1; i >= 0; i--) { + RemoteInputHistoryItem historyMessage = remoteInputHistory[i]; + Notification.MessagingStyle.Message message = new Notification.MessagingStyle.Message( + historyMessage.getText(), 0, (Person) null, true /* remoteHistory */); + if (historyMessage.getUri() != null) { + message.setData(historyMessage.getMimeType(), historyMessage.getUri()); + } + newMessages.add(message); + } + } + + private void bind(List<Notification.MessagingStyle.Message> newMessages, + List<Notification.MessagingStyle.Message> newHistoricMessages, + boolean showSpinner) { + // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding + // if they exist + List<MessagingMessage> historicMessages = createMessages(newHistoricMessages, + true /* isHistoric */); + List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */); + + // Copy our groups, before they get clobbered + ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups); + + // Add our new MessagingMessages to groups + List<List<MessagingMessage>> groups = new ArrayList<>(); + List<Person> senders = new ArrayList<>(); + + // Lets first find the groups (populate `groups` and `senders`) + findGroups(historicMessages, messages, groups, senders); + + // Let's now create the views and reorder them accordingly + // side-effect: updates mGroups, mAddedGroups + createGroupViews(groups, senders, showSpinner); + + // Let's first check which groups were removed altogether and remove them in one animation + removeGroups(oldGroups); + + // Let's remove the remaining messages + mMessages.forEach(REMOVE_MESSAGE); + mHistoricMessages.forEach(REMOVE_MESSAGE); + + mMessages = messages; + mHistoricMessages = historicMessages; + + updateHistoricMessageVisibility(); + updateTitleAndNamesDisplay(); + + updateConversationLayout(); + + } + + /** + * Update the layout according to the data provided (i.e mIsOneToOne, expanded etc); + */ + private void updateConversationLayout() { + // TODO: resolve this from shortcuts + // Set avatar and name + CharSequence conversationText = mConversationTitle; + // TODO: display the secondary text somewhere + if (mIsOneToOne) { + // Let's resolve the icon / text from the last sender + mConversationIcon.setVisibility(VISIBLE); + mConversationFacePile.setVisibility(GONE); + CharSequence userKey = getKey(mUser); + for (int i = mGroups.size() - 1; i >= 0; i--) { + MessagingGroup messagingGroup = mGroups.get(i); + Person messageSender = messagingGroup.getSender(); + if ((messageSender != null && !TextUtils.equals(userKey, getKey(messageSender))) + || i == 0) { + if (TextUtils.isEmpty(conversationText)) { + // We use the sendername as header text if no conversation title is provided + // (This usually happens for most 1:1 conversations) + conversationText = messagingGroup.getSenderName(); + } + Icon avatarIcon = messagingGroup.getAvatarIcon(); + if (avatarIcon == null) { + avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor); + } + mConversationIcon.setImageIcon(avatarIcon); + break; + } + } + } else { + if (mIsCollapsed) { + if (mLargeIcon != null) { + mConversationIcon.setVisibility(VISIBLE); + mConversationFacePile.setVisibility(GONE); + mConversationIcon.setImageIcon(mLargeIcon); + } else { + mConversationIcon.setVisibility(GONE); + // This will also inflate it! + mConversationFacePile.setVisibility(VISIBLE); + mConversationFacePile = findViewById(R.id.conversation_face_pile); + bindFacePile(); + } + } else { + mConversationFacePile.setVisibility(GONE); + mConversationIcon.setVisibility(GONE); + } + } + if (TextUtils.isEmpty(conversationText)) { + conversationText = mIsOneToOne ? mFallbackChatName : mFallbackGroupChatName; + } + mConversationText.setText(conversationText); + // Update if the groups can hide the sender if they are first (applies to 1:1 conversations) + // This needs to happen after all of the above o update all of the groups + for (int i = mGroups.size() - 1; i >= 0; i--) { + MessagingGroup messagingGroup = mGroups.get(i); + CharSequence messageSender = messagingGroup.getSenderName(); + boolean canHide = mIsOneToOne + && TextUtils.equals(conversationText, messageSender); + messagingGroup.setCanHideSenderIfFirst(canHide); + } + updateIconPositionAndSize(); + } + + private void bindFacePile() { + // Let's bind the face pile + View bottomBackground = mConversationFacePile.findViewById( + R.id.conversation_face_pile_bottom_background); + applyNotificationBackgroundColor(bottomBackground); + ImageView bottomView = mConversationFacePile.findViewById( + R.id.conversation_face_pile_bottom); + ImageView topView = mConversationFacePile.findViewById( + R.id.conversation_face_pile_top); + // Let's find the two last conversations: + Icon secondLastIcon = null; + CharSequence lastKey = null; + Icon lastIcon = null; + CharSequence userKey = getKey(mUser); + for (int i = mGroups.size() - 1; i >= 0; i--) { + MessagingGroup messagingGroup = mGroups.get(i); + Person messageSender = messagingGroup.getSender(); + boolean notUser = messageSender != null + && !TextUtils.equals(userKey, getKey(messageSender)); + boolean notIncluded = messageSender != null + && !TextUtils.equals(lastKey, getKey(messageSender)); + if ((notUser && notIncluded) + || (i == 0 && lastKey == null)) { + if (lastIcon == null) { + lastIcon = messagingGroup.getAvatarIcon(); + lastKey = getKey(messageSender); + } else { + secondLastIcon = messagingGroup.getAvatarIcon(); + break; + } + } + } + if (lastIcon == null) { + lastIcon = createAvatarSymbol(" ", "", mLayoutColor); + } + bottomView.setImageIcon(lastIcon); + if (secondLastIcon == null) { + secondLastIcon = createAvatarSymbol("", "", mLayoutColor); + } + topView.setImageIcon(secondLastIcon); + } + + /** + * update the icon position and sizing + */ + private void updateIconPositionAndSize() { + int gravity; + int marginStart; + int marginTop; + int iconSize; + if (mIsOneToOne || mIsCollapsed) { + // Baded format + gravity = Gravity.LEFT; + marginStart = mBadgedSideMargins; + marginTop = mBadgedSideMargins; + iconSize = mIconSizeBadged; + } else { + gravity = Gravity.CENTER_HORIZONTAL; + marginStart = 0; + marginTop = mExpandedGroupTopMargin; + iconSize = mIconSizeCentered; + } + LayoutParams layoutParams = + (LayoutParams) mConversationIconBadge.getLayoutParams(); + layoutParams.gravity = gravity; + layoutParams.topMargin = marginTop; + layoutParams.setMarginStart(marginStart); + mConversationIconBadge.setLayoutParams(layoutParams); + ViewGroup.LayoutParams iconParams = mIcon.getLayoutParams(); + iconParams.width = iconSize; + iconParams.height = iconSize; + mIcon.setLayoutParams(iconParams); + } + + @RemotableViewMethod + public void setLargeIcon(Icon largeIcon) { + mLargeIcon = largeIcon; + } + + /** + * Sets the conversation title of this conversation. + * + * @param conversationTitle the conversation title + */ + @RemotableViewMethod + public void setConversationTitle(CharSequence conversationTitle) { + mConversationTitle = conversationTitle; + } + + private void removeGroups(ArrayList<MessagingGroup> oldGroups) { + int size = oldGroups.size(); + for (int i = 0; i < size; i++) { + MessagingGroup group = oldGroups.get(i); + if (!mGroups.contains(group)) { + List<MessagingMessage> messages = group.getMessages(); + Runnable endRunnable = () -> { + mMessagingLinearLayout.removeTransientView(group); + group.recycle(); + }; + + boolean wasShown = group.isShown(); + mMessagingLinearLayout.removeView(group); + if (wasShown && !MessagingLinearLayout.isGone(group)) { + mMessagingLinearLayout.addTransientView(group, 0); + group.removeGroupAnimated(endRunnable); + } else { + endRunnable.run(); + } + mMessages.removeAll(messages); + mHistoricMessages.removeAll(messages); + } + } + } + + private void updateTitleAndNamesDisplay() { + ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>(); + ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>(); + for (int i = 0; i < mGroups.size(); i++) { + MessagingGroup group = mGroups.get(i); + CharSequence senderName = group.getSenderName(); + if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) { + continue; + } + if (!uniqueNames.containsKey(senderName)) { + // Only use visible characters to get uniqueNames + String pureSenderName = IGNORABLE_CHAR_PATTERN + .matcher(senderName).replaceAll("" /* replacement */); + char c = pureSenderName.charAt(0); + if (uniqueCharacters.containsKey(c)) { + // this character was already used, lets make it more unique. We first need to + // resolve the existing character if it exists + CharSequence existingName = uniqueCharacters.get(c); + if (existingName != null) { + uniqueNames.put(existingName, findNameSplit((String) existingName)); + uniqueCharacters.put(c, null); + } + uniqueNames.put(senderName, findNameSplit((String) senderName)); + } else { + uniqueNames.put(senderName, Character.toString(c)); + uniqueCharacters.put(c, pureSenderName); + } + } + } + + // Now that we have the correct symbols, let's look what we have cached + ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>(); + for (int i = 0; i < mGroups.size(); i++) { + // Let's now set the avatars + MessagingGroup group = mGroups.get(i); + boolean isOwnMessage = group.getSender() == mUser; + CharSequence senderName = group.getSenderName(); + if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName) + || (mIsOneToOne && mAvatarReplacement != null && !isOwnMessage)) { + continue; + } + String symbol = uniqueNames.get(senderName); + Icon cachedIcon = group.getAvatarSymbolIfMatching(senderName, + symbol, mLayoutColor); + if (cachedIcon != null) { + cachedAvatars.put(senderName, cachedIcon); + } + } + + for (int i = 0; i < mGroups.size(); i++) { + // Let's now set the avatars + MessagingGroup group = mGroups.get(i); + CharSequence senderName = group.getSenderName(); + if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) { + continue; + } + if (mIsOneToOne && mAvatarReplacement != null && group.getSender() != mUser) { + group.setAvatar(mAvatarReplacement); + } else { + Icon cachedIcon = cachedAvatars.get(senderName); + if (cachedIcon == null) { + cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName), + mLayoutColor); + cachedAvatars.put(senderName, cachedIcon); + } + group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName), + mLayoutColor); + } + } + } + + private Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) { + if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol) || + SPECIAL_CHAR_PATTERN.matcher(symbol).find()) { + Icon avatarIcon = Icon.createWithResource(getContext(), + R.drawable.messaging_user); + avatarIcon.setTint(findColor(senderName, layoutColor)); + return avatarIcon; + } else { + Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + float radius = mAvatarSize / 2.0f; + int color = findColor(senderName, layoutColor); + mPaint.setColor(color); + canvas.drawCircle(radius, radius, radius, mPaint); + boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f; + mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE); + mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f); + int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2)); + canvas.drawText(symbol, radius, yPos, mTextPaint); + return Icon.createWithBitmap(bitmap); + } + } + + private int findColor(CharSequence senderName, int layoutColor) { + double luminance = ContrastColorUtil.calculateLuminance(layoutColor); + float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f; + + // we need to offset the range if the luminance is too close to the borders + shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0); + shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0); + return ContrastColorUtil.getShiftedColor(layoutColor, + (int) (shift * COLOR_SHIFT_AMOUNT)); + } + + private String findNameSplit(String existingName) { + String[] split = existingName.split(" "); + if (split.length > 1) { + return Character.toString(split[0].charAt(0)) + + Character.toString(split[1].charAt(0)); + } + return existingName.substring(0, 1); + } + + @RemotableViewMethod + public void setLayoutColor(int color) { + mLayoutColor = color; + } + + @RemotableViewMethod + public void setIsOneToOne(boolean oneToOne) { + mIsOneToOne = oneToOne; + } + + @RemotableViewMethod + public void setSenderTextColor(int color) { + mSenderTextColor = color; + } + + /** + * @param color the color of the notification background + */ + @RemotableViewMethod + public void setNotificationBackgroundColor(int color) { + mNotificationBackgroundColor = color; + applyNotificationBackgroundColor(mConversationIconBadge); + } + + private void applyNotificationBackgroundColor(View view) { + view.setBackgroundTintList(ColorStateList.valueOf(mNotificationBackgroundColor)); + } + + @RemotableViewMethod + public void setMessageTextColor(int color) { + mMessageTextColor = color; + } + + private void setUser(Person user) { + mUser = user; + if (mUser.getIcon() == null) { + Icon userIcon = Icon.createWithResource(getContext(), + R.drawable.messaging_user); + userIcon.setTint(mLayoutColor); + mUser = mUser.toBuilder().setIcon(userIcon).build(); + } + } + + private void createGroupViews(List<List<MessagingMessage>> groups, + List<Person> senders, boolean showSpinner) { + mGroups.clear(); + for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) { + List<MessagingMessage> group = groups.get(groupIndex); + MessagingGroup newGroup = null; + // we'll just take the first group that exists or create one there is none + for (int messageIndex = group.size() - 1; messageIndex >= 0; messageIndex--) { + MessagingMessage message = group.get(messageIndex); + newGroup = message.getGroup(); + if (newGroup != null) { + break; + } + } + // Create a new group, adding it to the linear layout as well + if (newGroup == null) { + newGroup = MessagingGroup.createGroup(mMessagingLinearLayout); + mAddedGroups.add(newGroup); + } + newGroup.setDisplayImagesAtEnd(mIsCollapsed); + newGroup.setLayoutColor(mLayoutColor); + newGroup.setTextColors(mSenderTextColor, mMessageTextColor); + Person sender = senders.get(groupIndex); + CharSequence nameOverride = null; + if (sender != mUser && mNameReplacement != null) { + nameOverride = mNameReplacement; + } + newGroup.setShowingAvatar(!mIsOneToOne && !mIsCollapsed); + newGroup.setSingleLine(mIsCollapsed); + newGroup.setSender(sender, nameOverride); + newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner); + mGroups.add(newGroup); + + // Reposition to the correct place (if we're re-using a group) + if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) { + mMessagingLinearLayout.removeView(newGroup); + mMessagingLinearLayout.addView(newGroup, groupIndex); + } + newGroup.setMessages(group); + } + } + + private void findGroups(List<MessagingMessage> historicMessages, + List<MessagingMessage> messages, List<List<MessagingMessage>> groups, + List<Person> senders) { + CharSequence currentSenderKey = null; + List<MessagingMessage> currentGroup = null; + int histSize = historicMessages.size(); + for (int i = 0; i < histSize + messages.size(); i++) { + MessagingMessage message; + if (i < histSize) { + message = historicMessages.get(i); + } else { + message = messages.get(i - histSize); + } + boolean isNewGroup = currentGroup == null; + Person sender = message.getMessage().getSenderPerson(); + CharSequence key = getKey(sender); + isNewGroup |= !TextUtils.equals(key, currentSenderKey); + if (isNewGroup) { + currentGroup = new ArrayList<>(); + groups.add(currentGroup); + if (sender == null) { + sender = mUser; + } + senders.add(sender); + currentSenderKey = key; + } + currentGroup.add(message); + } + } + + private CharSequence getKey(Person person) { + return person == null ? null : person.getKey() == null ? person.getName() : person.getKey(); + } + + /** + * Creates new messages, reusing existing ones if they are available. + * + * @param newMessages the messages to parse. + */ + private List<MessagingMessage> createMessages( + List<Notification.MessagingStyle.Message> newMessages, boolean historic) { + List<MessagingMessage> result = new ArrayList<>(); + for (int i = 0; i < newMessages.size(); i++) { + Notification.MessagingStyle.Message m = newMessages.get(i); + MessagingMessage message = findAndRemoveMatchingMessage(m); + if (message == null) { + message = MessagingMessage.createMessage(this, m, mImageResolver); + } + message.setIsHistoric(historic); + result.add(message); + } + return result; + } + + private MessagingMessage findAndRemoveMatchingMessage(Notification.MessagingStyle.Message m) { + for (int i = 0; i < mMessages.size(); i++) { + MessagingMessage existing = mMessages.get(i); + if (existing.sameAs(m)) { + mMessages.remove(i); + return existing; + } + } + for (int i = 0; i < mHistoricMessages.size(); i++) { + MessagingMessage existing = mHistoricMessages.get(i); + if (existing.sameAs(m)) { + mHistoricMessages.remove(i); + return existing; + } + } + return null; + } + + public void showHistoricMessages(boolean show) { + mShowHistoricMessages = show; + updateHistoricMessageVisibility(); + } + + private void updateHistoricMessageVisibility() { + int numHistoric = mHistoricMessages.size(); + for (int i = 0; i < numHistoric; i++) { + MessagingMessage existing = mHistoricMessages.get(i); + existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE); + } + int numGroups = mGroups.size(); + for (int i = 0; i < numGroups; i++) { + MessagingGroup group = mGroups.get(i); + int visibleChildren = 0; + List<MessagingMessage> messages = group.getMessages(); + int numGroupMessages = messages.size(); + for (int j = 0; j < numGroupMessages; j++) { + MessagingMessage message = messages.get(j); + if (message.getVisibility() != GONE) { + visibleChildren++; + } + } + if (visibleChildren > 0 && group.getVisibility() == GONE) { + group.setVisibility(VISIBLE); + } else if (visibleChildren == 0 && group.getVisibility() != GONE) { + group.setVisibility(GONE); + } + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (!mAddedGroups.isEmpty()) { + getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + for (MessagingGroup group : mAddedGroups) { + if (!group.isShown()) { + continue; + } + MessagingPropertyAnimator.fadeIn(group.getAvatar()); + MessagingPropertyAnimator.fadeIn(group.getSenderView()); + MessagingPropertyAnimator.startLocalTranslationFrom(group, + group.getHeight(), LINEAR_OUT_SLOW_IN); + } + mAddedGroups.clear(); + getViewTreeObserver().removeOnPreDrawListener(this); + return true; + } + }); + } + } + + public MessagingLinearLayout getMessagingLinearLayout() { + return mMessagingLinearLayout; + } + + public ArrayList<MessagingGroup> getMessagingGroups() { + return mGroups; + } + + private void updateExpandButton() { + int drawableId; + int contentDescriptionId; + int gravity; + int topMargin = 0; + ViewGroup newContainer; + int newContainerHeight; + if (mIsCollapsed) { + drawableId = R.drawable.ic_expand_notification; + contentDescriptionId = R.string.expand_button_content_description_collapsed; + gravity = Gravity.CENTER; + newContainer = mExpandButtonAndContentContainer; + newContainerHeight = LayoutParams.MATCH_PARENT; + } else { + drawableId = R.drawable.ic_collapse_notification; + contentDescriptionId = R.string.expand_button_content_description_expanded; + gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; + topMargin = mExpandButtonExpandedTopMargin; + newContainer = this; + newContainerHeight = mExpandButtonExpandedSize; + } + mExpandButton.setImageDrawable(getContext().getDrawable(drawableId)); + mExpandButton.setColorFilter(mExpandButton.getOriginalNotificationColor()); + + // We need to make sure that the expand button is in the linearlayout pushing over the + // content when collapsed, but allows the content to flow under it when expanded. + if (newContainer != mExpandButtonContainer.getParent()) { + ((ViewGroup) mExpandButtonContainer.getParent()).removeView(mExpandButtonContainer); + newContainer.addView(mExpandButtonContainer); + MarginLayoutParams layoutParams = + (MarginLayoutParams) mExpandButtonContainer.getLayoutParams(); + layoutParams.height = newContainerHeight; + mExpandButtonContainer.setLayoutParams(layoutParams); + } + + // update if the expand button is centered + FrameLayout.LayoutParams layoutParams = (LayoutParams) mExpandButton.getLayoutParams(); + layoutParams.gravity = gravity; + layoutParams.topMargin = topMargin; + mExpandButton.setLayoutParams(layoutParams); + + mExpandButtonContainer.setContentDescription(mContext.getText(contentDescriptionId)); + + } + + public void updateExpandability(boolean expandable, @Nullable OnClickListener onClickListener) { + if (expandable) { + mExpandButtonContainer.setVisibility(VISIBLE); + mExpandButtonContainer.setOnClickListener(onClickListener); + } else { + // TODO: handle content paddings to end of layout + mExpandButtonContainer.setVisibility(GONE); + } + } +} diff --git a/core/java/com/android/internal/widget/IMessagingLayout.java b/core/java/com/android/internal/widget/IMessagingLayout.java new file mode 100644 index 000000000000..149d05641a0b --- /dev/null +++ b/core/java/com/android/internal/widget/IMessagingLayout.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 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.internal.widget; + +import android.content.Context; + +import java.util.ArrayList; + +/** + * An interface for a MessagingLayout + */ +public interface IMessagingLayout { + + /** + * @return the layout containing the messages + */ + MessagingLinearLayout getMessagingLinearLayout(); + + /** + * @return the context of this view + */ + Context getContext(); + + /** + * @return the list of messaging groups + */ + ArrayList<MessagingGroup> getMessagingGroups(); +} diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index c9a916187d33..99779032eebc 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -55,8 +55,9 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou private static Pools.SimplePool<MessagingGroup> sInstancePool = new Pools.SynchronizedPool<>(10); private MessagingLinearLayout mMessageContainer; - private ImageFloatingTextView mSenderName; + ImageFloatingTextView mSenderView; private ImageView mAvatarView; + private View mAvatarContainer; private String mAvatarSymbol = ""; private int mLayoutColor; private CharSequence mAvatarName = ""; @@ -72,10 +73,18 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou private boolean mImagesAtEnd; private ViewGroup mImageContainer; private MessagingImageMessage mIsolatedMessage; - private boolean mTransformingImages; + private boolean mClippingDisabled; private Point mDisplaySize = new Point(); private ProgressBar mSendingSpinner; private View mSendingSpinnerContainer; + private boolean mShowingAvatar = true; + private CharSequence mSenderName; + private boolean mSingleLine = false; + private LinearLayout mContentContainer; + private int mRequestedMaxDisplayedLines = Integer.MAX_VALUE; + private int mSenderTextPaddingSingleLine; + private boolean mIsFirstGroupInLayout = true; + private boolean mCanHideSenderIfFirst; public MessagingGroup(@NonNull Context context) { super(context); @@ -99,26 +108,34 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou protected void onFinishInflate() { super.onFinishInflate(); mMessageContainer = findViewById(R.id.group_message_container); - mSenderName = findViewById(R.id.message_name); + mSenderView = findViewById(R.id.message_name); mAvatarView = findViewById(R.id.message_icon); mImageContainer = findViewById(R.id.messaging_group_icon_container); mSendingSpinner = findViewById(R.id.messaging_group_sending_progress); + mContentContainer = findViewById(R.id.messaging_group_content_container); mSendingSpinnerContainer = findViewById(R.id.messaging_group_sending_progress_container); DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); mDisplaySize.x = displayMetrics.widthPixels; mDisplaySize.y = displayMetrics.heightPixels; + mSenderTextPaddingSingleLine = getResources().getDimensionPixelSize( + R.dimen.messaging_group_singleline_sender_padding_end); } public void updateClipRect() { // We want to clip to the senderName if it's available, otherwise our images will come // from a weird position Rect clipRect; - if (mSenderName.getVisibility() != View.GONE && !mTransformingImages) { - ViewGroup parent = (ViewGroup) mSenderName.getParent(); - int top = getDistanceFromParent(mSenderName, parent) - getDistanceFromParent( - mMessageContainer, parent) + mSenderName.getHeight(); + if (mSenderView.getVisibility() != View.GONE && !mClippingDisabled) { + int top; + if (mSingleLine) { + top = 0; + } else { + top = getDistanceFromParent(mSenderView, mContentContainer) + - getDistanceFromParent(mMessageContainer, mContentContainer) + + mSenderView.getHeight(); + } int size = Math.max(mDisplaySize.x, mDisplaySize.y); - clipRect = new Rect(0, top, size, size); + clipRect = new Rect(-size, top, size, size); } else { clipRect = null; } @@ -140,17 +157,31 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou if (nameOverride == null) { nameOverride = sender.getName(); } - mSenderName.setText(nameOverride); + mSenderName = nameOverride; + if (mSingleLine && !TextUtils.isEmpty(nameOverride)) { + nameOverride = mContext.getResources().getString( + R.string.conversation_single_line_name_display, nameOverride); + } + mSenderView.setText(nameOverride); mNeedsGeneratedAvatar = sender.getIcon() == null; if (!mNeedsGeneratedAvatar) { setAvatar(sender.getIcon()); } - mAvatarView.setVisibility(VISIBLE); - mSenderName.setVisibility(TextUtils.isEmpty(nameOverride) ? GONE : VISIBLE); + updateSenderVisibility(); + } + + /** + * Should the avatar be shown for this view. + * + * @param showingAvatar should it be shown + */ + public void setShowingAvatar(boolean showingAvatar) { + mAvatarView.setVisibility(showingAvatar ? VISIBLE : GONE); + mShowingAvatar = showingAvatar; } public void setSending(boolean sending) { - int visibility = sending ? View.VISIBLE : View.GONE; + int visibility = sending ? VISIBLE : GONE; if (mSendingSpinnerContainer.getVisibility() != visibility) { mSendingSpinnerContainer.setVisibility(visibility); updateMessageColor(); @@ -171,7 +202,9 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public void setAvatar(Icon icon) { mAvatarIcon = icon; - mAvatarView.setImageIcon(icon); + if (mShowingAvatar || icon == null) { + mAvatarView.setImageIcon(icon); + } mAvatarSymbol = ""; mAvatarName = ""; } @@ -220,13 +253,20 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou setAvatar(null); mAvatarView.setAlpha(1.0f); mAvatarView.setTranslationY(0.0f); - mSenderName.setAlpha(1.0f); - mSenderName.setTranslationY(0.0f); + mSenderView.setAlpha(1.0f); + mSenderView.setTranslationY(0.0f); setAlpha(1.0f); mIsolatedMessage = null; mMessages = null; + mSenderName = null; mAddedMessages.clear(); mFirstLayout = true; + setCanHideSenderIfFirst(false); + setIsFirstInLayout(true); + + setMaxDisplayedLines(Integer.MAX_VALUE); + setSingleLine(false); + setShowingAvatar(true); MessagingPropertyAnimator.recycle(this); sInstancePool.release(MessagingGroup.this); } @@ -252,7 +292,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } public CharSequence getSenderName() { - return mSenderName.getText(); + return mSenderName; } public static void dropCache() { @@ -310,7 +350,12 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou @Override public void setMaxDisplayedLines(int lines) { - mMessageContainer.setMaxDisplayedLines(lines); + mRequestedMaxDisplayedLines = lines; + updateMaxDisplayedLines(); + } + + private void updateMaxDisplayedLines() { + mMessageContainer.setMaxDisplayedLines(mSingleLine ? 1 : mRequestedMaxDisplayedLines); } @Override @@ -324,6 +369,35 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return mIsHidingAnimated; } + @Override + public void setIsFirstInLayout(boolean first) { + if (first != mIsFirstGroupInLayout) { + mIsFirstGroupInLayout = first; + updateSenderVisibility(); + } + } + + /** + * @param canHide true if the sender can be hidden if it is first + */ + public void setCanHideSenderIfFirst(boolean canHide) { + if (mCanHideSenderIfFirst != canHide) { + mCanHideSenderIfFirst = canHide; + updateSenderVisibility(); + } + } + + private void updateSenderVisibility() { + boolean hidden = (mIsFirstGroupInLayout || mSingleLine) && mCanHideSenderIfFirst + || TextUtils.isEmpty(mSenderName); + mSenderView.setVisibility(hidden ? GONE : VISIBLE); + } + + @Override + public boolean hasDifferentHeightWhenFirst() { + return mCanHideSenderIfFirst && !mSingleLine && !TextUtils.isEmpty(mSenderName); + } + private void setIsHidingAnimated(boolean isHiding) { ViewParent parent = getParent(); mIsHidingAnimated = isHiding; @@ -362,7 +436,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou mTextColor = messageTextColor; mSendingTextColor = calculateSendingTextColor(); updateMessageColor(); - mSenderName.setTextColor(senderTextColor); + mSenderView.setTextColor(senderTextColor); } public void setLayoutColor(int layoutColor) { @@ -506,13 +580,17 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } public View getSenderView() { - return mSenderName; + return mSenderView; } public View getAvatar() { return mAvatarView; } + public Icon getAvatarIcon() { + return mAvatarIcon; + } + public MessagingLinearLayout getMessageContainer() { return mMessageContainer; } @@ -529,8 +607,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return mSender; } - public void setTransformingImages(boolean transformingImages) { - mTransformingImages = transformingImages; + public void setClippingDisabled(boolean disabled) { + mClippingDisabled = disabled; } public void setDisplayImagesAtEnd(boolean atEnd) { @@ -543,4 +621,27 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public List<MessagingMessage> getMessages() { return mMessages; } + + /** + * Set this layout to be single line and therefore displaying both the sender and the text on + * the same line. + * + * @param singleLine should be layout be single line + */ + public void setSingleLine(boolean singleLine) { + if (singleLine != mSingleLine) { + mSingleLine = singleLine; + mContentContainer.setOrientation( + singleLine ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); + MarginLayoutParams layoutParams = (MarginLayoutParams) mSenderView.getLayoutParams(); + layoutParams.setMarginEnd(singleLine ? mSenderTextPaddingSingleLine : 0); + updateMaxDisplayedLines(); + updateClipRect(); + updateSenderVisibility(); + } + } + + public boolean isSingleLine() { + return mSingleLine; + } } diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java index 64650a7ebc2f..c243f3b583e5 100644 --- a/core/java/com/android/internal/widget/MessagingImageMessage.java +++ b/core/java/com/android/internal/widget/MessagingImageMessage.java @@ -120,7 +120,7 @@ public class MessagingImageMessage extends ImageView implements MessagingMessage return true; } - static MessagingMessage createMessage(MessagingLayout layout, + static MessagingMessage createMessage(IMessagingLayout layout, Notification.MessagingStyle.Message m, ImageResolver resolver) { MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout(); MessagingImageMessage createdMessage = sInstancePool.acquire(); diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index f6089589a994..503f3f161c96 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -58,7 +58,8 @@ import java.util.regex.Pattern; * messages and adapts the layout accordingly. */ @RemoteViews.RemoteView -public class MessagingLayout extends FrameLayout implements ImageMessageConsumer { +public class MessagingLayout extends FrameLayout + implements ImageMessageConsumer, IMessagingLayout { private static final float COLOR_SHIFT_AMOUNT = 60; /** @@ -143,9 +144,29 @@ public class MessagingLayout extends FrameLayout implements ImageMessageConsumer mNameReplacement = nameReplacement; } + /** + * Set this layout to show the collapsed representation. + * + * @param isCollapsed is it collapsed + */ + @RemotableViewMethod + public void setIsCollapsed(boolean isCollapsed) { + mDisplayImagesAtEnd = isCollapsed; + } + + @RemotableViewMethod + public void setLargeIcon(Icon largeIcon) { + // Unused + } + + /** + * Sets the conversation title of this conversation. + * + * @param conversationTitle the conversation title + */ @RemotableViewMethod - public void setDisplayImagesAtEnd(boolean atEnd) { - mDisplayImagesAtEnd = atEnd; + public void setConversationTitle(CharSequence conversationTitle) { + // Unused } @RemotableViewMethod @@ -371,6 +392,15 @@ public class MessagingLayout extends FrameLayout implements ImageMessageConsumer mSenderTextColor = color; } + + /** + * @param color the color of the notification background + */ + @RemotableViewMethod + public void setNotificationBackgroundColor(int color) { + // Nothing to do with this + } + @RemotableViewMethod public void setMessageTextColor(int color) { mMessageTextColor = color; diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index 0c8613b460f6..ac04862d9a7d 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -43,7 +43,7 @@ public class MessagingLinearLayout extends ViewGroup { private int mMaxDisplayedLines = Integer.MAX_VALUE; - private MessagingLayout mMessagingLayout; + private IMessagingLayout mMessagingLayout; public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -84,6 +84,11 @@ public class MessagingLinearLayout extends ViewGroup { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.hide = true; + if (child instanceof MessagingChild) { + MessagingChild messagingChild = (MessagingChild) child; + // Whenever we encounter the message first, it's always first in the layout + messagingChild.setIsFirstInLayout(true); + } } totalHeight = mPaddingTop + mPaddingBottom; @@ -91,6 +96,11 @@ public class MessagingLinearLayout extends ViewGroup { int linesRemaining = mMaxDisplayedLines; // Starting from the bottom: we measure every view as if it were the only one. If it still // fits, we take it, otherwise we stop there. + MessagingChild previousChild = null; + View previousView = null; + int previousChildHeight = 0; + int previousTotalHeight = 0; + int previousLinesConsumed = 0; for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) { if (getChildAt(i).getVisibility() == GONE) { continue; @@ -99,7 +109,16 @@ public class MessagingLinearLayout extends ViewGroup { LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); MessagingChild messagingChild = null; int spacing = mSpacing; + int previousChildIncrease = 0; if (child instanceof MessagingChild) { + // We need to remeasure the previous child again if it's not the first anymore + if (previousChild != null && previousChild.hasDifferentHeightWhenFirst()) { + previousChild.setIsFirstInLayout(false); + measureChildWithMargins(previousView, widthMeasureSpec, 0, heightMeasureSpec, + previousTotalHeight - previousChildHeight); + previousChildIncrease = previousView.getMeasuredHeight() - previousChildHeight; + linesRemaining -= previousChild.getConsumedLines() - previousLinesConsumed; + } messagingChild = (MessagingChild) child; messagingChild.setMaxDisplayedLines(linesRemaining); spacing += messagingChild.getExtraSpacing(); @@ -110,18 +129,26 @@ public class MessagingLinearLayout extends ViewGroup { final int childHeight = child.getMeasuredHeight(); int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin + - lp.bottomMargin + spacing); + lp.bottomMargin + spacing + previousChildIncrease); int measureType = MessagingChild.MEASURED_NORMAL; if (messagingChild != null) { measureType = messagingChild.getMeasuredType(); - linesRemaining -= messagingChild.getConsumedLines(); } // We never measure the first item as too small, we want to at least show something. boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL && !first; boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED || measureType == MessagingChild.MEASURED_TOO_SMALL && first; - if (newHeight <= targetHeight && !isTooSmall) { + boolean showView = newHeight <= targetHeight && !isTooSmall; + if (showView) { + if (messagingChild != null) { + previousLinesConsumed = messagingChild.getConsumedLines(); + linesRemaining -= previousLinesConsumed; + previousChild = messagingChild; + previousView = child; + previousChildHeight = childHeight; + previousTotalHeight = totalHeight; + } totalHeight = newHeight; measuredWidth = Math.max(measuredWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin @@ -131,6 +158,16 @@ public class MessagingLinearLayout extends ViewGroup { break; } } else { + // We now became too short, let's make sure to reset any previous views to be first + // and remeasure it. + if (previousChild != null && previousChild.hasDifferentHeightWhenFirst()) { + previousChild.setIsFirstInLayout(true); + // We need to remeasure the previous child again since it became first + measureChildWithMargins(previousView, widthMeasureSpec, 0, heightMeasureSpec, + previousTotalHeight - previousChildHeight); + // The totalHeight is already correct here since we only set it during the + // first pass + } break; } first = false; @@ -255,11 +292,11 @@ public class MessagingLinearLayout extends ViewGroup { mMaxDisplayedLines = numberLines; } - public void setMessagingLayout(MessagingLayout layout) { + public void setMessagingLayout(IMessagingLayout layout) { mMessagingLayout = layout; } - public MessagingLayout getMessagingLayout() { + public IMessagingLayout getMessagingLayout() { return mMessagingLayout; } @@ -273,6 +310,20 @@ public class MessagingLinearLayout extends ViewGroup { void setMaxDisplayedLines(int lines); void hideAnimated(); boolean isHidingAnimated(); + + /** + * Set that this view is first in layout. Relevant and only set if + * {@link #hasDifferentHeightWhenFirst()}. + * @param first is this first? + */ + default void setIsFirstInLayout(boolean first) {} + + /** + * @return if this layout has different height it is first in the layout + */ + default boolean hasDifferentHeightWhenFirst() { + return false; + } default int getExtraSpacing() { return 0; } diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java index c32d3705bba7..8c8437951402 100644 --- a/core/java/com/android/internal/widget/MessagingMessage.java +++ b/core/java/com/android/internal/widget/MessagingMessage.java @@ -32,7 +32,7 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild { **/ String IMAGE_MIME_TYPE_PREFIX = "image/"; - static MessagingMessage createMessage(MessagingLayout layout, + static MessagingMessage createMessage(IMessagingLayout layout, Notification.MessagingStyle.Message m, ImageResolver resolver) { if (hasImage(m) && !ActivityManager.isLowRamDeviceStatic()) { return MessagingImageMessage.createMessage(layout, m, resolver); diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java index 4081a866f993..d778c5967046 100644 --- a/core/java/com/android/internal/widget/MessagingTextMessage.java +++ b/core/java/com/android/internal/widget/MessagingTextMessage.java @@ -26,14 +26,10 @@ import android.text.Layout; import android.util.AttributeSet; import android.util.Pools; import android.view.LayoutInflater; -import android.view.ViewGroup; -import android.view.ViewParent; import android.widget.RemoteViews; import com.android.internal.R; -import java.util.Objects; - /** * A message of a {@link MessagingLayout}. */ @@ -74,7 +70,7 @@ public class MessagingTextMessage extends ImageFloatingTextView implements Messa return true; } - static MessagingMessage createMessage(MessagingLayout layout, + static MessagingMessage createMessage(IMessagingLayout layout, Notification.MessagingStyle.Message m) { MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout(); MessagingTextMessage createdMessage = sInstancePool.acquire(); diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java index 39f82a5fb349..a49980696e6b 100644 --- a/core/java/com/android/internal/widget/NotificationExpandButton.java +++ b/core/java/com/android/internal/widget/NotificationExpandButton.java @@ -20,7 +20,7 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; -import android.view.View; +import android.view.RemotableViewMethod; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Button; import android.widget.ImageView; @@ -32,6 +32,8 @@ import android.widget.RemoteViews; @RemoteViews.RemoteView public class NotificationExpandButton extends ImageView { + private int mOriginalNotificationColor; + public NotificationExpandButton(Context context) { super(context); } @@ -56,6 +58,15 @@ public class NotificationExpandButton extends ImageView { extendRectToMinTouchSize(outRect); } + @RemotableViewMethod + public void setOriginalNotificationColor(int color) { + mOriginalNotificationColor = color; + } + + public int getOriginalNotificationColor() { + return mOriginalNotificationColor; + } + private void extendRectToMinTouchSize(Rect rect) { int touchTargetSize = (int) (getResources().getDisplayMetrics().density * 48); rect.left = rect.centerX() - touchTargetSize / 2; diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java index e352b45ef413..7b154a54fc85 100644 --- a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java +++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java @@ -23,6 +23,8 @@ import android.view.View; import android.widget.LinearLayout; import android.widget.RemoteViews; +import java.util.ArrayList; + /** * A LinearLayout that sets it's height again after the last measure pass. This is needed for * MessagingLayouts where groups need to be able to snap it's height to. @@ -30,6 +32,8 @@ import android.widget.RemoteViews; @RemoteViews.RemoteView public class RemeasuringLinearLayout extends LinearLayout { + private ArrayList<View> mMatchParentViews = new ArrayList<>(); + public RemeasuringLinearLayout(Context context) { super(context); } @@ -53,6 +57,8 @@ public class RemeasuringLinearLayout extends LinearLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); int height = 0; + boolean isVertical = getOrientation() == LinearLayout.VERTICAL; + boolean isWrapContent = getLayoutParams().height == LayoutParams.WRAP_CONTENT; for (int i = 0; i < count; ++i) { final View child = getChildAt(i); if (child == null || child.getVisibility() == View.GONE) { @@ -60,9 +66,25 @@ public class RemeasuringLinearLayout extends LinearLayout { } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - height = Math.max(height, height + child.getMeasuredHeight() + lp.topMargin + - lp.bottomMargin); + if (!isWrapContent || lp.height != LayoutParams.MATCH_PARENT || isVertical) { + int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; + height = Math.max(height, isVertical ? height + childHeight : childHeight); + } else { + // We have match parent children in a wrap content view, let's measure the + // view properly + mMatchParentViews.add(child); + } + } + if (mMatchParentViews.size() > 0) { + int exactHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + for (View child : mMatchParentViews) { + child.measure(getChildMeasureSpec( + widthMeasureSpec, getPaddingStart() + getPaddingEnd(), + child.getLayoutParams().width), + exactHeightSpec); + } } + mMatchParentViews.clear(); setMeasuredDimension(getMeasuredWidth(), height); } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4001defe51a9..7a3ec9555f7c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3756,6 +3756,12 @@ <permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS" android:protectionLevel="signature|installer" /> + <!-- @SystemApi Allows an application to an exempt an app from having its permission be + auto-revoked when unused for an extended period of time. + @hide --> + <permission android:name="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS" + android:protectionLevel="signature|installer" /> + <!-- @hide Allows an application to observe permission changes. --> <permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" android:protectionLevel="signature|privileged" /> diff --git a/core/res/res/drawable/conversation_badge_background.xml b/core/res/res/drawable/conversation_badge_background.xml new file mode 100644 index 000000000000..0dd0dcda40fb --- /dev/null +++ b/core/res/res/drawable/conversation_badge_background.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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 + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + + <solid + android:color="#ffffff"/> + + <size + android:width="26dp" + android:height="26dp"/> +</shape> + diff --git a/core/res/res/drawable/ic_collapse_notification.xml b/core/res/res/drawable/ic_collapse_notification.xml index 124e99e3a4bb..ca4f0ed27a0b 100644 --- a/core/res/res/drawable/ic_collapse_notification.xml +++ b/core/res/res/drawable/ic_collapse_notification.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -Copyright (C) 2015 The Android Open Source Project +Copyright (C) 2020 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. @@ -15,11 +15,14 @@ Copyright (C) 2015 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="14.0dp" - android:height="14.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:width="22.0dp" + android:height="22.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> <path android:fillColor="#FF000000" - android:pathData="M12.0,8.0l-6.0,6.0l1.4,1.4l4.6,-4.6l4.6,4.6L18.0,14.0L12.0,8.0z"/> -</vector> + android:pathData="M18.59,16.41L20.0,15.0l-8.0,-8.0 -8.0,8.0 1.41,1.41L12.0,9.83"/> + <path + android:pathData="M0 0h24v24H0V0z" + android:fillColor="#00000000"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_expand_notification.xml b/core/res/res/drawable/ic_expand_notification.xml index 847e3269398d..a080ce43cfec 100644 --- a/core/res/res/drawable/ic_expand_notification.xml +++ b/core/res/res/drawable/ic_expand_notification.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -Copyright (C) 2015 The Android Open Source Project +Copyright (C) 2014 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. @@ -15,11 +15,14 @@ Copyright (C) 2015 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="14.0dp" - android:height="14.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:width="22.0dp" + android:height="22.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> <path android:fillColor="#FF000000" - android:pathData="M16.6,8.6L12.0,13.2L7.4,8.6L6.0,10.0l6.0,6.0l6.0,-6.0L16.6,8.6z"/> -</vector> + android:pathData="M5.41,7.59L4.0,9.0l8.0,8.0 8.0,-8.0 -1.41,-1.41L12.0,14.17"/> + <path + android:pathData="M24 24H0V0h24v24z" + android:fillColor="#00000000"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/layout/conversation_face_pile_layout.xml b/core/res/res/layout/conversation_face_pile_layout.xml new file mode 100644 index 000000000000..1db38702f926 --- /dev/null +++ b/core/res/res/layout/conversation_face_pile_layout.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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 + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/conversation_face_pile" + android:layout_width="@dimen/conversation_avatar_size" + android:layout_height="@dimen/conversation_avatar_size" + android:forceHasOverlappingRendering="false" + > + <ImageView + android:id="@+id/conversation_face_pile_top" + android:layout_width="36dp" + android:layout_height="36dp" + android:scaleType="centerCrop" + android:layout_gravity="end|top" + /> + <FrameLayout + android:id="@+id/conversation_face_pile_bottom_background" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_gravity="start|bottom" + android:background="@drawable/conversation_badge_background"> + <ImageView + android:id="@+id/conversation_face_pile_bottom" + android:layout_width="36dp" + android:layout_height="36dp" + android:scaleType="centerCrop" + android:layout_gravity="center" + /> + </FrameLayout> +</FrameLayout> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index f5fa1b6a795a..6f36aae8a1d4 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -91,7 +91,6 @@ android:textAppearance="@style/TextAppearance.Material.Notification.Time" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center" android:layout_marginStart="@dimen/notification_header_separating_margin" android:layout_marginEnd="@dimen/notification_header_separating_margin" android:showRelative="true" diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml new file mode 100644 index 000000000000..dc52e979a310 --- /dev/null +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -0,0 +1,205 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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 + --> +<com.android.internal.widget.ConversationLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:tag="conversation" + android:theme="@style/Theme.DeviceDefault.Notification" + > + + <FrameLayout + android:layout_width="@dimen/conversation_content_start" + android:layout_height="wrap_content" + android:gravity="start|top" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingTop="12dp" + > + + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + > + + <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right --> + <ImageView + android:id="@+id/conversation_icon" + android:layout_width="@dimen/conversation_avatar_size" + android:layout_height="@dimen/conversation_avatar_size" + android:scaleType="centerCrop" + android:importantForAccessibility="no" + /> + + <ViewStub + android:layout="@layout/conversation_face_pile_layout" + android:layout_width="@dimen/conversation_avatar_size" + android:layout_height="@dimen/conversation_avatar_size" + android:id="@+id/conversation_face_pile" + /> + + <FrameLayout + android:id="@+id/conversation_icon_badge" + android:layout_width="20dp" + android:layout_height="20dp" + android:layout_marginLeft="@dimen/conversation_badge_side_margin" + android:layout_marginTop="@dimen/conversation_badge_side_margin" + android:background="@drawable/conversation_badge_background" > + <!-- Badge: 20x20, 48dp padding left + top --> + <com.android.internal.widget.CachingIconView + android:id="@+id/icon" + android:layout_width="@dimen/conversation_icon_size_badged" + android:layout_height="@dimen/conversation_icon_size_badged" + android:layout_gravity="center" + /> + </FrameLayout> + </FrameLayout> + </FrameLayout> + + <!-- Wraps entire "expandable" notification --> + <com.android.internal.widget.RemeasuringLinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:clipToPadding="false" + android:clipChildren="false" + android:orientation="vertical" + > + <!-- LinearLayout for Expand Button--> + <com.android.internal.widget.RemeasuringLinearLayout + android:id="@+id/expand_button_and_content_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="start|top" + android:orientation="horizontal" + android:clipChildren="false" + android:clipToPadding="false"> + <!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc--> + <FrameLayout + android:id="@+id/notification_action_list_margin_target" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"> + + <!-- Header --> + <LinearLayout + android:id="@+id/conversation_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingTop="16dp" + android:paddingStart="@dimen/conversation_content_start" + > + <TextView + android:id="@+id/conversation_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" + android:textSize="16sp" + android:singleLine="true" + /> + + <TextView + android:id="@+id/time_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?attr/notificationHeaderTextAppearance" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:text="@string/notification_header_divider_symbol" + android:layout_gravity="center" + android:paddingTop="1sp" + android:singleLine="true" + android:visibility="gone" + /> + + <DateTimeView + android:id="@+id/time" + android:textAppearance="@style/TextAppearance.Material.Notification.Time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:paddingTop="1sp" + android:showRelative="true" + android:singleLine="true" + android:visibility="gone" + /> + + <ImageView + android:id="@+id/profile_badge" + android:layout_width="@dimen/notification_badge_size" + android:layout_height="@dimen/notification_badge_size" + android:layout_gravity="center" + android:layout_marginStart="4dp" + android:paddingTop="2dp" + android:scaleType="fitCenter" + android:visibility="gone" + android:contentDescription="@string/notification_work_profile_content_description" + /> + </LinearLayout> + + <!-- Messages --> + <com.android.internal.widget.MessagingLinearLayout + android:id="@+id/notification_messaging" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="40dp" + android:spacing="@dimen/notification_messaging_spacing" + android:clipToPadding="false" + android:clipChildren="false" + /> + </FrameLayout> + <!-- Unread Count --> + <!-- <TextView /> --> + + <!-- This is where the expand button will be placed when collapsed--> + </com.android.internal.widget.RemeasuringLinearLayout> + + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_content_margin" + android:layout_marginStart="@dimen/conversation_content_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" /> + <include layout="@layout/notification_material_action_list" /> + </com.android.internal.widget.RemeasuringLinearLayout> + + <!--This is dynamically placed between here and at the end of the layout--> + <FrameLayout + android:id="@+id/expand_button_container" + android:layout_width="wrap_content" + android:layout_height="@dimen/conversation_expand_button_expanded_size" + android:layout_gravity="end|top" + android:paddingStart="16dp" + android:paddingEnd="@dimen/notification_content_margin_end"> + <com.android.internal.widget.NotificationExpandButton + android:id="@+id/expand_button" + android:layout_width="@dimen/notification_header_expand_icon_size" + android:layout_height="@dimen/notification_header_expand_icon_size" + android:layout_gravity="center" + android:drawable="@drawable/ic_expand_notification" + android:clickable="false" + android:importantForAccessibility="no" + /> + </FrameLayout> +</com.android.internal.widget.ConversationLayout> diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml index 483b479538a1..15146c073e46 100644 --- a/core/res/res/layout/notification_template_messaging_group.xml +++ b/core/res/res/layout/notification_template_messaging_group.xml @@ -20,14 +20,19 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > - <ImageView - android:id="@+id/message_icon" - android:layout_width="@dimen/messaging_avatar_size" - android:layout_height="@dimen/messaging_avatar_size" - android:layout_marginEnd="12dp" - android:scaleType="centerCrop" - android:importantForAccessibility="no" /> + <FrameLayout + android:layout_width="@dimen/conversation_content_start" + android:layout_height="wrap_content"> <!--TODO: make sure to make this padding dynamic--> + <ImageView + android:layout_gravity="top|center_horizontal" + android:id="@+id/message_icon" + android:layout_width="@dimen/messaging_avatar_size" + android:layout_height="@dimen/messaging_avatar_size" + android:scaleType="centerCrop" + android:importantForAccessibility="no" /> + </FrameLayout> <com.android.internal.widget.RemeasuringLinearLayout + android:id="@+id/messaging_group_content_container" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" @@ -43,7 +48,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/notification_text_margin_top" - android:spacing="2dp"/> + android:spacing="2dp" /> </com.android.internal.widget.RemeasuringLinearLayout> <FrameLayout android:id="@+id/messaging_group_icon_container" diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index cb88aee33bfc..f3ca5ac9a4c4 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1831,6 +1831,19 @@ <attr name="enableGwpAsan" /> + <!-- If {@code true} allow requesting that its permissions don't get automatically + revoked when the app is unused for an extended amount of time. + + The default value is {@code false}. --> + <attr name="requestDontAutoRevokePermissions" format="boolean" /> + + <!-- If {@code true} its permissions shouldn't get automatically + revoked when the app is unused for an extended amount of time. + + This implies {@code requestDontAutoRevokePermissions=true} + + The default value is {@code false}. --> + <attr name="allowDontAutoRevokePermissions" format="boolean" /> </declare-styleable> <!-- An attribution is a logical part of an app and is identified by a tag. diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 2faa0c9c8036..e3fe982c226d 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -682,7 +682,25 @@ <!-- The size of the right icon image when on low ram --> <dimen name="notification_right_icon_size_low_ram">@dimen/notification_right_icon_size</dimen> - <dimen name="messaging_avatar_size">52dp</dimen> + <dimen name="messaging_avatar_size">@dimen/notification_right_icon_size</dimen> + <dimen name="conversation_avatar_size">52dp</dimen> + <!-- Start of the content in the conversation template --> + <dimen name="conversation_content_start">80dp</dimen> + <!-- Size of the expand button when expanded --> + <dimen name="conversation_expand_button_expanded_size">80dp</dimen> + <!-- Top margin of the expand button for conversations when expanded --> + <dimen name="conversation_expand_button_top_margin_expanded">18dp</dimen> + <!-- Side margins of the conversation badge in relation to the conversation icon --> + <dimen name="conversation_badge_side_margin">36dp</dimen> + <!-- size of the notification icon when badged in a conversation --> + <dimen name="conversation_icon_size_badged">15dp</dimen> + <!-- size of the notification icon when centered in a conversation --> + <dimen name="conversation_icon_size_centered">20dp</dimen> + <!-- margin on the top when the icon is centered for group conversations --> + <dimen name="conversation_icon_margin_top_centered">5dp</dimen> + + <!-- Padding between text and sender when singleline --> + <dimen name="messaging_group_singleline_sender_padding_end">4dp</dimen> <dimen name="messaging_group_sending_progress_size">24dp</dimen> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 96807ccc2b22..7230cc49ac5b 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3014,6 +3014,8 @@ <!-- @hide @SystemApi --> <public name="minExtensionVersion" /> <public name="allowNativeHeapPointerTagging" /> + <public name="requestDontAutoRevokePermissions" /> + <public name="allowDontAutoRevokePermissions" /> <public name="preserveLegacyExternalStorage" /> <public name="mimeGroup" /> <public name="enableGwpAsan" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 789628d63d1b..f101f590cab1 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5440,6 +5440,15 @@ <string name="as_app_forced_to_restricted_bucket"> <xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string> + <!-- The way a conversation name is displayed when single line. The text will be displayed to the end of this text with some spacing --> + <string name="conversation_single_line_name_display"><xliff:g id="sender_name" example="Sara">%1$s</xliff:g>:</string> + + <!-- Conversation Title fallback if the there is no name provided in a 1:1 conversation [CHAR LIMIT=40]--> + <string name="conversation_title_fallback_one_to_one">Conversation</string> + + <!-- Conversation Title fallback if the there is no name provided in a group chat conversation [CHAR LIMIT=40]--> + <string name="conversation_title_fallback_group_chat">Group Conversation</string> + <!-- ResolverActivity - profile tabs --> <!-- Label of a tab on a screen. A user can tap this tap to switch to the 'Personal' view (that shows their personal content) if they have a work profile on their device. [CHAR LIMIT=NONE] --> <string name="resolver_personal_tab">Personal</string> diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml index 966f495c96e5..64768cf4c730 100644 --- a/core/res/res/values/styles_device_defaults.xml +++ b/core/res/res/values/styles_device_defaults.xml @@ -146,7 +146,7 @@ easier. <item name="textAppearance">@style/TextAppearance.DeviceDefault.Notification</item> </style> <style name="Widget.DeviceDefault.Notification.MessagingName" parent="Widget.Material.Notification.MessagingName"> - <item name="textAppearance">@style/TextAppearance.DeviceDefault.Notification.MessagingName</item> + <item name="textAppearance">@style/TextAppearance.DeviceDefault.Notification.Title</item> </style> <style name="Widget.DeviceDefault.PreferenceFrameLayout" parent="Widget.Material.PreferenceFrameLayout"/> <style name="Widget.DeviceDefault.ProgressBar.Inverse" parent="Widget.Material.ProgressBar.Inverse"/> @@ -290,9 +290,6 @@ easier. <style name="TextAppearance.DeviceDefault.Notification.Title" parent="TextAppearance.Material.Notification.Title"> <item name="fontFamily">@string/config_headlineFontFamilyMedium</item> </style> - <style name="TextAppearance.DeviceDefault.Notification.MessagingName" parent="TextAppearance.DeviceDefault.Notification.Title"> - <item name="textSize">16sp</item> - </style> <style name="TextAppearance.DeviceDefault.Notification.Reply" parent="TextAppearance.Material.Notification.Reply"> <item name="fontFamily">@string/config_bodyFontFamily</item> </style> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 63ac0e6bfc3e..2415837cf826 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -504,7 +504,7 @@ please see styles_device_defaults.xml. <style name="Widget.Material.Notification.MessagingText" parent="Widget.Material.Notification.Text"> <item name="layout_width">wrap_content</item> <item name="layout_height">wrap_content</item> - <item name="ellipsize">end</item>z + <item name="ellipsize">end</item> </style> <style name="Widget.Material.Notification.MessagingName" parent="Widget.Material.Light.TextView"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a9008d78e19a..49a0f17aad1a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3876,6 +3876,30 @@ <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionPkgs" /> <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionProcStates" /> + <java-symbol type="string" name="conversation_single_line_name_display" /> + <java-symbol type="string" name="conversation_title_fallback_one_to_one" /> + <java-symbol type="string" name="conversation_title_fallback_group_chat" /> + <java-symbol type="id" name="conversation_icon" /> + <java-symbol type="id" name="conversation_icon_badge" /> + <java-symbol type="id" name="expand_button_container" /> + <java-symbol type="id" name="messaging_group_content_container" /> + <java-symbol type="id" name="expand_button_and_content_container" /> + <java-symbol type="id" name="conversation_header" /> + <java-symbol type="id" name="conversation_face_pile_bottom_background" /> + <java-symbol type="id" name="conversation_face_pile_bottom" /> + <java-symbol type="id" name="conversation_face_pile_top" /> + <java-symbol type="id" name="conversation_face_pile" /> + <java-symbol type="id" name="conversation_text" /> + <java-symbol type="dimen" name="conversation_expand_button_top_margin_expanded" /> + <java-symbol type="dimen" name="conversation_expand_button_expanded_size" /> + <java-symbol type="dimen" name="messaging_group_singleline_sender_padding_end" /> + <java-symbol type="dimen" name="conversation_badge_side_margin" /> + <java-symbol type="dimen" name="conversation_icon_size_badged" /> + <java-symbol type="dimen" name="conversation_icon_size_centered" /> + <java-symbol type="dimen" name="conversation_icon_margin_top_centered" /> + <java-symbol type="layout" name="notification_template_material_conversation" /> + <java-symbol type="layout" name="conversation_face_pile_layout" /> + <!-- Intent resolver and share sheet --> <java-symbol type="color" name="resolver_tabs_active_color" /> <java-symbol type="color" name="resolver_tabs_inactive_color" /> diff --git a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java index 44f64079d831..37b2817928aa 100644 --- a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java +++ b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java @@ -37,6 +37,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -129,6 +131,48 @@ public class SourceStampVerifierTest { assertNull(result.getCertificate()); } + @Test + public void testSourceStamp_multiApk_validStamps() throws Exception { + File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk"); + File testApk2 = getApk("SourceStampVerifierTest/valid-stamp.apk"); + ZipFile apkZipFile = new ZipFile(testApk1); + ZipEntry stampCertZipEntry = apkZipFile.getEntry("stamp-cert-sha256"); + int size = (int) stampCertZipEntry.getSize(); + byte[] expectedStampCertHash = new byte[size]; + try (InputStream inputStream = apkZipFile.getInputStream(stampCertZipEntry)) { + inputStream.read(expectedStampCertHash); + } + List<String> apkFiles = new ArrayList<>(); + apkFiles.add(testApk1.getAbsolutePath()); + apkFiles.add(testApk2.getAbsolutePath()); + + SourceStampVerificationResult result = + SourceStampVerifier.verify(apkFiles); + + assertTrue(result.isPresent()); + assertTrue(result.isVerified()); + assertNotNull(result.getCertificate()); + byte[] actualStampCertHash = + MessageDigest.getInstance("SHA-256").digest(result.getCertificate().getEncoded()); + assertArrayEquals(expectedStampCertHash, actualStampCertHash); + } + + @Test + public void testSourceStamp_multiApk_invalidStamps() throws Exception { + File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk"); + File testApk2 = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch.apk"); + List<String> apkFiles = new ArrayList<>(); + apkFiles.add(testApk1.getAbsolutePath()); + apkFiles.add(testApk2.getAbsolutePath()); + + SourceStampVerificationResult result = + SourceStampVerifier.verify(apkFiles); + + assertTrue(result.isPresent()); + assertFalse(result.isVerified()); + assertNull(result.getCertificate()); + } + private File getApk(String apkPath) throws IOException { File testApk = File.createTempFile("SourceStampApk", ".apk"); try (InputStream inputStream = mContext.getAssets().open(apkPath)) { diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index 02a88fc8aecb..5ea071835de2 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; +import android.graphics.Insets; import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; @@ -172,6 +173,11 @@ public class ContentCaptureSessionTest { } @Override + void internalNotifyViewInsetsChanged(Insets viewInsets) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override public void updateContentCaptureContext(ContentCaptureContext context) { throw new UnsupportedOperationException("should not have been called"); } diff --git a/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java b/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java new file mode 100644 index 000000000000..e4cfc53cc53a --- /dev/null +++ b/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 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 android.view.textclassifier; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + + +import android.os.Parcel; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SystemTextClassifierMetadataTest { + + @Test + public void testInvalidPackageNameThrowsException() { + assertThrows(NullPointerException.class, + () -> new SystemTextClassifierMetadata(/* packageName= */ null, /* userId= */ + 1, /* useDefaultTextClassifier= */ false)); + } + + @Test + public void testParcel() { + SystemTextClassifierMetadata sysTcMetadata = new SystemTextClassifierMetadata( + "package", /* userId= */ 1, /* useDefaultTextClassifier= */ false); + + Parcel p = Parcel.obtain(); + sysTcMetadata.writeToParcel(p, 0); + p.setDataPosition(0); + + SystemTextClassifierMetadata targetSysTcMetadata = + SystemTextClassifierMetadata.CREATOR.createFromParcel(p); + + assertThat(targetSysTcMetadata.getUserId()).isEqualTo(sysTcMetadata.getUserId()); + assertThat(targetSysTcMetadata.getCallingPackageName()).isEqualTo( + sysTcMetadata.getCallingPackageName()); + assertThat(targetSysTcMetadata.useDefaultTextClassifier()).isEqualTo( + sysTcMetadata.useDefaultTextClassifier()); + } +} diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index d54402975f65..39ededaf1f67 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -17,6 +17,7 @@ package android.view.textclassifier; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -199,7 +200,9 @@ public class TextClassificationTest { .setReferenceTime(referenceTime) .setExtras(BUNDLE) .build(); - reference.setCallingPackageName(packageName); + final SystemTextClassifierMetadata systemTcMetadata = + new SystemTextClassifierMetadata(packageName, 1, false); + reference.setSystemTextClassifierMetadata(systemTcMetadata); // Parcel and unparcel. final Parcel parcel = Parcel.obtain(); @@ -216,5 +219,11 @@ public class TextClassificationTest { assertEquals(referenceTime, result.getReferenceTime()); assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY)); assertEquals(packageName, result.getCallingPackageName()); + final SystemTextClassifierMetadata resultSystemTcMetadata = + result.getSystemTextClassifierMetadata(); + assertNotNull(resultSystemTcMetadata); + assertEquals(packageName, resultSystemTcMetadata.getCallingPackageName()); + assertEquals(1, resultSystemTcMetadata.getUserId()); + assertFalse(resultSystemTcMetadata.useDefaultTextClassifier()); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java index d0d32e3a507a..31f8029550dd 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java @@ -17,6 +17,8 @@ package android.view.textclassifier; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import android.icu.util.ULocale; import android.os.Bundle; @@ -75,7 +77,9 @@ public final class TextLanguageTest { final TextLanguage.Request reference = new TextLanguage.Request.Builder(text) .setExtras(bundle) .build(); - reference.setCallingPackageName(packageName); + final SystemTextClassifierMetadata systemTcMetadata = + new SystemTextClassifierMetadata(packageName, 1, false); + reference.setSystemTextClassifierMetadata(systemTcMetadata); final Parcel parcel = Parcel.obtain(); reference.writeToParcel(parcel, 0); @@ -85,5 +89,11 @@ public final class TextLanguageTest { assertEquals(text, result.getText()); assertEquals("bundle", result.getExtras().getString(bundleKey)); assertEquals(packageName, result.getCallingPackageName()); + final SystemTextClassifierMetadata resultSystemTcMetadata = + result.getSystemTextClassifierMetadata(); + assertNotNull(resultSystemTcMetadata); + assertEquals(packageName, resultSystemTcMetadata.getCallingPackageName()); + assertEquals(1, resultSystemTcMetadata.getUserId()); + assertFalse(resultSystemTcMetadata.useDefaultTextClassifier()); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java index ec5813fd4123..4f0b44bfb864 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java @@ -17,6 +17,8 @@ package android.view.textclassifier; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import android.os.Bundle; import android.os.LocaleList; @@ -115,7 +117,9 @@ public class TextLinksTest { .setExtras(BUNDLE) .setReferenceTime(referenceTime) .build(); - reference.setCallingPackageName(packageName); + final SystemTextClassifierMetadata systemTcMetadata = + new SystemTextClassifierMetadata(packageName, 1, false); + reference.setSystemTextClassifierMetadata(systemTcMetadata); // Parcel and unparcel. final Parcel parcel = Parcel.obtain(); @@ -132,5 +136,11 @@ public class TextLinksTest { assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY)); assertEquals(packageName, result.getCallingPackageName()); assertEquals(referenceTime, result.getReferenceTime()); + final SystemTextClassifierMetadata resultSystemTcMetadata = + result.getSystemTextClassifierMetadata(); + assertNotNull(resultSystemTcMetadata); + assertEquals(packageName, resultSystemTcMetadata.getCallingPackageName()); + assertEquals(1, resultSystemTcMetadata.getUserId()); + assertFalse(resultSystemTcMetadata.useDefaultTextClassifier()); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java index 30cc4e8990e0..82788c806fed 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java @@ -17,6 +17,8 @@ package android.view.textclassifier; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import android.os.Bundle; import android.os.LocaleList; @@ -82,7 +84,9 @@ public class TextSelectionTest { .setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY)) .setExtras(BUNDLE) .build(); - reference.setCallingPackageName(packageName); + final SystemTextClassifierMetadata systemTcMetadata = + new SystemTextClassifierMetadata(packageName, 1, false); + reference.setSystemTextClassifierMetadata(systemTcMetadata); // Parcel and unparcel. final Parcel parcel = Parcel.obtain(); @@ -96,5 +100,11 @@ public class TextSelectionTest { assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags()); assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY)); assertEquals(packageName, result.getCallingPackageName()); + final SystemTextClassifierMetadata resultSystemTcMetadata = + result.getSystemTextClassifierMetadata(); + assertNotNull(resultSystemTcMetadata); + assertEquals(packageName, resultSystemTcMetadata.getCallingPackageName()); + assertEquals(1, resultSystemTcMetadata.getUserId()); + assertFalse(resultSystemTcMetadata.useDefaultTextClassifier()); } } diff --git a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java index 36dd3e4e72b9..03ed68cd1205 100644 --- a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java +++ b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java @@ -98,6 +98,11 @@ public class AbstractResolverComparatorTest { @Override void handleResultMessage(Message message) {} + + @Override + List<ComponentName> getTopComponentNames(int topK) { + return null; + } }; return testComparator; } diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 24e96d4b8463..a6cbc1ae466d 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -987,7 +987,8 @@ public class ChooserActivityTest { /* resolveInfoPresentationGetter */ null), serviceTargets, TARGET_TYPE_CHOOSER_TARGET, - directShareToShortcutInfos) + directShareToShortcutInfos, + null) ); // Thread.sleep shouldn't be a thing in an integration test but it's @@ -1058,7 +1059,8 @@ public class ChooserActivityTest { /* resolveInfoPresentationGetter */ null), serviceTargets, TARGET_TYPE_CHOOSER_TARGET, - directShareToShortcutInfos) + directShareToShortcutInfos, + null) ); // Thread.sleep shouldn't be a thing in an integration test but it's // necessary here because of the way the code is structured @@ -1145,7 +1147,8 @@ public class ChooserActivityTest { /* resolveInfoPresentationGetter */ null), serviceTargets, TARGET_TYPE_CHOOSER_TARGET, - directShareToShortcutInfos) + directShareToShortcutInfos, + null) ); // Thread.sleep shouldn't be a thing in an integration test but it's // necessary here because of the way the code is structured diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 90f55dd6f38d..4a0d16c8d2db 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -108,14 +108,12 @@ public class LocalMediaManager implements BluetoothCallback { new InfoMediaManager(context, packageName, notification, mLocalBluetoothManager); } - @VisibleForTesting - LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager, + public LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager, InfoMediaManager infoMediaManager, String packageName) { mContext = context; mLocalBluetoothManager = localBluetoothManager; mInfoMediaManager = infoMediaManager; mPackageName = packageName; - } /** diff --git a/packages/SystemUI/res/color/control_background.xml b/packages/SystemUI/res/color/control_background.xml deleted file mode 100644 index 977310cfae01..000000000000 --- a/packages/SystemUI/res/color/control_background.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" - android:color="@color/control_default_background" /> - <item android:color="@color/GM2_blue_200" - android:alpha="0.2" /> -</selector> diff --git a/packages/SystemUI/res/color/light_background.xml b/packages/SystemUI/res/color/light_background.xml deleted file mode 100644 index 12994646a346..000000000000 --- a/packages/SystemUI/res/color/light_background.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" - android:color="@color/control_default_background" /> - <item android:color="@color/GM2_yellow_200" - android:alpha="0.2" /> -</selector> diff --git a/packages/SystemUI/res/color/thermo_cool_background.xml b/packages/SystemUI/res/color/thermo_cool_background.xml deleted file mode 100644 index 977310cfae01..000000000000 --- a/packages/SystemUI/res/color/thermo_cool_background.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" - android:color="@color/control_default_background" /> - <item android:color="@color/GM2_blue_200" - android:alpha="0.2" /> -</selector> diff --git a/packages/SystemUI/res/color/thermo_heat_background.xml b/packages/SystemUI/res/color/thermo_heat_background.xml deleted file mode 100644 index 2709ebe4dcfd..000000000000 --- a/packages/SystemUI/res/color/thermo_heat_background.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" - android:color="@color/control_default_background" /> - <item android:color="@color/GM2_red_200" - android:alpha="0.2" /> -</selector> diff --git a/packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml b/packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml index 64b57c5aac2b..3acebc12a807 100644 --- a/packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml +++ b/packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml @@ -14,10 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:viewportHeight="24" - android:viewportWidth="24" - android:height="52dp" - android:width="52dp"> - <path android:fillColor="#1A73E8" - android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/> -</vector>
\ No newline at end of file + android:viewportWidth="24" + android:viewportHeight="24" + android:width="24dp" + android:height="24dp"> + <path + android:fillColor="#1A73E8" + android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> +</vector> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 80c1ac8e3496..56e2b061ebc2 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -225,4 +225,8 @@ <color name="control_default_background">@color/GM2_grey_900</color> <color name="control_list_popup_background">@*android:color/background_floating_material_dark</color> <color name="control_spinner_dropdown">@*android:color/foreground_material_dark</color> + <color name="control_enabled_light_background">@color/GM2_yellow_200</color> + <color name="control_enabled_thermo_heat_background">@color/GM2_red_200</color> + <color name="control_enabled_thermo_cool_background">@color/GM2_blue_200</color> + <color name="control_enabled_default_background">@color/GM2_blue_200</color> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 20cafd046452..c8b0d99a329f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -468,12 +468,18 @@ <!-- On debuggable builds, alert the user if SystemUI PSS goes over this number (in kb) --> <integer name="watch_heap_limit">256000</integer> + <!-- Animation duration for resizing of PIP when entering/exiting. --> + <integer name="config_pipResizeAnimationDuration">425</integer> + <!-- Allow dragging the PIP to a location to close it --> <bool name="config_pipEnableDismissDragToEdge">true</bool> <!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu --> <bool name="config_pipEnableResizeForMenu">true</bool> + <!-- Allow PIP to enable round corner, see also R.dimen.pip_corner_radius --> + <bool name="config_pipEnableRoundCorner">false</bool> + <!-- SystemUI Plugins that can be loaded on user builds. --> <string-array name="config_pluginWhitelist" translatable="false"> <item>com.android.systemui</item> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 10b47e69184c..e45cbecd3aa1 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -124,9 +124,6 @@ <!-- Increased height of a collapsed media notification in the status bar --> <dimen name="notification_min_height_media">160dp</dimen> - <!-- Increased height of a collapsed messaging notification in the status bar --> - <dimen name="notification_min_height_messaging">118dp</dimen> - <!-- Height of a small notification in the status bar which was used before android N --> <dimen name="notification_min_height_legacy">64dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index cbb19824eadf..0018d33bdacb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -99,6 +99,8 @@ public class AuthContainerView extends LinearLayout // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet. @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason; + // HAT received from LockSettingsService when credential is verified. + @Nullable byte[] mCredentialAttestation; static class Config { Context mContext; @@ -109,6 +111,7 @@ public class AuthContainerView extends LinearLayout String mOpPackageName; int mModalityMask; boolean mSkipIntro; + long mOperationId; } public static class Builder { @@ -149,6 +152,11 @@ public class AuthContainerView extends LinearLayout return this; } + public Builder setOperationId(long operationId) { + mConfig.mOperationId = operationId; + return this; + } + public AuthContainerView build(int modalityMask) { mConfig.mModalityMask = modalityMask; return new AuthContainerView(mConfig, new Injector()); @@ -224,7 +232,8 @@ public class AuthContainerView extends LinearLayout final class CredentialCallback implements AuthCredentialView.Callback { @Override - public void onCredentialMatched() { + public void onCredentialMatched(byte[] attestation) { + mCredentialAttestation = attestation; animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED); } } @@ -341,6 +350,7 @@ public class AuthContainerView extends LinearLayout mCredentialView.setContainerView(this); mCredentialView.setUserId(mConfig.mUserId); + mCredentialView.setOperationId(mConfig.mOperationId); mCredentialView.setEffectiveUserId(mEffectiveUserId); mCredentialView.setCredentialType(credentialType); mCredentialView.setCallback(mCredentialCallback); @@ -558,7 +568,7 @@ public class AuthContainerView extends LinearLayout private void sendPendingCallbackIfNotNull() { Log.d(TAG, "pendingCallback: " + mPendingCallbackReason); if (mPendingCallbackReason != null) { - mConfig.mCallback.onDismissed(mPendingCallbackReason); + mConfig.mCallback.onDismissed(mPendingCallbackReason, mCredentialAttestation); mPendingCallbackReason = null; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 6149b0b8bfd4..c30477c77bbb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -20,6 +20,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; @@ -99,7 +100,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, try { if (mReceiver != null) { - mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, + null /* credentialAttestation */); mReceiver = null; } } catch (RemoteException e) { @@ -124,7 +126,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, mCurrentDialog = null; if (mReceiver != null) { mReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + BiometricPrompt.DISMISSED_REASON_USER_CANCEL, + null /* credentialAttestation */); mReceiver = null; } } @@ -162,35 +165,42 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } @Override - public void onDismissed(@DismissedReason int reason) { + public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) { switch (reason) { case AuthDialogCallback.DISMISSED_USER_CANCELED: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, + credentialAttestation); break; case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE); + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE, + credentialAttestation); break; case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, + credentialAttestation); break; case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED: sendResultAndCleanUp( - BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED); + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, + credentialAttestation); break; case AuthDialogCallback.DISMISSED_ERROR: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR); + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR, + credentialAttestation); break; case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED); + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED, + credentialAttestation); break; case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED); + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED, + credentialAttestation); break; default: @@ -199,13 +209,14 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } } - private void sendResultAndCleanUp(@DismissedReason int reason) { + private void sendResultAndCleanUp(@DismissedReason int reason, + @Nullable byte[] credentialAttestation) { if (mReceiver == null) { Log.e(TAG, "sendResultAndCleanUp: Receiver is null"); return; } try { - mReceiver.onDialogDismissed(reason); + mReceiver.onDialogDismissed(reason, credentialAttestation); } catch (RemoteException e) { Log.w(TAG, "Remote exception", e); } @@ -251,13 +262,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Override public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int biometricModality, boolean requireConfirmation, int userId, String opPackageName) { + int biometricModality, boolean requireConfirmation, int userId, String opPackageName, + long operationId) { final int authenticators = Utils.getAuthenticators(bundle); if (DEBUG) { Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators + ", biometricModality: " + biometricModality - + ", requireConfirmation: " + requireConfirmation); + + ", requireConfirmation: " + requireConfirmation + + ", operationId: " + operationId); } SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; @@ -266,6 +279,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, args.arg3 = requireConfirmation; args.argi2 = userId; args.arg4 = opPackageName; + args.arg5 = operationId; boolean skipAnimation = false; if (mCurrentDialog != null) { @@ -354,6 +368,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, final boolean requireConfirmation = (boolean) args.arg3; final int userId = args.argi2; final String opPackageName = (String) args.arg4; + final long operationId = (long) args.arg5; // Create a new dialog but do not replace the current one yet. final AuthDialog newDialog = buildDialog( @@ -362,7 +377,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, userId, type, opPackageName, - skipAnimation); + skipAnimation, + operationId); if (newDialog == null) { Log.e(TAG, "Unsupported type: " + type); @@ -429,7 +445,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation, - int userId, int type, String opPackageName, boolean skipIntro) { + int userId, int type, String opPackageName, boolean skipIntro, long operationId) { return new AuthContainerView.Builder(mContext) .setCallback(this) .setBiometricPromptBundle(biometricPromptBundle) @@ -437,6 +453,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, .setUserId(userId) .setOpPackageName(opPackageName) .setSkipIntro(skipIntro) + .setOperationId(operationId) .build(type); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java index 82c8a469a057..b986f6c9e680 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java @@ -103,14 +103,16 @@ public class AuthCredentialPasswordView extends AuthCredentialView return; } - mPendingLockCheck = LockPatternChecker.checkCredential(mLockPatternUtils, - password, mEffectiveUserId, this::onCredentialChecked); + mPendingLockCheck = LockPatternChecker.verifyCredential(mLockPatternUtils, + password, mOperationId, mEffectiveUserId, this::onCredentialVerified); } } @Override - protected void onCredentialChecked(boolean matched, int timeoutMs) { - super.onCredentialChecked(matched, timeoutMs); + protected void onCredentialVerified(byte[] attestation, int timeoutMs) { + super.onCredentialVerified(attestation, timeoutMs); + + final boolean matched = attestation != null; if (matched) { mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java index 03136a4b6c0f..6d16f4397115 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java @@ -61,21 +61,22 @@ public class AuthCredentialPatternView extends AuthCredentialView { if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { // Pattern size is less than the minimum, do not count it as a failed attempt. - onPatternChecked(false /* matched */, 0 /* timeoutMs */); + onPatternVerified(null /* attestation */, 0 /* timeoutMs */); return; } try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) { - mPendingLockCheck = LockPatternChecker.checkCredential( + mPendingLockCheck = LockPatternChecker.verifyCredential( mLockPatternUtils, credential, + mOperationId, mEffectiveUserId, - this::onPatternChecked); + this::onPatternVerified); } } - private void onPatternChecked(boolean matched, int timeoutMs) { - AuthCredentialPatternView.this.onCredentialChecked(matched, timeoutMs); + private void onPatternVerified(byte[] attestation, int timeoutMs) { + AuthCredentialPatternView.this.onCredentialVerified(attestation, timeoutMs); if (timeoutMs > 0) { mLockPatternView.setEnabled(false); } else { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java index 48c66215bdde..0d9d4269230f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java @@ -42,7 +42,7 @@ import com.android.systemui.R; /** * Abstract base class for Pin, Pattern, or Password authentication, for - * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} + * {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}} */ public abstract class AuthCredentialView extends LinearLayout { @@ -70,11 +70,12 @@ public abstract class AuthCredentialView extends LinearLayout { protected Callback mCallback; protected AsyncTask<?, ?, ?> mPendingLockCheck; protected int mUserId; + protected long mOperationId; protected int mEffectiveUserId; protected ErrorTimer mErrorTimer; interface Callback { - void onCredentialMatched(); + void onCredentialMatched(byte[] attestation); } protected static class ErrorTimer extends CountDownTimer { @@ -148,6 +149,10 @@ public abstract class AuthCredentialView extends LinearLayout { mUserId = userId; } + void setOperationId(long operationId) { + mOperationId = operationId; + } + void setEffectiveUserId(int effectiveUserId) { mEffectiveUserId = effectiveUserId; } @@ -245,10 +250,13 @@ public abstract class AuthCredentialView extends LinearLayout { protected void onErrorTimeoutFinish() {} - protected void onCredentialChecked(boolean matched, int timeoutMs) { + protected void onCredentialVerified(byte[] attestation, int timeoutMs) { + + final boolean matched = attestation != null; + if (matched) { mClearErrorRunnable.run(); - mCallback.onCredentialMatched(); + mCallback.onCredentialMatched(attestation); } else { if (timeoutMs > 0) { mHandler.removeCallbacks(mClearErrorRunnable); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java index 12bb1228a53b..a47621d12122 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java @@ -17,6 +17,7 @@ package com.android.systemui.biometrics; import android.annotation.IntDef; +import android.annotation.Nullable; /** * Callback interface for dialog views. These should be implemented by the controller (e.g. @@ -44,8 +45,9 @@ public interface AuthDialogCallback { /** * Invoked when the dialog is dismissed * @param reason + * @param credentialAttestation the HAT received from LockSettingsService upon verification */ - void onDismissed(@DismissedReason int reason); + void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation); /** * Invoked when the "try again" button is clicked diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index cf5a4d3840cc..54b83c0b134d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -207,12 +207,13 @@ public class BubbleData { // Preserve new order for next repack, which sorts by last updated time. bubble.markUpdatedAt(mTimeSource.currentTimeMillis()); - setSelectedBubbleInternal(bubble); mOverflowBubbles.remove(bubble); - bubble.inflate( - b -> notificationEntryUpdated(bubble, /* suppressFlyout */ - false, /* showInShade */ true), + b -> { + notificationEntryUpdated(bubble, /* suppressFlyout */ + false, /* showInShade */ true); + setSelectedBubbleInternal(bubble); + }, mContext, stack, factory); dispatchPendingChanges(); } @@ -225,6 +226,14 @@ public class BubbleData { Bubble getOrCreateBubble(NotificationEntry entry) { Bubble bubble = getBubbleWithKey(entry.getKey()); if (bubble == null) { + for (int i = 0; i < mOverflowBubbles.size(); i++) { + Bubble b = mOverflowBubbles.get(i); + if (b.getKey().equals(entry.getKey())) { + mOverflowBubbles.remove(b); + mPendingBubbles.add(b); + return b; + } + } // Check for it in pending for (int i = 0; i < mPendingBubbles.size(); i++) { Bubble b = mPendingBubbles.get(i); 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 23dc4c65d2b1..b4392067d953 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -17,8 +17,8 @@ package com.android.systemui.controls.ui import android.content.Context -import android.graphics.BlendMode import android.graphics.drawable.ClipDrawable +import android.graphics.drawable.GradientDrawable import android.graphics.drawable.LayerDrawable import android.service.controls.Control import android.service.controls.actions.ControlAction @@ -39,6 +39,8 @@ import com.android.systemui.R import kotlin.reflect.KClass private const val UPDATE_DELAY_IN_MILLIS = 3000L +private const val ALPHA_ENABLED = (255.0 * 0.2).toInt() +private const val ALPHA_DISABLED = 255 class ControlViewHolder( val layout: ViewGroup, @@ -144,16 +146,21 @@ class ControlViewHolder( val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset) val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) - val bg = context.getResources().getColorStateList(ri.background, context.getTheme()) + val (bg, alpha) = if (enabled) { + Pair(ri.enabledBackground, ALPHA_ENABLED) + } else { + Pair(R.color.control_default_background, ALPHA_DISABLED) + } + status.setTextColor(fg) statusExtra.setTextColor(fg) icon.setImageDrawable(ri.icon) icon.setImageTintList(fg) - clipLayer.getDrawable().apply { - setTintBlendMode(BlendMode.HUE) - setTintList(bg) + (clipLayer.getDrawable() as GradientDrawable).apply { + setColor(context.getResources().getColor(bg, context.getTheme())) + setAlpha(alpha) } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt index 56267beb1b71..27e46497b20a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt @@ -16,6 +16,7 @@ package com.android.systemui.controls.ui +import android.annotation.ColorRes import android.annotation.MainThread import android.content.ComponentName import android.content.Context @@ -37,8 +38,11 @@ data class IconState(val disabledResourceId: Int, val enabledResourceId: Int) { } } -data class RenderInfo(val icon: Drawable, val foreground: Int, val background: Int) { - +data class RenderInfo( + val icon: Drawable, + val foreground: Int, + @ColorRes val enabledBackground: Int +) { companion object { const val APP_ICON_ID = -1 private val iconMap = SparseArray<Drawable>() @@ -72,6 +76,7 @@ data class RenderInfo(val icon: Drawable, val foreground: Int, val background: I icon = iconMap.get(resourceId) if (icon == null) { icon = context.resources.getDrawable(resourceId, null) + icon.mutate() iconMap.put(resourceId, icon) } } @@ -94,12 +99,13 @@ private const val THERMOSTAT_RANGE = DeviceTypes.TYPE_THERMOSTAT * BUCKET_SIZE private val deviceColorMap = mapOf<Int, Pair<Int, Int>>( (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to - Pair(R.color.thermo_heat_foreground, R.color.thermo_heat_background), + Pair(R.color.thermo_heat_foreground, R.color.control_enabled_thermo_heat_background), (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to - Pair(R.color.thermo_cool_foreground, R.color.thermo_cool_background), - DeviceTypes.TYPE_LIGHT to Pair(R.color.light_foreground, R.color.light_background) + Pair(R.color.thermo_cool_foreground, R.color.control_enabled_thermo_cool_background), + DeviceTypes.TYPE_LIGHT + to Pair(R.color.light_foreground, R.color.control_enabled_light_background) ).withDefault { - Pair(R.color.control_foreground, R.color.control_background) + Pair(R.color.control_foreground, R.color.control_enabled_default_background) } private val deviceIconMap = mapOf<Int, IconState>( diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 27c81ce5321d..59b28b4372b4 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -99,7 +99,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; -import com.android.systemui.statusbar.BlurUtils; +import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -162,7 +162,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final IActivityManager mIActivityManager; private final TelecomManager mTelecomManager; private final MetricsLogger mMetricsLogger; - private final BlurUtils mBlurUtils; + private final NotificationShadeDepthController mDepthController; private ArrayList<Action> mItems; private ActionsDialog mDialog; @@ -207,7 +207,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, KeyguardStateController keyguardStateController, UserManager userManager, TrustManager trustManager, IActivityManager iActivityManager, @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, - BlurUtils blurUtils, SysuiColorExtractor colorExtractor, + NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, ControlsUiController controlsUiController, IWindowManager iWindowManager, @@ -229,7 +229,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mIActivityManager = iActivityManager; mTelecomManager = telecomManager; mMetricsLogger = metricsLogger; - mBlurUtils = blurUtils; + mDepthController = depthController; mSysuiColorExtractor = colorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; @@ -437,7 +437,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, : null; ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController, - mBlurUtils, mSysuiColorExtractor, mStatusBarService, + mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, shouldShowControls() ? mControlsUiController : null); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. @@ -1570,20 +1570,21 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private ResetOrientationData mResetOrientationData; private boolean mHadTopUi; private final NotificationShadeWindowController mNotificationShadeWindowController; - private final BlurUtils mBlurUtils; + private final NotificationShadeDepthController mDepthController; private ControlsUiController mControlsUiController; private ViewGroup mControlsView; ActionsDialog(Context context, MyAdapter adapter, - GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils, + GlobalActionsPanelPlugin.PanelViewController plugin, + NotificationShadeDepthController depthController, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, ControlsUiController controlsUiController) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); mContext = context; mAdapter = adapter; - mBlurUtils = blurUtils; + mDepthController = depthController; mColorExtractor = sysuiColorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; @@ -1792,8 +1793,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, float animatedValue = animation.getAnimatedFraction(); int alpha = (int) (animatedValue * mScrimAlpha * 255); mBackgroundDrawable.setAlpha(alpha); - mBlurUtils.applyBlur(mGlobalActionsLayout.getViewRootImpl(), - mBlurUtils.blurRadiusOfRatio(animatedValue)); + mDepthController.updateGlobalDialogVisibility(animatedValue, + mGlobalActionsLayout); }) .start(); if (mControlsUiController != null) { @@ -1822,8 +1823,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, float animatedValue = 1f - animation.getAnimatedFraction(); int alpha = (int) (animatedValue * mScrimAlpha * 255); mBackgroundDrawable.setAlpha(alpha); - mBlurUtils.applyBlur(mGlobalActionsLayout.getViewRootImpl(), - mBlurUtils.blurRadiusOfRatio(animatedValue)); + mDepthController.updateGlobalDialogVisibility(animatedValue, + mGlobalActionsLayout); }) .start(); dismissPanel(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java index 67802bc9888d..4bfcf2229e3e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java @@ -30,6 +30,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import javax.inject.Inject; + /** * Controller class of PiP animations (both from and to PiP mode). */ @@ -37,7 +39,6 @@ public class PipAnimationController { private static final float FRACTION_START = 0f; private static final float FRACTION_END = 1f; - public static final int DURATION_DEFAULT_MS = 425; public static final int ANIM_TYPE_BOUNDS = 0; public static final int ANIM_TYPE_ALPHA = 1; @@ -63,12 +64,15 @@ public class PipAnimationController { @interface TransitionDirection {} private final Interpolator mFastOutSlowInInterpolator; + private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private PipTransitionAnimator mCurrentAnimator; - PipAnimationController(Context context) { + @Inject + PipAnimationController(Context context, PipSurfaceTransactionHelper helper) { mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); + mSurfaceTransactionHelper = helper; } @SuppressWarnings("unchecked") @@ -111,6 +115,7 @@ public class PipAnimationController { } private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) { + animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); animator.setInterpolator(mFastOutSlowInInterpolator); animator.setFloatValues(FRACTION_START, FRACTION_END); return animator; @@ -152,9 +157,10 @@ public class PipAnimationController { private T mEndValue; private T mCurrentValue; private PipAnimationCallback mPipAnimationCallback; - private SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; + private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + mSurfaceControlTransactionFactory; + private PipSurfaceTransactionHelper mSurfaceTransactionHelper; private @TransitionDirection int mTransitionDirection; - private int mCornerRadius; private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType, Rect destinationBounds, T startValue, T endValue) { @@ -243,15 +249,6 @@ public class PipAnimationController { mCurrentValue = value; } - int getCornerRadius() { - return mCornerRadius; - } - - PipTransitionAnimator<T> setCornerRadius(int cornerRadius) { - mCornerRadius = cornerRadius; - return this; - } - boolean shouldApplyCornerRadius() { return mTransitionDirection != TRANSITION_DIRECTION_TO_FULLSCREEN; } @@ -274,10 +271,19 @@ public class PipAnimationController { } @VisibleForTesting - void setSurfaceControlTransactionFactory(SurfaceControlTransactionFactory factory) { + void setSurfaceControlTransactionFactory( + PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { mSurfaceControlTransactionFactory = factory; } + PipSurfaceTransactionHelper getSurfaceTransactionHelper() { + return mSurfaceTransactionHelper; + } + + void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) { + mSurfaceTransactionHelper = helper; + } + abstract void applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction); @@ -290,14 +296,11 @@ public class PipAnimationController { SurfaceControl.Transaction tx, float fraction) { final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction; setCurrentValue(alpha); - tx.setAlpha(leash, alpha); + getSurfaceTransactionHelper().alpha(tx, leash, alpha); if (Float.compare(fraction, FRACTION_START) == 0) { - // Ensure the start condition - final Rect bounds = getDestinationBounds(); - tx.setPosition(leash, bounds.left, bounds.top) - .setWindowCrop(leash, bounds.width(), bounds.height()); - tx.setCornerRadius(leash, - shouldApplyCornerRadius() ? getCornerRadius() : 0); + getSurfaceTransactionHelper() + .crop(tx, leash, getDestinationBounds()) + .round(tx, leash, shouldApplyCornerRadius()); } tx.apply(); } @@ -326,21 +329,16 @@ public class PipAnimationController { getCastedFractionValue(start.right, end.right, fraction), getCastedFractionValue(start.bottom, end.bottom, fraction)); setCurrentValue(mTmpRect); - tx.setPosition(leash, mTmpRect.left, mTmpRect.top) - .setWindowCrop(leash, mTmpRect.width(), mTmpRect.height()); + getSurfaceTransactionHelper().crop(tx, leash, mTmpRect); if (Float.compare(fraction, FRACTION_START) == 0) { // Ensure the start condition - tx.setAlpha(leash, 1f); - tx.setCornerRadius(leash, - shouldApplyCornerRadius() ? getCornerRadius() : 0); + getSurfaceTransactionHelper() + .alpha(tx, leash, 1f) + .round(tx, leash, shouldApplyCornerRadius()); } tx.apply(); } }; } } - - interface SurfaceControlTransactionFactory { - SurfaceControl.Transaction getTransaction(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java new file mode 100644 index 000000000000..21f9301fe794 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 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.pip; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.view.SurfaceControl; + +import com.android.systemui.R; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition. + */ +@Singleton +public class PipSurfaceTransactionHelper { + + private final boolean mEnableCornerRadius; + private final int mCornerRadius; + + @Inject + public PipSurfaceTransactionHelper(Context context) { + final Resources res = context.getResources(); + mEnableCornerRadius = res.getBoolean(R.bool.config_pipEnableRoundCorner); + mCornerRadius = res.getDimensionPixelSize(R.dimen.pip_corner_radius); + } + + /** + * Operates the alpha on a given transaction and leash + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash, + float alpha) { + tx.setAlpha(leash, alpha); + return this; + } + + /** + * Operates the crop (and position) on a given transaction and leash + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect destinationBounds) { + tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()) + .setPosition(leash, destinationBounds.left, destinationBounds.top); + return this; + } + + /** + * Operates the round corner radius on a given transaction and leash + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash, + boolean applyCornerRadius) { + if (mEnableCornerRadius) { + tx.setCornerRadius(leash, applyCornerRadius ? mCornerRadius : 0); + } + return this; + } + + interface SurfaceControlTransactionFactory { + SurfaceControl.Transaction getTransaction(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 51113acd0e82..4a4a638fa2b1 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -18,7 +18,6 @@ package com.android.systemui.pip; import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS; -import static com.android.systemui.pip.PipAnimationController.DURATION_DEFAULT_MS; import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_NONE; import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_SAME; import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN; @@ -33,7 +32,6 @@ import android.app.PictureInPictureParams; import android.content.Context; import android.graphics.Rect; import android.os.Handler; -import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; @@ -47,9 +45,7 @@ import com.android.systemui.R; import com.android.systemui.pip.phone.PipUpdateThread; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -79,8 +75,8 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { private final PipAnimationController mPipAnimationController; private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>(); private final Rect mLastReportedBounds = new Rect(); - private final int mCornerRadius; - private final Map<IBinder, Rect> mBoundsToRestore = new HashMap<>(); + private final int mEnterExitAnimationDuration; + private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; // These callbacks are called on the update thread private final PipAnimationController.PipAnimationCallback mPipAnimationCallback = @@ -172,14 +168,20 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { private SurfaceControl mLeash; private boolean mInPip; private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; + private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + mSurfaceControlTransactionFactory; - public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler) { + public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler, + @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper) { mMainHandler = new Handler(Looper.getMainLooper()); mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks); mTaskOrganizerController = ActivityTaskManager.getTaskOrganizerController(); mPipBoundsHandler = boundsHandler; - mPipAnimationController = new PipAnimationController(context); - mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius); + mEnterExitAnimationDuration = context.getResources() + .getInteger(R.integer.config_pipResizeAnimationDuration); + mSurfaceTransactionHelper = surfaceTransactionHelper; + mPipAnimationController = new PipAnimationController(context, surfaceTransactionHelper); + mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; } public Handler getUpdateHandler() { @@ -232,17 +234,15 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { throw new RuntimeException("Unable to get leash", e); } final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); - mBoundsToRestore.put(mToken.asBinder(), currentBounds); if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { scheduleAnimateResizePip(currentBounds, destinationBounds, - TRANSITION_DIRECTION_TO_PIP, DURATION_DEFAULT_MS, null); + TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, null); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { mUpdateHandler.post(() -> mPipAnimationController .getAnimator(mLeash, destinationBounds, 0f, 1f) .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) - .setCornerRadius(mCornerRadius) .setPipAnimationCallback(mPipAnimationCallback) - .setDuration(DURATION_DEFAULT_MS) + .setDuration(mEnterExitAnimationDuration) .start()); mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { @@ -258,9 +258,9 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { Log.wtf(TAG, "Unrecognized token: " + token); return; } - final Rect boundsToRestore = mBoundsToRestore.remove(mToken.asBinder()); - scheduleAnimateResizePip(mLastReportedBounds, boundsToRestore, - TRANSITION_DIRECTION_TO_FULLSCREEN, DURATION_DEFAULT_MS, null); + scheduleAnimateResizePip(mLastReportedBounds, + info.configuration.windowConfiguration.getBounds(), + TRANSITION_DIRECTION_TO_FULLSCREEN, mEnterExitAnimationDuration, null); mInPip = false; } @@ -278,7 +278,7 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( getAspectRatioOrDefault(newParams), null /* bounds */); Objects.requireNonNull(destinationBounds, "Missing destination bounds"); - scheduleAnimateResizePip(destinationBounds, DURATION_DEFAULT_MS, null); + scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration, null); } /** @@ -305,7 +305,6 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, int durationMs, Consumer<Rect> updateBoundsCallback) { - Objects.requireNonNull(mToken, "Requires valid IWindowContainer"); if (!mInPip) { // Ignore animation when we are no longer in PIP return; @@ -324,7 +323,6 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called. */ public void scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback) { - Objects.requireNonNull(mToken, "Requires valid IWindowContainer"); SomeArgs args = SomeArgs.obtain(); args.arg1 = updateBoundsCallback; args.arg2 = toBounds; @@ -332,22 +330,20 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { } /** - * Finish a intermediate resize operation. This is expected to be called after + * Finish an intermediate resize operation. This is expected to be called after * {@link #scheduleResizePip}. */ public void scheduleFinishResizePip(Rect destinationBounds) { - Objects.requireNonNull(mToken, "Requires valid IWindowContainer"); - SurfaceControl.Transaction tx = new SurfaceControl.Transaction() - .setPosition(mLeash, destinationBounds.left, destinationBounds.top) - .setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height()) - .setCornerRadius(mLeash, mInPip ? mCornerRadius : 0); + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + mSurfaceTransactionHelper + .crop(tx, mLeash, destinationBounds) + .round(tx, mLeash, mInPip); scheduleFinishResizePip(tx, destinationBounds, TRANSITION_DIRECTION_NONE, null); } private void scheduleFinishResizePip(SurfaceControl.Transaction tx, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, Consumer<Rect> updateBoundsCallback) { - Objects.requireNonNull(mToken, "Requires valid IWindowContainer"); SomeArgs args = SomeArgs.obtain(); args.arg1 = updateBoundsCallback; args.arg2 = tx; @@ -365,7 +361,6 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { // Ignore offsets when we are no longer in PIP return; } - Objects.requireNonNull(mToken, "Requires valid IWindowContainer"); SomeArgs args = SomeArgs.obtain(); args.arg1 = updateBoundsCallback; args.arg2 = originalBounds; @@ -394,17 +389,16 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { throw new RuntimeException("Callers should call scheduleResizePip() instead of this " + "directly"); } - Objects.requireNonNull(mToken, "Requires valid IWindowContainer"); // Could happen when dismissPip if (mToken == null || mLeash == null) { Log.w(TAG, "Abort animation, invalid leash"); return; } - new SurfaceControl.Transaction() - .setPosition(mLeash, destinationBounds.left, destinationBounds.top) - .setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height()) - .setCornerRadius(mLeash, mInPip ? mCornerRadius : 0) - .apply(); + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + mSurfaceTransactionHelper + .crop(tx, mLeash, destinationBounds) + .round(tx, mLeash, mInPip); + tx.apply(); } private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds, @@ -447,7 +441,6 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { mUpdateHandler.post(() -> mPipAnimationController .getAnimator(mLeash, currentBounds, destinationBounds) .setTransitionDirection(direction) - .setCornerRadius(mCornerRadius) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(durationMs) .start()); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 2aac188c130d..020627a20458 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -42,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.pip.BasePipManager; import com.android.systemui.pip.PipBoundsHandler; import com.android.systemui.pip.PipSnapAlgorithm; +import com.android.systemui.pip.PipSurfaceTransactionHelper; import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -210,7 +211,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio FloatingContentCoordinator floatingContentCoordinator, DeviceConfigProxy deviceConfig, PipBoundsHandler pipBoundsHandler, - PipSnapAlgorithm pipSnapAlgorithm) { + PipSnapAlgorithm pipSnapAlgorithm, + PipSurfaceTransactionHelper surfaceTransactionHelper) { mContext = context; mActivityManager = ActivityManager.getService(); @@ -224,7 +226,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService(); mPipBoundsHandler = pipBoundsHandler; - mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler); + mPipTaskOrganizer = new PipTaskOrganizer(context, pipBoundsHandler, + surfaceTransactionHelper); mPipTaskOrganizer.registerPipTransitionCallback(this); mInputConsumerController = InputConsumerController.getPipInputConsumer(); mMediaController = new PipMediaController(context, mActivityManager, broadcastDispatcher); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 33760be3f469..15a008803a52 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -78,10 +78,10 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private final Rect mBounds = new Rect(); /** The bounds within which PIP's top-left coordinate is allowed to move. */ - private Rect mMovementBounds = new Rect(); + private final Rect mMovementBounds = new Rect(); /** The region that all of PIP must stay within. */ - private Rect mFloatingAllowedArea = new Rect(); + private final Rect mFloatingAllowedArea = new Rect(); /** * Bounds that are animated using the physics animator. @@ -89,7 +89,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private final Rect mAnimatedBounds = new Rect(); /** The destination bounds to which PIP is animating. */ - private Rect mAnimatingToBounds = new Rect(); + private final Rect mAnimatingToBounds = new Rect(); /** Coordinator instance for resolving conflicts with other floating content. */ private FloatingContentCoordinator mFloatingContentCoordinator; @@ -120,7 +120,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, new PhysicsAnimator.SpringConfig( SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); - private final Consumer<Rect> mUpdateBoundsCallback = (toBounds) -> mBounds.set(toBounds); + private final Consumer<Rect> mUpdateBoundsCallback = mBounds::set; public PipMotionHelper(Context context, IActivityTaskManager activityTaskManager, PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController, @@ -437,7 +437,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}. */ private void setAnimatingToBounds(Rect bounds) { - mAnimatingToBounds = bounds; + mAnimatingToBounds.set(bounds); mFloatingContentCoordinator.onContentMoved(this); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index 0c5a4d782e69..050acd5a8728 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -20,8 +20,6 @@ import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static com.android.systemui.pip.PipAnimationController.DURATION_DEFAULT_MS; - import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; import android.app.ActivityTaskManager; @@ -53,6 +51,7 @@ import com.android.systemui.UiOffloadThread; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.pip.BasePipManager; import com.android.systemui.pip.PipBoundsHandler; +import com.android.systemui.pip.PipSurfaceTransactionHelper; import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener; @@ -136,6 +135,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio private String[] mLastPackagesResourceGranted; private PipNotification mPipNotification; private ParceledListSlice mCustomActions; + private int mResizeAnimationDuration; // Used to calculate the movement bounds private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); @@ -230,7 +230,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio @Inject public PipManager(Context context, BroadcastDispatcher broadcastDispatcher, - PipBoundsHandler pipBoundsHandler) { + PipBoundsHandler pipBoundsHandler, + PipSurfaceTransactionHelper surfaceTransactionHelper) { if (mInitialized) { return; } @@ -238,7 +239,10 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mInitialized = true; mContext = context; mPipBoundsHandler = pipBoundsHandler; - mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler); + mResizeAnimationDuration = context.getResources() + .getInteger(R.integer.config_pipResizeAnimationDuration); + mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler, + surfaceTransactionHelper); mPipTaskOrganizer.registerPipTransitionCallback(this); mActivityTaskManager = ActivityTaskManager.getService(); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); @@ -436,7 +440,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mCurrentPipBounds = mPipBounds; break; } - mPipTaskOrganizer.scheduleAnimateResizePip(mCurrentPipBounds, DURATION_DEFAULT_MS, null); + mPipTaskOrganizer.scheduleAnimateResizePip(mCurrentPipBounds, mResizeAnimationDuration, + null); } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 9ab47145f92d..bf72b33ada42 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -44,6 +44,8 @@ import android.widget.LinearLayout; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.Dependency; @@ -98,6 +100,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private final LinearLayout mMediaCarousel; private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>(); private final NotificationMediaManager mNotificationMediaManager; + private final LocalBluetoothManager mLocalBluetoothManager; private final Executor mBackgroundExecutor; private LocalMediaManager mLocalMediaManager; private MediaDevice mDevice; @@ -157,7 +160,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne BroadcastDispatcher broadcastDispatcher, QSLogger qsLogger, NotificationMediaManager notificationMediaManager, - @Background Executor backgroundExecutor + @Background Executor backgroundExecutor, + @Nullable LocalBluetoothManager localBluetoothManager ) { super(context, attrs); mContext = context; @@ -165,6 +169,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mDumpManager = dumpManager; mNotificationMediaManager = notificationMediaManager; mBackgroundExecutor = backgroundExecutor; + mLocalBluetoothManager = localBluetoothManager; setOrientation(VERTICAL); @@ -286,7 +291,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne // Set up listener for device changes // TODO: integrate with MediaTransferManager? - mLocalMediaManager = new LocalMediaManager(mContext, null, null); + InfoMediaManager imm = + new InfoMediaManager(mContext, null, null, mLocalBluetoothManager); + mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager, imm, null); mLocalMediaManager.startScan(); mDevice = mLocalMediaManager.getCurrentConnectedDevice(); mLocalMediaManager.registerCallback(mDeviceCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 3da767e51be0..6654b7ab0749 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -18,6 +18,7 @@ package com.android.systemui.qs; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; @@ -26,6 +27,7 @@ import android.view.Gravity; import android.view.View; import android.widget.LinearLayout; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -77,10 +79,11 @@ public class QuickQSPanel extends QSPanel { BroadcastDispatcher broadcastDispatcher, QSLogger qsLogger, NotificationMediaManager notificationMediaManager, - @Background Executor backgroundExecutor + @Background Executor backgroundExecutor, + @Nullable LocalBluetoothManager localBluetoothManager ) { super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, notificationMediaManager, - backgroundExecutor); + backgroundExecutor, localBluetoothManager); if (mFooter != null) { removeView(mFooter.getView()); } @@ -155,8 +158,6 @@ public class QuickQSPanel extends QSPanel { Dependency.get(TunerService.class).removeTunable(mNumTiles); } - - @Override protected String getDumpableTag() { return TAG; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 3fe348f3ea02..94afde786e78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -262,7 +262,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< default void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int biometricModality, - boolean requireConfirmation, int userId, String opPackageName) { } + boolean requireConfirmation, int userId, String opPackageName, + long operationId) { } default void onBiometricAuthenticated() { } default void onBiometricHelp(String message) { } default void onBiometricError(int modality, int error, int vendorCode) { } @@ -780,7 +781,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< @Override public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int biometricModality, boolean requireConfirmation, int userId, String opPackageName) { + int biometricModality, boolean requireConfirmation, int userId, String opPackageName, + long operationId) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; @@ -789,6 +791,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< args.arg3 = requireConfirmation; args.argi2 = userId; args.arg4 = opPackageName; + args.arg5 = operationId; mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args) .sendToTarget(); } @@ -1164,7 +1167,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< someArgs.argi1 /* biometricModality */, (boolean) someArgs.arg3 /* requireConfirmation */, someArgs.argi2 /* userId */, - (String) someArgs.arg4 /* opPackageName */); + (String) someArgs.arg4 /* opPackageName */, + (long) someArgs.arg5 /* operationId */); } someArgs.recycle(); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java index 18574f0f891e..ac3523b2fffd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java @@ -32,6 +32,8 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaOutputSliceConstants; @@ -106,7 +108,9 @@ public class MediaTransferManager { public MediaTransferManager(Context context) { mContext = context; mActivityStarter = Dependency.get(ActivityStarter.class); - mLocalMediaManager = new LocalMediaManager(mContext, null, null); + LocalBluetoothManager lbm = Dependency.get(LocalBluetoothManager.class); + InfoMediaManager imm = new InfoMediaManager(mContext, null, null, lbm); + mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, null); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java index 0bfcdbdb1118..4759d56099f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java @@ -23,6 +23,7 @@ import android.graphics.drawable.Icon; import android.text.TextUtils; import android.view.NotificationHeaderView; import android.view.View; +import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; @@ -182,24 +183,24 @@ public class NotificationHeaderUtil { private void sanitizeChild(View child) { if (child != null) { - NotificationHeaderView header = (NotificationHeaderView) child.findViewById( + ViewGroup header = child.findViewById( com.android.internal.R.id.notification_header); sanitizeHeader(header); } } - private void sanitizeHeader(NotificationHeaderView rowHeader) { + private void sanitizeHeader(ViewGroup rowHeader) { if (rowHeader == null) { return; } final int childCount = rowHeader.getChildCount(); View time = rowHeader.findViewById(com.android.internal.R.id.time); boolean hasVisibleText = false; - for (int i = 1; i < childCount - 1 ; i++) { + for (int i = 0; i < childCount; i++) { View child = rowHeader.getChildAt(i); if (child instanceof TextView && child.getVisibility() != View.GONE - && !mDividers.contains(Integer.valueOf(child.getId())) + && !mDividers.contains(child.getId()) && child != time) { hasVisibleText = true; break; @@ -212,14 +213,14 @@ public class NotificationHeaderUtil { time.setVisibility(timeVisibility); View left = null; View right; - for (int i = 1; i < childCount - 1 ; i++) { + for (int i = 0; i < childCount; i++) { View child = rowHeader.getChildAt(i); - if (mDividers.contains(Integer.valueOf(child.getId()))) { + if (mDividers.contains(child.getId())) { boolean visible = false; // Lets find the item to the right - for (i++; i < childCount - 1; i++) { + for (i++; i < childCount; i++) { right = rowHeader.getChildAt(i); - if (mDividers.contains(Integer.valueOf(right.getId()))) { + if (mDividers.contains(right.getId())) { // A divider was found, this needs to be hidden i--; break; @@ -276,14 +277,18 @@ public class NotificationHeaderUtil { if (!mApply) { return; } - NotificationHeaderView header = row.getContractedNotificationHeader(); - if (header == null) { - // No header found. We still consider this to be the same to avoid weird flickering + View contractedChild = row.getPrivateLayout().getContractedChild(); + if (contractedChild == null) { + return; + } + View ownView = contractedChild.findViewById(mId); + if (ownView == null) { + // No view found. We still consider this to be the same to avoid weird flickering // when for example showing an undo notification return; } Object childData = mExtractor == null ? null : mExtractor.extractData(row); - mApply = mComparator.compare(mParentView, header.findViewById(mId), + mApply = mComparator.compare(mParentView, ownView, mParentData, childData); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 901ed3f94760..eb8526d0ef91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -58,6 +58,7 @@ class NotificationShadeDepthController @Inject constructor( } lateinit var root: View + private var blurRoot: View? = null private var keyguardAnimator: Animator? = null private var notificationAnimator: Animator? = null private var updateScheduled: Boolean = false @@ -72,6 +73,7 @@ class NotificationShadeDepthController @Inject constructor( return shadeBlurRadius.toFloat() } }) + private val zoomInterpolator = Interpolators.ACCELERATE_DECELERATE private var shadeBlurRadius = 0 set(value) { if (field == value) return @@ -84,6 +86,7 @@ class NotificationShadeDepthController @Inject constructor( field = value scheduleUpdate() } + private var globalDialogVisibility = 0f /** * Callback that updates the window blur value and is called only once per frame. @@ -91,9 +94,12 @@ class NotificationShadeDepthController @Inject constructor( private val updateBlurCallback = Choreographer.FrameCallback { updateScheduled = false - val blur = max(shadeBlurRadius, wakeAndUnlockBlurRadius) - blurUtils.applyBlur(root.viewRootImpl, blur) - wallpaperManager.setWallpaperZoomOut(root.windowToken, blurUtils.ratioOfBlurRadius(blur)) + val blur = max(shadeBlurRadius, + max(wakeAndUnlockBlurRadius, blurUtils.blurRadiusOfRatio(globalDialogVisibility))) + blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur) + val rawZoom = max(blurUtils.ratioOfBlurRadius(blur), globalDialogVisibility) + wallpaperManager.setWallpaperZoomOut(root.windowToken, + zoomInterpolator.getInterpolation(rawZoom)) } /** @@ -162,14 +168,23 @@ class NotificationShadeDepthController @Inject constructor( shadeSpring.animateToFinalPosition(newBlur.toFloat()) } - private fun scheduleUpdate() { + private fun scheduleUpdate(viewToBlur: View? = null) { if (updateScheduled) { return } updateScheduled = true + blurRoot = viewToBlur choreographer.postFrameCallback(updateBlurCallback) } + fun updateGlobalDialogVisibility(visibility: Float, dialogView: View) { + if (visibility == globalDialogVisibility) { + return + } + globalDialogVisibility = visibility + scheduleUpdate(dialogView) + } + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { IndentingPrintWriter(pw, " ").let { it.println("StatusBarWindowBlurController:") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java index 7b5a70eb5430..2a45bc210580 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java @@ -41,6 +41,7 @@ public class ViewTransformationHelper implements TransformableView, private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view; private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>(); + private ArraySet<Integer> mKeysTransformingToSimilar = new ArraySet<>(); private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>(); private ValueAnimator mViewTransformationAnimation; @@ -48,8 +49,22 @@ public class ViewTransformationHelper implements TransformableView, mTransformedViews.put(key, transformedView); } + /** + * Add a view that transforms to a similar sibling, meaning that we should consider any mapping + * found treated as the same viewType. This is useful for imageViews, where it's hard to compare + * if the source images are the same when they are bitmap based. + * + * @param key The key how this is added + * @param transformedView the view that is added + */ + public void addViewTransformingToSimilar(int key, View transformedView) { + addTransformedView(key, transformedView); + mKeysTransformingToSimilar.add(key); + } + public void reset() { mTransformedViews.clear(); + mKeysTransformingToSimilar.clear(); } public void setCustomTransformation(CustomTransformation transformation, int viewType) { @@ -60,7 +75,11 @@ public class ViewTransformationHelper implements TransformableView, public TransformState getCurrentState(int fadingView) { View view = mTransformedViews.get(fadingView); if (view != null && view.getVisibility() != View.GONE) { - return TransformState.createFrom(view, this); + TransformState transformState = TransformState.createFrom(view, this); + if (mKeysTransformingToSimilar.contains(fadingView)) { + transformState.setIsSameAsAnyView(true); + } + return transformState; } return null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java index b732966b32db..9383f537db45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java @@ -21,9 +21,9 @@ import android.util.Pools; import android.view.View; import android.view.ViewGroup; +import com.android.internal.widget.IMessagingLayout; import com.android.internal.widget.MessagingGroup; import com.android.internal.widget.MessagingImageMessage; -import com.android.internal.widget.MessagingLayout; import com.android.internal.widget.MessagingLinearLayout; import com.android.internal.widget.MessagingMessage; import com.android.internal.widget.MessagingPropertyAnimator; @@ -41,7 +41,7 @@ public class MessagingLayoutTransformState extends TransformState { private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool = new Pools.SimplePool<>(40); private MessagingLinearLayout mMessageContainer; - private MessagingLayout mMessagingLayout; + private IMessagingLayout mMessagingLayout; private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>(); private float mRelativeTranslationOffset; @@ -266,8 +266,9 @@ public class MessagingLayoutTransformState extends TransformState { transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */ useLinearTransformation); boolean otherIsIsolated = otherGroup.getIsolatedMessage() == otherChild; - if (transformationAmount == 0.0f && otherIsIsolated) { - ownGroup.setTransformingImages(true); + if (transformationAmount == 0.0f + && (otherIsIsolated || otherGroup.isSingleLine())) { + ownGroup.setClippingDisabled(true); } if (otherChild == null) { child.setTranslationY(previousTranslation); @@ -291,11 +292,20 @@ public class MessagingLayoutTransformState extends TransformState { if (useLinearTransformation) { ownState.setDefaultInterpolator(Interpolators.LINEAR); } - ownState.setIsSameAsAnyView(sameAsAny); + ownState.setIsSameAsAnyView(sameAsAny && !isGone(otherView)); if (to) { if (otherView != null) { TransformState otherState = TransformState.createFrom(otherView, mTransformInfo); - ownState.transformViewTo(otherState, transformationAmount); + if (!isGone(otherView)) { + ownState.transformViewTo(otherState, transformationAmount); + } else { + if (!isGone(ownView)) { + ownState.disappear(transformationAmount, null); + } + // We still want to transform vertically if the view is gone, + // since avatars serve as anchors for the rest of the layout transition + ownState.transformViewVerticalTo(otherState, transformationAmount); + } otherState.recycle(); } else { ownState.disappear(transformationAmount, null); @@ -303,7 +313,16 @@ public class MessagingLayoutTransformState extends TransformState { } else { if (otherView != null) { TransformState otherState = TransformState.createFrom(otherView, mTransformInfo); - ownState.transformViewFrom(otherState, transformationAmount); + if (!isGone(otherView)) { + ownState.transformViewFrom(otherState, transformationAmount); + } else { + if (!isGone(ownView)) { + ownState.appear(transformationAmount, null); + } + // We still want to transform vertically if the view is gone, + // since avatars serve as anchors for the rest of the layout transition + ownState.transformViewVerticalFrom(otherState, transformationAmount); + } otherState.recycle(); } else { ownState.appear(transformationAmount, null); @@ -337,6 +356,9 @@ public class MessagingLayoutTransformState extends TransformState { } private boolean isGone(View view) { + if (view == null) { + return true; + } if (view.getVisibility() == View.GONE) { return true; } @@ -408,7 +430,7 @@ public class MessagingLayoutTransformState extends TransformState { ownGroup.getMessageContainer().setTranslationY(0); ownGroup.getSenderView().setTranslationY(0); } - ownGroup.setTransformingImages(false); + ownGroup.setClippingDisabled(false); ownGroup.updateClipRect(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9a4e789a2e03..f61fe9830939 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -31,7 +31,6 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Notification; import android.app.NotificationChannel; import android.content.Context; import android.content.pm.PackageInfo; @@ -151,7 +150,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mNotificationMinHeight; private int mNotificationMinHeightLarge; private int mNotificationMinHeightMedia; - private int mNotificationMinHeightMessaging; private int mNotificationMaxHeight; private int mIncreasedPaddingBetweenElements; private int mNotificationLaunchHeight; @@ -640,16 +638,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView && expandedView.findViewById(com.android.internal.R.id.media_actions) != null; boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar(); - Class<? extends Notification.Style> style = - mEntry.getSbn().getNotification().getNotificationStyle(); - boolean isMessagingLayout = Notification.MessagingStyle.class.equals(style); - if (customView && beforeP && !mIsSummaryWithChildren) { minHeight = beforeN ? mNotificationMinHeightBeforeN : mNotificationMinHeightBeforeP; } else if (isMediaLayout && showCompactMediaSeekbar) { minHeight = mNotificationMinHeightMedia; - } else if (isMessagingLayout) { - minHeight = mNotificationMinHeightMessaging; } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) { minHeight = mNotificationMinHeightLarge; } else { @@ -1057,19 +1049,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return getShowingLayout().getVisibleNotificationHeader(); } - - /** - * @return the contracted notification header. This can be different from - * {@link #getNotificationHeader()} and also {@link #getVisibleNotificationHeader()} and only - * returns the contracted version. - */ - public NotificationHeaderView getContractedNotificationHeader() { - if (mIsSummaryWithChildren) { - return mChildrenContainer.getHeaderView(); - } - return mPrivateLayout.getContractedNotificationHeader(); - } - public void setLongPressListener(LongPressListener longPressListener) { mLongPressListener = longPressListener; } @@ -1654,8 +1633,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView R.dimen.notification_min_height_increased); mNotificationMinHeightMedia = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_min_height_media); - mNotificationMinHeightMessaging = NotificationUtils.getFontScaledHeight(mContext, - R.dimen.notification_min_height_messaging); mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_height); mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 6dd4ff9235c4..91cf285f2262 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -25,6 +25,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; import android.os.AsyncTask; import android.os.CancellationSignal; import android.service.notification.StatusBarNotification; @@ -716,6 +718,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder sbn.getNotification()); Context packageContext = sbn.getPackageContext(mContext); + if (recoveredBuilder.usesTemplate()) { + // For all of our templates, we want it to be RTL + packageContext = new RtlEnabledContext(packageContext); + } Notification notification = sbn.getNotification(); if (notification.isMediaNotification()) { MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, @@ -782,6 +788,19 @@ public class NotificationContentInflater implements NotificationRowContentBinder // try to purge unnecessary cached entries. mRow.getImageResolver().purgeCache(); } + + private class RtlEnabledContext extends ContextWrapper { + private RtlEnabledContext(Context packageContext) { + super(packageContext); + } + + @Override + public ApplicationInfo getApplicationInfo() { + ApplicationInfo applicationInfo = super.getApplicationInfo(); + applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_RTL; + return applicationInfo; + } + } } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 27fd1b2c5aed..8b8a9012cbdc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1492,13 +1492,6 @@ public class NotificationContentView extends FrameLayout { } } - public NotificationHeaderView getContractedNotificationHeader() { - if (mContractedChild != null) { - return mContractedWrapper.getNotificationHeader(); - } - return null; - } - public NotificationHeaderView getVisibleNotificationHeader() { NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType); return wrapper == null ? null : wrapper.getNotificationHeader(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt new file mode 100644 index 000000000000..1e2571b5c801 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row.wrapper + +import android.content.Context +import android.view.View + +import com.android.internal.widget.ConversationLayout +import com.android.internal.widget.MessagingLinearLayout +import com.android.systemui.R +import com.android.systemui.statusbar.notification.NotificationUtils +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow + +/** + * Wraps a notification containing a converation template + */ +class NotificationConversationTemplateViewWrapper constructor( + ctx: Context, + view: View, + row: ExpandableNotificationRow +) + : NotificationTemplateViewWrapper(ctx, view, row) { + + private val minHeightWithActions: Int + private val conversationLayout: ConversationLayout + private var conversationIcon: View? = null + private var conversationBadge: View? = null + private var expandButton: View? = null + private var messagingLinearLayout: MessagingLinearLayout? = null + + init { + conversationLayout = view as ConversationLayout + minHeightWithActions = NotificationUtils.getFontScaledHeight(ctx, + R.dimen.notification_messaging_actions_min_height) + } + + private fun resolveViews() { + messagingLinearLayout = conversationLayout.messagingLinearLayout + conversationIcon = conversationLayout.requireViewById( + com.android.internal.R.id.conversation_icon) + conversationBadge = conversationLayout.requireViewById( + com.android.internal.R.id.conversation_icon_badge) + expandButton = conversationLayout.requireViewById( + com.android.internal.R.id.expand_button) + } + + override fun onContentUpdated(row: ExpandableNotificationRow) { + // Reinspect the notification. Before the super call, because the super call also updates + // the transformation types and we need to have our values set by then. + resolveViews() + super.onContentUpdated(row) + } + + override fun updateTransformedTypes() { + // This also clears the existing types + super.updateTransformedTypes() + messagingLinearLayout?.let { + mTransformationHelper.addTransformedView(it.id, it) + } + conversationIcon?.let { + mTransformationHelper.addViewTransformingToSimilar(it.id, it) + } + conversationBadge?.let { + mTransformationHelper.addViewTransformingToSimilar(it.id, it) + } + expandButton?.let { + mTransformationHelper.addViewTransformingToSimilar(it.id, it) + } + } + + override fun setRemoteInputVisible(visible: Boolean) { + conversationLayout.showHistoricMessages(visible) + } + + override fun updateExpandability(expandable: Boolean, onClickListener: View.OnClickListener?) { + conversationLayout.updateExpandability(expandable, onClickListener) + } + + override fun getMinLayoutHeight(): Int { + if (mActionsContainer != null && mActionsContainer.visibility != View.GONE) { + return minHeightWithActions + } else { + return super.getMinLayoutHeight() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 5e52c0a5a66f..1d061989a84c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -53,12 +53,13 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { protected final ViewTransformationHelper mTransformationHelper; protected int mColor; - private ImageView mIcon; + private ImageView mIcon; private NotificationExpandButton mExpandButton; protected NotificationHeaderView mNotificationHeader; private TextView mHeaderText; private ImageView mWorkProfileImage; + private boolean mIsLowPriority; private boolean mTransformLowPriorityTitle; private boolean mShowExpandButtonAtEnd; @@ -105,12 +106,16 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button); mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge); mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header); - mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd); - mColor = mNotificationHeader.getOriginalIconColor(); + if (mNotificationHeader != null) { + mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd); + mColor = mNotificationHeader.getOriginalIconColor(); + } } private void addAppOpsOnClickListener(ExpandableNotificationRow row) { - mNotificationHeader.setAppOpsOnClickListener(row.getAppOpsOnClickListener()); + if (mNotificationHeader != null) { + mNotificationHeader.setAppOpsOnClickListener(row.getAppOpsOnClickListener()); + } } @Override @@ -127,9 +132,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { updateCropToPaddingForImageViews(); Notification notification = row.getEntry().getSbn().getNotification(); mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon()); - // The work profile image is always the same lets just set the icon tag for it not to - // animate - mWorkProfileImage.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon()); + if (mWorkProfileImage != null) { + // The work profile image is always the same lets just set the icon tag for it not to + // animate + mWorkProfileImage.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon()); + } // We need to reset all views that are no longer transforming in case a view was previously // transformed, but now we decided to transform its container instead. @@ -174,8 +181,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { protected void updateTransformedTypes() { mTransformationHelper.reset(); - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon); - if (mIsLowPriority) { + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, + mIcon); + if (mIsLowPriority && mHeaderText != null) { mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mHeaderText); } @@ -184,7 +192,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { @Override public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) { mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); - mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); + if (mNotificationHeader != null) { + mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java index 0a1a2fe3ee54..d41f5af6c524 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java @@ -353,8 +353,12 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp @Override public void setHeaderVisibleAmount(float headerVisibleAmount) { super.setHeaderVisibleAmount(headerVisibleAmount); - mNotificationHeader.setAlpha(headerVisibleAmount); - mHeaderTranslation = (1.0f - headerVisibleAmount) * mFullHeaderTranslation; + float headerTranslation = 0f; + if (mNotificationHeader != null) { + mNotificationHeader.setAlpha(headerVisibleAmount); + headerTranslation = (1.0f - headerVisibleAmount) * mFullHeaderTranslation; + } + mHeaderTranslation = headerTranslation; mView.setTranslationY(mHeaderTranslation); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index c2eff8a6a776..c834e4b376ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -35,6 +35,7 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ContrastColorUtil; +import com.android.internal.widget.ConversationLayout; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.notification.TransformState; @@ -61,6 +62,9 @@ public abstract class NotificationViewWrapper implements TransformableView { return new NotificationMediaTemplateViewWrapper(ctx, v, row); } else if ("messaging".equals(v.getTag())) { return new NotificationMessagingTemplateViewWrapper(ctx, v, row); + } else if ("conversation".equals(v.getTag())) { + return new NotificationConversationTemplateViewWrapper(ctx, (ConversationLayout) v, + row); } Class<? extends Notification.Style> style = row.getEntry().getSbn().getNotification().getNotificationStyle(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java index c6c7b87da544..1db8e4c3d73e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java @@ -78,7 +78,9 @@ public class AuthContainerViewTest extends SysuiTestCase { mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_AUTHENTICATED); - verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED)); + verify(mCallback).onDismissed( + eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED), + eq(null) /* credentialAttestation */); } @Test @@ -87,7 +89,9 @@ public class AuthContainerViewTest extends SysuiTestCase { mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_USER_CANCELED); - verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_USER_CANCELED)); + verify(mCallback).onDismissed( + eq(AuthDialogCallback.DISMISSED_USER_CANCELED), + eq(null) /* credentialAttestation */); } @Test @@ -96,7 +100,9 @@ public class AuthContainerViewTest extends SysuiTestCase { mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE); - verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE)); + verify(mCallback).onDismissed( + eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE), + eq(null) /* credentialAttestation */); } @Test @@ -114,7 +120,9 @@ public class AuthContainerViewTest extends SysuiTestCase { mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_ERROR); - verify(mCallback).onDismissed(AuthDialogCallback.DISMISSED_ERROR); + verify(mCallback).onDismissed( + eq(AuthDialogCallback.DISMISSED_ERROR), + eq(null) /* credentialAttestation */); } @Test @@ -219,7 +227,8 @@ public class AuthContainerViewTest extends SysuiTestCase { @Override public void animateAway(int reason) { - mConfig.mCallback.onDismissed(reason); + // TODO: Credential attestation should be testable/tested + mConfig.mCallback.onDismissed(reason, null /* credentialAttestation */); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index 65399bfb901e..fc1ddf74a448 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -57,12 +57,14 @@ import com.android.systemui.statusbar.CommandQueue; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.AdditionalMatchers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.Random; @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -110,52 +112,75 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception { showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED); - verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED, + null /* credentialAttestation */); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL), + eq(null) /* credentialAttestation */); } @Test public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception { showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE); - verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE, + null /* credentialAttestation */); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_NEGATIVE), + eq(null) /* credentialAttestation */); } @Test public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception { showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE); - verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE, + null /* credentialAttestation */); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED), + eq(null) /* credentialAttestation */); } @Test public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception { showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED, + null /* credentialAttestation */); verify(mReceiver).onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED); + eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED), + eq(null) /* credentialAttestation */); } @Test public void testSendsReasonError_whenDismissedByError() throws Exception { showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR); - verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR, + null /* credentialAttestation */); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_ERROR), + eq(null) /* credentialAttestation */); } @Test public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception { showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER); - verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER, + null /* credentialAttestation */); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED), + eq(null) /* credentialAttestation */); } @Test public void testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated() throws Exception { showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED); - verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED); + + final byte[] credentialAttestation = generateRandomHAT(); + + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED, + credentialAttestation); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), + AdditionalMatchers.aryEq(credentialAttestation)); } // Statusbar tests @@ -302,8 +327,13 @@ public class AuthControllerTest extends SysuiTestCase { showDialog(Authenticators.DEVICE_CREDENTIAL, BiometricPrompt.TYPE_NONE); verify(mDialog1).show(any(), any()); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED); - verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED); + final byte[] credentialAttestation = generateRandomHAT(); + + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED, + credentialAttestation); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), + AdditionalMatchers.aryEq(credentialAttestation)); mAuthController.hideAuthenticationDialog(); } @@ -395,20 +425,24 @@ public class AuthControllerTest extends SysuiTestCase { assertNull(mAuthController.mCurrentDialog); assertNull(mAuthController.mReceiver); verify(mDialog1).dismissWithoutCallback(true /* animate */); - verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL)); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL), + eq(null) /* credentialAttestation */); } @Test public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() { showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED, + null /* credentialAttestation */); mAuthController.onTryAgainPressed(); } @Test public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() { showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED, + null /* credentialAttestation */); mAuthController.onDeviceCredentialPressed(); } @@ -422,7 +456,9 @@ public class AuthControllerTest extends SysuiTestCase { assertNull(mAuthController.mCurrentDialog); assertNull(mAuthController.mReceiver); verify(mDialog1).dismissWithoutCallback(true /* animate */); - verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL)); + verify(mReceiver).onDialogDismissed( + eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL), + eq(null) /* credentialAttestation */); } // Helpers @@ -433,7 +469,8 @@ public class AuthControllerTest extends SysuiTestCase { biometricModality, true /* requireConfirmation */, 0 /* userId */, - "testPackage"); + "testPackage", + 0 /* operationId */); } private Bundle createTestDialogBundle(int authenticators) { @@ -453,6 +490,13 @@ public class AuthControllerTest extends SysuiTestCase { return bundle; } + private byte[] generateRandomHAT() { + byte[] HAT = new byte[69]; + Random random = new Random(); + random.nextBytes(HAT); + return HAT; + } + private final class TestableAuthController extends AuthController { private int mBuildCount = 0; private Bundle mLastBiometricPromptBundle; @@ -464,7 +508,7 @@ public class AuthControllerTest extends SysuiTestCase { @Override protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation, int userId, int type, String opPackageName, - boolean skipIntro) { + boolean skipIntro, long operationId) { mLastBiometricPromptBundle = biometricPromptBundle; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index f40fc94763ab..866dfdc9c7e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -268,13 +268,18 @@ public class BubbleDataTest extends SysuiTestCase { sendUpdatedEntryAtTime(mEntryB2, 5000); mBubbleData.setListener(mListener); - // Test sendUpdatedEntryAtTime(mEntryC1, 6000); verifyUpdateReceived(); - - // Verify assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_AGED); - assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(mBubbleA1)); + assertOverflowChangedTo(ImmutableList.of(mBubbleA1)); + + Bubble bubbleA1 = mBubbleData.getOrCreateBubble(mEntryA1); + bubbleA1.markUpdatedAt(7000L); + mBubbleData.notificationEntryUpdated(bubbleA1, false /* suppressFlyout*/, + true /* showInShade */); + verifyUpdateReceived(); + assertBubbleRemoved(mBubbleA2, BubbleController.DISMISS_AGED); + assertOverflowChangedTo(ImmutableList.of(mBubbleA2)); } /** @@ -931,6 +936,11 @@ public class BubbleDataTest extends SysuiTestCase { assertThat(update.expanded).named("expanded").isEqualTo(expected); } + private void assertOverflowChangedTo(ImmutableList<Bubble> bubbles) { + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.overflowBubbles).isEqualTo(bubbles); + } + private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) { return createBubbleEntry(userId, notifKey, packageName, 1000); diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java index a2ab78483b68..b863f143056f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java @@ -58,7 +58,8 @@ public class PipAnimationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - mPipAnimationController = new PipAnimationController(mContext); + mPipAnimationController = new PipAnimationController( + mContext, new PipSurfaceTransactionHelper(mContext)); MockitoAnnotations.initMocks(this); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index 616399afe7a4..ac304210d416 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java @@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; @@ -85,6 +86,8 @@ public class QSPanelTest extends SysuiTestCase { private NotificationMediaManager mNotificationMediaManager; @Mock private Executor mBackgroundExecutor; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; @Before public void setup() throws Exception { @@ -94,7 +97,8 @@ public class QSPanelTest extends SysuiTestCase { mTestableLooper.runWithLooper(() -> { mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher, - mQSLogger, mNotificationMediaManager, mBackgroundExecutor); + mQSLogger, mNotificationMediaManager, mBackgroundExecutor, + mLocalBluetoothManager); // Provides a parent with non-zero size for QSPanel mParentView = new FrameLayout(mContext); mParentView.addView(mQsPanel); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 7c6da630b3bf..cffcabb55e14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -409,11 +409,12 @@ public class CommandQueueTest extends SysuiTestCase { public void testShowAuthenticationDialog() { Bundle bundle = new Bundle(); String packageName = "test"; + final long operationId = 1; mCommandQueue.showAuthenticationDialog(bundle, null /* receiver */, 1, true, 3, - packageName); + packageName, operationId); waitForIdleSync(); verify(mCallbacks).showAuthenticationDialog(eq(bundle), eq(null), eq(1), eq(true), eq(3), - eq(packageName)); + eq(packageName), eq(operationId)); } @Test diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml index 6f692c8021c0..b9c5f1ded3d5 100644 --- a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml @@ -16,6 +16,9 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string translatable="false" name="config_mainBuiltInDisplayCutout"></string> + <string translatable="false" name="config_mainBuiltInDisplayCutoutRectApproximation"></string> + <!-- Height of the status bar in portrait. The height should be Max((status bar content height + waterfall top size), top cutout size) --> <dimen name="status_bar_height_portrait">28dp</dimen> diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java index 7fe086df0729..3376f2bd74f5 100644 --- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.util.Slog; @@ -38,11 +39,8 @@ import com.android.server.inputmethod.InputMethodManagerInternal; import java.util.Collections; import java.util.Optional; -import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; /** * Maintains an autofill inline suggestion session that communicates with the IME. @@ -69,7 +67,7 @@ import java.util.concurrent.TimeoutException; final class InlineSuggestionSession { private static final String TAG = "AfInlineSuggestionSession"; - private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; + private static final int INLINE_REQUEST_TIMEOUT_MS = 200; @NonNull private final InputMethodManagerInternal mInputMethodManagerInternal; @@ -80,6 +78,8 @@ final class InlineSuggestionSession { private final Object mLock; @NonNull private final ImeStatusListener mImeStatusListener; + @NonNull + private final Handler mHandler; /** * To avoid the race condition, one should not access {@code mPendingImeResponse} without @@ -105,10 +105,11 @@ final class InlineSuggestionSession { private boolean mImeInputViewStarted = false; InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal, - int userId, ComponentName componentName) { + int userId, ComponentName componentName, Handler handler) { mInputMethodManagerInternal = inputMethodManagerInternal; mUserId = userId; mComponentName = componentName; + mHandler = handler; mLock = new Object(); mImeStatusListener = new ImeStatusListener() { @Override @@ -137,7 +138,8 @@ final class InlineSuggestionSession { }; } - public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId) { + public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId, + @NonNull Consumer<InlineSuggestionsRequest> requestConsumer) { if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId); synchronized (mLock) { @@ -154,26 +156,16 @@ final class InlineSuggestionSession { mUserId, new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()), new InlineSuggestionsRequestCallbackImpl(mPendingImeResponse, - mImeStatusListener)); + mImeStatusListener, requestConsumer, mHandler, mLock)); } } - public Optional<InlineSuggestionsRequest> waitAndGetInlineSuggestionsRequest() { + public Optional<InlineSuggestionsRequest> getInlineSuggestionsRequest() { final CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse(); - if (pendingImeResponse == null) { + if (pendingImeResponse == null || !pendingImeResponse.isDone()) { return Optional.empty(); } - try { - return Optional.ofNullable(pendingImeResponse.get(INLINE_REQUEST_TIMEOUT_MS, - TimeUnit.MILLISECONDS)).map(ImeResponse::getRequest); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions request in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions request cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - return Optional.empty(); + return Optional.ofNullable(pendingImeResponse.getNow(null)).map(ImeResponse::getRequest); } public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) { @@ -200,8 +192,7 @@ final class InlineSuggestionSession { if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked without IMS request"); return false; } - // There is no need to wait on the CompletableFuture since it should have been completed - // when {@link #waitAndGetInlineSuggestionsRequest()} was called. + // There is no need to wait on the CompletableFuture since it should have been completed. ImeResponse imeResponse = completedImsResponse.getNow(null); if (imeResponse == null) { if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked with pending IMS response"); @@ -249,20 +240,50 @@ final class InlineSuggestionSession { private static final class InlineSuggestionsRequestCallbackImpl extends IInlineSuggestionsRequestCallback.Stub { + private final Object mLock; + @GuardedBy("mLock") private final CompletableFuture<ImeResponse> mResponse; + @GuardedBy("mLock") + private final Consumer<InlineSuggestionsRequest> mRequestConsumer; private final ImeStatusListener mImeStatusListener; + private final Handler mHandler; + private final Runnable mTimeoutCallback; private InlineSuggestionsRequestCallbackImpl(CompletableFuture<ImeResponse> response, - ImeStatusListener imeStatusListener) { + ImeStatusListener imeStatusListener, + Consumer<InlineSuggestionsRequest> requestConsumer, + Handler handler, Object lock) { mResponse = response; mImeStatusListener = imeStatusListener; + mRequestConsumer = requestConsumer; + mLock = lock; + + mHandler = handler; + mTimeoutCallback = () -> { + Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest."); + synchronized (mLock) { + completeIfNotLocked(null); + } + }; + mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS); + } + + private void completeIfNotLocked(@Nullable ImeResponse response) { + if (mResponse.isDone()) { + return; + } + mResponse.complete(response); + mRequestConsumer.accept(response == null ? null : response.mRequest); + mHandler.removeCallbacks(mTimeoutCallback); } @BinderThread @Override public void onInlineSuggestionsUnsupported() throws RemoteException { if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called."); - mResponse.complete(null); + synchronized (mLock) { + completeIfNotLocked(null); + } } @BinderThread @@ -281,9 +302,13 @@ final class InlineSuggestionSession { mImeStatusListener.onInputMethodFinishInputView(imeFieldId); } if (request != null && callback != null) { - mResponse.complete(new ImeResponse(request, callback)); + synchronized (mLock) { + completeIfNotLocked(new ImeResponse(request, callback)); + } } else { - mResponse.complete(null); + synchronized (mLock) { + completeIfNotLocked(null); + } } } diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index e73f9ce4f7c0..53afa6e283d9 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -57,6 +57,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.IResultReceiver; import com.android.server.autofill.ui.InlineSuggestionFactory; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; @@ -255,8 +256,12 @@ final class RemoteAugmentedAutofillService mCallbacks.logAugmentedAutofillSelected(sessionId, dataset.getId()); try { - client.autofill(sessionId, dataset.getFieldIds(), - dataset.getFieldValues()); + final ArrayList<AutofillId> fieldIds = dataset.getFieldIds(); + final int size = fieldIds.size(); + final boolean hideHighlight = size == 1 + && fieldIds.get(0).equals(focusedId); + client.autofill(sessionId, fieldIds, dataset.getFieldValues(), + hideHighlight); } catch (RemoteException e) { Slog.w(TAG, "Encounter exception autofilling the values"); } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 826500952e60..5064663871e3 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -114,7 +114,9 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; /** * A session for a given activity. @@ -307,7 +309,47 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Receiver of assist data from the app's {@link Activity}. */ - private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() { + private final AssistDataReceiverImpl mAssistReceiver = new AssistDataReceiverImpl(); + + private final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub { + + @GuardedBy("mLock") + private InlineSuggestionsRequest mPendingInlineSuggestionsRequest; + @GuardedBy("mLock") + private FillRequest mPendingFillRequest; + @GuardedBy("mLock") + private CountDownLatch mCountDownLatch; + + @Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked( + boolean isInlineRequest) { + mCountDownLatch = new CountDownLatch(isInlineRequest ? 2 : 1); + mPendingFillRequest = null; + mPendingInlineSuggestionsRequest = null; + return isInlineRequest ? (inlineSuggestionsRequest) -> { + synchronized (mLock) { + mPendingInlineSuggestionsRequest = inlineSuggestionsRequest; + mCountDownLatch.countDown(); + maybeRequestFillLocked(); + } + } : null; + } + + void maybeRequestFillLocked() { + if (mCountDownLatch == null || mCountDownLatch.getCount() > 0 + || mPendingFillRequest == null) { + return; + } + if (mPendingInlineSuggestionsRequest != null) { + mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), + mPendingFillRequest.getFillContexts(), mPendingFillRequest.getClientState(), + mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest); + } + mRemoteFillService.onFillRequest(mPendingFillRequest); + mPendingInlineSuggestionsRequest = null; + mPendingFillRequest = null; + mCountDownLatch = null; + } + @Override public void onHandleAssistData(Bundle resultData) throws RemoteException { if (mRemoteFillService == null) { @@ -402,17 +444,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ false); - - final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest = - mInlineSuggestionSession.waitAndGetInlineSuggestionsRequest(); request = new FillRequest(requestId, contexts, mClientState, flags, - inlineSuggestionsRequest.orElse(null)); + /*inlineSuggestionsRequest=*/null); + + mPendingFillRequest = request; + mCountDownLatch.countDown(); + maybeRequestFillLocked(); } if (mActivityToken != null) { mService.sendActivityAssistDataToContentCapture(mActivityToken, resultData); } - mRemoteFillService.onFillRequest(request); } @Override @@ -605,9 +647,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState, int newState, int flags) { if (isInlineSuggestionsEnabledLocked()) { - mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId); + Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = + mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true); + if (inlineSuggestionsRequestConsumer != null) { + mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, + inlineSuggestionsRequestConsumer); + } + } else { + mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false); } - requestNewFillResponseLocked(viewState, newState, flags); } @@ -708,7 +756,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState setClientLocked(client); mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId, - componentName); + componentName, handler); mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); @@ -2663,7 +2711,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response, @Nullable String filterText) { final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest = - mInlineSuggestionSession.waitAndGetInlineSuggestionsRequest(); + mInlineSuggestionSession.getInlineSuggestionsRequest(); if (!inlineSuggestionsRequest.isPresent()) { Log.w(TAG, "InlineSuggestionsRequest unavailable"); return false; @@ -2896,6 +2944,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Tries to trigger Augmented Autofill when the standard service could not fulfill a request. * + * <p> The request may not have been sent when this method returns as it may be waiting for + * the inline suggestion request asynchronously. + * * @return callback to destroy the autofill UI, or {@code null} if not supported. */ // TODO(b/123099468): might need to call it in other places, like when the service returns a @@ -2978,6 +3029,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId); + final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill = + (inlineSuggestionsRequest) -> { + remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, + focusedId, + currentValue, inlineSuggestionsRequest, + /*inlineSuggestionsCallback=*/ + response -> mInlineSuggestionSession.onInlineSuggestionsResponse( + mCurrentViewId, response), + /*onErrorCallback=*/ () -> { + synchronized (mLock) { + cancelAugmentedAutofillLocked(); + } + }, mService.getRemoteInlineSuggestionRenderServiceLocked()); + }; + // There are 3 cases when augmented autofill should ask IME for a new request: // 1. standard autofill provider is None // 2. standard autofill provider doesn't support inline (and returns null response) @@ -2985,21 +3051,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // doesn't want autofill if (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabledLocked()) { if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); - mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId); - } - - Optional<InlineSuggestionsRequest> inlineSuggestionsRequest = - mInlineSuggestionSession.waitAndGetInlineSuggestionsRequest(); - remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId, - currentValue, inlineSuggestionsRequest.orElse(null), - response -> mInlineSuggestionSession.onInlineSuggestionsResponse( - mCurrentViewId, response), - () -> { - synchronized (mLock) { - cancelAugmentedAutofillLocked(); - } - }, mService.getRemoteInlineSuggestionRenderServiceLocked()); - + mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, + /*requestConsumer=*/ requestAugmentedAutofill); + } else { + requestAugmentedAutofill.accept( + mInlineSuggestionSession.getInlineSuggestionsRequest().orElse(null)); + } if (mAugmentedAutofillDestroyer == null) { mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(); } @@ -3382,6 +3439,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final List<AutofillId> ids = new ArrayList<>(entryCount); final List<AutofillValue> values = new ArrayList<>(entryCount); boolean waitingDatasetAuth = false; + boolean hideHighlight = (entryCount == 1 + && dataset.getFieldIds().get(0).equals(mCurrentViewId)); for (int i = 0; i < entryCount; i++) { if (dataset.getFieldValues().get(i) == null) { continue; @@ -3405,7 +3464,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); - mClient.autofill(id, ids, values); + mClient.autofill(id, ids, values, hideHighlight); if (dataset.getId() != null) { if (mSelectedDatasetIds == null) { mSelectedDatasetIds = new ArrayList<>(); diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index ee59d89b7f15..0ca9dd92877f 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -329,8 +329,14 @@ public final class InlineSuggestionFactory { @NonNull Runnable onErrorCallback) { return new IInlineSuggestionUiCallback.Stub() { @Override - public void onAutofill() throws RemoteException { + public void onClick() throws RemoteException { onAutofillCallback.run(); + callback.onClick(); + } + + @Override + public void onLongClick() throws RemoteException { + callback.onLongClick(); } @Override diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 7287a44600fa..ecf1f134a3de 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -7803,12 +7803,15 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleNetworkTestedWithExtras( @NonNull ConnectivityReportEvent reportEvent, @NonNull PersistableBundle extras) { final NetworkAgentInfo nai = reportEvent.mNai; + final NetworkCapabilities networkCapabilities = + new NetworkCapabilities(nai.networkCapabilities); + clearNetworkCapabilitiesUids(networkCapabilities); final ConnectivityReport report = new ConnectivityReport( reportEvent.mNai.network, reportEvent.mTimestampMillis, nai.linkProperties, - nai.networkCapabilities, + networkCapabilities, extras); final List<IConnectivityDiagnosticsCallback> results = getMatchingPermissionedCallbacks(nai); @@ -7824,13 +7827,16 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleDataStallSuspected( @NonNull NetworkAgentInfo nai, long timestampMillis, int detectionMethod, @NonNull PersistableBundle extras) { + final NetworkCapabilities networkCapabilities = + new NetworkCapabilities(nai.networkCapabilities); + clearNetworkCapabilitiesUids(networkCapabilities); final DataStallReport report = new DataStallReport( nai.network, timestampMillis, detectionMethod, nai.linkProperties, - nai.networkCapabilities, + networkCapabilities, extras); final List<IConnectivityDiagnosticsCallback> results = getMatchingPermissionedCallbacks(nai); @@ -7856,6 +7862,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void clearNetworkCapabilitiesUids(@NonNull NetworkCapabilities nc) { + nc.setUids(null); + nc.setAdministratorUids(Collections.EMPTY_LIST); + nc.setOwnerUid(Process.INVALID_UID); + } + private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks( @NonNull NetworkAgentInfo nai) { final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>(); diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index 80036bb4b4fa..808d322020cb 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -99,6 +99,8 @@ public class RescueParty { private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; + private static final String DEVICE_CONFIG_DISABLE_FLAG = "disable_rescue_party"; + private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; @@ -114,6 +116,14 @@ public class RescueParty { return false; } + // We're disabled if the DeviceConfig disable flag is set to true. + // This is in case that an emergency rollback of the feature is needed. + if (DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_CONFIGURATION, DEVICE_CONFIG_DISABLE_FLAG, false)) { + Slog.v(TAG, "Disabled because of DeviceConfig flag"); + return true; + } + // We're disabled on all engineering devices if (Build.IS_ENG) { Slog.v(TAG, "Disabled because of eng build"); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 0a8e70c7d772..bac7565adfa5 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -80,6 +80,7 @@ public class SettingsToPropertiesMapper { @VisibleForTesting static final String[] sDeviceConfigScopes = new String[] { DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + DeviceConfig.NAMESPACE_CONFIGURATION, DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT, DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS, DeviceConfig.NAMESPACE_MEDIA_NATIVE, diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index ecdafb0a7a79..e7c09baf3aeb 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -24,6 +24,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import static android.hardware.biometrics.BiometricManager.Authenticators; import android.annotation.IntDef; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.UserSwitchObserver; @@ -296,7 +297,7 @@ public class BiometricService extends SystemService { } case MSG_ON_DISMISSED: { - handleOnDismissed(msg.arg1); + handleOnDismissed(msg.arg1, (byte[]) msg.obj); break; } @@ -611,8 +612,12 @@ public class BiometricService extends SystemService { } @Override - public void onDialogDismissed(int reason) throws RemoteException { - mHandler.obtainMessage(MSG_ON_DISMISSED, reason, 0 /* arg2 */).sendToTarget(); + public void onDialogDismissed(int reason, @Nullable byte[] credentialAttestation) + throws RemoteException { + mHandler.obtainMessage(MSG_ON_DISMISSED, + reason, + 0 /* arg2 */, + credentialAttestation /* obj */).sendToTarget(); } @Override @@ -1422,7 +1427,8 @@ public class BiometricService extends SystemService { 0 /* biometricModality */, false /* requireConfirmation */, mCurrentAuthSession.mUserId, - mCurrentAuthSession.mOpPackageName); + mCurrentAuthSession.mOpPackageName, + mCurrentAuthSession.mSessionId); } else { mPendingAuthSession.mClientReceiver.onError(modality, error, vendorCode); mPendingAuthSession = null; @@ -1458,7 +1464,7 @@ public class BiometricService extends SystemService { } } - private void handleOnDismissed(int reason) { + private void handleOnDismissed(int reason, @Nullable byte[] credentialAttestation) { if (mCurrentAuthSession == null) { Slog.e(TAG, "onDismissed: " + reason + ", auth session null"); return; @@ -1469,6 +1475,7 @@ public class BiometricService extends SystemService { try { switch (reason) { case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED: + mKeyStore.addAuthToken(credentialAttestation); case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED: case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED: if (mCurrentAuthSession.mTokenEscrow != null) { @@ -1616,7 +1623,8 @@ public class BiometricService extends SystemService { try { mStatusBarService.showAuthenticationDialog(mCurrentAuthSession.mBundle, mInternalReceiver, modality, requireConfirmation, userId, - mCurrentAuthSession.mOpPackageName); + mCurrentAuthSession.mOpPackageName, + mCurrentAuthSession.mSessionId); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } @@ -1701,7 +1709,8 @@ public class BiometricService extends SystemService { 0 /* biometricModality */, false /* requireConfirmation */, mCurrentAuthSession.mUserId, - mCurrentAuthSession.mOpPackageName); + mCurrentAuthSession.mOpPackageName, + sessionId); } else { mPendingAuthSession.mState = STATE_AUTH_CALLED; for (AuthenticatorWrapper authenticator : mAuthenticators) { diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 7bdeb5969f1f..2e9818d15963 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -151,6 +151,15 @@ public final class CompatChange extends CompatibilityChangeInfo { return true; } + /** + * Checks whether a change has an override for a package. + * @param packageName name of the package + * @return true if there is such override + */ + boolean hasOverride(String packageName) { + return mPackageOverrides != null && mPackageOverrides.containsKey(packageName); + } + @Override public String toString() { StringBuilder sb = new StringBuilder("ChangeId(") diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index b2ea311bf999..aeaa1fedf9e3 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -247,11 +247,13 @@ final class CompatConfig { CompatChange c = mChanges.get(changeId); try { if (c != null) { - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(changeId, packageName); - allowedState.enforce(changeId, packageName); - overrideExists = true; - c.removePackageOverride(packageName); + overrideExists = c.hasOverride(packageName); + if (overrideExists) { + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(changeId, packageName); + allowedState.enforce(changeId, packageName); + c.removePackageOverride(packageName); + } } } catch (RemoteException e) { // Should never occur, since validator is in the same process. @@ -298,12 +300,14 @@ final class CompatConfig { for (int i = 0; i < mChanges.size(); ++i) { try { CompatChange change = mChanges.valueAt(i); - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(change.getId(), - packageName); - allowedState.enforce(change.getId(), packageName); - if (change != null) { - mChanges.valueAt(i).removePackageOverride(packageName); + if (change.hasOverride(packageName)) { + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(change.getId(), + packageName); + allowedState.enforce(change.getId(), packageName); + if (change != null) { + mChanges.valueAt(i).removePackageOverride(packageName); + } } } catch (RemoteException e) { // Should never occur, since validator is in the same process. diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 7c3cab17704f..20ffd9f51d6e 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -2563,7 +2563,7 @@ public class Vpn { public void exitIfOuterInterfaceIs(String interfaze) { if (interfaze.equals(mOuterInterface)) { Log.i(TAG, "Legacy VPN is going down with " + interfaze); - exit(); + exitVpnRunner(); } } @@ -2572,6 +2572,10 @@ public class Vpn { public void exitVpnRunner() { // We assume that everything is reset after stopping the daemons. interrupt(); + + // Always disconnect. This may be called again in cleanupVpnStateLocked() if + // exitVpnRunner() was called from exit(), but it will be a no-op. + agentDisconnect(); try { mContext.unregisterReceiver(mBroadcastReceiver); } catch (IllegalArgumentException e) {} @@ -2794,7 +2798,7 @@ public class Vpn { } catch (Exception e) { Log.i(TAG, "Aborting", e); updateState(DetailedState.FAILED, e.getMessage()); - exit(); + exitVpnRunner(); } } diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 6da0de13f623..c2d1364c9a01 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -69,8 +69,10 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; @@ -85,6 +87,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** Implementation of {@link AppIntegrityManagerService}. */ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { @@ -467,8 +470,23 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { if (installationPath == null) { throw new IllegalArgumentException("Installation path is null, package not found"); } - SourceStampVerificationResult sourceStampVerificationResult = - SourceStampVerifier.verify(installationPath.getAbsolutePath()); + + SourceStampVerificationResult sourceStampVerificationResult; + if (installationPath.isDirectory()) { + try { + List<String> apkFiles = + Files.list(installationPath.toPath()) + .map(path -> path.toAbsolutePath().toString()) + .collect(Collectors.toList()); + sourceStampVerificationResult = SourceStampVerifier.verify(apkFiles); + } catch (IOException e) { + throw new IllegalArgumentException("Could not read APK directory"); + } + } else { + sourceStampVerificationResult = + SourceStampVerifier.verify(installationPath.getAbsolutePath()); + } + appInstallMetadata.setIsStampPresent(sourceStampVerificationResult.isPresent()); appInstallMetadata.setIsStampVerified(sourceStampVerificationResult.isVerified()); // A verified stamp is set to be trusted. diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 483f83efd508..9dddf7badebd 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.app.AppOpsManager.MODE_DEFAULT; import static android.content.pm.DataLoaderType.INCREMENTAL; import static android.content.pm.DataLoaderType.STREAMING; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; @@ -166,6 +167,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission"; private static final String TAG_WHITELISTED_RESTRICTED_PERMISSION = "whitelisted-restricted-permission"; + private static final String TAG_AUTO_REVOKE_PERMISSIONS_MODE = + "auto-revoke-permissions-mode"; private static final String ATTR_SESSION_ID = "sessionId"; private static final String ATTR_USER_ID = "userId"; private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName"; @@ -623,6 +626,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } info.grantedRuntimePermissions = params.grantedRuntimePermissions; info.whitelistedRestrictedPermissions = params.whitelistedRestrictedPermissions; + info.autoRevokePermissionsMode = params.autoRevokePermissionsMode; info.installFlags = params.installFlags; info.isMultiPackage = params.isMultiPackage; info.isStaged = params.isStaged; @@ -2889,6 +2893,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + private static void writeAutoRevokePermissionsMode(@NonNull XmlSerializer out, int mode) + throws IOException { + out.startTag(null, TAG_AUTO_REVOKE_PERMISSIONS_MODE); + writeIntAttribute(out, ATTR_MODE, mode); + out.endTag(null, TAG_AUTO_REVOKE_PERMISSIONS_MODE); + } + private static File buildAppIconFile(int sessionId, @NonNull File sessionsDir) { return new File(sessionsDir, "app_icon." + sessionId + ".png"); @@ -2969,6 +2980,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions); writeWhitelistedRestrictedPermissionsLocked(out, params.whitelistedRestrictedPermissions); + writeAutoRevokePermissionsMode(out, params.autoRevokePermissionsMode); // Persist app icon if changed since last written File appIconFile = buildAppIconFile(sessionId, sessionsDir); @@ -3112,6 +3124,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // depth. List<String> grantedRuntimePermissions = new ArrayList<>(); List<String> whitelistedRestrictedPermissions = new ArrayList<>(); + int autoRevokePermissionsMode = MODE_DEFAULT; List<Integer> childSessionIds = new ArrayList<>(); List<InstallationFile> files = new ArrayList<>(); int outerDepth = in.getDepth(); @@ -3128,6 +3141,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { whitelistedRestrictedPermissions.add(readStringAttribute(in, ATTR_NAME)); } + if (TAG_AUTO_REVOKE_PERMISSIONS_MODE.equals(in.getName())) { + autoRevokePermissionsMode = readIntAttribute(in, ATTR_MODE); + } if (TAG_CHILD_SESSION.equals(in.getName())) { childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID)); } @@ -3150,6 +3166,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions; } + params.autoRevokePermissionsMode = autoRevokePermissionsMode; + int[] childSessionIdsArray; if (childSessionIds.size() > 0) { childSessionIdsArray = new int[childSessionIds.size()]; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 61bf5f0b6dfd..221e7dcc5eec 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -24,6 +24,9 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.REQUEST_DELETE_PACKAGES; import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_DEFAULT; +import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.Intent.CATEGORY_HOME; @@ -1654,12 +1657,13 @@ public class PackageManagerService extends IPackageManager.Stub && parentRes.pkg != null) ? parentRes.pkg.getRequestedPermissions() : args.whitelistedRestrictedPermissions; + int autoRevokePermissionsMode = args.autoRevokePermissionsMode; // Handle the parent package handlePackagePostInstall(parentRes, grantPermissions, killApp, virtualPreload, grantedPermissions, - whitelistedRestrictedPermissions, didRestore, - args.installSource.installerPackageName, args.observer, + whitelistedRestrictedPermissions, autoRevokePermissionsMode, + didRestore, args.installSource.installerPackageName, args.observer, args.mDataLoaderType); // Handle the child packages @@ -1669,7 +1673,8 @@ public class PackageManagerService extends IPackageManager.Stub PackageInstalledInfo childRes = parentRes.addedChildPackages.valueAt(i); handlePackagePostInstall(childRes, grantPermissions, killApp, virtualPreload, grantedPermissions, - whitelistedRestrictedPermissions, false /*didRestore*/, + whitelistedRestrictedPermissions, autoRevokePermissionsMode, + false /*didRestore*/, args.installSource.installerPackageName, args.observer, args.mDataLoaderType); } @@ -1998,6 +2003,7 @@ public class PackageManagerService extends IPackageManager.Stub private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions, boolean killApp, boolean virtualPreload, String[] grantedPermissions, List<String> whitelistedRestrictedPermissions, + int autoRevokePermissionsMode, boolean launchedForRestore, String installerPackage, IPackageInstallObserver2 installObserver, int dataLoaderType) { final boolean succeeded = res.returnCode == PackageManager.INSTALL_SUCCEEDED; @@ -2018,6 +2024,11 @@ public class PackageManagerService extends IPackageManager.Stub Process.myUid(), FLAG_PERMISSION_WHITELIST_INSTALLER); } + if (autoRevokePermissionsMode == MODE_ALLOWED || autoRevokePermissionsMode == MODE_IGNORED) { + mPermissionManager.setAutoRevokeWhitelisted(res.pkg.getPackageName(), + autoRevokePermissionsMode == MODE_IGNORED, UserHandle.myUserId()); + } + // Now that we successfully installed the package, grant runtime // permissions if requested before broadcasting the install. Also // for legacy apps in permission review mode we clear the permission @@ -14295,6 +14306,7 @@ public class PackageManagerService extends IPackageManager.Stub final String packageAbiOverride; final String[] grantedRuntimePermissions; final List<String> whitelistedRestrictedPermissions; + final int autoRevokePermissionsMode; final VerificationInfo verificationInfo; final PackageParser.SigningDetails signingDetails; final int installReason; @@ -14309,6 +14321,7 @@ public class PackageManagerService extends IPackageManager.Stub int installFlags, InstallSource installSource, String volumeUuid, VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride, String[] grantedPermissions, List<String> whitelistedRestrictedPermissions, + int autoRevokePermissionsMode, SigningDetails signingDetails, int installReason, long requiredInstalledVersionCode, int dataLoaderType) { super(user); @@ -14322,6 +14335,7 @@ public class PackageManagerService extends IPackageManager.Stub this.packageAbiOverride = packageAbiOverride; this.grantedRuntimePermissions = grantedPermissions; this.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions; + this.autoRevokePermissionsMode = autoRevokePermissionsMode; this.signingDetails = signingDetails; this.installReason = installReason; this.requiredInstalledVersionCode = requiredInstalledVersionCode; @@ -14358,6 +14372,7 @@ public class PackageManagerService extends IPackageManager.Stub packageAbiOverride = sessionParams.abiOverride; grantedRuntimePermissions = sessionParams.grantedRuntimePermissions; whitelistedRestrictedPermissions = sessionParams.whitelistedRestrictedPermissions; + autoRevokePermissionsMode = sessionParams.autoRevokePermissionsMode; signingDetails = activeInstallSession.getSigningDetails(); requiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode; forceQueryableOverride = sessionParams.forceQueryableOverride; @@ -14726,9 +14741,8 @@ public class PackageManagerService extends IPackageManager.Stub verificationState.setRequiredVerifierUid(requiredUid); final int installerUid = verificationInfo == null ? -1 : verificationInfo.installerUid; - if (!origin.existing && requiredUid != -1 - && isVerificationEnabled( - pkgLite, verifierUser.getIdentifier(), installFlags, installerUid)) { + if (!origin.existing && isVerificationEnabled(pkgLite, verifierUser.getIdentifier(), + installFlags, installerUid)) { final Intent verification = new Intent( Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -14794,9 +14808,9 @@ public class PackageManagerService extends IPackageManager.Stub } } - final ComponentName requiredVerifierComponent = matchComponentForVerifier( - mRequiredVerifierPackage, receivers); if (mRequiredVerifierPackage != null) { + final ComponentName requiredVerifierComponent = matchComponentForVerifier( + mRequiredVerifierPackage, receivers); /* * Send the intent to the required verification agent, * but only start the verification timeout after the @@ -14954,6 +14968,7 @@ public class PackageManagerService extends IPackageManager.Stub final String abiOverride; final String[] installGrantPermissions; final List<String> whitelistedRestrictedPermissions; + final int autoRevokePermissionsMode; /** If non-null, drop an async trace when the install completes */ final String traceMethod; final int traceCookie; @@ -14973,6 +14988,7 @@ public class PackageManagerService extends IPackageManager.Stub UserHandle user, String[] instructionSets, String abiOverride, String[] installGrantPermissions, List<String> whitelistedRestrictedPermissions, + int autoRevokePermissionsMode, String traceMethod, int traceCookie, SigningDetails signingDetails, int installReason, boolean forceQueryableOverride, MultiPackageInstallParams multiPackageInstallParams, int dataLoaderType) { @@ -14987,6 +15003,7 @@ public class PackageManagerService extends IPackageManager.Stub this.abiOverride = abiOverride; this.installGrantPermissions = installGrantPermissions; this.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions; + this.autoRevokePermissionsMode = autoRevokePermissionsMode; this.traceMethod = traceMethod; this.traceCookie = traceCookie; this.signingDetails = signingDetails; @@ -15002,6 +15019,7 @@ public class PackageManagerService extends IPackageManager.Stub params.installSource, params.volumeUuid, params.getUser(), null /*instructionSets*/, params.packageAbiOverride, params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions, + params.autoRevokePermissionsMode, params.traceMethod, params.traceCookie, params.signingDetails, params.installReason, params.forceQueryableOverride, params.mParentInstallParams, params.mDataLoaderType); @@ -15093,7 +15111,7 @@ public class PackageManagerService extends IPackageManager.Stub /** Existing install */ FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) { super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY, - null, null, instructionSets, null, null, null, null, 0, + null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0, PackageParser.SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN, false, null /* parent */, DataLoaderType.NONE); @@ -22469,7 +22487,8 @@ public class PackageManagerService extends IPackageManager.Stub final InstallParams params = new InstallParams(origin, move, installObserver, installFlags, installSource, volumeUuid, null /*verificationInfo*/, user, packageAbiOverride, null /*grantedPermissions*/, - null /*whitelistedRestrictedPermissions*/, PackageParser.SigningDetails.UNKNOWN, + null /*whitelistedRestrictedPermissions*/, MODE_DEFAULT /* autoRevokePermissions */, + PackageParser.SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST, DataLoaderType.NONE); params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params)); @@ -24361,6 +24380,24 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public boolean isAutoRevokeWhitelisted(String packageName) { + int mode = mInjector.getAppOpsManager().checkOpNoThrow( + AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, + Binder.getCallingUid(), packageName); + if (mode == MODE_ALLOWED) { + return false; + } else if (mode == MODE_IGNORED) { + return true; + } else { + synchronized (mLock) { + boolean manifestWhitelisted = + mPackages.get(packageName).isAllowDontAutoRevokePermmissions(); + return manifestWhitelisted; + } + } + } + + @Override public int getInstallReason(String packageName, int userId) { final int callingUid = Binder.getCallingUid(); mPermissionManager.enforceCrossUserPermission(callingUid, userId, 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 85da5593223b..46f121d8c09d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -19,6 +19,8 @@ package com.android.server.pm.permission; import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; @@ -53,6 +55,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.AppOpsManager; import android.app.ApplicationPackageManager; import android.app.IActivityManager; import android.app.admin.DeviceAdminInfo; @@ -217,6 +220,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { /** Default permission policy to provide proper behaviour out-of-the-box */ private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy; + /** App ops manager */ + private final AppOpsManager mAppOpsManager; + /** * Built-in permissions. Read from system configuration files. Mapping is from * UID to permission name. @@ -356,6 +362,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class); mUserManagerInt = LocalServices.getService(UserManagerInternal.class); mSettings = new PermissionSettings(mLock); + mAppOpsManager = context.getSystemService(AppOpsManager.class); mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); @@ -1199,6 +1206,77 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override + public boolean setAutoRevokeWhitelisted( + @NonNull String packageName, boolean whitelisted, int userId) { + Objects.requireNonNull(packageName); + + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + final int callingUid = Binder.getCallingUid(); + + if (!checkAutoRevokeAccess(pkg, callingUid)) { + return false; + } + + if (mAppOpsManager + .checkOpNoThrow(AppOpsManager.OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, + callingUid, packageName) + != MODE_ALLOWED) { + // Whitelist user set - don't override + return false; + } + + final long identity = Binder.clearCallingIdentity(); + try { + mAppOpsManager.setMode(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, + callingUid, packageName, + whitelisted ? MODE_IGNORED : MODE_ALLOWED); + } finally { + Binder.restoreCallingIdentity(identity); + } + return true; + } + + private boolean checkAutoRevokeAccess(AndroidPackage pkg, int callingUid) { + if (pkg == null) { + return false; + } + + final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS) + == PackageManager.PERMISSION_GRANTED; + final boolean isCallerInstallerOnRecord = + mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); + + if (!isCallerPrivileged && !isCallerInstallerOnRecord) { + throw new SecurityException("Caller must either hold " + + Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS + + " or be the installer on record"); + } + return true; + } + + @Override + public boolean isAutoRevokeWhitelisted(@NonNull String packageName, int userId) { + Objects.requireNonNull(packageName); + + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + final int callingUid = Binder.getCallingUid(); + + if (!checkAutoRevokeAccess(pkg, callingUid)) { + return false; + } + + final long identity = Binder.clearCallingIdentity(); + try { + return mAppOpsManager.checkOpNoThrow( + AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, callingUid, packageName) + == MODE_IGNORED; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void grantRuntimePermission(String packageName, String permName, final int userId) { final int callingUid = Binder.getCallingUid(); final boolean overridePolicy = @@ -3093,6 +3171,36 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } + @Override + public List<String> getAutoRevokeExemptionRequestedPackages(int userId) { + mContext.enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, + "Must hold " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY); + + List<String> result = new ArrayList<>(); + mPackageManagerInt.forEachInstalledPackage(pkg -> { + if (pkg.isDontAutoRevokePermmissions()) { + result.add(pkg.getPackageName()); + } + }, userId); + + return result; + } + + @Override + public List<String> getAutoRevokeExemptionGrantedPackages(int userId) { + mContext.enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, + "Must hold " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY); + + List<String> result = new ArrayList<>(); + mPackageManagerInt.forEachInstalledPackage(pkg -> { + if (pkg.isAllowDontAutoRevokePermmissions()) { + result.add(pkg.getPackageName()); + } + }, userId); + + return result; + } + private boolean isNewPlatformPermissionForPackage(String perm, AndroidPackage pkg) { boolean allowed = false; final int NP = PackageParser.NEW_PERMISSIONS.length; @@ -4347,6 +4455,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { packageName, permissions, flags, userId); } @Override + public void setAutoRevokeWhitelisted( + @NonNull String packageName, boolean whitelisted, int userId) { + PermissionManagerService.this.setAutoRevokeWhitelisted( + packageName, whitelisted, userId); + } + @Override public void updatePermissions(@NonNull String packageName, @Nullable AndroidPackage pkg) { PermissionManagerService.this .updatePermissions(packageName, pkg, mDefaultPermissionCallback); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 32ef2cee5685..356d0abc1d19 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -189,7 +189,9 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager /** Sets the whitelisted, restricted permissions for the given package. */ public abstract void setWhitelistedRestrictedPermissions( @NonNull String packageName, @NonNull List<String> permissions, - @PackageManager.PermissionWhitelistFlags int flags, @NonNull int userId); + @PackageManager.PermissionWhitelistFlags int flags, int userId); + public abstract void setAutoRevokeWhitelisted( + @NonNull String packageName, boolean whitelisted, int userId); /** * Update permissions when a package changed. diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index d589353cf3a0..161f30449a52 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -32,6 +32,7 @@ import android.app.AppOpsManager; import android.app.AppOpsManagerInternal; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -173,6 +174,65 @@ public final class PermissionPolicyService extends SystemService { } catch (RemoteException doesNotHappen) { Slog.wtf(LOG_TAG, "Cannot set up app-ops listener"); } + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + intentFilter.addDataScheme("package"); + + + /* TODO ntmyren: enable receiver when test flakes are fixed + getContext().registerReceiverAsUser(new BroadcastReceiver() { + final List<Integer> mUserSetupUids = new ArrayList<>(200); + final Map<UserHandle, PermissionControllerManager> mPermControllerManagers = + new HashMap<>(); + + @Override + public void onReceive(Context context, Intent intent) { + boolean hasSetupRun = true; + try { + hasSetupRun = Settings.Secure.getInt(getContext().getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE) != 0; + } catch (Settings.SettingNotFoundException e) { + // Ignore error, assume setup has run + } + int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + // If there is no valid package for the given UID, return immediately + if (packageManagerInternal.getPackage(uid) == null) { + return; + } + + if (hasSetupRun) { + if (!mUserSetupUids.isEmpty()) { + synchronized (mUserSetupUids) { + for (int i = mUserSetupUids.size() - 1; i >= 0; i--) { + updateUid(mUserSetupUids.get(i)); + } + mUserSetupUids.clear(); + } + } + updateUid(uid); + } else { + synchronized (mUserSetupUids) { + if (!mUserSetupUids.contains(uid)) { + mUserSetupUids.add(uid); + } + } + } + } + + private void updateUid(int uid) { + UserHandle user = UserHandle.getUserHandleForUid(uid); + PermissionControllerManager manager = mPermControllerManagers.get(user); + if (manager == null) { + manager = new PermissionControllerManager( + getUserContext(getContext(), user), FgThread.getHandler()); + mPermControllerManagers.put(user, manager); + } + manager.updateUserSensitiveForApp(uid); + } + }, UserHandle.ALL, intentFilter, null, null); + */ } /** @@ -182,7 +242,6 @@ public final class PermissionPolicyService extends SystemService { * {@link AppOpsManager#sOpToSwitch share an op} to control the access. * * @param permission The permission - * * @return The op that controls the access of the permission */ private static int getSwitchOp(@NonNull String permission) { diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 84cd4cfd67be..78ef68c09988 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -664,12 +664,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int biometricModality, boolean requireConfirmation, int userId, String opPackageName) { + int biometricModality, boolean requireConfirmation, int userId, String opPackageName, + long operationId) { enforceBiometricDialog(); if (mBar != null) { try { mBar.showAuthenticationDialog(bundle, receiver, biometricModality, - requireConfirmation, userId, opPackageName); + requireConfirmation, userId, opPackageName, operationId); } catch (RemoteException ex) { } } diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 816452679c45..74a6383bc01c 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -41,6 +41,7 @@ import android.util.Slog; import android.util.SparseArray; import android.view.textclassifier.ConversationActions; import android.view.textclassifier.SelectionEvent; +import android.view.textclassifier.SystemTextClassifierMetadata; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationConstants; import android.view.textclassifier.TextClassificationContext; @@ -179,12 +180,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi TextSelection.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); + Objects.requireNonNull(request.getSystemTextClassifierMetadata()); handleRequest( - request.getUserId(), - request.getCallingPackageName(), + request.getSystemTextClassifierMetadata(), + /* verifyCallingPackage= */ true, /* attemptToBind= */ true, - request.getUseDefaultTextClassifier(), service -> service.onSuggestSelection(sessionId, request, callback), "onSuggestSelection", callback); @@ -196,12 +197,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi TextClassification.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); + Objects.requireNonNull(request.getSystemTextClassifierMetadata()); handleRequest( - request.getUserId(), - request.getCallingPackageName(), + request.getSystemTextClassifierMetadata(), + /* verifyCallingPackage= */ true, /* attemptToBind= */ true, - request.getUseDefaultTextClassifier(), service -> service.onClassifyText(sessionId, request, callback), "onClassifyText", callback); @@ -213,12 +214,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi TextLinks.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); + Objects.requireNonNull(request.getSystemTextClassifierMetadata()); handleRequest( - request.getUserId(), - request.getCallingPackageName(), + request.getSystemTextClassifierMetadata(), + /* verifyCallingPackage= */ true, /* attemptToBind= */ true, - request.getUseDefaultTextClassifier(), service -> service.onGenerateLinks(sessionId, request, callback), "onGenerateLinks", callback); @@ -229,12 +230,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Nullable TextClassificationSessionId sessionId, SelectionEvent event) throws RemoteException { Objects.requireNonNull(event); + Objects.requireNonNull(event.getSystemTextClassifierMetadata()); handleRequest( - event.getUserId(), - /* callingPackageName= */ null, + event.getSystemTextClassifierMetadata(), + /* verifyCallingPackage= */ false, /* attemptToBind= */ false, - event.getUseDefaultTextClassifier(), service -> service.onSelectionEvent(sessionId, event), "onSelectionEvent", NO_OP_CALLBACK); @@ -246,18 +247,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi TextClassifierEvent event) throws RemoteException { Objects.requireNonNull(event); - final int userId = event.getEventContext() == null - ? UserHandle.getCallingUserId() - : event.getEventContext().getUserId(); - final boolean useDefaultTextClassifier = - event.getEventContext() != null - ? event.getEventContext().getUseDefaultTextClassifier() - : true; + final TextClassificationContext eventContext = event.getEventContext(); + final SystemTextClassifierMetadata systemTcMetadata = + eventContext != null ? eventContext.getSystemTextClassifierMetadata() : null; + handleRequest( - userId, - /* callingPackageName= */ null, + systemTcMetadata, + /* verifyCallingPackage= */ false, /* attemptToBind= */ false, - useDefaultTextClassifier, service -> service.onTextClassifierEvent(sessionId, event), "onTextClassifierEvent", NO_OP_CALLBACK); @@ -269,12 +266,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi TextLanguage.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); + Objects.requireNonNull(request.getSystemTextClassifierMetadata()); handleRequest( - request.getUserId(), - request.getCallingPackageName(), + request.getSystemTextClassifierMetadata(), + /* verifyCallingPackage= */ true, /* attemptToBind= */ true, - request.getUseDefaultTextClassifier(), service -> service.onDetectLanguage(sessionId, request, callback), "onDetectLanguage", callback); @@ -286,12 +283,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi ConversationActions.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); + Objects.requireNonNull(request.getSystemTextClassifierMetadata()); handleRequest( - request.getUserId(), - request.getCallingPackageName(), + request.getSystemTextClassifierMetadata(), + /* verifyCallingPackage= */ true, /* attemptToBind= */ true, - request.getUseDefaultTextClassifier(), service -> service.onSuggestConversationActions(sessionId, request, callback), "onSuggestConversationActions", callback); @@ -303,13 +300,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi throws RemoteException { Objects.requireNonNull(sessionId); Objects.requireNonNull(classificationContext); + Objects.requireNonNull(classificationContext.getSystemTextClassifierMetadata()); - final int userId = classificationContext.getUserId(); handleRequest( - userId, - classificationContext.getPackageName(), + classificationContext.getSystemTextClassifierMetadata(), + /* verifyCallingPackage= */ true, /* attemptToBind= */ false, - classificationContext.getUseDefaultTextClassifier(), service -> { service.onCreateTextClassificationSession(classificationContext, sessionId); mSessionCache.put(sessionId, classificationContext); @@ -333,11 +329,13 @@ public final class TextClassificationManagerService extends ITextClassifierServi textClassificationContext != null ? textClassificationContext.useDefaultTextClassifier : true; + final SystemTextClassifierMetadata sysTcMetadata = new SystemTextClassifierMetadata( + "", userId, useDefaultTextClassifier); + handleRequest( - userId, - /* callingPackageName= */ null, + sysTcMetadata, + /* verifyCallingPackage= */ false, /* attemptToBind= */ false, - useDefaultTextClassifier, service -> { service.onDestroyTextClassificationSession(sessionId); mSessionCache.remove(sessionId); @@ -412,10 +410,9 @@ public final class TextClassificationManagerService extends ITextClassifierServi } private void handleRequest( - @UserIdInt int userId, - @Nullable String callingPackageName, + @Nullable SystemTextClassifierMetadata sysTcMetadata, + boolean verifyCallingPackage, boolean attemptToBind, - boolean useDefaultTextClassifier, @NonNull ThrowingConsumer<ITextClassifierService> textClassifierServiceConsumer, @NonNull String methodName, @NonNull ITextClassifierCallback callback) throws RemoteException { @@ -423,8 +420,17 @@ public final class TextClassificationManagerService extends ITextClassifierServi Objects.requireNonNull(methodName); Objects.requireNonNull(callback); + final int userId = + sysTcMetadata == null ? UserHandle.getCallingUserId() : sysTcMetadata.getUserId(); + final String callingPackageName = + sysTcMetadata == null ? null : sysTcMetadata.getCallingPackageName(); + final boolean useDefaultTextClassifier = + sysTcMetadata == null ? true : sysTcMetadata.useDefaultTextClassifier(); + try { - validateCallingPackage(callingPackageName); + if (verifyCallingPackage) { + validateCallingPackage(callingPackageName); + } validateUser(userId); } catch (Exception e) { throw new RemoteException("Invalid request: " + e.getMessage(), e, @@ -636,8 +642,10 @@ public final class TextClassificationManagerService extends ITextClassifierServi public final boolean useDefaultTextClassifier; StrippedTextClassificationContext(TextClassificationContext textClassificationContext) { - userId = textClassificationContext.getUserId(); - useDefaultTextClassifier = textClassificationContext.getUseDefaultTextClassifier(); + SystemTextClassifierMetadata sysTcMetadata = + textClassificationContext.getSystemTextClassifierMetadata(); + userId = sysTcMetadata.getUserId(); + useDefaultTextClassifier = sysTcMetadata.useDefaultTextClassifier(); } } diff --git a/services/core/java/com/android/server/tv/UinputBridge.java b/services/core/java/com/android/server/tv/UinputBridge.java index 752aa6683a88..a2fe5fcde8c2 100644 --- a/services/core/java/com/android/server/tv/UinputBridge.java +++ b/services/core/java/com/android/server/tv/UinputBridge.java @@ -28,7 +28,7 @@ import java.io.IOException; public final class UinputBridge { private final CloseGuard mCloseGuard = CloseGuard.get(); private long mPtr; - private IBinder mToken = null; + private IBinder mToken; private static native long nativeOpen(String name, String uniqueId, int width, int height, int maxPointers); @@ -39,6 +39,25 @@ public final class UinputBridge { private static native void nativeSendPointerUp(long ptr, int pointerId); private static native void nativeSendPointerSync(long ptr); + /** Opens a gamepad - will support gamepad key and axis sending */ + private static native long nativeGamepadOpen(String name, String uniqueId); + + /** Marks the specified key up/down for a gamepad */ + private static native void nativeSendGamepadKey(long ptr, int keyIndex, boolean down); + + /** + * Gamepads pre-define the following axes: + * - Left joystick X, axis == ABS_X == 0, range [0, 254] + * - Left joystick Y, axis == ABS_Y == 1, range [0, 254] + * - Right joystick X, axis == ABS_RX == 3, range [0, 254] + * - Right joystick Y, axis == ABS_RY == 4, range [0, 254] + * - Left trigger, axis == ABS_Z == 2, range [0, 254] + * - Right trigger, axis == ABS_RZ == 5, range [0, 254] + * - DPad X, axis == ABS_HAT0X == 0x10, range [-1, 1] + * - DPad Y, axis == ABS_HAT0Y == 0x11, range [-1, 1] + */ + private static native void nativeSendGamepadAxisValue(long ptr, int axis, int value); + public UinputBridge(IBinder token, String name, int width, int height, int maxPointers) throws IOException { if (width < 1 || height < 1) { @@ -58,12 +77,31 @@ public final class UinputBridge { mCloseGuard.open("close"); } + /** Constructor used by static factory methods */ + private UinputBridge(IBinder token, long ptr) { + mPtr = ptr; + mToken = token; + mCloseGuard.open("close"); + } + + /** Opens a UinputBridge that supports gamepad buttons and axes. */ + public static UinputBridge openGamepad(IBinder token, String name) + throws IOException { + if (token == null) { + throw new IllegalArgumentException("Token cannot be null"); + } + long ptr = nativeGamepadOpen(name, token.toString()); + if (ptr == 0) { + throw new IOException("Could not open uinput device " + name); + } + + return new UinputBridge(token, ptr); + } + @Override protected void finalize() throws Throwable { try { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } + mCloseGuard.warnIfOpen(); close(mToken); } finally { mToken = null; @@ -119,7 +157,35 @@ public final class UinputBridge { if (isTokenValid(token)) { nativeSendPointerSync(mPtr); } + } + + /** Send a gamepad key + * @param keyIndex - the index of the w3-spec key + * @param down - is the key pressed ? + */ + public void sendGamepadKey(IBinder token, int keyIndex, boolean down) { + if (isTokenValid(token)) { + nativeSendGamepadKey(mPtr, keyIndex, down); + } + } + /** Send a gamepad axis value. + * - Left joystick X, axis == ABS_X == 0, range [0, 254] + * - Left joystick Y, axis == ABS_Y == 1, range [0, 254] + * - Right joystick X, axis == ABS_RX == 3, range [0, 254] + * - Right joystick Y, axis == ABS_RY == 4, range [0, 254] + * - Left trigger, axis == ABS_Z == 2, range [0, 254] + * - Right trigger, axis == ABS_RZ == 5, range [0, 254] + * - DPad X, axis == ABS_HAT0X == 0x10, range [-1, 1] + * - DPad Y, axis == ABS_HAT0Y == 0x11, range [-1, 1] + * + * @param axis is the axis index + * @param value is the value to set for that axis + */ + public void sendGamepadAxisValue(IBinder token, int axis, int value) { + if (isTokenValid(token)) { + nativeSendGamepadAxisValue(mPtr, axis, value); + } } public void clear(IBinder token) { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 958c8af3af24..30912e55f908 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -409,6 +409,13 @@ class InsetsPolicy { t.close(); } + // Since we don't push applySurfaceParams to a Handler-queue we don't need + // to push release in this case. + @Override + public void releaseSurfaceControlFromRt(SurfaceControl sc) { + sc.release(); + } + @Override public void startAnimation(InsetsAnimationControlImpl controller, WindowInsetsAnimationControlListener listener, int types, diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp index 6f9d012d3145..b96fbf5f5359 100644 --- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp +++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp @@ -172,26 +172,25 @@ static bool readChunk(int fd, std::vector<uint8_t>& data) { BlockHeader readHeader(std::span<uint8_t>& data); -static inline int32_t readBEInt32(borrowed_fd fd) { +static inline int32_t readLEInt32(borrowed_fd fd) { int32_t result; ReadFully(fd, &result, sizeof(result)); - result = int32_t(be32toh(result)); + result = int32_t(le32toh(result)); return result; } static inline std::vector<char> readBytes(borrowed_fd fd) { - int32_t size = readBEInt32(fd); + int32_t size = readLEInt32(fd); std::vector<char> result(size); ReadFully(fd, result.data(), size); return result; } static inline int32_t skipIdSigHeaders(borrowed_fd fd) { - readBEInt32(fd); // version - readBytes(fd); // verityRootHash - readBytes(fd); // v3Digest - readBytes(fd); // pkcs7SignatureBlock - return readBEInt32(fd); // size of the verity tree + readLEInt32(fd); // version + readBytes(fd); // hashingInfo + readBytes(fd); // signingInfo + return readLEInt32(fd); // size of the verity tree } static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) { diff --git a/services/core/jni/com_android_server_tv_GamepadKeys.h b/services/core/jni/com_android_server_tv_GamepadKeys.h new file mode 100644 index 000000000000..11fc9031da3b --- /dev/null +++ b/services/core/jni/com_android_server_tv_GamepadKeys.h @@ -0,0 +1,79 @@ +#ifndef ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_ +#define ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_ + +#include <linux/input.h> + +namespace android { + +// Follows the W3 spec for gamepad buttons and their corresponding mapping into +// Linux keycodes. Note that gamepads are generally not very well standardized +// and various controllers will result in different buttons. This mapping tries +// to be reasonable. +// +// W3 Button spec: https://www.w3.org/TR/gamepad/#remapping +// +// Standard gamepad keycodes are added plus 2 additional buttons (e.g. Stadia +// has "Assistant" and "Share", PS4 has the touchpad button). +// +// To generate this list, PS4, XBox, Stadia and Nintendo Switch Pro were tested. +static const int GAMEPAD_KEY_CODES[19] = { + // Right-side buttons. A/B/X/Y or circle/triangle/square/X or similar + BTN_A, // "South", A, GAMEPAD and SOUTH have the same constant + BTN_B, // "East", BTN_B, BTN_EAST have the same constant + BTN_X, // "West", Note that this maps to X and NORTH in constants + BTN_Y, // "North", Note that this maps to Y and WEST in constants + + BTN_TL, // "Left Bumper" / "L1" - Nintendo sends BTN_WEST instead + BTN_TR, // "Right Bumper" / "R1" - Nintendo sends BTN_Z instead + + // For triggers, gamepads vary: + // - Stadia sends analog values over ABS_GAS/ABS_BRAKE and sends + // TriggerHappy3/4 as digital presses + // - PS4 and Xbox send analog values as ABS_Z/ABS_RZ + // - Nintendo Pro sends BTN_TL/BTN_TR (since bumpers behave differently) + // As placeholders we chose the stadia trigger-happy values since TL/TR are + // sent for bumper button presses + BTN_TRIGGER_HAPPY4, // "Left Trigger" / "L2" + BTN_TRIGGER_HAPPY3, // "Right Trigger" / "R2" + + BTN_SELECT, // "Select/Back". Often "options" or similar + BTN_START, // "Start/forward". Often "hamburger" icon + + BTN_THUMBL, // "Left Joystick Pressed" + BTN_THUMBR, // "Right Joystick Pressed" + + // For DPads, gamepads generally only send axis changes + // on ABS_HAT0X and ABS_HAT0Y. + KEY_UP, // "Digital Pad up" + KEY_DOWN, // "Digital Pad down" + KEY_LEFT, // "Digital Pad left" + KEY_RIGHT, // "Digital Pad right" + + BTN_MODE, // "Main button" (Stadia/PS/XBOX/Home) + + BTN_TRIGGER_HAPPY1, // Extra button: "Assistant" for Stadia + BTN_TRIGGER_HAPPY2, // Extra button: "Share" for Stadia +}; + +// Defines information for an axis. +struct Axis { + int number; + int rangeMin; + int rangeMax; +}; + +// List of all axes supported by a gamepad +static const Axis GAMEPAD_AXES[] = { + {ABS_X, 0, 254}, // Left joystick X + {ABS_Y, 0, 254}, // Left joystick Y + {ABS_RX, 0, 254}, // Right joystick X + {ABS_RY, 0, 254}, // Right joystick Y + {ABS_Z, 0, 254}, // Left trigger + {ABS_RZ, 0, 254}, // Right trigger + {ABS_HAT0X, -1, 1}, // DPad X + {ABS_HAT0Y, -1, 1}, // DPad Y +}; + +} // namespace android + +#endif // ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_ diff --git a/services/core/jni/com_android_server_tv_TvKeys.h b/services/core/jni/com_android_server_tv_TvKeys.h index 4895f343ad8a..7eacdf6cb253 100644 --- a/services/core/jni/com_android_server_tv_TvKeys.h +++ b/services/core/jni/com_android_server_tv_TvKeys.h @@ -110,4 +110,4 @@ static Key KEYS[] = { } // namespace android -#endif // SERVICE_JNI_KEYS_H_ +#endif // ANDROIDTVREMOTE_SERVICE_JNI_KEYS_H_ diff --git a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp index c832c18517ce..0e96bd7ae47e 100644 --- a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp +++ b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "TvRemote-native-uiBridge" +#include "com_android_server_tv_GamepadKeys.h" #include "com_android_server_tv_TvKeys.h" #include "jni.h" @@ -92,27 +93,156 @@ static void unassignSlot(int32_t pointerId) { } } +static const int kInvalidFileDescriptor = -1; + +// Convenience class to manage an opened /dev/uinput device +class UInputDescriptor { +public: + UInputDescriptor() : mFd(kInvalidFileDescriptor) { + memset(&mUinputDescriptor, 0, sizeof(mUinputDescriptor)); + } + + // Auto-closes any open /dev/uinput descriptor unless detached. + ~UInputDescriptor(); + + // Open /dev/uinput and prepare to register + // the device with the given name and unique Id + bool Open(const char* name, const char* uniqueId); + + // Checks if the current file descriptor is valid + bool IsValid() const { return mFd != kInvalidFileDescriptor; } + + void EnableKey(int keyCode); + + void EnableAxesEvents(); + void EnableAxis(int axis, int rangeMin, int rangeMax); + + bool Create(); + + // Detaches from the current file descriptor + // Returns the file descriptor for /dev/uniput + int Detach(); + +private: + int mFd; + struct uinput_user_dev mUinputDescriptor; +}; + +UInputDescriptor::~UInputDescriptor() { + if (mFd != kInvalidFileDescriptor) { + close(mFd); + mFd = kInvalidFileDescriptor; + } +} + +int UInputDescriptor::Detach() { + int fd = mFd; + mFd = kInvalidFileDescriptor; + return fd; +} + +bool UInputDescriptor::Open(const char* name, const char* uniqueId) { + if (IsValid()) { + ALOGE("UInput device already open"); + return false; + } + + mFd = ::open("/dev/uinput", O_WRONLY | O_NDELAY); + if (mFd < 0) { + ALOGE("Cannot open /dev/uinput: %s.", strerror(errno)); + mFd = kInvalidFileDescriptor; + return false; + } + + // write device unique id to the phys property + ioctl(mFd, UI_SET_PHYS, uniqueId); + + memset(&mUinputDescriptor, 0, sizeof(mUinputDescriptor)); + strlcpy(mUinputDescriptor.name, name, UINPUT_MAX_NAME_SIZE); + mUinputDescriptor.id.version = 1; + mUinputDescriptor.id.bustype = BUS_VIRTUAL; + + // All UInput devices we use process keys + ioctl(mFd, UI_SET_EVBIT, EV_KEY); + + return true; +} + +void UInputDescriptor::EnableKey(int keyCode) { + ioctl(mFd, UI_SET_KEYBIT, keyCode); +} + +void UInputDescriptor::EnableAxesEvents() { + ioctl(mFd, UI_SET_EVBIT, EV_ABS); +} + +void UInputDescriptor::EnableAxis(int axis, int rangeMin, int rangeMax) { + if ((axis < 0) || (axis >= NELEM(mUinputDescriptor.absmin))) { + ALOGE("Invalid axis number: %d", axis); + return; + } + + if (ioctl(mFd, UI_SET_ABSBIT, axis) != 0) { + ALOGE("Failed to set absbit for %d", axis); + } + + mUinputDescriptor.absmin[axis] = rangeMin; + mUinputDescriptor.absmax[axis] = rangeMax; + mUinputDescriptor.absfuzz[axis] = 0; + mUinputDescriptor.absflat[axis] = 0; +} + +bool UInputDescriptor::Create() { + // register the input device + if (write(mFd, &mUinputDescriptor, sizeof(mUinputDescriptor)) != sizeof(mUinputDescriptor)) { + ALOGE("Cannot write uinput_user_dev to fd %d: %s.", mFd, strerror(errno)); + return false; + } + + if (ioctl(mFd, UI_DEV_CREATE) != 0) { + ALOGE("Unable to create uinput device: %s.", strerror(errno)); + return false; + } + + ALOGV("Created uinput device, fd=%d.", mFd); + + return true; +} + class NativeConnection { public: + enum class ConnectionType { + kRemoteDevice, + kGamepadDevice, + }; + ~NativeConnection(); static NativeConnection* open(const char* name, const char* uniqueId, int32_t width, int32_t height, int32_t maxPointerId); + static NativeConnection* openGamepad(const char* name, const char* uniqueId); + void sendEvent(int32_t type, int32_t code, int32_t value); int32_t getMaxPointers() const { return mMaxPointers; } + ConnectionType getType() const { return mType; } + + bool IsGamepad() const { return getType() == ConnectionType::kGamepadDevice; } + + bool IsRemote() const { return getType() == ConnectionType::kRemoteDevice; } + private: - NativeConnection(int fd, int32_t maxPointers); + NativeConnection(int fd, int32_t maxPointers, ConnectionType type); const int mFd; const int32_t mMaxPointers; + const ConnectionType mType; }; -NativeConnection::NativeConnection(int fd, int32_t maxPointers) : - mFd(fd), mMaxPointers(maxPointers) { -} +NativeConnection::NativeConnection(int fd, int32_t maxPointers, ConnectionType type) + : mFd(fd), mMaxPointers(maxPointers), mType(type) {} NativeConnection::~NativeConnection() { ALOGI("Un-Registering uinput device %d.", mFd); @@ -125,44 +255,50 @@ NativeConnection* NativeConnection::open(const char* name, const char* uniqueId, ALOGI("Registering uinput device %s: touch pad size %dx%d, " "max pointers %d.", name, width, height, maxPointers); - int fd = ::open("/dev/uinput", O_WRONLY | O_NDELAY); - if (fd < 0) { - ALOGE("Cannot open /dev/uinput: %s.", strerror(errno)); + initKeysMap(); + + UInputDescriptor descriptor; + if (!descriptor.Open(name, uniqueId)) { return nullptr; } - struct uinput_user_dev uinp; - memset(&uinp, 0, sizeof(struct uinput_user_dev)); - strlcpy(uinp.name, name, UINPUT_MAX_NAME_SIZE); - uinp.id.version = 1; - uinp.id.bustype = BUS_VIRTUAL; + // set the keys mapped + for (size_t i = 0; i < NELEM(KEYS); i++) { + descriptor.EnableKey(KEYS[i].linuxKeyCode); + } - // initialize keymap - initKeysMap(); + if (!descriptor.Create()) { + return nullptr; + } - // write device unique id to the phys property - ioctl(fd, UI_SET_PHYS, uniqueId); + return new NativeConnection(descriptor.Detach(), maxPointers, ConnectionType::kRemoteDevice); +} - // set the keys mapped - ioctl(fd, UI_SET_EVBIT, EV_KEY); - for (size_t i = 0; i < NELEM(KEYS); i++) { - ioctl(fd, UI_SET_KEYBIT, KEYS[i].linuxKeyCode); +NativeConnection* NativeConnection::openGamepad(const char* name, const char* uniqueId) { + ALOGI("Registering uinput device %s: gamepad", name); + + UInputDescriptor descriptor; + if (!descriptor.Open(name, uniqueId)) { + return nullptr; } - // register the input device - if (write(fd, &uinp, sizeof(uinp)) != sizeof(uinp)) { - ALOGE("Cannot write uinput_user_dev to fd %d: %s.", fd, strerror(errno)); - close(fd); - return NULL; + // set the keys mapped for gamepads + for (size_t i = 0; i < NELEM(GAMEPAD_KEY_CODES); i++) { + descriptor.EnableKey(GAMEPAD_KEY_CODES[i]); } - if (ioctl(fd, UI_DEV_CREATE) != 0) { - ALOGE("Unable to create uinput device: %s.", strerror(errno)); - close(fd); + + // define the axes that are required + descriptor.EnableAxesEvents(); + for (size_t i = 0; i < NELEM(GAMEPAD_AXES); i++) { + const Axis& axis = GAMEPAD_AXES[i]; + descriptor.EnableAxis(axis.number, axis.rangeMin, axis.rangeMax); + } + + if (!descriptor.Create()) { return nullptr; } - ALOGV("Created uinput device, fd=%d.", fd); - return new NativeConnection(fd, maxPointers); + return new NativeConnection(descriptor.Detach(), 0, ConnectionType::kGamepadDevice); } void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) { @@ -174,7 +310,6 @@ void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) { write(mFd, &iev, sizeof(iev)); } - static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring nameStr, jstring uniqueIdStr, jint width, jint height, jint maxPointers) { @@ -186,6 +321,14 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, return reinterpret_cast<jlong>(connection); } +static jlong nativeGamepadOpen(JNIEnv* env, jclass clazz, jstring nameStr, jstring uniqueIdStr) { + ScopedUtfChars name(env, nameStr); + ScopedUtfChars uniqueId(env, uniqueIdStr); + + NativeConnection* connection = NativeConnection::openGamepad(name.c_str(), uniqueId.c_str()); + return reinterpret_cast<jlong>(connection); +} + static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); delete connection; @@ -194,6 +337,12 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jboolean down) { int32_t code = getLinuxKeyCode(keyCode); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); + + if (connection->IsGamepad()) { + ALOGE("Invalid key even for a gamepad - need to send gamepad events"); + return; + } + if (code != KEY_UNKNOWN) { connection->sendEvent(EV_KEY, code, down ? 1 : 0); } else { @@ -201,10 +350,44 @@ static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jb } } +static void nativeSendGamepadKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyIndex, + jboolean down) { + NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); + + if (!connection->IsGamepad()) { + ALOGE("Invalid gamepad key for non-gamepad device"); + return; + } + + if ((keyIndex < 0) || (keyIndex >= NELEM(GAMEPAD_KEY_CODES))) { + ALOGE("Invalid gamepad key index: %d", keyIndex); + return; + } + + connection->sendEvent(EV_KEY, GAMEPAD_KEY_CODES[keyIndex], down ? 1 : 0); +} + +static void nativeSendGamepadAxisValue(JNIEnv* env, jclass clazz, jlong ptr, jint axis, + jint value) { + NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); + + if (!connection->IsGamepad()) { + ALOGE("Invalid axis send for non-gamepad device"); + return; + } + + connection->sendEvent(EV_ABS, axis, value); +} + static void nativeSendPointerDown(JNIEnv* env, jclass clazz, jlong ptr, jint pointerId, jint x, jint y) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); + if (connection->IsGamepad()) { + ALOGE("Invalid pointer down event for a gamepad."); + return; + } + int32_t slot = findSlot(pointerId); if (slot == SLOT_UNKNOWN) { slot = assignSlot(pointerId); @@ -221,6 +404,11 @@ static void nativeSendPointerUp(JNIEnv* env, jclass clazz, jlong ptr, jint pointerId) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); + if (connection->IsGamepad()) { + ALOGE("Invalid pointer up event for a gamepad."); + return; + } + int32_t slot = findSlot(pointerId); if (slot != SLOT_UNKNOWN) { connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); @@ -238,17 +426,34 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); // Clear keys. - for (size_t i = 0; i < NELEM(KEYS); i++) { - connection->sendEvent(EV_KEY, KEYS[i].linuxKeyCode, 0); - } + if (connection->IsRemote()) { + for (size_t i = 0; i < NELEM(KEYS); i++) { + connection->sendEvent(EV_KEY, KEYS[i].linuxKeyCode, 0); + } + + // Clear pointers. + int32_t slot = SLOT_UNKNOWN; + for (int32_t i = 0; i < connection->getMaxPointers(); i++) { + slot = findSlot(i); + if (slot != SLOT_UNKNOWN) { + connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); + connection->sendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1); + } + } + } else { + for (size_t i = 0; i < NELEM(GAMEPAD_KEY_CODES); i++) { + connection->sendEvent(EV_KEY, GAMEPAD_KEY_CODES[i], 0); + } - // Clear pointers. - int32_t slot = SLOT_UNKNOWN; - for (int32_t i = 0; i < connection->getMaxPointers(); i++) { - slot = findSlot(i); - if (slot != SLOT_UNKNOWN) { - connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); - connection->sendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1); + for (size_t i = 0; i < NELEM(GAMEPAD_AXES); i++) { + const Axis& axis = GAMEPAD_AXES[i]; + if ((axis.number == ABS_Z) || (axis.number == ABS_RZ)) { + // Mark triggers unpressed + connection->sendEvent(EV_ABS, axis.number, 0); + } else { + // Joysticks and dpad rests on center + connection->sendEvent(EV_ABS, axis.number, (axis.rangeMin + axis.rangeMax) / 2); + } } } @@ -261,20 +466,16 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) { */ static JNINativeMethod gUinputBridgeMethods[] = { - { "nativeOpen", "(Ljava/lang/String;Ljava/lang/String;III)J", - (void*)nativeOpen }, - { "nativeClose", "(J)V", - (void*)nativeClose }, - { "nativeSendKey", "(JIZ)V", - (void*)nativeSendKey }, - { "nativeSendPointerDown", "(JIII)V", - (void*)nativeSendPointerDown }, - { "nativeSendPointerUp", "(JI)V", - (void*)nativeSendPointerUp }, - { "nativeClear", "(J)V", - (void*)nativeClear }, - { "nativeSendPointerSync", "(J)V", - (void*)nativeSendPointerSync }, + {"nativeOpen", "(Ljava/lang/String;Ljava/lang/String;III)J", (void*)nativeOpen}, + {"nativeGamepadOpen", "(Ljava/lang/String;Ljava/lang/String;)J", (void*)nativeGamepadOpen}, + {"nativeClose", "(J)V", (void*)nativeClose}, + {"nativeSendKey", "(JIZ)V", (void*)nativeSendKey}, + {"nativeSendPointerDown", "(JIII)V", (void*)nativeSendPointerDown}, + {"nativeSendPointerUp", "(JI)V", (void*)nativeSendPointerUp}, + {"nativeClear", "(J)V", (void*)nativeClear}, + {"nativeSendPointerSync", "(J)V", (void*)nativeSendPointerSync}, + {"nativeSendGamepadKey", "(JIZ)V", (void*)nativeSendGamepadKey}, + {"nativeSendGamepadAxisValue", "(JII)V", (void*)nativeSendGamepadAxisValue}, }; int register_android_server_tv_TvUinputBridge(JNIEnv* env) { diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 3fcb57a83cf5..2dbbc5ac6806 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -178,15 +178,9 @@ static std::tuple<int, incfs::FileId, incfs::NewFileParams> toMakeFileParams( nfp.size = params.size; nfp.metadata = {(const char*)params.metadata.data(), (IncFsSize)params.metadata.size()}; if (!params.signature) { - nfp.verification = {}; + nfp.signature = {}; } else { - nfp.verification.hashAlgorithm = IncFsHashAlgortithm(params.signature->hashAlgorithm); - nfp.verification.rootHash = {(const char*)params.signature->rootHash.data(), - (IncFsSize)params.signature->rootHash.size()}; - nfp.verification.additionalData = {(const char*)params.signature->additionalData.data(), - (IncFsSize)params.signature->additionalData.size()}; - nfp.verification.signature = {(const char*)params.signature->signature.data(), - (IncFsSize)params.signature->signature.size()}; + nfp.signature = {(const char*)params.signature->data(), (IncFsSize)params.signature->size()}; } return {0, id, nfp}; } diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index cccd01339177..727593664895 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -1155,7 +1155,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ // Create new lib file without signature info incfs::NewFileParams libFileParams{}; libFileParams.size = uncompressedLen; - libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE; + libFileParams.signature = {}; // Metadata of the new lib file is its relative path IncFsSpan libFileMetadata; libFileMetadata.data = targetLibPath.c_str(); diff --git a/services/net/Android.bp b/services/net/Android.bp index dbc2df8369f8..c54102fb1d3d 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -20,6 +20,44 @@ java_library_static { ], } +// Version of services.net for usage by the wifi mainline module. +// Note: This is compiled against module_current. +// TODO(b/145825329): This should be moved to networkstack-client, +// with dependencies moved to frameworks/libs/net right. +java_library { + name: "services.net-module-wifi", + srcs: [ + ":framework-services-net-module-wifi-shared-srcs", + ":net-module-utils-srcs", + "java/android/net/ip/IpClientCallbacks.java", + "java/android/net/ip/IpClientManager.java", + "java/android/net/ip/IpClientUtil.java", + "java/android/net/util/KeepalivePacketDataUtil.java", + "java/android/net/util/NetworkConstants.java", + "java/android/net/IpMemoryStore.java", + "java/android/net/NetworkMonitorManager.java", + "java/android/net/TcpKeepalivePacketData.java", + ], + sdk_version: "module_current", + libs: [ + "unsupportedappusage", + ], + static_libs: [ + "dnsresolver_aidl_interface-V2-java", + "netd_aidl_interface-unstable-java", + "netlink-client", + "networkstack-client", + "net-utils-services-common", + ], + apex_available: [ + "com.android.wifi", + ], + visibility: [ + "//frameworks/opt/net/wifi/service", + "//frameworks/opt/net/wifi/tests/wifitests", + ], +} + filegroup { name: "services-tethering-shared-srcs", srcs: [ diff --git a/services/net/java/android/net/IpMemoryStore.java b/services/net/java/android/net/IpMemoryStore.java index dcefb537d0a0..8df2e0d08e0e 100644 --- a/services/net/java/android/net/IpMemoryStore.java +++ b/services/net/java/android/net/IpMemoryStore.java @@ -18,6 +18,7 @@ package android.net; import android.annotation.NonNull; import android.content.Context; +import android.net.networkstack.ModuleNetworkStackClient; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -41,7 +42,7 @@ public class IpMemoryStore extends IpMemoryStoreClient { super(context); mService = new CompletableFuture<>(); mTailNode = new AtomicReference<CompletableFuture<IIpMemoryStore>>(mService); - getNetworkStackClient().fetchIpMemoryStore( + getModuleNetworkStackClient(context).fetchIpMemoryStore( new IIpMemoryStoreCallbacks.Stub() { @Override public void onIpMemoryStoreFetched(@NonNull final IIpMemoryStore memoryStore) { @@ -85,8 +86,8 @@ public class IpMemoryStore extends IpMemoryStoreClient { } @VisibleForTesting - protected NetworkStackClient getNetworkStackClient() { - return NetworkStackClient.getInstance(); + protected ModuleNetworkStackClient getModuleNetworkStackClient(Context context) { + return ModuleNetworkStackClient.getInstance(context); } /** Gets an instance of the memory store */ diff --git a/services/net/java/android/net/TcpKeepalivePacketData.java b/services/net/java/android/net/TcpKeepalivePacketData.java index aad75ae16aa9..fcf3a56de448 100644 --- a/services/net/java/android/net/TcpKeepalivePacketData.java +++ b/services/net/java/android/net/TcpKeepalivePacketData.java @@ -74,6 +74,19 @@ public class TcpKeepalivePacketData extends KeepalivePacketData implements Parce ipTtl = tcpDetails.ttl; } + private TcpKeepalivePacketData(final InetAddress srcAddress, int srcPort, + final InetAddress dstAddress, int dstPort, final byte[] data, int tcpSeq, + int tcpAck, int tcpWnd, int tcpWndScale, int ipTos, int ipTtl) + throws InvalidPacketException { + super(srcAddress, srcPort, dstAddress, dstPort, data); + this.tcpSeq = tcpSeq; + this.tcpAck = tcpAck; + this.tcpWnd = tcpWnd; + this.tcpWndScale = tcpWndScale; + this.ipTos = ipTos; + this.ipTtl = ipTtl; + } + /** * Factory method to create tcp keepalive packet structure. */ @@ -169,7 +182,11 @@ public class TcpKeepalivePacketData extends KeepalivePacketData implements Parce /** Write to parcel. */ public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); + out.writeString(srcAddress.getHostAddress()); + out.writeString(dstAddress.getHostAddress()); + out.writeInt(srcPort); + out.writeInt(dstPort); + out.writeByteArray(getPacket()); out.writeInt(tcpSeq); out.writeInt(tcpAck); out.writeInt(tcpWnd); @@ -178,21 +195,32 @@ public class TcpKeepalivePacketData extends KeepalivePacketData implements Parce out.writeInt(ipTtl); } - private TcpKeepalivePacketData(Parcel in) { - super(in); - tcpSeq = in.readInt(); - tcpAck = in.readInt(); - tcpWnd = in.readInt(); - tcpWndScale = in.readInt(); - ipTos = in.readInt(); - ipTtl = in.readInt(); + private static TcpKeepalivePacketData readFromParcel(Parcel in) throws InvalidPacketException { + InetAddress srcAddress = InetAddresses.parseNumericAddress(in.readString()); + InetAddress dstAddress = InetAddresses.parseNumericAddress(in.readString()); + int srcPort = in.readInt(); + int dstPort = in.readInt(); + byte[] packet = in.createByteArray(); + int tcpSeq = in.readInt(); + int tcpAck = in.readInt(); + int tcpWnd = in.readInt(); + int tcpWndScale = in.readInt(); + int ipTos = in.readInt(); + int ipTtl = in.readInt(); + return new TcpKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, packet, tcpSeq, + tcpAck, tcpWnd, tcpWndScale, ipTos, ipTtl); } /** Parcelable Creator. */ public static final @NonNull Parcelable.Creator<TcpKeepalivePacketData> CREATOR = new Parcelable.Creator<TcpKeepalivePacketData>() { public TcpKeepalivePacketData createFromParcel(Parcel in) { - return new TcpKeepalivePacketData(in); + try { + return readFromParcel(in); + } catch (InvalidPacketException e) { + throw new IllegalArgumentException( + "Invalid NAT-T keepalive data: " + e.error); + } } public TcpKeepalivePacketData[] newArray(int size) { diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index a3618b47171a..b329aeec4853 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -22,7 +22,7 @@ import android.content.Context; import android.net.DhcpResultsParcelable; import android.net.Layer2PacketParcelable; import android.net.LinkProperties; -import android.net.NetworkStackClient; +import android.net.networkstack.ModuleNetworkStackClient; import android.os.ConditionVariable; import java.io.FileDescriptor; @@ -75,11 +75,11 @@ public class IpClientUtil { * * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of * {@link IIpClientCallbacks}. - * @see {@link NetworkStackClient#makeIpClient(String, IIpClientCallbacks)} + * @see {@link ModuleNetworkStackClient#makeIpClient(String, IIpClientCallbacks)} */ public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) { - // TODO: migrate clients and remove context argument - NetworkStackClient.getInstance().makeIpClient(ifName, new IpClientCallbacksProxy(callback)); + ModuleNetworkStackClient.getInstance(context) + .makeIpClient(ifName, new IpClientCallbacksProxy(callback)); } /** diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index c94bb879f460..5c8220049d09 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -79,6 +79,7 @@ public class RescuePartyTest { private static final String CALLING_PACKAGE2 = "com.package.name2"; private static final String NAMESPACE1 = "namespace1"; private static final String NAMESPACE2 = "namespace2"; + private static final String DISABLE_RESCUE_PARTY_FLAG = "disable_rescue_party"; private MockitoSession mSession; private HashMap<String, String> mSystemSettingsMap; @@ -316,6 +317,13 @@ public class RescuePartyTest { @Test public void testExplicitlyEnablingAndDisablingRescue() { + // mock the DeviceConfig get call to avoid hitting + // android.permission.READ_DEVICE_CONFIG when calling real DeviceConfig. + doReturn(true) + .when(() -> DeviceConfig.getBoolean( + eq(DeviceConfig.NAMESPACE_CONFIGURATION), + eq(DISABLE_RESCUE_PARTY_FLAG), + eq(false))); SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, @@ -327,6 +335,22 @@ public class RescuePartyTest { } @Test + public void testDisablingRescueByDeviceConfigFlag() { + doReturn(true) + .when(() -> DeviceConfig.getBoolean( + eq(DeviceConfig.NAMESPACE_CONFIGURATION), + eq(DISABLE_RESCUE_PARTY_FLAG), + eq(false))); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); + + assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false); + + // Restore the property value initalized in SetUp() + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); + } + + @Test public void testHealthCheckLevels() { RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 164ee3184f5a..3ad905476190 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -182,7 +182,8 @@ public class BiometricServiceTest { eq(0), anyBoolean() /* requireConfirmation */, anyInt() /* userId */, - eq(TEST_PACKAGE_NAME)); + eq(TEST_PACKAGE_NAME), + anyLong() /* sessionId */); } @Test @@ -264,7 +265,8 @@ public class BiometricServiceTest { eq(BiometricAuthenticator.TYPE_FACE), eq(false) /* requireConfirmation */, anyInt() /* userId */, - eq(TEST_PACKAGE_NAME)); + eq(TEST_PACKAGE_NAME), + anyLong() /* sessionId */); } @Test @@ -391,7 +393,8 @@ public class BiometricServiceTest { eq(BiometricAuthenticator.TYPE_FINGERPRINT), anyBoolean() /* requireConfirmation */, anyInt() /* userId */, - eq(TEST_PACKAGE_NAME)); + eq(TEST_PACKAGE_NAME), + anyLong() /* sessionId */); // Hardware authenticated mBiometricService.mInternalReceiver.onAuthenticationSucceeded( @@ -406,7 +409,8 @@ public class BiometricServiceTest { // SystemUI sends callback with dismissed reason mBiometricService.mInternalReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED); + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, + null /* credentialAttestation */); waitForIdle(); // HAT sent to keystore verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); @@ -438,7 +442,8 @@ public class BiometricServiceTest { eq(0 /* biometricModality */), anyBoolean() /* requireConfirmation */, anyInt() /* userId */, - eq(TEST_PACKAGE_NAME)); + eq(TEST_PACKAGE_NAME), + anyLong() /* sessionId */); } @Test @@ -460,7 +465,8 @@ public class BiometricServiceTest { // SystemUI sends confirm, HAT is sent to keystore and client is notified. mBiometricService.mInternalReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, + null /* credentialAttestation */); waitForIdle(); verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); verify(mReceiver1).onAuthenticationSucceeded( @@ -567,7 +573,8 @@ public class BiometricServiceTest { anyInt(), anyBoolean() /* requireConfirmation */, anyInt() /* userId */, - anyString()); + anyString(), + anyLong() /* sessionId */); } @Test @@ -627,8 +634,8 @@ public class BiometricServiceTest { verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); // SystemUI animation completed, client is notified, auth session is over - mBiometricService.mInternalReceiver - .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR); + mBiometricService.mInternalReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_ERROR, null /* credentialAttestation */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_FINGERPRINT), @@ -667,7 +674,8 @@ public class BiometricServiceTest { eq(0 /* biometricModality */), anyBoolean() /* requireConfirmation */, anyInt() /* userId */, - eq(TEST_PACKAGE_NAME)); + eq(TEST_PACKAGE_NAME), + anyLong() /* sessionId */); } @Test @@ -825,8 +833,8 @@ public class BiometricServiceTest { invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mInternalReceiver - .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + mBiometricService.mInternalReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_FINGERPRINT), @@ -854,7 +862,7 @@ public class BiometricServiceTest { BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, 0 /* vendorCode */); mBiometricService.mInternalReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_NEGATIVE); + BiometricPrompt.DISMISSED_REASON_NEGATIVE, null /* credentialAttestation */); waitForIdle(); verify(mBiometricService.mAuthenticators.get(0).impl, @@ -880,7 +888,7 @@ public class BiometricServiceTest { BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, 0 /* vendorCode */); mBiometricService.mInternalReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */); waitForIdle(); verify(mBiometricService.mAuthenticators.get(0).impl, @@ -903,7 +911,7 @@ public class BiometricServiceTest { true /* requireConfirmation */, new byte[69] /* HAT */); mBiometricService.mInternalReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */); waitForIdle(); // doesn't send cancel to HAL @@ -1160,7 +1168,8 @@ public class BiometricServiceTest { eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */), anyBoolean() /* requireConfirmation */, anyInt() /* userId */, - eq(TEST_PACKAGE_NAME)); + eq(TEST_PACKAGE_NAME), + anyLong() /* sessionId */); // Requesting strong and credential, when credential is setup resetReceiver(); @@ -1179,7 +1188,8 @@ public class BiometricServiceTest { eq(BiometricAuthenticator.TYPE_NONE /* biometricModality */), anyBoolean() /* requireConfirmation */, anyInt() /* userId */, - eq(TEST_PACKAGE_NAME)); + eq(TEST_PACKAGE_NAME), + anyLong() /* sessionId */); // Un-downgrading the authenticator allows successful strong auth for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) { @@ -1201,7 +1211,8 @@ public class BiometricServiceTest { eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */), anyBoolean() /* requireConfirmation */, anyInt() /* userId */, - eq(TEST_PACKAGE_NAME)); + eq(TEST_PACKAGE_NAME), + anyLong() /* sessionId */); } @Test(expected = IllegalStateException.class) diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index 2944643b8d12..8be9213fd925 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -251,6 +251,28 @@ public class CompatConfigTest { } @Test + public void testAllowRemoveOverrideNoOverride() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L) + .addLoggingOnlyChangeWithId(2L) + .build(); + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.some.package") + .build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(applicationInfo); + + // Reject all override attempts. + // Force the validator to prevent overriding the change by using a user build. + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + // Try to remove a non existing override, and it doesn't fail. + assertThat(compatConfig.removeOverride(1234L, "com.some.package")).isFalse(); + assertThat(compatConfig.removeOverride(2L, "com.some.package")).isFalse(); + compatConfig.removePackageOverrides("com.some.package"); + } + + @Test public void testRemovePackageOverride() throws Exception { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addEnabledChangeWithId(1234L) diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index 3dd150479ddc..81545d4c74ba 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -28,6 +28,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -321,6 +322,11 @@ public class AppIntegrityManagerServiceImplTest { // we cannot check installer cert because it seems to be device specific. assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode()); assertFalse(appInstallMetadata.isPreInstalled()); + // Asserting source stamp not present. + assertFalse(appInstallMetadata.isStampPresent()); + assertFalse(appInstallMetadata.isStampVerified()); + assertFalse(appInstallMetadata.isStampTrusted()); + assertNull(appInstallMetadata.getStampCertificateHash()); // These are hardcoded in the test apk android manifest Map<String, String> allowedInstallers = appInstallMetadata.getAllowedInstallersAndCertificates(); diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index ef9a73b794f7..9d48955c87be 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -98,8 +98,7 @@ public class UsageStatsDatabase { // Persist versioned backup files. // Should be false, except when testing new versions - // STOPSHIP: b/139937606 this should be false on launch - static final boolean KEEP_BACKUP_DIR = true; + static final boolean KEEP_BACKUP_DIR = false; private static final String TAG = "UsageStatsDatabase"; private static final boolean DEBUG = UsageStatsService.DEBUG; @@ -412,6 +411,7 @@ public class UsageStatsDatabase { } if (mBackupsDir.exists() && !KEEP_BACKUP_DIR) { + mUpgradePerformed = true; // updated here to ensure that data is cleaned up deleteDirectory(mBackupsDir); } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 0d1b3523bf9b..bbe9851520a1 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -137,8 +137,7 @@ public class UsageStatsService extends SystemService implements private static final File USAGE_STATS_LEGACY_DIR = new File( Environment.getDataSystemDirectory(), "usagestats"); // For migration purposes, indicates whether to keep the legacy usage stats directory or not - // STOPSHIP: b/138323140 this should be false on launch - private static final boolean KEEP_LEGACY_DIR = true; + private static final boolean KEEP_LEGACY_DIR = false; private static final char TOKEN_DELIMITER = '/'; @@ -648,7 +647,7 @@ public class UsageStatsService extends SystemService implements private void deleteLegacyDir(int userId) { final File legacyUserDir = new File(USAGE_STATS_LEGACY_DIR, Integer.toString(userId)); - if (!KEEP_LEGACY_DIR) { + if (!KEEP_LEGACY_DIR && legacyUserDir.exists()) { deleteRecursively(legacyUserDir); if (legacyUserDir.exists()) { Slog.w(TAG, "Error occurred while attempting to delete legacy usage stats " diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java index 87d94bfda662..c75de42707a6 100644 --- a/telephony/java/android/telephony/NetworkService.java +++ b/telephony/java/android/telephony/NetworkService.java @@ -234,6 +234,9 @@ public abstract class NetworkService extends Service { * this method to facilitate the creation of {@link NetworkServiceProvider} instances. The system * will call this method after binding the network service for each active SIM slot id. * + * This methead is guaranteed to be invoked in {@link NetworkService}'s internal handler thread + * whose looper can be retrieved with {@link Looper.myLooper()} when override this method. + * * @param slotIndex SIM slot id the network service associated with. * @return Network service object. Null if failed to create the provider (e.g. invalid slot * index) diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 08251da609e8..610ec5eee897 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -12463,7 +12463,6 @@ public class TelephonyManager { * @throws {@link SecurityException} if the caller is not the system or phone process. * @hide */ - @SystemApi @TestApi // TODO: add new permission tag indicating that this is system-only. public @NonNull List<ApnSetting> getDevicePolicyOverrideApns(@NonNull Context context) { @@ -12494,7 +12493,6 @@ public class TelephonyManager { * @throws {@link SecurityException} if the caller is not the system or phone process. * @hide */ - @SystemApi @TestApi // TODO: add new permission tag indicating that this is system-only. public int addDevicePolicyOverrideApn(@NonNull Context context, @@ -12525,7 +12523,6 @@ public class TelephonyManager { * @throws {@link SecurityException} if the caller is not the system or phone process. * @hide */ - @SystemApi @TestApi // TODO: add new permission tag indicating that this is system-only. public boolean modifyDevicePolicyOverrideApn(@NonNull Context context, int apnId, diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 6c4e7ce51ebf..f56bbe13051c 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -458,6 +458,9 @@ public abstract class DataService extends Service { * this method to facilitate the creation of {@link DataServiceProvider} instances. The system * will call this method after binding the data service for each active SIM slot id. * + * This methead is guaranteed to be invoked in {@link DataService}'s internal handler thread + * whose looper can be retrieved with {@link Looper.myLooper()} when override this method. + * * @param slotIndex SIM slot id the data service associated with. * @return Data service object. Null if failed to create the provider (e.g. invalid slot index) */ diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java index f13371c1d0fa..386a58e1d8b6 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java @@ -44,52 +44,62 @@ import java.lang.annotation.RetentionPolicy; public class ImsUtImplBase { /** * Bar all incoming calls. (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BARRING_ALL_INCOMING = 1; /** * Bar all outgoing calls. (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BARRING_ALL_OUTGOING = 2; /** * Bar all outgoing international calls. (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BARRING_OUTGOING_INTL = 3; /** * Bar all outgoing international calls, excluding those to the home PLMN country * (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BARRING_OUTGOING_INTL_EXCL_HOME = 4; /** * Bar all incoming calls when roaming (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BLOCKING_INCOMING_WHEN_ROAMING = 5; /** * Enable Anonymous Communication Rejection (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BARRING_ANONYMOUS_INCOMING = 6; /** * Bar all incoming and outgoing calls. (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BARRING_ALL = 7; /** * Bar all outgoing service requests, including calls. (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BARRING_OUTGOING_ALL_SERVICES = 8; /** * Bar all incoming service requests, including calls. (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BARRING_INCOMING_ALL_SERVICES = 9; /** * Bar specific incoming calls. (See 3GPP TS 24.611) + * @hide */ public static final int CALL_BARRING_SPECIFIC_INCOMING_CALLS = 10; @@ -104,6 +114,7 @@ public class ImsUtImplBase { /** * Constant used to denote an invalid return value. + * @hide */ public static final int INVALID_RESULT = -1; diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java index 504bd1727682..b805744a8387 100644 --- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java @@ -38,38 +38,75 @@ import java.util.concurrent.TimeUnit; public class DummyBlobData { private static final long DEFAULT_SIZE_BYTES = 10 * 1024L * 1024L; - private final Context mContext; private final Random mRandom; private final File mFile; private final long mFileSize; - private final String mLabel; + private final CharSequence mLabel; byte[] mFileDigest; long mExpiryTimeMs; - public DummyBlobData(Context context) { - this(context, new Random(0), "blob_" + System.nanoTime()); + public DummyBlobData(Builder builder) { + mRandom = new Random(builder.getRandomSeed()); + mFile = new File(builder.getContext().getFilesDir(), builder.getFileName()); + mFileSize = builder.getFileSize(); + mLabel = builder.getLabel(); } - public DummyBlobData(Context context, long fileSize) { - this(context, fileSize, new Random(0), "blob_" + System.nanoTime(), "Test label"); - } + public static class Builder { + private final Context mContext; + private int mRandomSeed = 0; + private long mFileSize = DEFAULT_SIZE_BYTES; + private CharSequence mLabel = "Test label"; + private String mFileName = "blob_" + System.nanoTime(); - public DummyBlobData(Context context, Random random, String fileName) { - this(context, DEFAULT_SIZE_BYTES, random, fileName, "Test label"); - } + public Builder(Context context) { + mContext = context; + } - public DummyBlobData(Context context, Random random, String fileName, String label) { - this(context, DEFAULT_SIZE_BYTES, random, fileName, label); - } + public Context getContext() { + return mContext; + } + + public Builder setRandomSeed(int randomSeed) { + mRandomSeed = randomSeed; + return this; + } + + public int getRandomSeed() { + return mRandomSeed; + } + + public Builder setFileSize(int fileSize) { + mFileSize = fileSize; + return this; + } - public DummyBlobData(Context context, long fileSize, Random random, String fileName, - String label) { - mContext = context; - mRandom = random; - mFile = new File(mContext.getFilesDir(), fileName); - mFileSize = fileSize; - mLabel = label; + public long getFileSize() { + return mFileSize; + } + + public Builder setLabel(CharSequence label) { + mLabel = label; + return this; + } + + public CharSequence getLabel() { + return mLabel; + } + + public Builder setFileName(String fileName) { + mFileName = fileName; + return this; + } + + public String getFileName() { + return mFileName; + } + + public DummyBlobData build() { + return new DummyBlobData(this); + } } public void prepare() throws Exception { diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java index c35385cd0429..654c1e21999d 100644 --- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java @@ -16,7 +16,13 @@ package com.android.utils.blob; +import static com.google.common.truth.Truth.assertThat; + +import android.app.blob.BlobHandle; import android.app.blob.BlobStoreManager; +import android.app.blob.LeaseInfo; +import android.content.Context; +import android.content.res.Resources; import android.os.ParcelFileDescriptor; import java.io.FileInputStream; @@ -56,4 +62,76 @@ public class Utils { copy(in, out, lengthBytes); } } + + public static void assertLeasedBlobs(BlobStoreManager blobStoreManager, + BlobHandle... expectedBlobHandles) throws IOException { + assertThat(blobStoreManager.getLeasedBlobs()).containsExactly(expectedBlobHandles); + } + + public static void assertNoLeasedBlobs(BlobStoreManager blobStoreManager) + throws IOException { + assertThat(blobStoreManager.getLeasedBlobs()).isEmpty(); + } + + public static void acquireLease(Context context, + BlobHandle blobHandle, CharSequence description) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.acquireLease(blobHandle, description); + + final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle); + assertLeaseInfo(leaseInfo, context.getPackageName(), 0, + Resources.ID_NULL, description); + } + + public static void acquireLease(Context context, + BlobHandle blobHandle, int descriptionResId) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.acquireLease(blobHandle, descriptionResId); + + final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle); + assertLeaseInfo(leaseInfo, context.getPackageName(), 0, + descriptionResId, context.getString(descriptionResId)); + } + + public static void acquireLease(Context context, + BlobHandle blobHandle, CharSequence description, + long expiryTimeMs) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.acquireLease(blobHandle, description, expiryTimeMs); + + final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle); + assertLeaseInfo(leaseInfo, context.getPackageName(), expiryTimeMs, + Resources.ID_NULL, description); + } + + public static void acquireLease(Context context, + BlobHandle blobHandle, int descriptionResId, + long expiryTimeMs) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.acquireLease(blobHandle, descriptionResId, expiryTimeMs); + + final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle); + assertLeaseInfo(leaseInfo, context.getPackageName(), expiryTimeMs, + descriptionResId, context.getString(descriptionResId)); + } + + public static void releaseLease(Context context, + BlobHandle blobHandle) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.releaseLease(blobHandle); + assertThat(blobStoreManager.getLeaseInfo(blobHandle)).isNull(); + } + + private static void assertLeaseInfo(LeaseInfo leaseInfo, String packageName, + long expiryTimeMs, int descriptionResId, CharSequence description) { + assertThat(leaseInfo.getPackageName()).isEqualTo(packageName); + assertThat(leaseInfo.getExpiryTimeMillis()).isEqualTo(expiryTimeMs); + assertThat(leaseInfo.getDescriptionResId()).isEqualTo(descriptionResId); + assertThat(leaseInfo.getDescription()).isEqualTo(description); + } } diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java index b81ca36429ff..442ac5605e90 100644 --- a/tests/net/java/android/net/IpMemoryStoreTest.java +++ b/tests/net/java/android/net/IpMemoryStoreTest.java @@ -35,6 +35,7 @@ import android.net.ipmemorystore.IOnStatusListener; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.NetworkAttributesParcelable; import android.net.ipmemorystore.Status; +import android.net.networkstack.ModuleNetworkStackClient; import android.os.RemoteException; import androidx.test.filters.SmallTest; @@ -67,7 +68,7 @@ public class IpMemoryStoreTest { @Mock Context mMockContext; @Mock - NetworkStackClient mNetworkStackClient; + ModuleNetworkStackClient mModuleNetworkStackClient; @Mock IIpMemoryStore mMockService; @Mock @@ -90,14 +91,14 @@ public class IpMemoryStoreTest { ((IIpMemoryStoreCallbacks) invocation.getArgument(0)) .onIpMemoryStoreFetched(mMockService); return null; - }).when(mNetworkStackClient).fetchIpMemoryStore(any()); + }).when(mModuleNetworkStackClient).fetchIpMemoryStore(any()); } else { - doNothing().when(mNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture()); + doNothing().when(mModuleNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture()); } mStore = new IpMemoryStore(mMockContext) { @Override - protected NetworkStackClient getNetworkStackClient() { - return mNetworkStackClient; + protected ModuleNetworkStackClient getModuleNetworkStackClient(Context ctx) { + return mModuleNetworkStackClient; } }; } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 8c0c36b89bf9..6985415a6a37 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -23,8 +23,6 @@ import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; -import static android.net.ConnectivityDiagnosticsManager.DataStallReport; import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL; @@ -100,6 +98,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Matchers.anyInt; @@ -6870,8 +6869,13 @@ public class ConnectivityServiceTest { HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); // Verify onConnectivityReport fired - verify(mConnectivityDiagnosticsCallback) - .onConnectivityReport(any(ConnectivityReport.class)); + verify(mConnectivityDiagnosticsCallback).onConnectivityReport( + argThat(report -> { + final NetworkCapabilities nc = report.getNetworkCapabilities(); + return nc.getUids() == null + && nc.getAdministratorUids().isEmpty() + && nc.getOwnerUid() == Process.INVALID_UID; + })); } @Test @@ -6886,7 +6890,13 @@ public class ConnectivityServiceTest { HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); // Verify onDataStallSuspected fired - verify(mConnectivityDiagnosticsCallback).onDataStallSuspected(any(DataStallReport.class)); + verify(mConnectivityDiagnosticsCallback).onDataStallSuspected( + argThat(report -> { + final NetworkCapabilities nc = report.getNetworkCapabilities(); + return nc.getUids() == null + && nc.getAdministratorUids().isEmpty() + && nc.getOwnerUid() == Process.INVALID_UID; + })); } @Test diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt index 950361c1b244..eeb006ee6ab2 100644 --- a/wifi/jarjar-rules.txt +++ b/wifi/jarjar-rules.txt @@ -1,7 +1,32 @@ +# used by wifi-service +rule android.net.DhcpResultsParcelable* @0 +rule android.net.DhcpResults* com.android.server.x.wifi.net.DhcpResults@1 rule android.net.InterfaceConfigurationParcel* @0 rule android.net.InterfaceConfiguration* com.android.server.x.wifi.net.InterfaceConfiguration@1 +rule android.net.IpMemoryStore* com.android.server.x.wifi.net.IpMemoryStore@1 +rule android.net.NetworkMonitorManager* com.android.server.x.wifi.net.NetworkMonitorManager@1 +rule android.net.TcpKeepalivePacketData* com.android.server.x.wifi.net.TcpKeepalivePacketData@1 rule android.net.NetworkFactory* com.android.server.x.wifi.net.NetworkFactory@1 +rule android.net.ip.IpClientCallbacks* com.android.server.x.wifi.net.ip.IpClientCallbacks@1 +rule android.net.ip.IpClientManager* com.android.server.x.wifi.net.ip.IpClientManager@1 +rule android.net.ip.IpClientUtil* com.android.server.x.wifi.net.ip.IpClientUtil@1 +rule android.net.shared.InetAddressUtils* com.android.server.x.wifi.net.shared.InetAddressUtils@1 +rule android.net.shared.InitialConfiguration* com.android.server.x.wifi.net.shared.InitialConfiguration@1 +rule android.net.shared.IpConfigurationParcelableUtil* com.android.server.x.wifi.net.shared.IpConfigurationParcelableUtil@1 +rule android.net.shared.LinkPropertiesParcelableUtil* com.android.server.x.wifi.net.shared.LinkPropertiesParcelableUtil@1 +rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1 +rule android.net.shared.NetdUtils* com.android.server.x.wifi.net.shared.NetdUtils@1 +rule android.net.shared.NetworkMonitorUtils* com.android.server.x.wifi.net.shared.NetworkMonitorUtils@1 +rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1 +rule android.net.shared.PrivateDnsConfig* com.android.server.x.wifi.net.shared.PrivateDnsConfig@1 +rule android.net.shared.ProvisioningConfiguration* com.android.server.x.wifi.net.shared.ProvisioningConfiguration@1 +rule android.net.shared.RouteUtils* com.android.server.x.wifi.net.shared.RouteUtils@1 +rule android.net.util.KeepalivePacketDataUtil* com.android.server.x.wifi.net.util.KeepalivePacketDataUtil@1 +rule android.net.util.NetworkConstants* com.android.server.x.wifi.net.util.NetworkConstants@1 +rule android.net.util.InterfaceParams* com.android.server.x.wifi.net.util.InterfaceParams@1 +rule android.net.util.SharedLog* com.android.server.x.wifi.net.util.SharedLog@1 rule android.net.util.NetUtils* com.android.server.x.wifi.net.util.NetUtils@1 +rule android.net.util.IpUtils* com.android.server.x.wifi.net.util.IpUtils@1 # We don't jar-jar the entire package because, we still use some classes (like # AsyncChannel in com.android.internal.util) from these packages which are not @@ -29,7 +54,6 @@ rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi # Use our statically linked PlatformProperties library rule android.sysprop.** com.android.server.x.wifi.sysprop.@1 - # used by both framework-wifi and wifi-service rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1 rule android.content.pm.ParceledListSlice* android.x.net.wifi.util.ParceledListSlice@1 diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java index 65e8b3d9283d..fa806e7797cd 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java @@ -1037,18 +1037,7 @@ public final class Credential implements Parcelable { * @return a Unique identifier for a Credential object */ public int getUniqueId() { - int usedCredential; - - // Initialize usedCredential based on the credential type of the profile - if (mUserCredential != null) { - usedCredential = 0; - } else if (mCertCredential != null) { - usedCredential = 1; - } else { - usedCredential = 2; - } - - return Objects.hash(usedCredential, mRealm); + return Objects.hash(mUserCredential, mCertCredential, mSimCredential, mRealm); } @Override diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java index c6825822f4cc..829d8f0a9a3a 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java @@ -18,6 +18,7 @@ package android.net.wifi.hotspot2.pps; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import android.net.wifi.EAPConstants; @@ -551,4 +552,68 @@ public class CredentialTest { public void validateTwoCertificateDifferent() { assertFalse(Credential.isX509CertificateEquals(FakeKeys.CA_CERT0, FakeKeys.CA_CERT1)); } + + /** + * Verify that unique identifiers are the same for objects with the same credentials + */ + @Test + public void testUniqueIdSameCredentialTypes() throws Exception { + assertEquals(createCredentialWithSimCredential().getUniqueId(), + createCredentialWithSimCredential().getUniqueId()); + assertEquals(createCredentialWithCertificateCredential().getUniqueId(), + createCredentialWithCertificateCredential().getUniqueId()); + assertEquals(createCredentialWithUserCredential().getUniqueId(), + createCredentialWithUserCredential().getUniqueId()); + } + + /** + * Verify that unique identifiers are different for each credential + */ + @Test + public void testUniqueIdDifferentForDifferentCredentialTypes() throws Exception { + Credential simCred = createCredentialWithSimCredential(); + Credential certCred = createCredentialWithCertificateCredential(); + Credential userCred = createCredentialWithUserCredential(); + + assertNotEquals(simCred.getUniqueId(), userCred.getUniqueId()); + assertNotEquals(simCred.getUniqueId(), certCred.getUniqueId()); + assertNotEquals(certCred.getUniqueId(), userCred.getUniqueId()); + } + + /** + * Verify that unique identifiers are different for a credential with different values + */ + @Test + public void testUniqueIdDifferentForSimCredentialsWithDifferentValues() throws Exception { + Credential simCred1 = createCredentialWithSimCredential(); + Credential simCred2 = createCredentialWithSimCredential(); + simCred2.getSimCredential().setImsi("567890*"); + + assertNotEquals(simCred1.getUniqueId(), simCred2.getUniqueId()); + } + + /** + * Verify that unique identifiers are different for a credential with different values + */ + @Test + public void testUniqueIdDifferentForUserCredentialsWithDifferentValues() throws Exception { + Credential userCred1 = createCredentialWithUserCredential(); + Credential userCred2 = createCredentialWithUserCredential(); + userCred2.getUserCredential().setUsername("anotheruser"); + + assertNotEquals(userCred1.getUniqueId(), userCred2.getUniqueId()); + } + + /** + * Verify that unique identifiers are different for a credential with different values + */ + @Test + public void testUniqueIdDifferentForCertCredentialsWithDifferentValues() throws Exception { + Credential certCred1 = createCredentialWithCertificateCredential(); + Credential certCred2 = createCredentialWithCertificateCredential(); + certCred2.getCertCredential().setCertSha256Fingerprint( + MessageDigest.getInstance("SHA-256").digest(FakeKeys.CA_CERT0.getEncoded())); + + assertNotEquals(certCred1.getUniqueId(), certCred2.getUniqueId()); + } } |