diff options
169 files changed, 3736 insertions, 2610 deletions
diff --git a/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java b/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java new file mode 100644 index 000000000000..3725ad4a6c09 --- /dev/null +++ b/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java @@ -0,0 +1,119 @@ +/* + * 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.app.blob; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * Class to provide information about an accessor of a shared blob. + * + * @hide + */ +public final class AccessorInfo implements Parcelable { + private final String mPackageName; + private final long mExpiryTimeMs; + private final int mDescriptionResId; + private final CharSequence mDescription; + + public AccessorInfo(String packageName, long expiryTimeMs, + int descriptionResId, CharSequence description) { + mPackageName = packageName; + mExpiryTimeMs = expiryTimeMs; + mDescriptionResId = descriptionResId; + mDescription = description; + } + + private AccessorInfo(Parcel in) { + mPackageName = in.readString(); + mExpiryTimeMs = in.readLong(); + mDescriptionResId = in.readInt(); + mDescription = in.readCharSequence(); + } + + public String getPackageName() { + return mPackageName; + } + + public long getExpiryTimeMs() { + return mExpiryTimeMs; + } + + public int getDescriptionResId() { + return mDescriptionResId; + } + + public CharSequence getDescription() { + return mDescription; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mPackageName); + dest.writeLong(mExpiryTimeMs); + dest.writeInt(mDescriptionResId); + dest.writeCharSequence(mDescription); + } + + @Override + public String toString() { + return "AccessorInfo {" + + "package: " + mPackageName + "," + + "expiryMs: " + mExpiryTimeMs + "," + + "descriptionResId: " + mDescriptionResId + "," + + "description: " + mDescription + "," + + "}"; + } + + private String toShortString() { + return mPackageName; + } + + public static String toShortString(List<AccessorInfo> accessors) { + final StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i = 0, size = accessors.size(); i < size; ++i) { + sb.append(accessors.get(i).toShortString()); + sb.append(","); + } + sb.append("]"); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator<AccessorInfo> CREATOR = new Creator<AccessorInfo>() { + @Override + @NonNull + public AccessorInfo createFromParcel(Parcel source) { + return new AccessorInfo(source); + } + + @Override + @NonNull + public AccessorInfo[] newArray(int size) { + return new AccessorInfo[size]; + } + }; +} diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.aidl b/apex/blobstore/framework/java/android/app/blob/BlobInfo.aidl new file mode 100644 index 000000000000..25497738f685 --- /dev/null +++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.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 BlobInfo;
\ No newline at end of file diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java new file mode 100644 index 000000000000..9746dd023002 --- /dev/null +++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java @@ -0,0 +1,109 @@ +/* + * 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.app.blob; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Collections; +import java.util.List; + +/** + * Class to provide information about a shared blob. + * + * @hide + */ +public final class BlobInfo implements Parcelable { + private final long mId; + private final long mExpiryTimeMs; + private final CharSequence mLabel; + private final List<AccessorInfo> mAccessors; + + public BlobInfo(long id, long expiryTimeMs, CharSequence label, + List<AccessorInfo> accessors) { + mId = id; + mExpiryTimeMs = expiryTimeMs; + mLabel = label; + mAccessors = accessors; + } + + private BlobInfo(Parcel in) { + mId = in.readLong(); + mExpiryTimeMs = in.readLong(); + mLabel = in.readCharSequence(); + mAccessors = in.readArrayList(null /* classloader */); + } + + public long getId() { + return mId; + } + + public long getExpiryTimeMs() { + return mExpiryTimeMs; + } + + public CharSequence getLabel() { + return mLabel; + } + + public List<AccessorInfo> getAccessors() { + return Collections.unmodifiableList(mAccessors); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mId); + dest.writeLong(mExpiryTimeMs); + dest.writeCharSequence(mLabel); + dest.writeList(mAccessors); + } + + @Override + public String toString() { + return toShortString(); + } + + private String toShortString() { + return "BlobInfo {" + + "id: " + mId + "," + + "expiryMs: " + mExpiryTimeMs + "," + + "label: " + mLabel + "," + + "accessors: " + AccessorInfo.toShortString(mAccessors) + "," + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator<BlobInfo> CREATOR = new Creator<BlobInfo>() { + @Override + @NonNull + public BlobInfo createFromParcel(Parcel source) { + return new BlobInfo(source); + } + + @Override + @NonNull + public BlobInfo[] newArray(int size) { + return new BlobInfo[size]; + } + }; +} diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index d1e28e99b6d6..7af3f662c195 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -27,11 +27,13 @@ import android.os.ParcelFileDescriptor; import android.os.ParcelableException; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.UserHandle; import com.android.internal.util.function.pooled.PooledLambda; import java.io.Closeable; import java.io.IOException; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -145,9 +147,6 @@ public class BlobStoreManager { /** @hide */ public static final int INVALID_RES_ID = -1; - /** @hide */ - public static final String DESC_RES_TYPE_STRING = "string"; - private final Context mContext; private final IBlobStoreManager mService; @@ -495,6 +494,31 @@ public class BlobStoreManager { } } + /** @hide */ + @NonNull + public List<BlobInfo> queryBlobsForUser(@NonNull UserHandle user) throws IOException { + try { + return mService.queryBlobsForUser(user.getIdentifier()); + } catch (ParcelableException e) { + e.maybeRethrow(IOException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public void deleteBlob(@NonNull BlobInfo blobInfo) throws IOException { + try { + mService.deleteBlob(blobInfo.getId()); + } 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 a85a25c9c4ad..beeb03f06ed7 100644 --- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl +++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl @@ -16,6 +16,7 @@ package android.app.blob; import android.app.blob.BlobHandle; +import android.app.blob.BlobInfo; import android.app.blob.IBlobStoreSession; import android.os.RemoteCallback; @@ -31,4 +32,7 @@ interface IBlobStoreManager { void releaseLease(in BlobHandle handle, in String packageName); void waitForIdle(in RemoteCallback callback); + + List<BlobInfo> queryBlobsForUser(int userId); + void deleteBlob(long blobId); }
\ No newline at end of file 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 dab4797b313f..970766d2c8a6 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -15,7 +15,6 @@ */ package com.android.server.blob; -import static android.app.blob.BlobStoreManager.DESC_RES_TYPE_STRING; import static android.app.blob.XmlTags.ATTR_DESCRIPTION; import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME; import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME; @@ -30,16 +29,16 @@ import static android.app.blob.XmlTags.TAG_LEASEE; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.O_RDONLY; -import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC; +import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId; +import static com.android.server.blob.BlobStoreUtils.getPackageResources; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.blob.BlobHandle; import android.content.Context; -import android.content.pm.PackageManager; import android.content.res.ResourceId; import android.content.res.Resources; import android.os.ParcelFileDescriptor; @@ -65,6 +64,7 @@ import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.util.Objects; +import java.util.function.Consumer; class BlobMetadata { private final Object mMetadataLock = new Object(); @@ -281,6 +281,10 @@ class BlobMetadata { return false; } + void forEachLeasee(Consumer<Leasee> consumer) { + mLeasees.forEach(consumer); + } + File getBlobFile() { if (mBlobFile == null) { mBlobFile = BlobStoreConfig.getBlobFile(mBlobId); @@ -506,16 +510,9 @@ class BlobMetadata { if (resources == null) { return null; } - try { - final int resId = resources.getIdentifier(descriptionResEntryName, - DESC_RES_TYPE_STRING, packageName); - return resId <= 0 ? null : resources.getString(resId); - } catch (Resources.NotFoundException e) { - if (LOGV) { - Slog.w(TAG, "Description resource not found", e); - } - return null; - } + final int resId = getDescriptionResourceId(resources, descriptionResEntryName, + packageName); + return resId == Resources.ID_NULL ? null : resources.getString(resId); } @Nullable @@ -546,19 +543,6 @@ class BlobMetadata { return desc == null ? "<none>" : desc; } - @Nullable - private static Resources getPackageResources(@NonNull Context context, - @NonNull String packageName, int userId) { - try { - return context.getPackageManager() - .getResourcesForApplicationAsUser(packageName, userId); - } catch (PackageManager.NameNotFoundException e) { - Slog.d(TAG, "Unknown package in user " + userId + ": " - + packageName, e); - return null; - } - } - void writeToXml(@NonNull XmlSerializer out) throws IOException { XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); XmlUtils.writeIntAttribute(out, ATTR_UID, uid); 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 96f7b7aee165..ed3dda44e131 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -36,6 +36,8 @@ import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID; import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_VALID; import static com.android.server.blob.BlobStoreSession.stateToString; +import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId; +import static com.android.server.blob.BlobStoreUtils.getPackageResources; import android.annotation.CurrentTimeSecondsLong; import android.annotation.IdRes; @@ -43,7 +45,9 @@ 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.content.BroadcastReceiver; @@ -100,6 +104,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.ArrayList; @@ -110,6 +115,7 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.function.Function; /** * Service responsible for maintaining and facilitating access to data blobs published by apps. @@ -434,6 +440,48 @@ public class BlobStoreManagerService extends SystemService { } } + private List<BlobInfo> queryBlobsForUserInternal(int userId) { + final ArrayList<BlobInfo> blobInfos = new ArrayList<>(); + synchronized (mBlobsLock) { + final ArrayMap<String, WeakReference<Resources>> resources = new ArrayMap<>(); + final Function<String, Resources> resourcesGetter = (packageName) -> { + final WeakReference<Resources> resourcesRef = resources.get(packageName); + Resources packageResources = resourcesRef == null ? null : resourcesRef.get(); + if (packageResources == null) { + packageResources = getPackageResources(mContext, packageName, userId); + resources.put(packageName, new WeakReference<>(packageResources)); + } + return packageResources; + }; + getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> { + final ArrayList<AccessorInfo> accessorInfos = 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, + descriptionResId, leasee.description)); + }); + blobInfos.add(new BlobInfo(blobMetadata.getBlobId(), + blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), accessorInfos)); + }); + } + return blobInfos; + } + + private void deleteBlobInternal(long blobId, int callingUid) { + synchronized (mBlobsLock) { + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked( + UserHandle.getUserId(callingUid)); + userBlobs.entrySet().removeIf(entry -> { + final BlobMetadata blobMetadata = entry.getValue(); + return blobMetadata.getBlobId() == blobId; + }); + writeBlobsInfoAsync(); + } + } + private void verifyCallingPackage(int callingUid, String callingPackage) { if (mPackageManagerInternal.getPackageUid( callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) { @@ -1250,6 +1298,28 @@ public class BlobStoreManagerService extends SystemService { } @Override + @NonNull + public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only system uid is allowed to call " + + "queryBlobsForUser()"); + } + + return queryBlobsForUserInternal(userId); + } + + @Override + public void deleteBlob(long blobId) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID) { + throw new SecurityException("Only system uid is allowed to call " + + "deleteBlob()"); + } + + deleteBlobInternal(blobId, callingUid); + } + + @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 new file mode 100644 index 000000000000..6af540acd6a4 --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java @@ -0,0 +1,50 @@ +/* + * 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.server.blob; + +import static com.android.server.blob.BlobStoreConfig.TAG; + +import android.annotation.IdRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.util.Slog; + +class BlobStoreUtils { + private static final String DESC_RES_TYPE_STRING = "string"; + + @Nullable + static Resources getPackageResources(@NonNull Context context, + @NonNull String packageName, int userId) { + try { + return context.getPackageManager() + .getResourcesForApplicationAsUser(packageName, userId); + } catch (PackageManager.NameNotFoundException e) { + Slog.d(TAG, "Unknown package in user " + userId + ": " + + packageName, e); + return null; + } + } + + @IdRes + static int getDescriptionResourceId(@NonNull Resources resources, + @NonNull String resourceEntryName, @NonNull String packageName) { + return resources.getIdentifier(resourceEntryName, DESC_RES_TYPE_STRING, packageName); + } +} diff --git a/api/current.txt b/api/current.txt index 59afab200fff..6052d93d1f60 100644 --- a/api/current.txt +++ b/api/current.txt @@ -18118,9 +18118,7 @@ package android.hardware.fingerprint { ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature); ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher); ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac); - ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull android.security.identity.IdentityCredential); method @Deprecated public javax.crypto.Cipher getCipher(); - method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential(); method @Deprecated public javax.crypto.Mac getMac(); method @Deprecated public java.security.Signature getSignature(); } @@ -42690,7 +42688,7 @@ package android.security.identity { ctor public PersonalizationData.Builder(); method @NonNull public android.security.identity.PersonalizationData.Builder addAccessControlProfile(@NonNull android.security.identity.AccessControlProfile); method @NonNull public android.security.identity.PersonalizationData build(); - method @NonNull public android.security.identity.PersonalizationData.Builder setEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]); + method @NonNull public android.security.identity.PersonalizationData.Builder putEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]); } public abstract class ResultData { @@ -42698,7 +42696,7 @@ package android.security.identity { method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String); method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String); method @Nullable public abstract byte[] getMessageAuthenticationCode(); - method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaceNames(); + method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaces(); method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String); method @NonNull public abstract byte[] getStaticAuthenticationData(); method public abstract int getStatus(@NonNull String, @NonNull String); diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 6863221f7fda..69b52693aa46 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -47,6 +47,7 @@ package android.net { method @NonNull public String[] getTetheredIfaces(); method @NonNull public String[] getTetheringErroredIfaces(); method public boolean isTetheringSupported(); + method public boolean isTetheringSupported(@NonNull String); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback); method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener); method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean); @@ -86,6 +87,9 @@ package android.net { field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 + field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 + field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 + field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 } public static interface TetheringManager.OnTetheringEntitlementResultListener { @@ -102,6 +106,7 @@ package android.net { ctor public TetheringManager.TetheringEventCallback(); method public void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>); method public void onError(@NonNull String, int); + method public void onOffloadStatusChanged(int); method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); diff --git a/api/system-current.txt b/api/system-current.txt index 0c4460081718..0add184d8f5d 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2014,7 +2014,6 @@ package android.content.pm { } public final class InstallationFile { - ctor public InstallationFile(int, @NonNull String, long, @Nullable byte[], @Nullable byte[]); method public long getLengthBytes(); method public int getLocation(); method @Nullable public byte[] getMetadata(); @@ -6128,7 +6127,7 @@ package android.net { } public class EthernetManager { - method @NonNull public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull android.net.EthernetManager.TetheredInterfaceCallback); + method @NonNull public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback); } public static interface EthernetManager.TetheredInterfaceCallback { @@ -6594,6 +6593,9 @@ package android.net { field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 + field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 + field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 + field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 } public static interface TetheringManager.OnTetheringEntitlementResultListener { @@ -6610,6 +6612,7 @@ package android.net { ctor public TetheringManager.TetheringEventCallback(); method public void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>); method public void onError(@NonNull String, int); + method public void onOffloadStatusChanged(int); method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); @@ -7812,9 +7815,6 @@ package android.net.wifi.nl80211 { method public int getMaxNumberTxSpatialStreams(); method public boolean isChannelWidthSupported(int); method public boolean isWifiStandardSupported(int); - method public void setChannelWidthSupported(int, boolean); - method public void setMaxNumberRxSpatialStreams(int); - method public void setMaxNumberTxSpatialStreams(int); method public void setWifiStandardSupport(int, boolean); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.nl80211.DeviceWiphyCapabilities> CREATOR; @@ -9863,10 +9863,10 @@ package android.service.autofill.augmented { public static final class FillResponse.Builder { ctor public FillResponse.Builder(); method @NonNull public android.service.autofill.augmented.FillResponse build(); - method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@Nullable android.os.Bundle); - method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@Nullable android.service.autofill.augmented.FillWindow); - method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineActions(@Nullable java.util.List<android.service.autofill.InlinePresentation>); - method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@Nullable java.util.List<android.service.autofill.Dataset>); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@NonNull android.os.Bundle); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@NonNull android.service.autofill.augmented.FillWindow); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineActions(@NonNull java.util.List<android.service.autofill.InlinePresentation>); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@NonNull java.util.List<android.service.autofill.Dataset>); } public final class FillWindow implements java.lang.AutoCloseable { @@ -11697,6 +11697,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void resetIms(int); + method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void resetOtaEmergencyNumberDbFilePath(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig(); method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings(); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); @@ -11734,8 +11735,8 @@ package android.telephony { method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff(); + method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor); method public void updateServiceLocation(); - method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String); field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED"; field public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE"; field public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE"; @@ -12703,8 +12704,8 @@ package android.telephony.ims { field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11 field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17 field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16 + field public static final int KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC = 16; // 0x10 field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15 - field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10 field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf field public static final int KEY_REGISTRATION_DOMAIN_NAME = 12; // 0xc field public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33; // 0x21 diff --git a/api/test-current.txt b/api/test-current.txt index d03539c70d55..2644f05d1bea 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -784,6 +784,8 @@ package android.content { } public abstract class ContentResolver { + method @NonNull public static android.net.Uri decodeFromFile(@NonNull java.io.File); + method @NonNull public static java.io.File encodeToFile(@NonNull android.net.Uri); method public static String[] getSyncAdapterPackagesForAuthorityAsUser(String, int); } @@ -1727,7 +1729,7 @@ package android.net { } public class EthernetManager { - method @NonNull public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull android.net.EthernetManager.TetheredInterfaceCallback); + method @NonNull public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback); } public static interface EthernetManager.TetheredInterfaceCallback { @@ -1909,6 +1911,9 @@ package android.net { field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 + field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 + field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 + field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 } public static interface TetheringManager.OnTetheringEntitlementResultListener { @@ -1925,6 +1930,7 @@ package android.net { ctor public TetheringManager.TetheringEventCallback(); method public void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>); method public void onError(@NonNull String, int); + method public void onOffloadStatusChanged(int); method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); @@ -2915,6 +2921,11 @@ package android.provider { method @NonNull public android.provider.DeviceConfig.Properties.Builder setString(@NonNull String, @Nullable String); } + public final class DocumentsContract { + method public static boolean isManageMode(@NonNull android.net.Uri); + method @NonNull public static android.net.Uri setManageMode(@NonNull android.net.Uri); + } + public final class MediaStore { method @NonNull @WorkerThread public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File); method @WorkerThread public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String); @@ -3223,10 +3234,10 @@ package android.service.autofill.augmented { public static final class FillResponse.Builder { ctor public FillResponse.Builder(); method @NonNull public android.service.autofill.augmented.FillResponse build(); - method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@Nullable android.os.Bundle); - method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@Nullable android.service.autofill.augmented.FillWindow); - method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineActions(@Nullable java.util.List<android.service.autofill.InlinePresentation>); - method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@Nullable java.util.List<android.service.autofill.Dataset>); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@NonNull android.os.Bundle); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@NonNull android.service.autofill.augmented.FillWindow); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineActions(@NonNull java.util.List<android.service.autofill.InlinePresentation>); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@NonNull java.util.List<android.service.autofill.Dataset>); } public final class FillWindow implements java.lang.AutoCloseable { @@ -3728,11 +3739,12 @@ package android.telephony { method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion(); method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile(); + method @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public void resetOtaEmergencyNumberDbFilePath(); method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String); method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>); - method @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String); + method @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor); field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 @@ -4288,8 +4300,8 @@ package android.telephony.ims { field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11 field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17 field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16 + field public static final int KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC = 16; // 0x10 field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15 - field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10 field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf field public static final int KEY_REGISTRATION_DOMAIN_NAME = 12; // 0xc field public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33; // 0x21 diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index f18aaa5a9ea0..cd10457dcd40 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -930,8 +930,6 @@ Status StatsService::informOnePackage(const string& app, int32_t uid, int64_t ve ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::informOnePackage was called"); - // TODO(b/149254662): This is gross. We should consider changing statsd - // internals to use std::string. String16 utf16App = String16(app.c_str()); String16 utf16VersionString = String16(versionString.c_str()); String16 utf16Installer = String16(installer.c_str()); diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.cpp b/cmds/statsd/src/anomaly/AlarmMonitor.cpp index 02291181b81b..b632d040eb43 100644 --- a/cmds/statsd/src/anomaly/AlarmMonitor.cpp +++ b/cmds/statsd/src/anomaly/AlarmMonitor.cpp @@ -38,8 +38,6 @@ AlarmMonitor::~AlarmMonitor() {} void AlarmMonitor::setStatsCompanionService( shared_ptr<IStatsCompanionService> statsCompanionService) { std::lock_guard<std::mutex> lock(mLock); - // TODO(b/149254662): determine if tmpForLock is needed now that we have moved - // from sp to shared_ptr shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService; mStatsCompanionService = statsCompanionService; if (statsCompanionService == nullptr) { diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 9bdb5881cc94..6d9c644bb40e 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -54,20 +54,22 @@ struct ConfigReceiverDeathCookie { shared_ptr<IPendingIntentRef> mPir; }; -static void configReceiverDied(void* cookie) { +void ConfigManager::configReceiverDied(void* cookie) { auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie); - sp<ConfigManager> configManager = cookie_->mConfigManager; - ConfigKey configKey = cookie_->mConfigKey; - shared_ptr<IPendingIntentRef> pir = cookie_->mPir; - - // TODO(b/149254662): Fix threading. This currently fails if a new - // pir gets set between the get and the remove. - if (configManager->GetConfigReceiver(configKey) == pir) { - configManager->RemoveConfigReceiver(configKey); + sp<ConfigManager>& thiz = cookie_->mConfigManager; + ConfigKey& configKey = cookie_->mConfigKey; + shared_ptr<IPendingIntentRef>& pir = cookie_->mPir; + + // Erase the mapping from the config key to the config receiver (pir) if the + // mapping still exists. + lock_guard<mutex> lock(thiz->mMutex); + auto it = thiz->mConfigReceivers.find(configKey); + if (it != thiz->mConfigReceivers.end() && it->second == pir) { + thiz->mConfigReceivers.erase(configKey); } - // The death recipient corresponding to this specific pir can never - // be triggered again, so free up resources. - // TODO(b/149254662): Investigate other options to manage the memory. + + // The death recipient corresponding to this specific pir can never be + // triggered again, so free up resources. delete cookie_; } @@ -83,26 +85,29 @@ struct ActiveConfigChangedReceiverDeathCookie { shared_ptr<IPendingIntentRef> mPir; }; -static void activeConfigChangedReceiverDied(void* cookie) { +void ConfigManager::activeConfigChangedReceiverDied(void* cookie) { auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie); - sp<ConfigManager> configManager = cookie_->mConfigManager; + sp<ConfigManager>& thiz = cookie_->mConfigManager; int uid = cookie_->mUid; - shared_ptr<IPendingIntentRef> pir = cookie_->mPir; - - // TODO(b/149254662): Fix threading. This currently fails if a new - // pir gets set between the get and the remove. - if (configManager->GetActiveConfigsChangedReceiver(uid) == pir) { - configManager->RemoveActiveConfigsChangedReceiver(uid); + shared_ptr<IPendingIntentRef>& pir = cookie_->mPir; + + // Erase the mapping from the config key to the active config changed + // receiver (pir) if the mapping still exists. + lock_guard<mutex> lock(thiz->mMutex); + auto it = thiz->mActiveConfigsChangedReceivers.find(uid); + if (it != thiz->mActiveConfigsChangedReceivers.end() && it->second == pir) { + thiz->mActiveConfigsChangedReceivers.erase(uid); } + // The death recipient corresponding to this specific pir can never // be triggered again, so free up resources. delete cookie_; } -ConfigManager::ConfigManager(): +ConfigManager::ConfigManager() : mConfigReceiverDeathRecipient(AIBinder_DeathRecipient_new(configReceiverDied)), mActiveConfigChangedReceiverDeathRecipient( - AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) { + AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) { } ConfigManager::~ConfigManager() { @@ -189,8 +194,10 @@ void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) { void ConfigManager::SetActiveConfigsChangedReceiver(const int uid, const shared_ptr<IPendingIntentRef>& pir) { - lock_guard<mutex> lock(mMutex); - mActiveConfigsChangedReceivers[uid] = pir; + { + lock_guard<mutex> lock(mMutex); + mActiveConfigsChangedReceivers[uid] = pir; + } AIBinder_linkToDeath(pir->asBinder().get(), mActiveConfigChangedReceiverDeathRecipient.get(), new ActiveConfigChangedReceiverDeathCookie(this, uid, pir)); } diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h index 824e58849b78..40146b1b2bec 100644 --- a/cmds/statsd/src/config/ConfigManager.h +++ b/cmds/statsd/src/config/ConfigManager.h @@ -161,6 +161,19 @@ private: // IPendingIntentRef dies. ::ndk::ScopedAIBinder_DeathRecipient mConfigReceiverDeathRecipient; ::ndk::ScopedAIBinder_DeathRecipient mActiveConfigChangedReceiverDeathRecipient; + + /** + * Death recipient callback that is called when a config receiver dies. + * The cookie is a pointer to a ConfigReceiverDeathCookie. + */ + static void configReceiverDied(void* cookie); + + /** + * Death recipient callback that is called when an active config changed + * receiver dies. The cookie is a pointer to an + * ActiveConfigChangedReceiverDeathCookie. + */ + static void activeConfigChangedReceiverDied(void* cookie); }; } // namespace statsd diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 1c38542b9017..0115ba2b6bc0 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -85,12 +85,9 @@ void StatsPullerManager::updateAlarmLocked() { return; } - // TODO(b/149254662): Why are we creating a copy here? This is different - // from the other places where we create a copy because we don't reassign - // mStatsCompanionService so a destructor can't implicitly be called... - shared_ptr<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService; - if (statsCompanionServiceCopy != nullptr) { - statsCompanionServiceCopy->setPullingAlarm(mNextPullTimeNs / 1000000); + // TODO(b/151045771): do not hold a lock while making a binder call + if (mStatsCompanionService != nullptr) { + mStatsCompanionService->setPullingAlarm(mNextPullTimeNs / 1000000); } else { VLOG("StatsCompanionService not available. Alarm not set."); } @@ -99,8 +96,6 @@ void StatsPullerManager::updateAlarmLocked() { void StatsPullerManager::SetStatsCompanionService( shared_ptr<IStatsCompanionService> statsCompanionService) { - // TODO(b/149254662): Why are we using AutoMutex instead of lock_guard? - // Additionally, do we need the temporary shared_ptr to prevent deadlocks? AutoMutex _l(mLock); shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService; mStatsCompanionService = statsCompanionService; diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp index 93af5e937228..c915ef3bf069 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -39,33 +39,47 @@ struct BroadcastSubscriberDeathCookie { shared_ptr<IPendingIntentRef> mPir; }; -static void broadcastSubscriberDied(void* cookie) { - BroadcastSubscriberDeathCookie* cookie_ = (BroadcastSubscriberDeathCookie*)cookie; - ConfigKey configKey = cookie_->mConfigKey; +void SubscriberReporter::broadcastSubscriberDied(void* cookie) { + auto cookie_ = static_cast<BroadcastSubscriberDeathCookie*>(cookie); + ConfigKey& configKey = cookie_->mConfigKey; int64_t subscriberId = cookie_->mSubscriberId; - shared_ptr<IPendingIntentRef> pir = cookie_->mPir; - - // TODO(b/149254662): Fix threading. This currently fails if a new pir gets - // set between the get and the unset. - if (SubscriberReporter::getInstance().getBroadcastSubscriber(configKey, subscriberId) == pir) { - SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey, subscriberId); + shared_ptr<IPendingIntentRef>& pir = cookie_->mPir; + + SubscriberReporter& thiz = getInstance(); + + // Erase the mapping from a (config_key, subscriberId) to a pir if the + // mapping exists. + lock_guard<mutex> lock(thiz.mLock); + auto subscriberMapIt = thiz.mIntentMap.find(configKey); + if (subscriberMapIt != thiz.mIntentMap.end()) { + auto subscriberMap = subscriberMapIt->second; + auto pirIt = subscriberMap.find(subscriberId); + if (pirIt != subscriberMap.end() && pirIt->second == pir) { + subscriberMap.erase(subscriberId); + if (subscriberMap.empty()) { + thiz.mIntentMap.erase(configKey); + } + } } + // The death recipient corresponding to this specific pir can never be // triggered again, so free up resources. delete cookie_; } -static ::ndk::ScopedAIBinder_DeathRecipient sBroadcastSubscriberDeathRecipient( - AIBinder_DeathRecipient_new(broadcastSubscriberDied)); +SubscriberReporter::SubscriberReporter() : + mBroadcastSubscriberDeathRecipient(AIBinder_DeathRecipient_new(broadcastSubscriberDied)) { +} void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId, const shared_ptr<IPendingIntentRef>& pir) { VLOG("SubscriberReporter::setBroadcastSubscriber called."); - lock_guard<mutex> lock(mLock); - mIntentMap[configKey][subscriberId] = pir; - // TODO(b/149254662): Is it ok to call linkToDeath while holding a lock? - AIBinder_linkToDeath(pir->asBinder().get(), sBroadcastSubscriberDeathRecipient.get(), + { + lock_guard<mutex> lock(mLock); + mIntentMap[configKey][subscriberId] = pir; + } + AIBinder_linkToDeath(pir->asBinder().get(), mBroadcastSubscriberDeathRecipient.get(), new BroadcastSubscriberDeathCookie(configKey, subscriberId, pir)); } @@ -103,8 +117,6 @@ void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, } int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id(); - // TODO(b/149254662): Is there a way to convert a RepeatedPtrField into a - // vector without copying? vector<string> cookies; cookies.reserve(subscription.broadcast_subscriber_details().cookie_size()); for (auto& cookie : subscription.broadcast_subscriber_details().cookie()) { diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h index 0f97d397e331..4fe428198e71 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.h +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -78,7 +78,7 @@ public: int64_t subscriberId); private: - SubscriberReporter() {}; + SubscriberReporter(); mutable mutex mLock; @@ -94,6 +94,14 @@ private: const Subscription& subscription, const vector<string>& cookies, const MetricDimensionKey& dimKey) const; + + ::ndk::ScopedAIBinder_DeathRecipient mBroadcastSubscriberDeathRecipient; + + /** + * Death recipient callback that is called when a broadcast subscriber dies. + * The cookie is a pointer to a BroadcastSubscriberDeathCookie. + */ + static void broadcastSubscriberDied(void* cookie); }; } // namespace statsd diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index ae786aa30ae0..911ffa06ed38 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -23,6 +23,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; @@ -4014,6 +4015,10 @@ public abstract class ContentResolver implements ContentInterface { * @hide */ @SystemApi + @TestApi + // We can't accept an already-opened FD here, since these methods are + // rewriting actual filesystem paths + @SuppressLint("StreamFiles") public static @NonNull Uri decodeFromFile(@NonNull File file) { return translateDeprecatedDataPath(file.getAbsolutePath()); } @@ -4030,6 +4035,10 @@ public abstract class ContentResolver implements ContentInterface { * @hide */ @SystemApi + @TestApi + // We can't accept an already-opened FD here, since these methods are + // rewriting actual filesystem paths + @SuppressLint("StreamFiles") public static @NonNull File encodeToFile(@NonNull Uri uri) { return new File(translateDeprecatedDataPath(uri)); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index aba86e9f1054..6cba3270042d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5128,7 +5128,7 @@ public abstract class Context { * {@link android.os.incremental.IncrementalManager}. * @hide */ - public static final String INCREMENTAL_SERVICE = "incremental_service"; + public static final String INCREMENTAL_SERVICE = "incremental"; /** * Use with {@link #getSystemService(String)} to retrieve an diff --git a/core/java/android/content/pm/InstallationFile.java b/core/java/android/content/pm/InstallationFile.java index edc04c9e7248..de761ad1a305 100644 --- a/core/java/android/content/pm/InstallationFile.java +++ b/core/java/android/content/pm/InstallationFile.java @@ -21,13 +21,25 @@ import android.annotation.Nullable; import android.annotation.SystemApi; /** - * Defines the properties of a file in an installation session. + * Definition of a file in a streaming installation session. + * You can use this class to retrieve the information of such a file, such as its name, size and + * metadata. These file attributes will be consistent with those used in: + * {@code PackageInstaller.Session#addFile}, when the file was first added into the session. + * + * WARNING: This is a system API to aid internal development. + * Use at your own risk. It will change or be removed without warning. + * + * @see android.content.pm.PackageInstaller.Session#addFile * @hide */ @SystemApi public final class InstallationFile { private final @NonNull InstallationFileParcel mParcel; + /** + * Constructor, internal use only + * @hide + */ public InstallationFile(@PackageInstaller.FileLocation int location, @NonNull String name, long lengthBytes, @Nullable byte[] metadata, @Nullable byte[] signature) { mParcel = new InstallationFileParcel(); @@ -38,22 +50,44 @@ public final class InstallationFile { mParcel.signature = signature; } + /** + * Installation Location of this file. Can be one of the following three locations: + * <ul> + * <li>(1) {@code PackageInstaller.LOCATION_DATA_APP}</li> + * <li>(2) {@code PackageInstaller.LOCATION_MEDIA_OBB}</li> + * <li>(3) {@code PackageInstaller.LOCATION_MEDIA_DATA}</li> + * </ul> + * @see android.content.pm.PackageInstaller + * @return Integer that denotes the installation location of the file. + */ public @PackageInstaller.FileLocation int getLocation() { return mParcel.location; } + /** + * @return Name of the file. + */ public @NonNull String getName() { return mParcel.name; } + /** + * @return File size in bytes. + */ public long getLengthBytes() { return mParcel.size; } + /** + * @return File metadata as a byte array + */ public @Nullable byte[] getMetadata() { return mParcel.metadata; } + /** + * @return File signature info as a byte array + */ public @Nullable byte[] getSignature() { return mParcel.signature; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9b28cb5e88ab..4065b110bfe9 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2227,6 +2227,9 @@ public abstract class PackageManager { * <li>{@code VkPhysicalDeviceSamplerYcbcrConversionFeatures::samplerYcbcrConversion} is * supported.</li> * </ul> + * A subset of devices that support Vulkan 1.1 do so via software emulation. For more + * information, see + * <a href="{@docRoot}ndk/guides/graphics/design-notes">Vulkan Design Guidelines</a>. */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version"; diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 2a71da83027d..91451427b45e 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -129,10 +129,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing super(mac); } - public CryptoObject(@NonNull IdentityCredential credential) { - super(credential); - } - /** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. @@ -160,8 +156,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Get {@link IdentityCredential} object. * @return {@link IdentityCredential} object or null if this doesn't contain one. + * @hide */ - public @Nullable IdentityCredential getIdentityCredential() { + public IdentityCredential getIdentityCredential() { return super.getIdentityCredential(); } } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 38ef814561e6..fc6954fb4808 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -53,7 +53,6 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ServiceSpecificException; -import android.os.SystemClock; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -808,7 +807,7 @@ public class ConnectivityManager { private INetworkManagementService mNMService; private INetworkPolicyManager mNPManager; - private TetheringManager mTetheringManager; + private final TetheringManager mTetheringManager; /** * Tests if a given integer represents a valid network type. @@ -2274,6 +2273,7 @@ public class ConnectivityManager { public ConnectivityManager(Context context, IConnectivityManager service) { mContext = Preconditions.checkNotNull(context, "missing context"); mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + mTetheringManager = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE); sInstance = this; } @@ -2347,28 +2347,6 @@ public class ConnectivityManager { return getInstanceOrNull(); } - private static final int TETHERING_TIMEOUT_MS = 60_000; - private final Object mTetheringLock = new Object(); - - private TetheringManager getTetheringManager() { - synchronized (mTetheringLock) { - if (mTetheringManager != null) { - return mTetheringManager; - } - final long before = System.currentTimeMillis(); - while ((mTetheringManager = (TetheringManager) mContext.getSystemService( - Context.TETHERING_SERVICE)) == null) { - if (System.currentTimeMillis() - before > TETHERING_TIMEOUT_MS) { - Log.e(TAG, "Timeout waiting tethering service not ready yet"); - throw new IllegalStateException("No tethering service yet"); - } - SystemClock.sleep(100); - } - - return mTetheringManager; - } - } - /** * Get the set of tetherable, available interfaces. This list is limited by * device configuration and current interface existence. @@ -2382,7 +2360,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public String[] getTetherableIfaces() { - return getTetheringManager().getTetherableIfaces(); + return mTetheringManager.getTetherableIfaces(); } /** @@ -2397,7 +2375,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public String[] getTetheredIfaces() { - return getTetheringManager().getTetheredIfaces(); + return mTetheringManager.getTetheredIfaces(); } /** @@ -2418,7 +2396,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public String[] getTetheringErroredIfaces() { - return getTetheringManager().getTetheringErroredIfaces(); + return mTetheringManager.getTetheringErroredIfaces(); } /** @@ -2462,7 +2440,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public int tether(String iface) { - return getTetheringManager().tether(iface); + return mTetheringManager.tether(iface); } /** @@ -2486,7 +2464,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public int untether(String iface) { - return getTetheringManager().untether(iface); + return mTetheringManager.untether(iface); } /** @@ -2512,7 +2490,7 @@ public class ConnectivityManager { @RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported() { - return getTetheringManager().isTetheringSupported(); + return mTetheringManager.isTetheringSupported(); } /** @@ -2605,7 +2583,7 @@ public class ConnectivityManager { final TetheringRequest request = new TetheringRequest.Builder(type) .setSilentProvisioning(!showProvisioningUi).build(); - getTetheringManager().startTethering(request, executor, tetheringCallback); + mTetheringManager.startTethering(request, executor, tetheringCallback); } /** @@ -2624,7 +2602,7 @@ public class ConnectivityManager { @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int type) { - getTetheringManager().stopTethering(type); + mTetheringManager.stopTethering(type); } /** @@ -2682,7 +2660,7 @@ public class ConnectivityManager { synchronized (mTetheringEventCallbacks) { mTetheringEventCallbacks.put(callback, tetherCallback); - getTetheringManager().registerTetheringEventCallback(executor, tetherCallback); + mTetheringManager.registerTetheringEventCallback(executor, tetherCallback); } } @@ -2704,7 +2682,7 @@ public class ConnectivityManager { synchronized (mTetheringEventCallbacks) { final TetheringEventCallback tetherCallback = mTetheringEventCallbacks.remove(callback); - getTetheringManager().unregisterTetheringEventCallback(tetherCallback); + mTetheringManager.unregisterTetheringEventCallback(tetherCallback); } } @@ -2724,7 +2702,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public String[] getTetherableUsbRegexs() { - return getTetheringManager().getTetherableUsbRegexs(); + return mTetheringManager.getTetherableUsbRegexs(); } /** @@ -2742,7 +2720,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public String[] getTetherableWifiRegexs() { - return getTetheringManager().getTetherableWifiRegexs(); + return mTetheringManager.getTetherableWifiRegexs(); } /** @@ -2761,7 +2739,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public String[] getTetherableBluetoothRegexs() { - return getTetheringManager().getTetherableBluetoothRegexs(); + return mTetheringManager.getTetherableBluetoothRegexs(); } /** @@ -2785,7 +2763,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public int setUsbTethering(boolean enable) { - return getTetheringManager().setUsbTethering(enable); + return mTetheringManager.setUsbTethering(enable); } /** @@ -2902,7 +2880,7 @@ public class ConnectivityManager { @UnsupportedAppUsage @Deprecated public int getLastTetherError(String iface) { - return getTetheringManager().getLastTetherError(iface); + return mTetheringManager.getLastTetherError(iface); } /** @hide */ @@ -2973,7 +2951,7 @@ public class ConnectivityManager { } }; - getTetheringManager().requestLatestTetheringEntitlementResult(type, wrappedListener, + mTetheringManager.requestLatestTetheringEntitlementResult(type, wrappedListener, showEntitlementUi); } @@ -4469,7 +4447,7 @@ public class ConnectivityManager { public void factoryReset() { try { mService.factoryReset(); - getTetheringManager().stopAllTethering(); + mTetheringManager.stopAllTethering(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java index a3899b705c1b..139f5bebcd89 100644 --- a/core/java/android/net/EthernetManager.java +++ b/core/java/android/net/EthernetManager.java @@ -28,6 +28,7 @@ import android.os.RemoteException; import java.util.ArrayList; import java.util.Objects; +import java.util.concurrent.Executor; /** * A class representing the IP configuration of the Ethernet network. @@ -248,18 +249,19 @@ public class EthernetManager { * @param callback A callback to be called once the request has been fulfilled. */ @NonNull - public TetheredInterfaceRequest requestTetheredInterface( - @NonNull TetheredInterfaceCallback callback) { + public TetheredInterfaceRequest requestTetheredInterface(@NonNull final Executor executor, + @NonNull final TetheredInterfaceCallback callback) { Objects.requireNonNull(callback, "Callback must be non-null"); + Objects.requireNonNull(executor, "Executor must be non-null"); final ITetheredInterfaceCallback cbInternal = new ITetheredInterfaceCallback.Stub() { @Override public void onAvailable(String iface) { - callback.onAvailable(iface); + executor.execute(() -> callback.onAvailable(iface)); } @Override public void onUnavailable() { - callback.onUnavailable(); + executor.execute(() -> callback.onUnavailable()); } }; diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index fe7c7c921b67..c889ee6307d1 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -248,6 +248,27 @@ public class Binder implements IBinder { } } + static ThreadLocal<Boolean> sWarnOnBlockingOnCurrentThread = + ThreadLocal.withInitial(() -> sWarnOnBlocking); + + /** + * Allow blocking calls for the current thread. See {@link #allowBlocking}. + * + * @hide + */ + public static void allowBlockingForCurrentThread() { + sWarnOnBlockingOnCurrentThread.set(false); + } + + /** + * Reset the current thread to the default blocking behavior. See {@link #defaultBlocking}. + * + * @hide + */ + public static void defaultBlockingForCurrentThread() { + sWarnOnBlockingOnCurrentThread.set(sWarnOnBlocking); + } + /** * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null. */ diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index be307ab4737d..dd3f9fdc3ec0 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -479,16 +479,21 @@ public final class BinderProxy implements IBinder { public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { Binder.checkParcel(this, code, data, "Unreasonably large binder buffer"); - if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)) { + if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0) + && Binder.sWarnOnBlockingOnCurrentThread.get()) { + // For now, avoid spamming the log by disabling after we've logged // about this interface at least once mWarnOnBlocking = false; + if (Build.IS_USERDEBUG) { // Log this as a WTF on userdebug builds. - Log.wtf(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY", + Log.wtf(Binder.TAG, + "Outgoing transactions from this process must be FLAG_ONEWAY", new Throwable()); } else { - Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY", + Log.w(Binder.TAG, + "Outgoing transactions from this process must be FLAG_ONEWAY", new Throwable()); } } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 47f24615d60a..a10a456bd6a6 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -22,6 +22,7 @@ import static com.android.internal.util.Preconditions.checkCollectionNotEmpty; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentInterface; import android.content.ContentProvider; @@ -1303,6 +1304,7 @@ public final class DocumentsContract { * {@hide} */ @SystemApi + @TestApi public static @NonNull Uri setManageMode(@NonNull Uri uri) { Preconditions.checkNotNull(uri, "uri can not be null"); return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build(); @@ -1314,6 +1316,7 @@ public final class DocumentsContract { * {@hide} */ @SystemApi + @TestApi public static boolean isManageMode(@NonNull Uri uri) { Preconditions.checkNotNull(uri, "uri can not be null"); return uri.getBooleanQueryParameter(PARAM_MANAGE, false); diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java index b7fdf5ab337f..37d75aa5d502 100644 --- a/core/java/android/service/autofill/augmented/FillResponse.java +++ b/core/java/android/service/autofill/augmented/FillResponse.java @@ -91,7 +91,7 @@ public final class FillResponse { - // Code below generated by codegen v1.0.14. + // Code below generated by codegen v1.0.15. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -183,7 +183,7 @@ public final class FillResponse { * The {@link FillWindow} used to display the Autofill UI. */ @DataClass.Generated.Member - public @NonNull Builder setFillWindow(@Nullable FillWindow value) { + public @NonNull Builder setFillWindow(@NonNull FillWindow value) { checkNotUsed(); mBuilderFieldsSet |= 0x1; mFillWindow = value; @@ -195,7 +195,7 @@ public final class FillResponse { * inline suggestions are available from the service. */ @DataClass.Generated.Member - public @NonNull Builder setInlineSuggestions(@Nullable List<Dataset> value) { + public @NonNull Builder setInlineSuggestions(@NonNull List<Dataset> value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; mInlineSuggestions = value; @@ -216,7 +216,7 @@ public final class FillResponse { * inline actions are provided. */ @DataClass.Generated.Member - public @NonNull Builder setInlineActions(@Nullable List<InlinePresentation> value) { + public @NonNull Builder setInlineActions(@NonNull List<InlinePresentation> value) { checkNotUsed(); mBuilderFieldsSet |= 0x4; mInlineActions = value; @@ -238,7 +238,7 @@ public final class FillResponse { * {@link AugmentedAutofillService#getFillEventHistory()}. */ @DataClass.Generated.Member - public @NonNull Builder setClientState(@Nullable Bundle value) { + public @NonNull Builder setClientState(@NonNull Bundle value) { checkNotUsed(); mBuilderFieldsSet |= 0x8; mClientState = value; @@ -279,8 +279,8 @@ public final class FillResponse { } @DataClass.Generated( - time = 1582682935951L, - codegenVersion = "1.0.14", + time = 1583780042587L, + codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/service/autofill/augmented/FillResponse.java", inputSignatures = "private @android.annotation.Nullable android.service.autofill.augmented.FillWindow mFillWindow\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineSuggestion\") @android.annotation.Nullable java.util.List<android.service.autofill.Dataset> mInlineSuggestions\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineAction\") @android.annotation.Nullable java.util.List<android.service.autofill.InlinePresentation> mInlineActions\nprivate @android.annotation.Nullable android.os.Bundle mClientState\nprivate static android.service.autofill.augmented.FillWindow defaultFillWindow()\nprivate static java.util.List<android.service.autofill.Dataset> defaultInlineSuggestions()\nprivate static java.util.List<android.service.autofill.InlinePresentation> defaultInlineActions()\nprivate static android.os.Bundle defaultClientState()\nclass FillResponse extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genHiddenGetters=true)\nabstract android.service.autofill.augmented.FillResponse.Builder addInlineSuggestion(android.service.autofill.Dataset)\nabstract android.service.autofill.augmented.FillResponse.Builder addInlineAction(android.service.autofill.InlinePresentation)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated diff --git a/core/java/android/service/controls/TokenProvider.aidl b/core/java/android/service/controls/TokenProvider.aidl deleted file mode 100644 index 8f4b7953f659..000000000000 --- a/core/java/android/service/controls/TokenProvider.aidl +++ /dev/null @@ -1,7 +0,0 @@ -package android.service.controls; - -/** @hide */ -interface TokenProvider { - void setAuthToken(String token); - String getAccountName(); -}
\ No newline at end of file diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java index 5bf1975a44ff..0b9a8aff26e8 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -172,7 +172,7 @@ public abstract class DataLoaderService extends Service { @Override public void prepareImage(InstallationFileParcel[] addedFiles, String[] removedFiles) { if (!nativePrepareImage(mId, addedFiles, removedFiles)) { - Slog.w(TAG, "Failed to destroy loader: " + mId); + Slog.w(TAG, "Failed to prepare image for data loader: " + mId); } } } diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java index ee3a8a79d5d7..a8db50e51782 100644 --- a/core/java/android/timezone/CountryTimeZones.java +++ b/core/java/android/timezone/CountryTimeZones.java @@ -140,7 +140,7 @@ public final class CountryTimeZones { @Override public String toString() { return "OffsetResult{" - + "mTimeZone=" + mTimeZone + + "mTimeZone(ID)=" + mTimeZone.getID() + ", mIsOnlyMatch=" + mIsOnlyMatch + '}'; } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 573d8fc65c84..f52960c5c3e0 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -992,4 +992,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } return mViewRoot.mWindowAttributes.insetsFlags.behavior; } + + /** + * At the time we receive new leashes (e.g. InsetsSourceConsumer is processing + * setControl) we need to release the old leash. But we may have already scheduled + * a SyncRtSurfaceTransaction applier to use it from the RenderThread. To avoid + * synchronization issues we also release from the RenderThread so this release + * happens after any existing items on the work queue. + */ + public void releaseSurfaceControlFromRt(SurfaceControl sc) { + if (mViewRoot.mView != null && mViewRoot.mView.isHardwareAccelerated()) { + mViewRoot.registerRtFrameCallback(frame -> { + sc.release(); + }); + // Make sure a frame gets scheduled. + mViewRoot.mView.invalidate(); + } else { + sc.release(); + } + } } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index f07f1ce186fe..252fc0c82cf7 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -117,7 +117,7 @@ public class InsetsSourceConsumer { } } if (lastControl != null) { - lastControl.release(); + lastControl.release(mController); } } diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 29ba56a5fbb9..75f6eab798ce 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -94,9 +94,9 @@ public class InsetsSourceControl implements Parcelable { dest.writeParcelable(mSurfacePosition, 0 /* flags*/); } - public void release() { + public void release(InsetsController controller) { if (mLeash != null) { - mLeash.release(); + controller.releaseSurfaceControlFromRt(mLeash); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8d3cffc512c3..9228fbdf82c8 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1735,7 +1735,7 @@ public final class ViewRootImpl implements ViewParent, mBoundsLayer = new SurfaceControl.Builder(mSurfaceSession) .setContainerLayer() .setName("Bounds for - " + getTitle().toString()) - .setParent(mSurfaceControl) + .setParent(getRenderSurfaceControl()) .build(); setBoundsLayerCrop(); mTransaction.show(mBoundsLayer).apply(); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 13c1f67ef85b..816612f1dcc7 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -4587,7 +4587,8 @@ public class Editor { protected int mHorizontalGravity; // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up private float mTouchOffsetY; - // Where the touch position should be on the handle to ensure a maximum cursor visibility + // Where the touch position should be on the handle to ensure a maximum cursor visibility. + // This is the distance in pixels from the top of the handle view. private float mIdealVerticalOffset; // Parent's (TextView) previous position in window private int mLastParentX, mLastParentY; @@ -4612,6 +4613,11 @@ public class Editor { // when magnifier is used. private float mTextViewScaleX; private float mTextViewScaleY; + /** + * The vertical distance in pixels from finger to the cursor Y while dragging. + * See {@link Editor.InsertionPointCursorController#getLineDuringDrag}. + */ + private final int mIdealFingerToCursorOffset; private HandleView(Drawable drawableLtr, Drawable drawableRtl, final int id) { super(mTextView.getContext()); @@ -4633,12 +4639,17 @@ public class Editor { final int handleHeight = getPreferredHeight(); mTouchOffsetY = -0.3f * handleHeight; mIdealVerticalOffset = 0.7f * handleHeight; + mIdealFingerToCursorOffset = (int)(mIdealVerticalOffset - mTouchOffsetY); } public float getIdealVerticalOffset() { return mIdealVerticalOffset; } + final int getIdealFingerToCursorOffset() { + return mIdealFingerToCursorOffset; + } + void setDrawables(final Drawable drawableLtr, final Drawable drawableRtl) { mDrawableLtr = drawableLtr; mDrawableRtl = drawableRtl; @@ -6123,36 +6134,34 @@ public class Editor { */ private int getLineDuringDrag(MotionEvent event) { final Layout layout = mTextView.getLayout(); - if (mTouchState.isOnHandle()) { - // The drag was initiated from the handle, so no need to apply the snap logic. See - // InsertionHandleView.touchThrough(). + if (mPrevLineDuringDrag == UNSET_LINE) { return getCurrentLineAdjustedForSlop(layout, mPrevLineDuringDrag, event.getY()); } + // In case of touch through on handle (when isOnHandle() returns true), event.getY() + // returns the midpoint of the cursor vertical bar, while event.getRawY() returns the + // finger location on the screen. See {@link InsertionHandleView#touchThrough}. + final float fingerY = mTouchState.isOnHandle() + ? event.getRawY() - mTextView.getLocationOnScreen()[1] + : event.getY(); + final float cursorY = fingerY - getHandle().getIdealFingerToCursorOffset(); + int line = getCurrentLineAdjustedForSlop(layout, mPrevLineDuringDrag, cursorY); if (mIsTouchSnappedToHandleDuringDrag) { - float cursorY = event.getY() - getHandle().getIdealVerticalOffset(); - return getCurrentLineAdjustedForSlop(layout, mPrevLineDuringDrag, cursorY); - } - int line = getCurrentLineAdjustedForSlop(layout, mPrevLineDuringDrag, event.getY()); - if (mPrevLineDuringDrag == UNSET_LINE || line <= mPrevLineDuringDrag) { - // User's finger is on the same line or moving up; continue positioning the cursor - // directly at the touch location. + // Just returns the line hit by cursor Y when already snapped. return line; } - // User's finger is moving downwards; delay jumping to the lower line to allow the - // touch to move to the handle. - float cursorY = event.getY() - getHandle().getIdealVerticalOffset(); - line = getCurrentLineAdjustedForSlop(layout, mPrevLineDuringDrag, cursorY); if (line < mPrevLineDuringDrag) { - return mPrevLineDuringDrag; + // The cursor Y aims too high & not yet snapped, check the finger Y. + // If finger Y is moving downwards, don't jump to lower line (until snap). + // If finger Y is moving upwards, can jump to upper line. + return Math.min(mPrevLineDuringDrag, + getCurrentLineAdjustedForSlop(layout, mPrevLineDuringDrag, fingerY)); } - // User's finger is now over the handle, at the ideal offset from the cursor. From now - // on, position the cursor higher up from the actual touch location so that the user's - // finger stays "snapped" to the handle. This provides better visibility of the text. + // The cursor Y aims not too high, so snap! mIsTouchSnappedToHandleDuringDrag = true; if (TextView.DEBUG_CURSOR) { logCursor("InsertionPointCursorController", - "snapped touch to handle: eventY=%d, cursorY=%d, mLastLine=%d, line=%d", - (int) event.getY(), (int) cursorY, mPrevLineDuringDrag, line); + "snapped touch to handle: fingerY=%d, cursorY=%d, mLastLine=%d, line=%d", + (int) fingerY, (int) cursorY, mPrevLineDuringDrag, line); } return line; } @@ -6252,7 +6261,7 @@ public class Editor { } } - private InsertionHandleView getHandle() { + public InsertionHandleView getHandle() { if (mHandle == null) { loadHandleDrawables(false /* overwrite */); mHandle = new InsertionHandleView(mSelectHandleCenter); diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java index 1daa5052a565..79b34c05bf87 100644 --- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java +++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java @@ -28,8 +28,6 @@ public final class ShortcutConstants { private ShortcutConstants() {} public static final char SERVICES_SEPARATOR = ':'; - public static final float DISABLED_ALPHA = 0.5f; - public static final float ENABLED_ALPHA = 1.0f; /** * Annotation for different user shortcut type UI type. @@ -80,6 +78,21 @@ public final class ShortcutConstants { } /** + * Annotation for different shortcut target. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TargetType.ACCESSIBILITY_SERVICE, + TargetType.ACCESSIBILITY_ACTIVITY, + TargetType.WHITE_LISTING, + }) + public @interface TargetType { + int ACCESSIBILITY_SERVICE = 0; + int ACCESSIBILITY_ACTIVITY = 1; + int WHITE_LISTING = 2; + } + + /** * Annotation for different shortcut menu mode. * * {@code LAUNCH} for clicking list item to trigger the service callback. diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java index 7df712fbcf7b..717e78078b1c 100644 --- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java +++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java @@ -39,6 +39,33 @@ public final class ShortcutUtils { new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR); /** + * Opts in component name into colon-separated {@link UserShortcutType} + * key's string in Settings. + * + * @param context The current context. + * @param shortcutType The preferred shortcut type user selected. + * @param componentId The component id that need to be opted out from Settings. + */ + public static void optInValueToSettings(Context context, @UserShortcutType int shortcutType, + String componentId) { + final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR)); + final String targetKey = convertToKey(shortcutType); + final String targetString = Settings.Secure.getString(context.getContentResolver(), + targetKey); + + if (hasValueInSettings(context, shortcutType, componentId)) { + return; + } + + if (!TextUtils.isEmpty(targetString)) { + joiner.add(targetString); + } + joiner.add(componentId); + + Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString()); + } + + /** * Opts out component name into colon-separated {@code shortcutType} key's string in Settings. * * @param context The current context. diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java index 852deb208ea6..c40864131a2e 100644 --- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java +++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java @@ -23,10 +23,10 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityServiceFragmentType; -import static com.android.internal.accessibility.common.ShortcutConstants.DISABLED_ALPHA; -import static com.android.internal.accessibility.common.ShortcutConstants.ENABLED_ALPHA; import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode; +import static com.android.internal.accessibility.common.ShortcutConstants.TargetType; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.COMPONENT_ID; import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.FRAGMENT_TYPE; import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.ICON_ID; @@ -36,6 +36,7 @@ import static com.android.internal.accessibility.util.AccessibilityUtils.getAcce import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState; import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType; import static com.android.internal.accessibility.util.ShortcutUtils.hasValuesInSettings; +import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings; import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings; import static com.android.internal.util.Preconditions.checkArgument; @@ -50,11 +51,12 @@ import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Bundle; +import android.os.storage.StorageManager; import android.provider.Settings; +import android.text.BidiFormatter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -62,28 +64,33 @@ import android.view.Window; import android.view.accessibility.AccessibilityManager; import android.widget.AdapterView; import android.widget.BaseAdapter; -import android.widget.FrameLayout; +import android.widget.Button; +import android.widget.CheckBox; import android.widget.ImageView; import android.widget.Switch; import android.widget.TextView; +import android.widget.Toast; import com.android.internal.R; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; /** * Activity used to display and persist a service or feature target for the Accessibility button. */ public class AccessibilityButtonChooserActivity extends Activity { @ShortcutType - private int mShortcutType; + private static int sShortcutType; @UserShortcutType private int mShortcutUserType; private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>(); private AlertDialog mAlertDialog; + private AlertDialog mEnableDialog; private TargetAdapter mTargetAdapter; + private AccessibilityButtonTarget mCurrentCheckedTarget; private static final String[][] WHITE_LISTING_FEATURES = { { @@ -118,19 +125,19 @@ public class AccessibilityButtonChooserActivity extends Activity { requestWindowFeature(Window.FEATURE_NO_TITLE); } - mShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, + sShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, /* unexpectedShortcutType */ -1); - final boolean existInShortcutType = (mShortcutType == ACCESSIBILITY_BUTTON) - || (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY); - checkArgument(existInShortcutType, "Unexpected shortcut type: " + mShortcutType); + final boolean existInShortcutType = (sShortcutType == ACCESSIBILITY_BUTTON) + || (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY); + checkArgument(existInShortcutType, "Unexpected shortcut type: " + sShortcutType); - mShortcutUserType = convertToUserType(mShortcutType); + mShortcutUserType = convertToUserType(sShortcutType); - mTargets.addAll(getServiceTargets(this, mShortcutType)); + mTargets.addAll(getServiceTargets(this, sShortcutType)); final String selectDialogTitle = getString(R.string.accessibility_select_shortcut_menu_title); - mTargetAdapter = new TargetAdapter(mTargets, mShortcutType); + mTargetAdapter = new TargetAdapter(mTargets); mAlertDialog = new AlertDialog.Builder(this) .setTitle(selectDialogTitle) .setAdapter(mTargetAdapter, /* listener= */ null) @@ -151,15 +158,21 @@ public class AccessibilityButtonChooserActivity extends Activity { private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context, @ShortcutType int shortcutType) { + final List<AccessibilityButtonTarget> targets = getInstalledServiceTargets(context); + final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); + final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType); + targets.removeIf(target -> !requiredTargets.contains(target.getId())); + + return targets; + } + + private static List<AccessibilityButtonTarget> getInstalledServiceTargets( + @NonNull Context context) { final List<AccessibilityButtonTarget> targets = new ArrayList<>(); targets.addAll(getAccessibilityServiceTargets(context)); targets.addAll(getAccessibilityActivityTargets(context)); targets.addAll(getWhiteListingServiceTargets(context)); - final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); - final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType); - targets.removeIf(target -> !requiredTargets.contains(target.getId())); - return targets; } @@ -174,6 +187,14 @@ public class AccessibilityButtonChooserActivity extends Activity { final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size()); for (AccessibilityServiceInfo info : installedServices) { + final int targetSdk = + info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion; + final boolean hasRequestAccessibilityButtonFlag = + (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; + if ((targetSdk < Build.VERSION_CODES.R) && !hasRequestAccessibilityButtonFlag + && (sShortcutType == ACCESSIBILITY_BUTTON)) { + continue; + } targets.add(new AccessibilityButtonTarget(context, info)); } @@ -249,35 +270,31 @@ public class AccessibilityButtonChooserActivity extends Activity { } } - private void disableService(String componentId) { + private void setServiceEnabled(String componentId, boolean enabled) { if (isWhiteListingService(componentId)) { - setWhiteListingServiceEnabled(componentId, /* settingsValueOff */ 0); + setWhiteListingServiceEnabled(componentId, + enabled ? /* settingsValueOn */ 1 : /* settingsValueOff */ 0); } else { final ComponentName componentName = ComponentName.unflattenFromString(componentId); - setAccessibilityServiceState(this, componentName, /* enabled= */ false); + setAccessibilityServiceState(this, componentName, enabled); } } private static class ViewHolder { View mItemView; + CheckBox mCheckBox; ImageView mIconView; TextView mLabelView; - FrameLayout mItemContainer; - ImageView mActionViewItem; Switch mSwitchItem; } private static class TargetAdapter extends BaseAdapter { @ShortcutMenuMode private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH; - @ShortcutType - private int mShortcutButtonType; private List<AccessibilityButtonTarget> mButtonTargets; - TargetAdapter(List<AccessibilityButtonTarget> targets, - @ShortcutType int shortcutButtonType) { + TargetAdapter(List<AccessibilityButtonTarget> targets) { this.mButtonTargets = targets; - this.mShortcutButtonType = shortcutButtonType; } void setShortcutMenuMode(@ShortcutMenuMode int shortcutMenuMode) { @@ -314,13 +331,11 @@ public class AccessibilityButtonChooserActivity extends Activity { false); holder = new ViewHolder(); holder.mItemView = convertView; + holder.mCheckBox = convertView.findViewById( + R.id.accessibility_button_target_checkbox); holder.mIconView = convertView.findViewById(R.id.accessibility_button_target_icon); holder.mLabelView = convertView.findViewById( R.id.accessibility_button_target_label); - holder.mItemContainer = convertView.findViewById( - R.id.accessibility_button_target_item_container); - holder.mActionViewItem = convertView.findViewById( - R.id.accessibility_button_target_view_item); holder.mSwitchItem = convertView.findViewById( R.id.accessibility_button_target_switch_item); convertView.setTag(holder); @@ -329,9 +344,6 @@ public class AccessibilityButtonChooserActivity extends Activity { } final AccessibilityButtonTarget target = mButtonTargets.get(position); - holder.mIconView.setImageDrawable(target.getDrawable()); - holder.mLabelView.setText(target.getLabel()); - updateActionItem(context, holder, target); return convertView; @@ -342,58 +354,42 @@ public class AccessibilityButtonChooserActivity extends Activity { switch (target.getFragmentType()) { case AccessibilityServiceFragmentType.LEGACY: - updateLegacyActionItemVisibility(context, holder); + updateLegacyActionItemVisibility(holder, target); break; case AccessibilityServiceFragmentType.INVISIBLE: - updateInvisibleActionItemVisibility(context, holder); + updateInvisibleActionItemVisibility(holder, target); break; case AccessibilityServiceFragmentType.INTUITIVE: updateIntuitiveActionItemVisibility(context, holder, target); break; case AccessibilityServiceFragmentType.BOUNCE: - updateBounceActionItemVisibility(context, holder); + updateBounceActionItemVisibility(holder, target); break; default: throw new IllegalStateException("Unexpected fragment type"); } } - private void updateLegacyActionItemVisibility(@NonNull Context context, - @NonNull ViewHolder holder) { + private void updateLegacyActionItemVisibility(@NonNull ViewHolder holder, + AccessibilityButtonTarget target) { final boolean isLaunchMenuMode = (mShortcutMenuMode == ShortcutMenuMode.LAUNCH); - final boolean isHardwareButtonTriggered = - (mShortcutButtonType == ACCESSIBILITY_SHORTCUT_KEY); - final boolean enabledState = (isLaunchMenuMode || isHardwareButtonTriggered); - final ColorMatrix grayScaleMatrix = new ColorMatrix(); - grayScaleMatrix.setSaturation(/* grayScale */0); - - holder.mIconView.setColorFilter(enabledState - ? null : new ColorMatrixColorFilter(grayScaleMatrix)); - holder.mIconView.setAlpha(enabledState - ? ENABLED_ALPHA : DISABLED_ALPHA); - holder.mLabelView.setEnabled(enabledState); - holder.mActionViewItem.setEnabled(enabledState); - holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); - holder.mActionViewItem.setVisibility(View.VISIBLE); + + holder.mCheckBox.setChecked(!isLaunchMenuMode && target.isChecked()); + holder.mCheckBox.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE); + holder.mIconView.setImageDrawable(target.getDrawable()); + holder.mLabelView.setText(target.getLabel()); holder.mSwitchItem.setVisibility(View.GONE); - holder.mItemContainer.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE); - holder.mItemView.setEnabled(enabledState); - holder.mItemView.setClickable(!enabledState); - } - - private void updateInvisibleActionItemVisibility(@NonNull Context context, - @NonNull ViewHolder holder) { - holder.mIconView.setColorFilter(null); - holder.mIconView.setAlpha(ENABLED_ALPHA); - holder.mLabelView.setEnabled(true); - holder.mActionViewItem.setEnabled(true); - holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); - holder.mActionViewItem.setVisibility(View.VISIBLE); + } + + private void updateInvisibleActionItemVisibility(@NonNull ViewHolder holder, + AccessibilityButtonTarget target) { + final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT); + + holder.mCheckBox.setChecked(isEditMenuMode && target.isChecked()); + holder.mCheckBox.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); + holder.mIconView.setImageDrawable(target.getDrawable()); + holder.mLabelView.setText(target.getLabel()); holder.mSwitchItem.setVisibility(View.GONE); - holder.mItemContainer.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT) - ? View.VISIBLE : View.GONE); - holder.mItemView.setEnabled(true); - holder.mItemView.setClickable(false); } private void updateIntuitiveActionItemVisibility(@NonNull Context context, @@ -403,37 +399,31 @@ public class AccessibilityButtonChooserActivity extends Activity { ? isWhiteListingServiceEnabled(context, target) : isAccessibilityServiceEnabled(context, target); - holder.mIconView.setColorFilter(null); - holder.mIconView.setAlpha(ENABLED_ALPHA); - holder.mLabelView.setEnabled(true); - holder.mActionViewItem.setEnabled(true); - holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); - holder.mActionViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); + holder.mCheckBox.setChecked(isEditMenuMode && target.isChecked()); + holder.mCheckBox.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); + holder.mIconView.setImageDrawable(target.getDrawable()); + holder.mLabelView.setText(target.getLabel()); holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE); holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled); - holder.mItemContainer.setVisibility(View.VISIBLE); - holder.mItemView.setEnabled(true); - holder.mItemView.setClickable(false); - } - - private void updateBounceActionItemVisibility(@NonNull Context context, - @NonNull ViewHolder holder) { - holder.mIconView.setColorFilter(null); - holder.mIconView.setAlpha(ENABLED_ALPHA); - holder.mLabelView.setEnabled(true); - holder.mActionViewItem.setEnabled(true); - holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); - holder.mActionViewItem.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT) - ? View.VISIBLE : View.GONE); + } + + private void updateBounceActionItemVisibility(@NonNull ViewHolder holder, + AccessibilityButtonTarget target) { + final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT); + + holder.mCheckBox.setChecked(isEditMenuMode && target.isChecked()); + holder.mCheckBox.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); + holder.mIconView.setImageDrawable(target.getDrawable()); + holder.mLabelView.setText(target.getLabel()); holder.mSwitchItem.setVisibility(View.GONE); - holder.mItemContainer.setVisibility(View.VISIBLE); - holder.mItemView.setEnabled(true); - holder.mItemView.setClickable(false); } } private static class AccessibilityButtonTarget { private String mId; + @TargetType + private int mType; + private boolean mChecked; private CharSequence mLabel; private Drawable mDrawable; @AccessibilityServiceFragmentType @@ -442,6 +432,8 @@ public class AccessibilityButtonChooserActivity extends Activity { AccessibilityButtonTarget(@NonNull Context context, @NonNull AccessibilityServiceInfo serviceInfo) { this.mId = serviceInfo.getComponentName().flattenToString(); + this.mType = TargetType.ACCESSIBILITY_SERVICE; + this.mChecked = isTargetShortcutUsed(context, mId); this.mLabel = serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()); this.mDrawable = serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()); this.mFragmentType = getAccessibilityServiceFragmentType(serviceInfo); @@ -450,6 +442,8 @@ public class AccessibilityButtonChooserActivity extends Activity { AccessibilityButtonTarget(@NonNull Context context, @NonNull AccessibilityShortcutInfo shortcutInfo) { this.mId = shortcutInfo.getComponentName().flattenToString(); + this.mType = TargetType.ACCESSIBILITY_ACTIVITY; + this.mChecked = isTargetShortcutUsed(context, mId); this.mLabel = shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager()); this.mDrawable = shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager()); this.mFragmentType = AccessibilityServiceFragmentType.BOUNCE; @@ -458,15 +452,29 @@ public class AccessibilityButtonChooserActivity extends Activity { AccessibilityButtonTarget(Context context, @NonNull String id, int labelResId, int iconRes, @AccessibilityServiceFragmentType int fragmentType) { this.mId = id; + this.mType = TargetType.WHITE_LISTING; + this.mChecked = isTargetShortcutUsed(context, mId); this.mLabel = context.getText(labelResId); this.mDrawable = context.getDrawable(iconRes); this.mFragmentType = fragmentType; } + public void setChecked(boolean checked) { + mChecked = checked; + } + public String getId() { return mId; } + public int getType() { + return mType; + } + + public boolean isChecked() { + return mChecked; + } + public CharSequence getLabel() { return mLabel; } @@ -519,19 +527,19 @@ public class AccessibilityButtonChooserActivity extends Activity { } private void onLegacyTargetSelected(AccessibilityButtonTarget target) { - if (mShortcutType == ACCESSIBILITY_BUTTON) { + if (sShortcutType == ACCESSIBILITY_BUTTON) { final AccessibilityManager ams = getSystemService(AccessibilityManager.class); ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId()); - } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + } else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { switchServiceState(target); } } private void onInvisibleTargetSelected(AccessibilityButtonTarget target) { final AccessibilityManager ams = getSystemService(AccessibilityManager.class); - if (mShortcutType == ACCESSIBILITY_BUTTON) { + if (sShortcutType == ACCESSIBILITY_BUTTON) { ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId()); - } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + } else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { ams.performAccessibilityShortcut(target.getId()); } } @@ -542,9 +550,9 @@ public class AccessibilityButtonChooserActivity extends Activity { private void onBounceTargetSelected(AccessibilityButtonTarget target) { final AccessibilityManager ams = getSystemService(AccessibilityManager.class); - if (mShortcutType == ACCESSIBILITY_BUTTON) { + if (sShortcutType == ACCESSIBILITY_BUTTON) { ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId()); - } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + } else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { ams.performAccessibilityShortcut(target.getId()); } } @@ -565,66 +573,101 @@ public class AccessibilityButtonChooserActivity extends Activity { } } - private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) { - final AccessibilityButtonTarget target = mTargets.get(position); - final String componentId = target.getId(); + private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) { + mCurrentCheckedTarget = mTargets.get(position); + if ((mCurrentCheckedTarget.getType() == TargetType.ACCESSIBILITY_SERVICE) + && !mCurrentCheckedTarget.isChecked()) { + mEnableDialog = new AlertDialog.Builder(this) + .setView(createEnableDialogContentView(this, mCurrentCheckedTarget, + this::onPermissionAllowButtonClicked, + this::onPermissionDenyButtonClicked)) + .create(); + mEnableDialog.show(); + return; + } + + onTargetChecked(mCurrentCheckedTarget, !mCurrentCheckedTarget.isChecked()); + } + + private void onTargetChecked(AccessibilityButtonTarget target, boolean checked) { switch (target.getFragmentType()) { case AccessibilityServiceFragmentType.LEGACY: - onLegacyTargetDeleted(position, componentId); + onLegacyTargetChecked(checked); break; case AccessibilityServiceFragmentType.INVISIBLE: - onInvisibleTargetDeleted(position, componentId); + onInvisibleTargetChecked(checked); break; case AccessibilityServiceFragmentType.INTUITIVE: - onIntuitiveTargetDeleted(position, componentId); + onIntuitiveTargetChecked(checked); break; case AccessibilityServiceFragmentType.BOUNCE: - onBounceTargetDeleted(position, componentId); + onBounceTargetChecked(checked); break; default: throw new IllegalStateException("Unexpected fragment type"); } - - if (mTargets.isEmpty()) { - mAlertDialog.dismiss(); - } } - private void onLegacyTargetDeleted(int position, String componentId) { - if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { - optOutValueFromSettings(this, mShortcutUserType, componentId); - - mTargets.remove(position); - mTargetAdapter.notifyDataSetChanged(); + private void onLegacyTargetChecked(boolean checked) { + if (sShortcutType == ACCESSIBILITY_BUTTON) { + setServiceEnabled(mCurrentCheckedTarget.getId(), checked); + if (!checked) { + optOutValueFromSettings(this, HARDWARE, mCurrentCheckedTarget.getId()); + final String warningText = + getString(R.string.accessibility_uncheck_legacy_item_warning, + mCurrentCheckedTarget.getLabel()); + Toast.makeText(this, warningText, Toast.LENGTH_SHORT).show(); + } + } else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + updateValueToSettings(mCurrentCheckedTarget.getId(), checked); + } else { + throw new IllegalStateException("Unexpected shortcut type"); } - } - private void onInvisibleTargetDeleted(int position, String componentId) { - optOutValueFromSettings(this, mShortcutUserType, componentId); + mCurrentCheckedTarget.setChecked(checked); + mTargetAdapter.notifyDataSetChanged(); + } - final int shortcutTypes = UserShortcutType.SOFTWARE | UserShortcutType.HARDWARE; - if (!hasValuesInSettings(this, shortcutTypes, componentId)) { - disableService(componentId); + private void onInvisibleTargetChecked(boolean checked) { + final int shortcutTypes = UserShortcutType.SOFTWARE | HARDWARE; + if (!hasValuesInSettings(this, shortcutTypes, mCurrentCheckedTarget.getId())) { + setServiceEnabled(mCurrentCheckedTarget.getId(), checked); } - mTargets.remove(position); + updateValueToSettings(mCurrentCheckedTarget.getId(), checked); + mCurrentCheckedTarget.setChecked(checked); mTargetAdapter.notifyDataSetChanged(); } - private void onIntuitiveTargetDeleted(int position, String componentId) { - optOutValueFromSettings(this, mShortcutUserType, componentId); - mTargets.remove(position); + private void onIntuitiveTargetChecked(boolean checked) { + updateValueToSettings(mCurrentCheckedTarget.getId(), checked); + mCurrentCheckedTarget.setChecked(checked); mTargetAdapter.notifyDataSetChanged(); } - private void onBounceTargetDeleted(int position, String componentId) { - optOutValueFromSettings(this, mShortcutUserType, componentId); - mTargets.remove(position); + private void onBounceTargetChecked(boolean checked) { + updateValueToSettings(mCurrentCheckedTarget.getId(), checked); + mCurrentCheckedTarget.setChecked(checked); mTargetAdapter.notifyDataSetChanged(); } - private void onCancelButtonClicked() { + private void updateValueToSettings(String componentId, boolean checked) { + if (checked) { + optInValueToSettings(this, mShortcutUserType, componentId); + } else { + optOutValueFromSettings(this, mShortcutUserType, componentId); + } + } + + private void onDoneButtonClicked() { + mTargets.clear(); + mTargets.addAll(getServiceTargets(this, sShortcutType)); + if (mTargets.isEmpty()) { + mAlertDialog.dismiss(); + return; + } + mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH); mTargetAdapter.notifyDataSetChanged(); @@ -635,11 +678,13 @@ public class AccessibilityButtonChooserActivity extends Activity { } private void onEditButtonClicked() { + mTargets.clear(); + mTargets.addAll(getInstalledServiceTargets(this)); mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT); mTargetAdapter.notifyDataSetChanged(); mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText( - getString(R.string.cancel_accessibility_shortcut_menu_button)); + getString(R.string.done_accessibility_shortcut_menu_button)); updateDialogListeners(); } @@ -649,15 +694,78 @@ public class AccessibilityButtonChooserActivity extends Activity { (mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT); final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title; final int editDialogTitleId = - (mShortcutType == ACCESSIBILITY_BUTTON) + (sShortcutType == ACCESSIBILITY_BUTTON) ? R.string.accessibility_edit_shortcut_menu_button_title : R.string.accessibility_edit_shortcut_menu_volume_title; mAlertDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId)); - mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener( - isEditMenuMode ? view -> onCancelButtonClicked() : view -> onEditButtonClicked()); + isEditMenuMode ? view -> onDoneButtonClicked() : view -> onEditButtonClicked()); mAlertDialog.getListView().setOnItemClickListener( - isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected); + isEditMenuMode ? this::onTargetChecked : this::onTargetSelected); + } + + private static boolean isTargetShortcutUsed(@NonNull Context context, String id) { + final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); + final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(sShortcutType); + return requiredTargets.contains(id); + } + + private void onPermissionAllowButtonClicked(View view) { + if (mCurrentCheckedTarget.getFragmentType() != AccessibilityServiceFragmentType.LEGACY) { + updateValueToSettings(mCurrentCheckedTarget.getId(), /* checked= */ true); + } + onTargetChecked(mCurrentCheckedTarget, /* checked= */ true); + mEnableDialog.dismiss(); + } + + private void onPermissionDenyButtonClicked(View view) { + mEnableDialog.dismiss(); + } + + private static View createEnableDialogContentView(Context context, + AccessibilityButtonTarget target, View.OnClickListener allowListener, + View.OnClickListener denyListener) { + final LayoutInflater inflater = (LayoutInflater) context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + final View content = inflater.inflate( + R.layout.accessibility_enable_service_encryption_warning, /* root= */ null); + + final TextView encryptionWarningView = (TextView) content.findViewById( + R.id.accessibility_encryption_warning); + if (StorageManager.isNonDefaultBlockEncrypted()) { + final String text = context.getString( + R.string.accessibility_enable_service_encryption_warning, + getServiceName(context, target.getLabel())); + encryptionWarningView.setText(text); + encryptionWarningView.setVisibility(View.VISIBLE); + } else { + encryptionWarningView.setVisibility(View.GONE); + } + + final ImageView permissionDialogIcon = content.findViewById( + R.id.accessibility_permissionDialog_icon); + permissionDialogIcon.setImageDrawable(target.getDrawable()); + + final TextView permissionDialogTitle = content.findViewById( + R.id.accessibility_permissionDialog_title); + permissionDialogTitle.setText(context.getString(R.string.accessibility_enable_service_title, + getServiceName(context, target.getLabel()))); + + final Button permissionAllowButton = content.findViewById( + R.id.accessibility_permission_enable_allow_button); + final Button permissionDenyButton = content.findViewById( + R.id.accessibility_permission_enable_deny_button); + permissionAllowButton.setOnClickListener(allowListener); + permissionDenyButton.setOnClickListener(denyListener); + + return content; + } + + // Gets the service name and bidi wrap it to protect from bidi side effects. + private static CharSequence getServiceName(Context context, CharSequence label) { + final Locale locale = context.getResources().getConfiguration().getLocales().get(0); + return BidiFormatter.getInstance(locale).unicodeWrap(label); } } diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java index bed41b37ac81..9a78ad2011cf 100644 --- a/core/java/com/android/internal/compat/OverrideAllowedState.java +++ b/core/java/com/android/internal/compat/OverrideAllowedState.java @@ -50,7 +50,7 @@ public final class OverrideAllowedState implements Parcelable { public static final int DISABLED_NOT_DEBUGGABLE = 1; /** * Change cannot be overridden, due to the build being non-debuggable and the change being - * non-targetSdk. + * enabled regardless of targetSdk. */ public static final int DISABLED_NON_TARGET_SDK = 2; /** @@ -159,4 +159,28 @@ public final class OverrideAllowedState implements Parcelable { && appTargetSdk == otherState.appTargetSdk && changeIdTargetSdk == otherState.changeIdTargetSdk; } + + private String stateName() { + switch (state) { + case ALLOWED: + return "ALLOWED"; + case DISABLED_NOT_DEBUGGABLE: + return "DISABLED_NOT_DEBUGGABLE"; + case DISABLED_NON_TARGET_SDK: + return "DISABLED_NON_TARGET_SDK"; + case DISABLED_TARGET_SDK_TOO_HIGH: + return "DISABLED_TARGET_SDK_TOO_HIGH"; + case PACKAGE_DOES_NOT_EXIST: + return "PACKAGE_DOES_NOT_EXIST"; + case LOGGING_ONLY_CHANGE: + return "LOGGING_ONLY_CHANGE"; + } + return "UNKNOWN"; + } + + @Override + public String toString() { + return "OverrideAllowedState(state=" + stateName() + "; appTargetSdk=" + appTargetSdk + + "; changeIdTargetSdk=" + changeIdTargetSdk + ")"; + } } diff --git a/core/res/res/drawable/ic_pan_tool.xml b/core/res/res/drawable/ic_pan_tool.xml new file mode 100644 index 000000000000..c1a8549b2141 --- /dev/null +++ b/core/res/res/drawable/ic_pan_tool.xml @@ -0,0 +1,26 @@ +<!-- + 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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:pathData="M23,5.5V20c0,2.2 -1.8,4 -4,4h-7.3c-1.08,0 -2.1,-0.43 -2.85,-1.19L1,14.83c0,0 1.26,-1.23 1.3,-1.25c0.22,-0.19 0.49,-0.29 0.79,-0.29c0.22,0 0.42,0.06 0.6,0.16C3.73,13.46 8,15.91 8,15.91V4c0,-0.83 0.67,-1.5 1.5,-1.5S11,3.17 11,4v7h1V1.5C12,0.67 12.67,0 13.5,0S15,0.67 15,1.5V11h1V2.5C16,1.67 16.67,1 17.5,1S19,1.67 19,2.5V11h1V5.5C20,4.67 20.67,4 21.5,4S23,4.67 23,5.5z" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/core/res/res/drawable/ic_delete_item.xml b/core/res/res/drawable/ic_visibility.xml index 8a398a44635e..195624156999 100644 --- a/core/res/res/drawable/ic_delete_item.xml +++ b/core/res/res/drawable/ic_visibility.xml @@ -12,7 +12,7 @@ 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. - --> +--> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" @@ -21,6 +21,6 @@ android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path - android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" + android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" android:fillColor="#FFFFFF"/> </vector> diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml index c01766b2c748..b7dd892fd161 100644 --- a/core/res/res/layout/accessibility_button_chooser_item.xml +++ b/core/res/res/layout/accessibility_button_chooser_item.xml @@ -21,10 +21,17 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:paddingStart="16dp" - android:paddingEnd="16dp" - android:paddingTop="12dp" - android:paddingBottom="12dp"> + android:padding="16dp"> + + <CheckBox + android:id="@+id/accessibility_button_target_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingStart="16dp" + android:background="@null" + android:clickable="false" + android:focusable="false" + android:visibility="gone"/> <ImageView android:id="@+id/accessibility_button_target_icon" @@ -36,31 +43,18 @@ android:id="@+id/accessibility_button_target_label" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="14dp" + android:layout_marginStart="16dp" android:layout_weight="1" android:textSize="20sp" android:textColor="?attr/textColorPrimary" android:fontFamily="sans-serif-medium"/> - <FrameLayout - android:id="@+id/accessibility_button_target_item_container" + <Switch + android:id="@+id/accessibility_button_target_switch_item" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:minWidth="56dp"> - - <ImageView - android:id="@+id/accessibility_button_target_view_item" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center"/> - - <Switch android:id="@+id/accessibility_button_target_switch_item" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:background="@null" - android:clickable="false" - android:focusable="false"/> - </FrameLayout> + android:background="@null" + android:clickable="false" + android:focusable="false"/> </LinearLayout> diff --git a/core/res/res/layout/accessibility_enable_service_encryption_warning.xml b/core/res/res/layout/accessibility_enable_service_encryption_warning.xml new file mode 100644 index 000000000000..400051660592 --- /dev/null +++ b/core/res/res/layout/accessibility_enable_service_encryption_warning.xml @@ -0,0 +1,175 @@ +<?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. +--> + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textDirection="locale" + android:scrollbarStyle="outsideOverlay"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="24dp" + android:paddingEnd="24dp" + android:gravity="center" + android:orientation="vertical"> + + <ImageView + android:id="@+id/accessibility_permissionDialog_icon" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" + android:scaleType="fitCenter"/> + + <TextView + android:id="@+id/accessibility_permissionDialog_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:textSize="20sp" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="google-sans-medium"/> + + <TextView + android:id="@+id/accessibility_encryption_warning" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="10dip" + android:textAlignment="viewStart" + android:textAppearance="?android:attr/textAppearanceMedium"/> + + <TextView + android:id="@+id/accessibility_permissionDialog_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginBottom="32dp" + android:text="@string/accessibility_service_warning_description" + android:textSize="16sp" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="sans-serif"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/accessibility_controlScreen_icon" + android:layout_width="18dp" + android:layout_height="18dp" + android:layout_marginRight="12dp" + android:src="@android:drawable/ic_visibility" + android:scaleType="fitCenter"/> + + <TextView + android:id="@+id/accessibility_controlScreen_title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/accessibility_service_screen_control_title" + android:textSize="16sp" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="sans-serif"/> + </LinearLayout> + + <TextView + android:id="@+id/accessibility_controlScreen_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="24dp" + android:paddingStart="30dp" + android:text="@string/accessibility_service_screen_control_description" + android:textSize="14sp" + android:textColor="?android:attr/textColorSecondary" + android:fontFamily="sans-serif"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/accessibility_performAction_icon" + android:layout_width="18dp" + android:layout_height="18dp" + android:layout_marginEnd="12dp" + android:src="@android:drawable/ic_pan_tool" + android:scaleType="fitCenter"/> + + <TextView + android:id="@+id/accessibility_performAction_title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/accessibility_service_action_perform_title" + android:textSize="16sp" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="sans-serif"/> + </LinearLayout> + + <TextView + android:id="@+id/accessibility_performAction_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="24dp" + android:paddingStart="30dp" + android:text="@string/accessibility_service_action_perform_description" + android:textSize="14sp" + android:textColor="?android:attr/textColorSecondary" + android:fontFamily="sans-serif" /> + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/chooser_row_divider"/> + + <Button + android:id="@+id/accessibility_permission_enable_allow_button" + android:layout_width="match_parent" + android:layout_height="56dp" + android:background="?android:attr/selectableItemBackground" + android:text="@string/accessibility_dialog_button_allow" + style="?attr/buttonBarPositiveButtonStyle"/> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/chooser_row_divider"/> + + <Button + android:id="@+id/accessibility_permission_enable_deny_button" + android:layout_width="match_parent" + android:layout_height="56dp" + android:background="?android:attr/selectableItemBackground" + android:text="@string/accessibility_dialog_button_deny" + style="?attr/buttonBarPositiveButtonStyle"/> + </LinearLayout> + +</ScrollView>
\ No newline at end of file diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index caec9edc7f1c..632c400d4d49 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1631,12 +1631,9 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Verhoog volume bo aanbevole vlak?\n\nOm lang tydperke teen hoë volume te luister, kan jou gehoor beskadig."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gebruik toeganklikheidkortpad?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Wanneer die kortpad aan is, sal \'n toeganklikheidkenmerk begin word as albei volumeknoppies 3 sekondes lank gedruk word."</string> - <!-- no translation found for accessibility_select_shortcut_menu_title (7310194076629867377) --> - <skip /> - <!-- no translation found for accessibility_edit_shortcut_menu_button_title (6096484087245145325) --> - <skip /> - <!-- no translation found for accessibility_edit_shortcut_menu_volume_title (4849108668454490699) --> - <skip /> + <string name="accessibility_select_shortcut_menu_title" msgid="7310194076629867377">"Tik op die toeganklikheidprogram wat jy wil gebruik"</string> + <string name="accessibility_edit_shortcut_menu_button_title" msgid="6096484087245145325">"Kies programme wat jy met toeganklikheidknoppie wil gebruik"</string> + <string name="accessibility_edit_shortcut_menu_volume_title" msgid="4849108668454490699">"Kies programme wat jy met die volumesleutelkortpad wil gebruik"</string> <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Wysig kortpaaie"</string> <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Kanselleer"</string> <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Skakel kortpad af"</string> @@ -2033,29 +2030,19 @@ <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> is in die BEPERK-groep geplaas"</string> <string name="resolver_personal_tab" msgid="2051260504014442073">"Persoonlik"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"Werk"</string> - <!-- no translation found for resolver_personal_tab_accessibility (5739524949153091224) --> - <skip /> - <!-- no translation found for resolver_work_tab_accessibility (4753168230363802734) --> - <skip /> + <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Persoonlike aansig"</string> + <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Werkaansig"</string> <string name="resolver_cant_share_with_work_apps" msgid="7539495559434146897">"Kan nie met werkprogramme deel nie"</string> <string name="resolver_cant_share_with_personal_apps" msgid="8020581735267157241">"Kan nie met persoonlike programme deel nie"</string> - <!-- no translation found for resolver_cant_share_cross_profile_explanation (5556640604460901386) --> - <skip /> - <!-- no translation found for resolver_cant_access_work_apps (375634344111233790) --> - <skip /> - <!-- no translation found for resolver_cant_access_work_apps_explanation (3958762224516867388) --> - <skip /> - <!-- no translation found for resolver_cant_access_personal_apps (1953215925406474177) --> - <skip /> - <!-- no translation found for resolver_cant_access_personal_apps_explanation (1725572276741281136) --> - <skip /> - <!-- no translation found for resolver_turn_on_work_apps_share (619263911204978175) --> - <skip /> - <!-- no translation found for resolver_turn_on_work_apps_view (3073389230905543680) --> - <skip /> + <string name="resolver_cant_share_cross_profile_explanation" msgid="5556640604460901386">"Jou IT-admin het deling tussen persoonlike en werkprofiele geblokkeer"</string> + <string name="resolver_cant_access_work_apps" msgid="375634344111233790">"Kan nie toegang tot werkprogramme kry nie"</string> + <string name="resolver_cant_access_work_apps_explanation" msgid="3958762224516867388">"Jou IT-admin laat jou nie toe om persoonlike inhoud in werkprogramme te bekyk nie"</string> + <string name="resolver_cant_access_personal_apps" msgid="1953215925406474177">"Kan nie toegang tot persoonlike programme kry nie"</string> + <string name="resolver_cant_access_personal_apps_explanation" msgid="1725572276741281136">"Jou IT-admin laat jou nie toe om werkinhoud in persoonlike programme te bekyk nie"</string> + <string name="resolver_turn_on_work_apps_share" msgid="619263911204978175">"Skakel werkprofiel aan om inhoud te deel"</string> + <string name="resolver_turn_on_work_apps_view" msgid="3073389230905543680">"Skakel werkprofiel aan om inhoud te bekyk"</string> <string name="resolver_no_apps_available" msgid="7710339903040989654">"Geen programme beskikbaar nie"</string> - <!-- no translation found for resolver_switch_on_work (2873009160846966379) --> - <skip /> + <string name="resolver_switch_on_work" msgid="2873009160846966379">"Skakel aan"</string> <string name="permlab_accessCallAudio" msgid="1682957511874097664">"Neem oudio in telefonie-oproepe op of speel dit"</string> <string name="permdesc_accessCallAudio" msgid="8448360894684277823">"Laat hierdie program, indien dit as die verstekbellerprogram aangewys is, toe om oudio in telefonie-oproepe op te neem of te speel."</string> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 63497aa692db..1a7f30159b68 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1631,12 +1631,9 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"推奨レベルを超えるまで音量を上げますか?\n\n大音量で長時間聞き続けると、聴力を損なう恐れがあります。"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ユーザー補助機能のショートカットの使用"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ショートカットが ON の場合、両方の音量ボタンを 3 秒ほど長押しするとユーザー補助機能が起動します。"</string> - <!-- no translation found for accessibility_select_shortcut_menu_title (7310194076629867377) --> - <skip /> - <!-- no translation found for accessibility_edit_shortcut_menu_button_title (6096484087245145325) --> - <skip /> - <!-- no translation found for accessibility_edit_shortcut_menu_volume_title (4849108668454490699) --> - <skip /> + <string name="accessibility_select_shortcut_menu_title" msgid="7310194076629867377">"使用するユーザー補助アプリをタップ"</string> + <string name="accessibility_edit_shortcut_menu_button_title" msgid="6096484087245145325">"ユーザー補助機能ボタンで使用できるアプリを選択"</string> + <string name="accessibility_edit_shortcut_menu_volume_title" msgid="4849108668454490699">"音量キーのショートカットで使用できるアプリを選択"</string> <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"ショートカットの編集"</string> <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"キャンセル"</string> <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"ショートカットを OFF にする"</string> @@ -2033,29 +2030,19 @@ <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> は RESTRICTED バケットに移動しました。"</string> <string name="resolver_personal_tab" msgid="2051260504014442073">"個人用"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"仕事用"</string> - <!-- no translation found for resolver_personal_tab_accessibility (5739524949153091224) --> - <skip /> - <!-- no translation found for resolver_work_tab_accessibility (4753168230363802734) --> - <skip /> + <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"個人用ビュー"</string> + <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"仕事用ビュー"</string> <string name="resolver_cant_share_with_work_apps" msgid="7539495559434146897">"仕事用アプリと共有できません"</string> <string name="resolver_cant_share_with_personal_apps" msgid="8020581735267157241">"個人用アプリと共有できません"</string> - <!-- no translation found for resolver_cant_share_cross_profile_explanation (5556640604460901386) --> - <skip /> - <!-- no translation found for resolver_cant_access_work_apps (375634344111233790) --> - <skip /> - <!-- no translation found for resolver_cant_access_work_apps_explanation (3958762224516867388) --> - <skip /> - <!-- no translation found for resolver_cant_access_personal_apps (1953215925406474177) --> - <skip /> - <!-- no translation found for resolver_cant_access_personal_apps_explanation (1725572276741281136) --> - <skip /> - <!-- no translation found for resolver_turn_on_work_apps_share (619263911204978175) --> - <skip /> - <!-- no translation found for resolver_turn_on_work_apps_view (3073389230905543680) --> - <skip /> + <string name="resolver_cant_share_cross_profile_explanation" msgid="5556640604460901386">"IT 管理者が個人用プロファイルと仕事用プロファイルの間の共有をブロックしました"</string> + <string name="resolver_cant_access_work_apps" msgid="375634344111233790">"仕事用アプリにアクセスできません"</string> + <string name="resolver_cant_access_work_apps_explanation" msgid="3958762224516867388">"IT 管理者は個人用コンテンツを仕事用アプリで表示することを許可していません"</string> + <string name="resolver_cant_access_personal_apps" msgid="1953215925406474177">"個人用アプリにアクセスできません"</string> + <string name="resolver_cant_access_personal_apps_explanation" msgid="1725572276741281136">"IT 管理者は仕事用コンテンツを個人用アプリで表示することを許可していません"</string> + <string name="resolver_turn_on_work_apps_share" msgid="619263911204978175">"コンテンツを共有するには、仕事用プロファイルをオンにしてください"</string> + <string name="resolver_turn_on_work_apps_view" msgid="3073389230905543680">"コンテンツを表示するには、仕事用プロファイルをオンにしてください"</string> <string name="resolver_no_apps_available" msgid="7710339903040989654">"利用できるアプリはありません"</string> - <!-- no translation found for resolver_switch_on_work (2873009160846966379) --> - <skip /> + <string name="resolver_switch_on_work" msgid="2873009160846966379">"オンにする"</string> <string name="permlab_accessCallAudio" msgid="1682957511874097664">"通話中に録音または音声の再生を行う"</string> <string name="permdesc_accessCallAudio" msgid="8448360894684277823">"デフォルトの電話アプリケーションとして割り当てられている場合、このアプリに通話中の録音または音声の再生を許可します。"</string> </resources> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 7b173054aaa3..4054a3abf108 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1631,12 +1631,9 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дууг санал болгосноос чанга болгож өсгөх үү?\n\nУрт хугацаанд чанга хөгжим сонсох нь таны сонсголыг муутгаж болно."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Хүртээмжийн товчлолыг ашиглах уу?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Товчлол асаалттай үед дууны түвшний хоёр товчлуурыг хамтад нь 3 секунд дарснаар хандалтын онцлогийг эхлүүлнэ."</string> - <!-- no translation found for accessibility_select_shortcut_menu_title (7310194076629867377) --> - <skip /> - <!-- no translation found for accessibility_edit_shortcut_menu_button_title (6096484087245145325) --> - <skip /> - <!-- no translation found for accessibility_edit_shortcut_menu_volume_title (4849108668454490699) --> - <skip /> + <string name="accessibility_select_shortcut_menu_title" msgid="7310194076629867377">"Ашиглахыг хүсэж буй хандалтын аппаа товших"</string> + <string name="accessibility_edit_shortcut_menu_button_title" msgid="6096484087245145325">"Хандалтын товчлуурын тусламжтай ашиглахыг хүсэж буй аппуудаа сонгох"</string> + <string name="accessibility_edit_shortcut_menu_volume_title" msgid="4849108668454490699">"Дууны түвшин тохируулах түлхүүрийн товчлолын тусламжтай ашиглахыг хүсэж буй аппуудаа сонгох"</string> <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Товчлолуудыг засах"</string> <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Болих"</string> <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Товчлолыг унтраах"</string> @@ -2033,29 +2030,19 @@ <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>-г ХЯЗГААРЛАСАН сагс руу орууллаа"</string> <string name="resolver_personal_tab" msgid="2051260504014442073">"Хувийн"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"Ажил"</string> - <!-- no translation found for resolver_personal_tab_accessibility (5739524949153091224) --> - <skip /> - <!-- no translation found for resolver_work_tab_accessibility (4753168230363802734) --> - <skip /> + <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Хувийн харагдах байдал"</string> + <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Ажлын харагдах байдал"</string> <string name="resolver_cant_share_with_work_apps" msgid="7539495559434146897">"Ажлын аппуудтай хуваалцах боломжгүй"</string> <string name="resolver_cant_share_with_personal_apps" msgid="8020581735267157241">"Хувийн аппуудтай хуваалцах боломжгүй"</string> - <!-- no translation found for resolver_cant_share_cross_profile_explanation (5556640604460901386) --> - <skip /> - <!-- no translation found for resolver_cant_access_work_apps (375634344111233790) --> - <skip /> - <!-- no translation found for resolver_cant_access_work_apps_explanation (3958762224516867388) --> - <skip /> - <!-- no translation found for resolver_cant_access_personal_apps (1953215925406474177) --> - <skip /> - <!-- no translation found for resolver_cant_access_personal_apps_explanation (1725572276741281136) --> - <skip /> - <!-- no translation found for resolver_turn_on_work_apps_share (619263911204978175) --> - <skip /> - <!-- no translation found for resolver_turn_on_work_apps_view (3073389230905543680) --> - <skip /> + <string name="resolver_cant_share_cross_profile_explanation" msgid="5556640604460901386">"Таны IT админ хувийн болон ажлын профайлуудын хооронд хуваалцахыг блоклосон"</string> + <string name="resolver_cant_access_work_apps" msgid="375634344111233790">"Ажлын аппуудад хандах боломжгүй байна"</string> + <string name="resolver_cant_access_work_apps_explanation" msgid="3958762224516867388">"Таны IT админ ажлын аппууд дахь хувийн контентыг харахыг танд зөвшөөрөхгүй байна"</string> + <string name="resolver_cant_access_personal_apps" msgid="1953215925406474177">"Хувийн аппуудад хандах боломжгүй байна"</string> + <string name="resolver_cant_access_personal_apps_explanation" msgid="1725572276741281136">"Таны IT админ хувийн аппууд дахь ажлын контентыг харахыг танд зөвшөөрөхгүй байна"</string> + <string name="resolver_turn_on_work_apps_share" msgid="619263911204978175">"Контентыг хуваалцахын тулд ажлын профайлыг асаана уу"</string> + <string name="resolver_turn_on_work_apps_view" msgid="3073389230905543680">"Контентыг харахын тулд ажлын профайлыг асаана уу"</string> <string name="resolver_no_apps_available" msgid="7710339903040989654">"Боломжтой апп алга байна"</string> - <!-- no translation found for resolver_switch_on_work (2873009160846966379) --> - <skip /> + <string name="resolver_switch_on_work" msgid="2873009160846966379">"Асаах"</string> <string name="permlab_accessCallAudio" msgid="1682957511874097664">"Утасны дуудлагын үеэр аудио бичих эсвэл тоглуулах"</string> <string name="permdesc_accessCallAudio" msgid="8448360894684277823">"Энэ аппыг залгагч өгөгдмөл аппликэйшн болгосон үед түүнд утасны дуудлагын үеэр аудио бичих эсвэл тоглуулахыг зөвшөөрдөг."</string> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index addcd81ae5c8..008c991a24ac 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4403,27 +4403,72 @@ accessibility feature. </string> + <!-- Title for a warning about security implications of enabling an accessibility + service. [CHAR LIMIT=NONE] --> + <string name="accessibility_enable_service_title">Allow + <xliff:g id="service" example="TalkBack">%1$s</xliff:g> to have full control of your + device?</string> + + <!-- Warning that the device data will not be encrypted with password or PIN if + enabling an accessibility service and there is a secure lock setup. [CHAR LIMIT=NONE] --> + <string name="accessibility_enable_service_encryption_warning">If you turn on + <xliff:g id="service" example="TalkBack">%1$s</xliff:g>, your device won’t use your screen + lock to enhance data encryption. + </string> + + <!-- Warning description that explains that it's appropriate for accessibility + services to have full control to help users with accessibility needs. [CHAR LIMIT=NONE] --> + <string name="accessibility_service_warning_description">Full control is appropriate for apps + that help you with accessibility needs, but not for most apps. + </string> + + <!-- Title for the screen control in accessibility dialog. [CHAR LIMIT=NONE] --> + <string name="accessibility_service_screen_control_title">View and control screen</string> + + <!-- Description for the screen control in accessibility dialog. [CHAR LIMIT=NONE] --> + <string name="accessibility_service_screen_control_description">It can read all content on the + screen and display content over other apps. + </string> + + <!-- Title for the action perform in accessibility dialog. [CHAR LIMIT=NONE] --> + <string name="accessibility_service_action_perform_title">View and perform actions</string> + + <!-- Description for the action perform in accessibility dialog. [CHAR LIMIT=NONE] --> + <string name="accessibility_service_action_perform_description">It can track your interactions + with an app or a hardware sensor, and interact with apps on your behalf. + </string> + + <!-- String for the allow button in accessibility permission dialog. [CHAR LIMIT=10] --> + <string name="accessibility_dialog_button_allow">Allow</string> + <!-- String for the deny button in accessibility permission dialog. [CHAR LIMIT=10] --> + <string name="accessibility_dialog_button_deny">Deny</string> + <!-- Title for accessibility select shortcut menu dialog. [CHAR LIMIT=100] --> - <string name="accessibility_select_shortcut_menu_title">Tap the accessibility app you want to use</string> + <string name="accessibility_select_shortcut_menu_title">Tap a feature to start using it:</string> <!-- Title for accessibility edit shortcut selection menu dialog, and dialog is triggered from accessibility button. [CHAR LIMIT=100] --> - <string name="accessibility_edit_shortcut_menu_button_title">Choose apps you want to use with + <string name="accessibility_edit_shortcut_menu_button_title">Choose apps to use with the accessibility button </string> <!-- Title for accessibility edit shortcut selection menu dialog, and dialog is triggered from volume key shortcut. [CHAR LIMIT=100] --> - <string name="accessibility_edit_shortcut_menu_volume_title">Choose apps you want to use with + <string name="accessibility_edit_shortcut_menu_volume_title">Choose apps to use with the volume key shortcut </string> + <!-- Text for showing the warning to user when uncheck the legacy app item in the accessibility + shortcut menu, user can aware the service to be disabled. [CHAR LIMIT=100] --> + <string name="accessibility_uncheck_legacy_item_warning"> + <xliff:g id="service_name" example="TalkBack">%s</xliff:g> has been turned off</string> + <!-- Text in button that edit the accessibility shortcut menu, user can delete any service item in the menu list. [CHAR LIMIT=100] --> <string name="edit_accessibility_shortcut_menu_button">Edit shortcuts</string> - <!-- Text in button that cancel the accessibility shortcut menu changed status. [CHAR LIMIT=100] --> - <string name="cancel_accessibility_shortcut_menu_button">Cancel</string> + <!-- Text in button that complete the accessibility shortcut menu changed status. [CHAR LIMIT=100] --> + <string name="done_accessibility_shortcut_menu_button">Done</string> <!-- Text in button that turns off the accessibility shortcut --> <string name="disable_accessibility_shortcut">Turn off Shortcut</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 0971aadfe4ab..9015cc4469ea 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3240,24 +3240,31 @@ <java-symbol type="string" name="accessibility_select_shortcut_menu_title" /> <java-symbol type="string" name="accessibility_edit_shortcut_menu_button_title" /> <java-symbol type="string" name="accessibility_edit_shortcut_menu_volume_title" /> + <java-symbol type="string" name="accessibility_uncheck_legacy_item_warning" /> + + <java-symbol type="layout" name="accessibility_enable_service_encryption_warning" /> + <java-symbol type="id" name="accessibility_permissionDialog_icon" /> + <java-symbol type="id" name="accessibility_permissionDialog_title" /> + <java-symbol type="id" name="accessibility_encryption_warning" /> + <java-symbol type="id" name="accessibility_permission_enable_allow_button" /> + <java-symbol type="id" name="accessibility_permission_enable_deny_button" /> + <java-symbol type="string" name="accessibility_enable_service_title" /> + <java-symbol type="string" name="accessibility_enable_service_encryption_warning" /> <!-- Accessibility Button --> <java-symbol type="layout" name="accessibility_button_chooser_item" /> + <java-symbol type="id" name="accessibility_button_target_checkbox" /> <java-symbol type="id" name="accessibility_button_target_icon" /> <java-symbol type="id" name="accessibility_button_target_label" /> - <java-symbol type="id" name="accessibility_button_target_item_container" /> - <java-symbol type="id" name="accessibility_button_target_view_item" /> <java-symbol type="id" name="accessibility_button_target_switch_item" /> <java-symbol type="string" name="accessibility_magnification_chooser_text" /> <java-symbol type="string" name="edit_accessibility_shortcut_menu_button" /> - <java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" /> + <java-symbol type="string" name="done_accessibility_shortcut_menu_button" /> <java-symbol type="drawable" name="ic_accessibility_color_inversion" /> <java-symbol type="drawable" name="ic_accessibility_color_correction" /> <java-symbol type="drawable" name="ic_accessibility_magnification" /> - <java-symbol type="drawable" name="ic_delete_item" /> - <!-- com.android.internal.widget.RecyclerView --> <java-symbol type="id" name="item_touch_helper_previous_elevation"/> <java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/> diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java index 0a094c61d4d5..f81964c9cdf9 100644 --- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java +++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java @@ -35,10 +35,13 @@ import static org.junit.Assert.assertTrue; import android.app.Activity; import android.app.Instrumentation; +import android.graphics.Rect; import android.text.Layout; +import android.util.ArraySet; import android.util.Log; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.View; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -50,11 +53,13 @@ import com.android.frameworks.coretests.R; import com.google.common.base.Strings; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Set; import java.util.concurrent.atomic.AtomicLong; @RunWith(AndroidJUnit4.class) @@ -70,6 +75,7 @@ public class EditorCursorDragTest { private Instrumentation mInstrumentation; private Activity mActivity; + private Set<MotionEvent> mMotionEvents = new ArraySet<>(); @Before public void before() throws Throwable { @@ -77,6 +83,14 @@ public class EditorCursorDragTest { mActivity = mActivityRule.getActivity(); } + @After + public void after() throws Throwable { + for (MotionEvent event : mMotionEvents) { + event.recycle(); + } + mMotionEvents.clear(); + } + @Test public void testCursorDrag_horizontal_whenTextViewContentsFitOnScreen() throws Throwable { String text = "Hello world!"; @@ -243,45 +257,45 @@ public class EditorCursorDragTest { // Simulate a tap-and-drag gesture. long event1Time = 1001; - MotionEvent event1 = downEvent(event1Time, event1Time, 5f, 10f); + MotionEvent event1 = downEvent(tv, event1Time, event1Time, 5f, 10f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event2Time = 1002; - MotionEvent event2 = moveEvent(event1Time, event2Time, 50f, 10f); + MotionEvent event2 = moveEvent(tv, event1Time, event2Time, 50f, 10f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); assertTrue(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event3Time = 1003; - MotionEvent event3 = moveEvent(event1Time, event3Time, 100f, 10f); + MotionEvent event3 = moveEvent(tv, event1Time, event3Time, 100f, 10f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); assertTrue(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event4Time = 2004; - MotionEvent event4 = upEvent(event1Time, event4Time, 100f, 10f); + MotionEvent event4 = upEvent(tv, event1Time, event4Time, 100f, 10f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); // Simulate a quick tap after the drag, near the location where the drag ended. long event5Time = 2005; - MotionEvent event5 = downEvent(event5Time, event5Time, 90f, 10f); + MotionEvent event5 = downEvent(tv, event5Time, event5Time, 90f, 10f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event6Time = 2006; - MotionEvent event6 = upEvent(event5Time, event6Time, 90f, 10f); + MotionEvent event6 = upEvent(tv, event5Time, event6Time, 90f, 10f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event6)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); // Simulate another quick tap in the same location; now selection should be triggered. long event7Time = 2007; - MotionEvent event7 = downEvent(event7Time, event7Time, 90f, 10f); + MotionEvent event7 = downEvent(tv, event7Time, event7Time, 90f, 10f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event7)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertTrue(editor.getSelectionController().isCursorBeingModified()); @@ -298,19 +312,19 @@ public class EditorCursorDragTest { // Simulate a mouse click and drag. This should NOT trigger a cursor drag. long event1Time = 1001; - MotionEvent event1 = mouseDownEvent(event1Time, event1Time, 20f, 30f); + MotionEvent event1 = mouseDownEvent(tv, event1Time, event1Time, 20f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event2Time = 1002; - MotionEvent event2 = mouseMoveEvent(event1Time, event2Time, 120f, 30f); + MotionEvent event2 = mouseMoveEvent(tv, event1Time, event2Time, 120f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertTrue(editor.getSelectionController().isCursorBeingModified()); long event3Time = 1003; - MotionEvent event3 = mouseUpEvent(event1Time, event3Time, 120f, 30f); + MotionEvent event3 = mouseUpEvent(tv, event1Time, event3Time, 120f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); @@ -327,25 +341,25 @@ public class EditorCursorDragTest { // Simulate a tap-and-drag gesture. This should trigger a cursor drag. long event1Time = 1001; - MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + MotionEvent event1 = downEvent(tv, event1Time, event1Time, 20f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event2Time = 1002; - MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f); + MotionEvent event2 = moveEvent(tv, event1Time, event2Time, 21f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event3Time = 1003; - MotionEvent event3 = moveEvent(event1Time, event3Time, 120f, 30f); + MotionEvent event3 = moveEvent(tv, event1Time, event3Time, 120f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); assertTrue(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event4Time = 1004; - MotionEvent event4 = upEvent(event1Time, event4Time, 120f, 30f); + MotionEvent event4 = upEvent(tv, event1Time, event4Time, 120f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); @@ -362,31 +376,31 @@ public class EditorCursorDragTest { // Simulate a double-tap followed by a drag. This should trigger a selection drag. long event1Time = 1001; - MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + MotionEvent event1 = downEvent(tv, event1Time, event1Time, 20f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event2Time = 1002; - MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); + MotionEvent event2 = upEvent(tv, event1Time, event2Time, 20f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event3Time = 1003; - MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f); + MotionEvent event3 = downEvent(tv, event3Time, event3Time, 20f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertTrue(editor.getSelectionController().isCursorBeingModified()); long event4Time = 1004; - MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f); + MotionEvent event4 = moveEvent(tv, event3Time, event4Time, 120f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertTrue(editor.getSelectionController().isCursorBeingModified()); long event5Time = 1005; - MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f); + MotionEvent event5 = upEvent(tv, event3Time, event5Time, 120f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); @@ -403,7 +417,7 @@ public class EditorCursorDragTest { // Simulate a tap. No error should be thrown. long event1Time = 1001; - MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + MotionEvent event1 = downEvent(tv, event1Time, event1Time, 20f, 30f); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); // Swipe left to right. No error should be thrown. @@ -440,7 +454,8 @@ public class EditorCursorDragTest { public void testCursorDrag_snapToHandle() throws Throwable { String text = "line1: This is the 1st line: A\n" + "line2: This is the 2nd line: B\n" - + "line3: This is the 3rd line: C\n"; + + "line3: This is the 3rd line: C\n" + + "line4: This is the 4th line: D\n"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); TextView tv = mActivity.findViewById(R.id.textview); @@ -454,8 +469,8 @@ public class EditorCursorDragTest { // Start dragging along the first line motionEventInfo(text.indexOf("line1"), 1.0f), motionEventInfo(text.indexOf("This is the 1st"), 1.0f), - // Move to the bottom of the third line; cursor should end up on second line - motionEventInfo(text.indexOf("he 3rd"), 0.0f, text.indexOf("he 2nd")), + // Move to the middle of the fourth line; cursor should end up on second line + motionEventInfo(text.indexOf("he 4th"), 0.5f, text.indexOf("he 2nd")), // Move to the middle of the second line; cursor should end up on the first line motionEventInfo(text.indexOf("he 2nd"), 0.5f, text.indexOf("he 1st")) }; @@ -473,39 +488,139 @@ public class EditorCursorDragTest { simulateDrag(tv, events, true); } - private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) { - return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); + @Test + public void testCursorDrag_snapDistance() throws Throwable { + String text = "line1: This is the 1st line: A\n" + + "line2: This is the 2nd line: B\n" + + "line3: This is the 3rd line: C\n"; + onView(withId(R.id.textview)).perform(replaceText(text)); + TextView tv = mActivity.findViewById(R.id.textview); + Editor editor = tv.getEditorForTesting(); + final int startIndex = text.indexOf("he 2nd"); + Layout layout = tv.getLayout(); + final float cursorStartX = layout.getPrimaryHorizontal(startIndex) + tv.getTotalPaddingLeft(); + final float cursorStartY = layout.getLineTop(1) + tv.getTotalPaddingTop(); + final float dragHandleStartX = 20; + final float dragHandleStartY = 20; + + // Drag the handle from the 2nd line to the 3rd line. + tapAtPoint(tv, cursorStartX, cursorStartY); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(startIndex)); + View handleView = editor.getInsertionController().getHandle(); + final int rawYOfHandleDrag = dragDownUntilLineChange( + handleView, dragHandleStartX, dragHandleStartY, tv.getSelectionStart()); + + // Drag the cursor from the 2nd line to the 3rd line. + tapAtPoint(tv, cursorStartX, cursorStartY); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(startIndex)); + final int rawYOfCursorDrag = + dragDownUntilLineChange(tv, cursorStartX, cursorStartY, tv.getSelectionStart()); + + // Drag the handle with touch through from the 2nd line to the 3rd line. + tv.getEditorForTesting().setFlagInsertionHandleGesturesEnabled(true); + tapAtPoint(tv, cursorStartX, cursorStartY); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(startIndex)); + handleView = editor.getInsertionController().getHandle(); + int rawYOfHandleDragWithTouchThrough = + dragDownUntilLineChange(handleView, dragHandleStartX, dragHandleStartY, tv.getSelectionStart()); + + String msg = String.format( + "rawYOfHandleDrag: %d, rawYOfCursorDrag: %d, rawYOfHandleDragWithTouchThrough: %d", + rawYOfHandleDrag, rawYOfCursorDrag, rawYOfHandleDragWithTouchThrough); + final int max = Math.max( + rawYOfCursorDrag, Math.max(rawYOfHandleDrag, rawYOfHandleDragWithTouchThrough)); + final int min = Math.min( + rawYOfCursorDrag, Math.min(rawYOfHandleDrag, rawYOfHandleDragWithTouchThrough)); + // The drag step is 5 pixels in dragDownUntilLineChange(). + // The difference among the 3 raw Y values should be no bigger than the drag step. + assertWithMessage(msg).that(max - min).isLessThan(6); } - private static MotionEvent upEvent(long downTime, long eventTime, float x, float y) { - return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); + private void dispatchTouchEvent(View view, MotionEvent event) { + mInstrumentation.runOnMainSync(() -> view.dispatchTouchEvent(event)); } - private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) { - return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0); + private void tapAtPoint(TextView tv, final float x, final float y) { + long downTime = sTicker.addAndGet(10_000); + dispatchTouchEvent(tv, downEvent(tv, downTime, downTime, x, y)); + dispatchTouchEvent(tv, upEvent(tv, downTime, downTime + 1, x, y)); } - private static MotionEvent mouseDownEvent(long downTime, long eventTime, float x, float y) { - MotionEvent event = downEvent(downTime, eventTime, x, y); - event.setSource(InputDevice.SOURCE_MOUSE); - event.setButtonState(MotionEvent.BUTTON_PRIMARY); - return event; + private int dragDownUntilLineChange(View view, final float startX, final float startY, + final int startOffset) { + TextView tv = mActivity.findViewById(R.id.textview); + final int startLine = tv.getLayout().getLineForOffset(startOffset); + + int rawY = 0; + long downTime = sTicker.addAndGet(10_000); + long eventTime = downTime; + // Move horizontally first to initiate the cursor drag. + dispatchTouchEvent(view, downEvent(view, downTime, eventTime++, startX, startY)); + dispatchTouchEvent(view, moveEvent(view, downTime, eventTime++, startX + 50, startY)); + dispatchTouchEvent(view, moveEvent(view, downTime, eventTime++, startX, startY)); + // Move downwards 5 pixels at a time until a line change occurs. + for (int i = 0; i < 200; i++) { + MotionEvent ev = moveEvent(view, downTime, eventTime++, startX, startY + i * 5); + rawY = (int) ev.getRawY(); + dispatchTouchEvent(view, ev); + if (tv.getLayout().getLineForOffset(tv.getSelectionStart()) > startLine) { + break; + } + } + String msg = String.format("The cursor didn't jump from %d!", startOffset); + assertWithMessage(msg).that( + tv.getLayout().getLineForOffset(tv.getSelectionStart())).isGreaterThan(startLine); + dispatchTouchEvent(view, upEvent(view, downTime, eventTime, startX, startY)); + return rawY; } - private static MotionEvent mouseUpEvent(long downTime, long eventTime, float x, float y) { - MotionEvent event = upEvent(downTime, eventTime, x, y); - event.setSource(InputDevice.SOURCE_MOUSE); - event.setButtonState(0); + private MotionEvent obtainTouchEvent( + View view, int action, long downTime, long eventTime, float x, float y) { + Rect r = new Rect(); + view.getBoundsOnScreen(r); + float rawX = x + r.left; + float rawY = y + r.top; + MotionEvent event = + MotionEvent.obtain(downTime, eventTime, action, rawX, rawY, 0); + view.toLocalMotionEvent(event); + mMotionEvents.add(event); return event; } - private static MotionEvent mouseMoveEvent(long downTime, long eventTime, float x, float y) { - MotionEvent event = moveEvent(downTime, eventTime, x, y); + private MotionEvent obtainMouseEvent( + View view, int action, long downTime, long eventTime, float x, float y) { + MotionEvent event = obtainTouchEvent(view, action, downTime, eventTime, x, y); event.setSource(InputDevice.SOURCE_MOUSE); - event.setButtonState(MotionEvent.BUTTON_PRIMARY); + if (action != MotionEvent.ACTION_UP) { + event.setButtonState(MotionEvent.BUTTON_PRIMARY); + } return event; } + private MotionEvent downEvent(View view, long downTime, long eventTime, float x, float y) { + return obtainTouchEvent(view, MotionEvent.ACTION_DOWN, downTime, eventTime, x, y); + } + + private MotionEvent moveEvent(View view, long downTime, long eventTime, float x, float y) { + return obtainTouchEvent(view, MotionEvent.ACTION_MOVE, downTime, eventTime, x, y); + } + + private MotionEvent upEvent(View view, long downTime, long eventTime, float x, float y) { + return obtainTouchEvent(view, MotionEvent.ACTION_UP, downTime, eventTime, x, y); + } + + private MotionEvent mouseDownEvent(View view, long downTime, long eventTime, float x, float y) { + return obtainMouseEvent(view, MotionEvent.ACTION_DOWN, downTime, eventTime, x, y); + } + + private MotionEvent mouseMoveEvent(View view, long downTime, long eventTime, float x, float y) { + return obtainMouseEvent(view, MotionEvent.ACTION_MOVE, downTime, eventTime, x, y); + } + + private MotionEvent mouseUpEvent(View view, long downTime, long eventTime, float x, float y) { + return obtainMouseEvent(view, MotionEvent.ACTION_UP, downTime, eventTime, x, y); + } + public static MotionEventInfo motionEventInfo(int index, float ratioToLineTop) { return new MotionEventInfo(index, ratioToLineTop, index); } @@ -543,14 +658,15 @@ public class EditorCursorDragTest { float[] downCoords = events[0].getCoordinates(tv); long downEventTime = sTicker.addAndGet(10_000); - MotionEvent downEvent = downEvent(downEventTime, downEventTime, + MotionEvent downEvent = downEvent(tv, downEventTime, downEventTime, downCoords[0], downCoords[1]); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(downEvent)); for (int i = 1; i < events.length; i++) { float[] moveCoords = events[i].getCoordinates(tv); long eventTime = downEventTime + i; - MotionEvent event = moveEvent(downEventTime, eventTime, moveCoords[0], moveCoords[1]); + MotionEvent event = moveEvent(tv, downEventTime, eventTime, moveCoords[0], + moveCoords[1]); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event)); assertCursorPosition(tv, events[i].expectedCursorIndex, runAssertions); } @@ -558,7 +674,7 @@ public class EditorCursorDragTest { MotionEventInfo lastEvent = events[events.length - 1]; float[] upCoords = lastEvent.getCoordinates(tv); long upEventTime = downEventTime + events.length; - MotionEvent upEvent = upEvent(downEventTime, upEventTime, upCoords[0], upCoords[1]); + MotionEvent upEvent = upEvent(tv, downEventTime, upEventTime, upCoords[0], upCoords[1]); mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(upEvent)); } diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index 88a6f9e4af4b..a72be25fedb5 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -497,7 +497,7 @@ public class TextViewActivityTest { @Test public void testInsertionHandle_multiLine() { - final String text = "abcd\n" + "efg\n" + "hijk\n"; + final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n"; onView(withId(R.id.textview)).perform(replaceText(text)); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); @@ -506,12 +506,12 @@ public class TextViewActivityTest { final TextView textView = mActivity.findViewById(R.id.textview); onHandleView(com.android.internal.R.id.insertion_handle) - .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a'))); - onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a"))); - - onHandleView(com.android.internal.R.id.insertion_handle) .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f'))); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f"))); + + onHandleView(com.android.internal.R.id.insertion_handle) + .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('i'))); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("i"))); } private void enableFlagsForInsertionHandleGestures() { diff --git a/identity/java/android/security/identity/CredstoreResultData.java b/identity/java/android/security/identity/CredstoreResultData.java index ef7afca6b888..2ef735eec81d 100644 --- a/identity/java/android/security/identity/CredstoreResultData.java +++ b/identity/java/android/security/identity/CredstoreResultData.java @@ -66,7 +66,7 @@ class CredstoreResultData extends ResultData { } @Override - public @NonNull Collection<String> getNamespaceNames() { + public @NonNull Collection<String> getNamespaces() { return Collections.unmodifiableCollection(mData.keySet()); } diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java index 335636cb07ae..725e3d8e429a 100644 --- a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java +++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java @@ -105,11 +105,11 @@ class CredstoreWritableIdentityCredential extends WritableIdentityCredential { n++; } - Collection<String> namespaceNames = personalizationData.getNamespaceNames(); + Collection<String> namespaces = personalizationData.getNamespaces(); - EntryNamespaceParcel[] ensParcels = new EntryNamespaceParcel[namespaceNames.size()]; + EntryNamespaceParcel[] ensParcels = new EntryNamespaceParcel[namespaces.size()]; n = 0; - for (String namespaceName : namespaceNames) { + for (String namespaceName : namespaces) { PersonalizationData.NamespaceData nsd = personalizationData.getNamespaceData(namespaceName); diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java index bd439199f914..1db2f6357308 100644 --- a/identity/java/android/security/identity/IdentityCredential.java +++ b/identity/java/android/security/identity/IdentityCredential.java @@ -209,6 +209,11 @@ public abstract class IdentityCredential { * <p>Note that only items referenced in {@code entriesToRequest} are returned - the * {@code requestMessage} parameter is only used to for enforcing reader authentication. * + * <p>The reason for having {@code requestMessage} and {@code entriesToRequest} as separate + * parameters is that the former represents a request from the remote verifier device + * (optionally signed) and this allows the application to filter the request to not include + * data elements which the user has not consented to sharing. + * * @param requestMessage If not {@code null}, must contain CBOR data conforming to * the schema mentioned above. * @param entriesToRequest The entries to request, organized as a map of namespace diff --git a/identity/java/android/security/identity/PersonalizationData.java b/identity/java/android/security/identity/PersonalizationData.java index 44370a1780f8..b34f2505a6a6 100644 --- a/identity/java/android/security/identity/PersonalizationData.java +++ b/identity/java/android/security/identity/PersonalizationData.java @@ -46,7 +46,7 @@ public class PersonalizationData { return Collections.unmodifiableCollection(mProfiles); } - Collection<String> getNamespaceNames() { + Collection<String> getNamespaces() { return Collections.unmodifiableCollection(mNamespaces.keySet()); } @@ -120,7 +120,7 @@ public class PersonalizationData { * @param value The value to add, in CBOR encoding. * @return The builder. */ - public @NonNull Builder setEntry(@NonNull String namespace, @NonNull String name, + public @NonNull Builder putEntry(@NonNull String namespace, @NonNull String name, @NonNull Collection<AccessControlProfileId> accessControlProfileIds, @NonNull byte[] value) { NamespaceData namespaceData = mData.mNamespaces.get(namespace); diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java index 0982c8a4ab31..13552d619e05 100644 --- a/identity/java/android/security/identity/ResultData.java +++ b/identity/java/android/security/identity/ResultData.java @@ -152,7 +152,7 @@ public abstract class ResultData { * @return collection of name of namespaces containing retrieved entries. May be empty if no * data was retrieved. */ - public abstract @NonNull Collection<String> getNamespaceNames(); + public abstract @NonNull Collection<String> getNamespaces(); /** * Get the names of all entries. @@ -196,8 +196,7 @@ public abstract class ResultData { * @param name the name of the entry to get the value for. * @return the status indicating whether the value was retrieved and if not, why. */ - @Status - public abstract int getStatus(@NonNull String namespaceName, @NonNull String name); + public abstract @Status int getStatus(@NonNull String namespaceName, @NonNull String name); /** * Gets the raw CBOR data for the value of an entry. diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index e2309178b297..550e41f28f85 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -103,6 +103,16 @@ public class LocationManager { private final Object mLock = new Object(); /** + * For apps targeting Android R and above, {@link #getProvider(String)} will no longer throw any + * security exceptions. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long GET_PROVIDER_SECURITY_EXCEPTIONS = 150935354L; + + /** * For apps targeting Android K and above, supplied {@link PendingIntent}s must be targeted to a * specific package. * @@ -1401,6 +1411,22 @@ public class LocationManager { */ public @Nullable LocationProvider getProvider(@NonNull String provider) { Preconditions.checkArgument(provider != null, "invalid null provider"); + + if (!Compatibility.isChangeEnabled(GET_PROVIDER_SECURITY_EXCEPTIONS)) { + if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) { + try { + mContext.enforcePermission(ACCESS_FINE_LOCATION, Process.myPid(), + Process.myUid(), null); + } catch (SecurityException e) { + mContext.enforcePermission(ACCESS_COARSE_LOCATION, Process.myPid(), + Process.myUid(), null); + } + } else { + mContext.enforcePermission(ACCESS_FINE_LOCATION, Process.myPid(), Process.myUid(), + null); + } + } + try { ProviderProperties properties = mService.getProviderProperties(provider); if (properties == null) { diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index 22c3acf6f1eb..6f985a4e4ddb 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -24,6 +24,7 @@ import android.content.Context; import com.android.keyguard.KeyguardViewController; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.CarDeviceProvisionedControllerImpl; +import com.android.systemui.car.CarNotificationInterruptionStateProvider; import com.android.systemui.dagger.SystemUIRootComponent; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; @@ -40,6 +41,7 @@ import com.android.systemui.statusbar.car.CarShadeControllerImpl; import com.android.systemui.statusbar.car.CarStatusBar; import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; @@ -63,6 +65,10 @@ import dagger.Provides; @Module(includes = {DividerModule.class}) abstract class CarSystemUIModule { + @Binds + abstract NotificationInterruptionStateProvider bindNotificationInterruptionStateProvider( + CarNotificationInterruptionStateProvider notificationInterruptionStateProvider); + @Singleton @Provides @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java new file mode 100644 index 000000000000..447e579ece42 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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.car; + +import android.content.Context; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.policy.BatteryController; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** Auto-specific implementation of {@link NotificationInterruptionStateProvider}. */ +@Singleton +public class CarNotificationInterruptionStateProvider extends + NotificationInterruptionStateProvider { + + @Inject + public CarNotificationInterruptionStateProvider(Context context, + NotificationFilter filter, + StatusBarStateController stateController, + BatteryController batteryController) { + super(context, filter, stateController, batteryController); + } + + @Override + public boolean shouldHeadsUp(NotificationEntry entry) { + // Because space is usually constrained in the auto use-case, there should not be a + // pinned notification when the shade has been expanded. Ensure this by not pinning any + // notification if the shade is already opened. + if (!getPresenter().isPresenterFullyCollapsed()) { + return false; + } + + return super.shouldHeadsUp(entry); + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index de768cbf0877..b2e21045f2a9 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -95,14 +95,13 @@ import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.NotificationAlertingManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -250,9 +249,10 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationInterruptStateProvider notificationInterruptStateProvider, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, + NotificationAlertingManager notificationAlertingManager, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @UiBackground Executor uiBgExecutor, @@ -335,9 +335,10 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt remoteInputQuickSettingsDisabler, notificationGutsManager, notificationLogger, - notificationInterruptStateProvider, + notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, + notificationAlertingManager, displayMetrics, metricsLogger, uiBgExecutor, @@ -487,22 +488,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt .isCurrentUserSetupInProgress(); } }); - - mNotificationInterruptStateProvider.addSuppressor(new NotificationInterruptSuppressor() { - @Override - public String getName() { - return TAG; - } - - @Override - public boolean suppressInterruptions(NotificationEntry entry) { - // Because space is usually constrained in the auto use-case, there should not be a - // pinned notification when the shade has been expanded. - // Ensure this by not allowing any interruptions (ie: pinning any notifications) if - // the shade is already opened. - return !getPresenter().isPresenterFullyCollapsed(); - } - }); } @Override diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index 9a535844d9d8..4754118e7a64 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -62,12 +62,13 @@ import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule; +import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.NotificationAlertingManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationRowModule; @@ -145,9 +146,10 @@ public class CarStatusBarModule { RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationInterruptStateProvider notificationInterruptionStateProvider, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, + NotificationAlertingManager notificationAlertingManager, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @UiBackground Executor uiBgExecutor, @@ -232,6 +234,7 @@ public class CarStatusBarModule { notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, + notificationAlertingManager, displayMetrics, metricsLogger, uiBgExecutor, diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 3f42ad40eb8e..4ea104705da0 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -2,6 +2,30 @@ android_library { name: "SettingsLib", + defaults: [ + "SettingsLibDependenciesWithoutWifiTracker", + ], + + // TODO(b/149540986): revert this change. + static_libs: [ + // All other dependent components should be put in + // "SettingsLibDependenciesWithoutWifiTracker". + "WifiTrackerLib", + ], + + // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES + // LOCAL_SHARED_JAVA_LIBRARIES := androidx.lifecycle_lifecycle-common + + resource_dirs: ["res"], + + srcs: ["src/**/*.java", "src/**/*.kt"], + + min_sdk_version: "21", + +} + +java_defaults { + name: "SettingsLibDependenciesWithoutWifiTracker", static_libs: [ "androidx.annotation_annotation", "androidx.legacy_legacy-support-v4", @@ -25,20 +49,9 @@ android_library { "SettingsLibProgressBar", "SettingsLibAdaptiveIcon", "SettingsLibRadioButtonPreference", - "WifiTrackerLib", "SettingsLibDisplayDensityUtils", "SettingsLibSchedulesProvider", ], - - // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES - // LOCAL_SHARED_JAVA_LIBRARIES := androidx.lifecycle_lifecycle-common - - resource_dirs: ["res"], - - srcs: ["src/**/*.java", "src/**/*.kt"], - - min_sdk_version: "21", - } // NOTE: Keep this module in sync with ./common.mk diff --git a/packages/SettingsProvider/res/values-ta/strings.xml b/packages/SettingsProvider/res/values-ta/strings.xml index fa6b8cd9c456..54d2242dced9 100644 --- a/packages/SettingsProvider/res/values-ta/strings.xml +++ b/packages/SettingsProvider/res/values-ta/strings.xml @@ -20,8 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4567566098528588863">"அமைப்புகளின் சேமிப்பிடம்"</string> - <!-- no translation found for wifi_softap_config_change (5688373762357941645) --> - <skip /> - <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) --> - <skip /> + <string name="wifi_softap_config_change" msgid="5688373762357941645">"ஹாட்ஸ்பாட் அமைப்புகள் மாற்றப்பட்டன"</string> + <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"விவரங்களைப் பார்க்க, தட்டவும்"</string> </resources> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e0662309f571..0eadcc741747 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -46,6 +46,7 @@ android_library { "SystemUIPluginLib", "SystemUISharedLib", "SettingsLib", + "androidx.viewpager2_viewpager2", "androidx.legacy_legacy-support-v4", "androidx.recyclerview_recyclerview", "androidx.preference_preference", @@ -106,6 +107,7 @@ android_library { "SystemUIPluginLib", "SystemUISharedLib", "SettingsLib", + "androidx.viewpager2_viewpager2", "androidx.legacy_legacy-support-v4", "androidx.recyclerview_recyclerview", "androidx.preference_preference", diff --git a/packages/SystemUI/res/layout/controls_dialog_pin.xml b/packages/SystemUI/res/layout/controls_dialog_pin.xml new file mode 100644 index 000000000000..b77d6fa6a956 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_dialog_pin.xml @@ -0,0 +1,36 @@ +<!-- + ~ 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingLeft="?android:attr/dialogPreferredPadding" + android:paddingRight="?android:attr/dialogPreferredPadding"> + <EditText + android:id="@+id/controls_pin_input" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/controls_pin_instructions" + android:inputType="numberPassword" /> + <CheckBox + android:id="@+id/controls_pin_use_alpha" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="5dp" + android:text="@string/controls_pin_use_alphanumeric" /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml index 6533c18a41a9..34a966ceea75 100644 --- a/packages/SystemUI/res/layout/controls_management.xml +++ b/packages/SystemUI/res/layout/controls_management.xml @@ -25,13 +25,40 @@ android:paddingStart="@dimen/controls_management_side_padding" android:paddingEnd="@dimen/controls_management_side_padding" > - <TextView - android:id="@+id/title" + <LinearLayout + android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" - android:textSize="@dimen/controls_title_size" - android:textAlignment="center" /> + android:gravity="center_vertical"> + + <FrameLayout + android:id="@+id/icon_frame" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="start|center_vertical" + android:minWidth="56dp" + android:visibility="gone" + android:paddingTop="@dimen/controls_app_icon_frame_top_padding" + android:paddingBottom="@dimen/controls_app_icon_frame_bottom_padding" + android:paddingEnd="@dimen/controls_app_icon_frame_side_padding" + android:paddingStart="@dimen/controls_app_icon_frame_side_padding" > + + <ImageView + android:id="@android:id/icon" + android:layout_width="@dimen/controls_app_icon_size" + android:layout_height="@dimen/controls_app_icon_size" /> + </FrameLayout> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textSize="@dimen/controls_title_size" + android:textAlignment="center" /> + + </LinearLayout> + <TextView android:id="@+id/subtitle" @@ -41,19 +68,11 @@ android:textAppearance="?android:attr/textAppearanceSmall" android:textAlignment="center" /> - <androidx.core.widget.NestedScrollView + <ViewStub + android:id="@+id/stub" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1" - android:orientation="vertical" - android:layout_marginTop="@dimen/controls_management_list_margin"> - - <ViewStub - android:id="@+id/stub" - android:layout_width="match_parent" - android:layout_height="match_parent"/> - - </androidx.core.widget.NestedScrollView> + android:layout_weight="1"/> <FrameLayout android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml index 2bab433d21f3..42d73f3cc9ce 100644 --- a/packages/SystemUI/res/layout/controls_management_apps.xml +++ b/packages/SystemUI/res/layout/controls_management_apps.xml @@ -14,12 +14,18 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<androidx.recyclerview.widget.RecyclerView +<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/list" android:layout_width="match_parent" - android:layout_height="match_parent" - > + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:layout_marginTop="@dimen/controls_management_list_margin"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> -</androidx.recyclerview.widget.RecyclerView>
\ No newline at end of file +</androidx.core.widget.NestedScrollView> diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml index aab32f45e77a..d2ccfcb11c5c 100644 --- a/packages/SystemUI/res/layout/controls_management_favorites.xml +++ b/packages/SystemUI/res/layout/controls_management_favorites.xml @@ -17,7 +17,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="0dp" android:orientation="vertical"> <TextView @@ -29,11 +29,17 @@ android:gravity="center_horizontal" /> - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/listAll" - android:layout_width="match_parent" + <com.android.systemui.controls.management.ManagementPageIndicator + android:id="@+id/structure_page_indicator" + android:layout_width="wrap_content" android:layout_height="match_parent" + android:layout_gravity="center" android:layout_marginTop="@dimen/controls_management_list_margin" - android:nestedScrollingEnabled="false"/> + android:visibility="gone" /> + + <androidx.viewpager2.widget.ViewPager2 + android:id="@+id/structure_pager" + android:layout_width="match_parent" + android:layout_height="match_parent"/> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_structure_page.xml b/packages/SystemUI/res/layout/controls_structure_page.xml new file mode 100644 index 000000000000..2c7e1681f2e1 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_structure_page.xml @@ -0,0 +1,31 @@ +<?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. + --> + +<androidx.core.widget.NestedScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_marginTop="@dimen/controls_management_list_margin"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/listAll" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + +</androidx.core.widget.NestedScrollView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml index ff0c6a756ca2..92ae1b95264f 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml @@ -1,71 +1,75 @@ <?xml version="1.0" encoding="utf-8"?> -<ScrollView +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/global_actions_container" android:layout_width="match_parent" - android:layout_height="match_parent"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/global_actions_grid_root" + android:layout_height="match_parent" + android:orientation="vertical" +> + <com.android.systemui.globalactions.GlobalActionsFlatLayout + android:id="@id/global_actions_view" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:theme="@style/qs_theme" + android:gravity="top | center_horizontal" android:clipChildren="false" android:clipToPadding="false" - android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" - android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset"> - - <com.android.systemui.globalactions.GlobalActionsFlatLayout - android:id="@id/global_actions_view" - android:layout_width="match_parent" + android:layout_marginTop="@dimen/global_actions_top_margin" + > + <LinearLayout + android:id="@android:id/list" + android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/global_actions_grid_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" android:orientation="horizontal" - android:theme="@style/qs_theme" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent" - android:gravity="top | center_horizontal" - android:clipChildren="false" - android:clipToPadding="false" - android:layout_marginTop="@dimen/global_actions_top_margin"> - <LinearLayout - android:id="@android:id/list" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/global_actions_grid_side_margin" - android:layout_marginRight="@dimen/global_actions_grid_side_margin" - android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" - android:paddingRight="@dimen/global_actions_grid_horizontal_padding" - android:paddingTop="@dimen/global_actions_grid_vertical_padding" - android:paddingBottom="@dimen/global_actions_grid_vertical_padding" - android:orientation="horizontal" - android:gravity="left" - android:translationZ="@dimen/global_actions_translate" /> - </com.android.systemui.globalactions.GlobalActionsFlatLayout> + android:gravity="left" + android:translationZ="@dimen/global_actions_translate" + /> + </com.android.systemui.globalactions.GlobalActionsFlatLayout> + <com.android.systemui.globalactions.MinHeightScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" + android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset" + android:orientation="vertical" + > <LinearLayout - android:id="@+id/global_actions_panel" + android:id="@+id/global_actions_grid_root" android:layout_width="match_parent" android:layout_height="wrap_content" + android:clipChildren="false" android:orientation="vertical" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent" - app:layout_constraintTop_toBottomOf="@id/global_actions_view"> + android:clipToPadding="false" + > + <LinearLayout + android:id="@+id/global_actions_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + > + <FrameLayout + android:id="@+id/global_actions_panel_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + </LinearLayout> - <FrameLayout - android:id="@+id/global_actions_panel_container" + <LinearLayout + android:id="@+id/global_actions_controls" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginRight="@dimen/global_actions_grid_horizontal_padding" + android:layout_marginLeft="@dimen/global_actions_grid_horizontal_padding" + /> </LinearLayout> - - <LinearLayout - android:id="@+id/global_actions_controls" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent" - app:layout_constraintTop_toBottomOf="@id/global_actions_panel" - app:layout_constraintBottom_toBottomOf="parent" /> - </androidx.constraintlayout.widget.ConstraintLayout> -</ScrollView> + </com.android.systemui.globalactions.MinHeightScrollView> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/global_actions_wrapped.xml b/packages/SystemUI/res/layout/global_actions_wrapped.xml deleted file mode 100644 index d4410702a7d1..000000000000 --- a/packages/SystemUI/res/layout/global_actions_wrapped.xml +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<com.android.systemui.HardwareUiLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@id/global_actions_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="top|right" - android:layout_marginBottom="0dp" - android:orientation="vertical" - android:paddingTop="@dimen/global_actions_top_padding" - android:clipToPadding="false" - android:theme="@style/qs_theme" - android:clipChildren="false"> - - <!-- Global actions is right-aligned to be physically near power button --> - <LinearLayout - android:id="@android:id/list" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top|right" - android:gravity="center" - android:orientation="vertical" - android:padding="@dimen/global_actions_padding" - android:translationZ="@dimen/global_actions_translate" /> - - <!-- For separated button--> - <FrameLayout - android:id="@+id/separated_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top|right" - android:layout_marginTop="6dp" - android:gravity="center" - android:orientation="vertical" - android:padding="@dimen/global_actions_padding" - android:translationZ="@dimen/global_actions_translate" /> - -</com.android.systemui.HardwareUiLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 9437485165a0..291db65da225 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1249,6 +1249,7 @@ <dimen name="controls_app_icon_size">32dp</dimen> <dimen name="controls_app_icon_frame_side_padding">8dp</dimen> <dimen name="controls_app_icon_frame_top_padding">4dp</dimen> + <dimen name="controls_app_icon_frame_bottom_padding">@dimen/controls_app_icon_frame_top_padding</dimen> <dimen name="controls_app_bottom_margin">8dp</dimen> <dimen name="controls_app_text_padding">8dp</dimen> <dimen name="controls_app_divider_height">2dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 5b28479ab5ca..caf22fe16beb 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2646,4 +2646,11 @@ <string name="controls_dialog_ok">Add to favorites</string> <!-- Controls dialog message [CHAR LIMIT=NONE] --> <string name="controls_dialog_message"><xliff:g id="app" example="System UI">%s</xliff:g> suggested this control to add to your favorites.</string> + + <!-- Controls PIN entry dialog, switch to alphanumeric keyboard [CHAR LIMIT=100] --> + <string name="controls_pin_use_alphanumeric">PIN contains letters or symbols</string> + <!-- Controls PIN entry dialog, title [CHAR LIMIT=30] --> + <string name="controls_pin_verify">Verify device PIN</string> + <!-- Controls PIN entry dialog, text hint [CHAR LIMIT=30] --> + <string name="controls_pin_instructions">Enter PIN</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index b6152dae33d6..a868cf58cf7c 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -71,11 +71,12 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController; import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; @@ -288,6 +289,7 @@ public class Dependency { @Inject Lazy<NotificationLogger> mNotificationLogger; @Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager; @Inject Lazy<NotificationFilter> mNotificationFilter; + @Inject Lazy<NotificationInterruptionStateProvider> mNotificationInterruptionStateProvider; @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil; @Inject Lazy<SmartReplyController> mSmartReplyController; @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler; @@ -487,6 +489,8 @@ public class Dependency { mProviders.put(NotificationViewHierarchyManager.class, mNotificationViewHierarchyManager::get); mProviders.put(NotificationFilter.class, mNotificationFilter::get); + mProviders.put(NotificationInterruptionStateProvider.class, + mNotificationInterruptionStateProvider::get); mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get); mProviders.put(SmartReplyController.class, mSmartReplyController::get); mProviders.put(RemoteInputQuickSettingsDisabler.class, diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java deleted file mode 100644 index ad2e0024065f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright (C) 2017 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; - -import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; -import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; -import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.provider.Settings; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; -import android.view.ViewTreeObserver; -import android.widget.LinearLayout; - -import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.leak.RotationUtils; - -/** - * Layout for placing two containers at a specific physical position on the device, relative to the - * device's hardware, regardless of screen rotation. - */ -public class HardwareUiLayout extends MultiListLayout implements Tunable { - - private static final String EDGE_BLEED = "sysui_hwui_edge_bleed"; - private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider"; - private final int[] mTmp2 = new int[2]; - private ViewGroup mList; - private ViewGroup mSeparatedView; - private int mOldHeight; - private boolean mAnimating; - private AnimatorSet mAnimation; - private View mDivision; - private HardwareBgDrawable mListBackground; - private HardwareBgDrawable mSeparatedViewBackground; - private Animator mAnimator; - private boolean mCollapse; - private int mEndPoint; - private boolean mEdgeBleed; - private boolean mRoundedDivider; - private boolean mRotatedBackground; - private boolean mSwapOrientation = true; - - public HardwareUiLayout(Context context, AttributeSet attrs) { - super(context, attrs); - // Manually re-initialize mRotation to portrait-mode, since this view must always - // be constructed in portrait mode and rotated into the correct initial position. - mRotation = ROTATION_NONE; - updateSettings(); - } - - @Override - protected ViewGroup getSeparatedView() { - return findViewById(com.android.systemui.R.id.separated_button); - } - - @Override - protected ViewGroup getListView() { - return findViewById(android.R.id.list); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - updateSettings(); - Dependency.get(TunerService.class).addTunable(this, EDGE_BLEED, ROUNDED_DIVIDER); - getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener); - Dependency.get(TunerService.class).removeTunable(this); - } - - @Override - public void onTuningChanged(String key, String newValue) { - updateSettings(); - } - - private void updateSettings() { - mEdgeBleed = Settings.Secure.getInt(getContext().getContentResolver(), - EDGE_BLEED, 0) != 0; - mRoundedDivider = Settings.Secure.getInt(getContext().getContentResolver(), - ROUNDED_DIVIDER, 0) != 0; - updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); - mListBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, getContext()); - mSeparatedViewBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, - getContext()); - if (mList != null) { - mList.setBackground(mListBackground); - mSeparatedView.setBackground(mSeparatedViewBackground); - requestLayout(); - } - } - - private void updateEdgeMargin(int edge) { - if (mList != null) { - MarginLayoutParams params = (MarginLayoutParams) mList.getLayoutParams(); - if (mRotation == ROTATION_LANDSCAPE) { - params.topMargin = edge; - } else if (mRotation == ROTATION_SEASCAPE) { - params.bottomMargin = edge; - } else { - params.rightMargin = edge; - } - mList.setLayoutParams(params); - } - - if (mSeparatedView != null) { - MarginLayoutParams params = (MarginLayoutParams) mSeparatedView.getLayoutParams(); - if (mRotation == ROTATION_LANDSCAPE) { - params.topMargin = edge; - } else if (mRotation == ROTATION_SEASCAPE) { - params.bottomMargin = edge; - } else { - params.rightMargin = edge; - } - mSeparatedView.setLayoutParams(params); - } - } - - private int getEdgePadding() { - return getContext().getResources().getDimensionPixelSize(R.dimen.edge_margin); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mList == null) { - if (getChildCount() != 0) { - mList = getListView(); - mList.setBackground(mListBackground); - mSeparatedView = getSeparatedView(); - mSeparatedView.setBackground(mSeparatedViewBackground); - updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); - mOldHeight = mList.getMeasuredHeight(); - - // Must be called to initialize view rotation correctly. - // Requires LayoutParams, hence why this isn't called during the constructor. - updateRotation(); - } else { - return; - } - } - int newHeight = mList.getMeasuredHeight(); - if (newHeight != mOldHeight) { - animateChild(mOldHeight, newHeight); - } - - post(() -> updatePaddingAndGravityIfTooTall()); - post(() -> updatePosition()); - } - - public void setSwapOrientation(boolean swapOrientation) { - mSwapOrientation = swapOrientation; - } - - private void updateRotation() { - int rotation = RotationUtils.getRotation(getContext()); - if (rotation != mRotation) { - rotate(mRotation, rotation); - mRotation = rotation; - } - } - - /** - * Requires LayoutParams to be set to work correctly, and therefore must be run after after - * the HardwareUILayout has been added to the view hierarchy. - */ - protected void rotate(int from, int to) { - super.rotate(from, to); - if (from != ROTATION_NONE && to != ROTATION_NONE) { - // Rather than handling this confusing case, just do 2 rotations. - rotate(from, ROTATION_NONE); - rotate(ROTATION_NONE, to); - return; - } - if (from == ROTATION_LANDSCAPE || to == ROTATION_SEASCAPE) { - rotateRight(); - } else { - rotateLeft(); - } - if (mAdapter.hasSeparatedItems()) { - if (from == ROTATION_SEASCAPE || to == ROTATION_SEASCAPE) { - // Separated view has top margin, so seascape separated view need special rotation, - // not a full left or right rotation. - swapLeftAndTop(mSeparatedView); - } else if (from == ROTATION_LANDSCAPE) { - rotateRight(mSeparatedView); - } else { - rotateLeft(mSeparatedView); - } - } - if (to != ROTATION_NONE) { - if (mList instanceof LinearLayout) { - mRotatedBackground = true; - mListBackground.setRotatedBackground(true); - mSeparatedViewBackground.setRotatedBackground(true); - LinearLayout linearLayout = (LinearLayout) mList; - if (mSwapOrientation) { - linearLayout.setOrientation(LinearLayout.HORIZONTAL); - setOrientation(LinearLayout.HORIZONTAL); - } - swapDimens(mList); - swapDimens(mSeparatedView); - } - } else { - if (mList instanceof LinearLayout) { - mRotatedBackground = false; - mListBackground.setRotatedBackground(false); - mSeparatedViewBackground.setRotatedBackground(false); - LinearLayout linearLayout = (LinearLayout) mList; - if (mSwapOrientation) { - linearLayout.setOrientation(LinearLayout.VERTICAL); - setOrientation(LinearLayout.VERTICAL); - } - swapDimens(mList); - swapDimens(mSeparatedView); - } - } - } - - @Override - public void onUpdateList() { - super.onUpdateList(); - - for (int i = 0; i < mAdapter.getCount(); i++) { - ViewGroup parent; - boolean separated = mAdapter.shouldBeSeparated(i); - if (separated) { - parent = getSeparatedView(); - } else { - parent = getListView(); - } - View v = mAdapter.getView(i, null, parent); - parent.addView(v); - } - } - - private void rotateRight() { - rotateRight(this); - rotateRight(mList); - swapDimens(this); - - LayoutParams p = (LayoutParams) mList.getLayoutParams(); - p.gravity = rotateGravityRight(p.gravity); - mList.setLayoutParams(p); - - LayoutParams separatedViewLayoutParams = (LayoutParams) mSeparatedView.getLayoutParams(); - separatedViewLayoutParams.gravity = rotateGravityRight(separatedViewLayoutParams.gravity); - mSeparatedView.setLayoutParams(separatedViewLayoutParams); - - setGravity(rotateGravityRight(getGravity())); - } - - private void swapDimens(View v) { - ViewGroup.LayoutParams params = v.getLayoutParams(); - int h = params.width; - params.width = params.height; - params.height = h; - v.setLayoutParams(params); - } - - private int rotateGravityRight(int gravity) { - int retGravity = 0; - int layoutDirection = getLayoutDirection(); - final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); - final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; - - switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { - case Gravity.CENTER_HORIZONTAL: - retGravity |= Gravity.CENTER_VERTICAL; - break; - case Gravity.RIGHT: - retGravity |= Gravity.BOTTOM; - break; - case Gravity.LEFT: - default: - retGravity |= Gravity.TOP; - break; - } - - switch (verticalGravity) { - case Gravity.CENTER_VERTICAL: - retGravity |= Gravity.CENTER_HORIZONTAL; - break; - case Gravity.BOTTOM: - retGravity |= Gravity.LEFT; - break; - case Gravity.TOP: - default: - retGravity |= Gravity.RIGHT; - break; - } - return retGravity; - } - - private void rotateLeft() { - rotateLeft(this); - rotateLeft(mList); - swapDimens(this); - - LayoutParams p = (LayoutParams) mList.getLayoutParams(); - p.gravity = rotateGravityLeft(p.gravity); - mList.setLayoutParams(p); - - LayoutParams separatedViewLayoutParams = (LayoutParams) mSeparatedView.getLayoutParams(); - separatedViewLayoutParams.gravity = rotateGravityLeft(separatedViewLayoutParams.gravity); - mSeparatedView.setLayoutParams(separatedViewLayoutParams); - - setGravity(rotateGravityLeft(getGravity())); - } - - private int rotateGravityLeft(int gravity) { - if (gravity == -1) { - gravity = Gravity.TOP | Gravity.START; - } - int retGravity = 0; - int layoutDirection = getLayoutDirection(); - final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); - final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; - - switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { - case Gravity.CENTER_HORIZONTAL: - retGravity |= Gravity.CENTER_VERTICAL; - break; - case Gravity.RIGHT: - retGravity |= Gravity.TOP; - break; - case Gravity.LEFT: - default: - retGravity |= Gravity.BOTTOM; - break; - } - - switch (verticalGravity) { - case Gravity.CENTER_VERTICAL: - retGravity |= Gravity.CENTER_HORIZONTAL; - break; - case Gravity.BOTTOM: - retGravity |= Gravity.RIGHT; - break; - case Gravity.TOP: - default: - retGravity |= Gravity.LEFT; - break; - } - return retGravity; - } - - private void rotateLeft(View v) { - v.setPadding(v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom(), - v.getPaddingLeft()); - MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); - params.setMargins(params.topMargin, params.rightMargin, params.bottomMargin, - params.leftMargin); - v.setLayoutParams(params); - } - - private void rotateRight(View v) { - v.setPadding(v.getPaddingBottom(), v.getPaddingLeft(), v.getPaddingTop(), - v.getPaddingRight()); - MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); - params.setMargins(params.bottomMargin, params.leftMargin, params.topMargin, - params.rightMargin); - v.setLayoutParams(params); - } - - private void swapLeftAndTop(View v) { - v.setPadding(v.getPaddingTop(), v.getPaddingLeft(), v.getPaddingBottom(), - v.getPaddingRight()); - MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); - params.setMargins(params.topMargin, params.leftMargin, params.bottomMargin, - params.rightMargin); - v.setLayoutParams(params); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - post(() -> updatePosition()); - - } - - private void animateChild(int oldHeight, int newHeight) { - if (true) return; - if (mAnimating) { - mAnimation.cancel(); - } - mAnimating = true; - mAnimation = new AnimatorSet(); - mAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mAnimating = false; - } - }); - int fromTop = mList.getTop(); - int fromBottom = mList.getBottom(); - int toTop = fromTop - ((newHeight - oldHeight) / 2); - int toBottom = fromBottom + ((newHeight - oldHeight) / 2); - ObjectAnimator top = ObjectAnimator.ofInt(mList, "top", fromTop, toTop); - top.addUpdateListener(animation -> mListBackground.invalidateSelf()); - mAnimation.playTogether(top, - ObjectAnimator.ofInt(mList, "bottom", fromBottom, toBottom)); - } - - public void setDivisionView(View v) { - mDivision = v; - if (mDivision != null) { - mDivision.addOnLayoutChangeListener( - (v1, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> - updatePosition()); - } - updatePosition(); - } - - private void updatePosition() { - if (mList == null) return; - // If got separated button, setRotatedBackground to false, - // all items won't get white background. - boolean separated = mAdapter.hasSeparatedItems(); - mListBackground.setRotatedBackground(separated); - mSeparatedViewBackground.setRotatedBackground(separated); - if (mDivision != null && mDivision.getVisibility() == VISIBLE) { - int index = mRotatedBackground ? 0 : 1; - mDivision.getLocationOnScreen(mTmp2); - float trans = mRotatedBackground ? mDivision.getTranslationX() - : mDivision.getTranslationY(); - int viewTop = (int) (mTmp2[index] + trans); - mList.getLocationOnScreen(mTmp2); - viewTop -= mTmp2[index]; - setCutPoint(viewTop); - } else { - setCutPoint(mList.getMeasuredHeight()); - } - } - - private void setCutPoint(int point) { - int curPoint = mListBackground.getCutPoint(); - if (curPoint == point) return; - if (getAlpha() == 0 || curPoint == 0) { - mListBackground.setCutPoint(point); - return; - } - if (mAnimator != null) { - if (mEndPoint == point) { - return; - } - mAnimator.cancel(); - } - mEndPoint = point; - mAnimator = ObjectAnimator.ofInt(mListBackground, "cutPoint", curPoint, point); - if (mCollapse) { - mAnimator.setStartDelay(300); - mCollapse = false; - } - mAnimator.start(); - } - - // If current power menu height larger then screen height, remove padding to break power menu - // alignment and set menu center vertical within the screen. - private void updatePaddingAndGravityIfTooTall() { - int defaultTopPadding; - int viewsTotalHeight; - int separatedViewTopMargin; - int screenHeight; - int totalHeight; - int targetGravity; - boolean separated = mAdapter.hasSeparatedItems(); - MarginLayoutParams params = (MarginLayoutParams) mSeparatedView.getLayoutParams(); - switch (RotationUtils.getRotation(getContext())) { - case RotationUtils.ROTATION_LANDSCAPE: - defaultTopPadding = getPaddingLeft(); - viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth(); - separatedViewTopMargin = separated ? params.leftMargin : 0; - screenHeight = getMeasuredWidth(); - targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.TOP; - break; - case RotationUtils.ROTATION_SEASCAPE: - defaultTopPadding = getPaddingRight(); - viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth(); - separatedViewTopMargin = separated ? params.leftMargin : 0; - screenHeight = getMeasuredWidth(); - targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM; - break; - default: // Portrait - defaultTopPadding = getPaddingTop(); - viewsTotalHeight = mList.getMeasuredHeight() + mSeparatedView.getMeasuredHeight(); - separatedViewTopMargin = separated ? params.topMargin : 0; - screenHeight = getMeasuredHeight(); - targetGravity = Gravity.CENTER_VERTICAL|Gravity.RIGHT; - break; - } - totalHeight = defaultTopPadding + viewsTotalHeight + separatedViewTopMargin; - if (totalHeight >= screenHeight) { - setPadding(0, 0, 0, 0); - setGravity(targetGravity); - } - } - - @Override - public ViewOutlineProvider getOutlineProvider() { - return super.getOutlineProvider(); - } - - public void setCollapse() { - mCollapse = true; - } - - private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> { - if (mHasOutsideTouch || (mList == null)) { - inoutInfo.setTouchableInsets( - ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); - return; - } - inoutInfo.setTouchableInsets( - ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT); - inoutInfo.contentInsets.set(mList.getLeft(), mList.getTop(), - 0, getBottom() - mList.getBottom()); - }; - - private float getAnimationDistance() { - return getContext().getResources().getDimension( - com.android.systemui.R.dimen.global_actions_panel_width) / 2; - } - - @Override - public float getAnimationOffsetX() { - if (RotationUtils.getRotation(mContext) == ROTATION_NONE) { - return getAnimationDistance(); - } - return 0; - } - - @Override - public float getAnimationOffsetY() { - switch (RotationUtils.getRotation(getContext())) { - case RotationUtils.ROTATION_LANDSCAPE: - return -getAnimationDistance(); - case RotationUtils.ROTATION_SEASCAPE: - return getAnimationDistance(); - default: // Portrait - return 0; - } - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index f873f42dd918..d754725af238 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -83,11 +83,11 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; @@ -169,7 +169,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Callback that updates BubbleOverflowActivity on data change. @Nullable private Runnable mOverflowCallback = null; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private IStatusBarService mBarService; // Used for determining view rect for touch interaction @@ -279,7 +279,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi ShadeController shadeController, BubbleData data, ConfigurationController configurationController, - NotificationInterruptStateProvider interruptionStateProvider, + NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager, @@ -304,7 +304,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, ConfigurationController configurationController, - NotificationInterruptStateProvider interruptionStateProvider, + NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager, @@ -316,7 +316,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi dumpManager.registerDumpable(TAG, this); mContext = context; mShadeController = shadeController; - mNotificationInterruptStateProvider = interruptionStateProvider; + mNotificationInterruptionStateProvider = interruptionStateProvider; mNotifUserManager = notifUserManager; mZenModeController = zenModeController; mFloatingContentCoordinator = floatingContentCoordinator; @@ -632,7 +632,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi for (NotificationEntry e : mNotificationEntryManager.getActiveNotificationsForCurrentUser()) { if (savedBubbleKeys.contains(e.getKey()) - && mNotificationInterruptStateProvider.shouldBubbleUp(e) + && mNotificationInterruptionStateProvider.shouldBubbleUp(e) && canLaunchInActivityView(mContext, e)) { updateBubble(e, /* suppressFlyout= */ true); } @@ -894,7 +894,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( mContext, entry, previouslyUserCreated, userBlocked); - if (mNotificationInterruptStateProvider.shouldBubbleUp(entry) + if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) { if (wasAdjusted && !previouslyUserCreated) { // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated @@ -910,7 +910,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( mContext, entry, previouslyUserCreated, userBlocked); - boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry) + boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && (canLaunchInActivityView(mContext, entry) || wasAdjusted); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) { // It was previously a bubble but no longer a bubble -- lets remove it @@ -1311,7 +1311,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { - if (mStackView != null && mStackView.getBubbleCount() > 0) { + if (mStackView != null) { mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight)); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 8cc10d9d148f..dc305f4a5c93 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -126,6 +126,11 @@ public class BubbleStackView extends FrameLayout { @VisibleForTesting static final int FLYOUT_HIDE_AFTER = 5000; + private static final PhysicsAnimator.SpringConfig FLYOUT_IME_ANIMATION_SPRING_CONFIG = + new PhysicsAnimator.SpringConfig( + StackAnimationController.IME_ANIMATION_STIFFNESS, + StackAnimationController.DEFAULT_BOUNCINESS); + /** * Interface to synchronize {@link View} state and the screen. * @@ -1240,7 +1245,8 @@ public class BubbleStackView extends FrameLayout { } /** - * @deprecated use {@link #setExpanded(boolean)} and {@link #setSelectedBubble(Bubble)} + * @deprecated use {@link #setExpanded(boolean)} and + * {@link BubbleData#setSelectedBubble(Bubble)} */ @Deprecated @MainThread @@ -1354,8 +1360,23 @@ public class BubbleStackView extends FrameLayout { public void onImeVisibilityChanged(boolean visible, int height) { mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0); - if (!mIsExpanded) { - mStackAnimationController.animateForImeVisibility(visible); + if (!mIsExpanded && getBubbleCount() > 0) { + final float stackDestinationY = + mStackAnimationController.animateForImeVisibility(visible); + + // How far the stack is animating due to IME, we'll just animate the flyout by that + // much too. + final float stackDy = + stackDestinationY - mStackAnimationController.getStackPosition().y; + + // If the flyout is visible, translate it along with the bubble stack. + if (mFlyout.getVisibility() == VISIBLE) { + PhysicsAnimator.getInstance(mFlyout) + .spring(DynamicAnimation.TRANSLATION_Y, + mFlyout.getTranslationY() + stackDy, + FLYOUT_IME_ANIMATION_SPRING_CONFIG) + .start(); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index b81665cd186a..86387f1cc546 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -68,9 +68,10 @@ public class StackAnimationController extends /** * Values to use for the default {@link SpringForce} provided to the physics animation layout. */ - private static final int DEFAULT_STIFFNESS = 12000; + public static final int DEFAULT_STIFFNESS = 12000; + public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW; private static final int FLING_FOLLOW_STIFFNESS = 20000; - private static final float DEFAULT_BOUNCINESS = 0.9f; + public static final float DEFAULT_BOUNCINESS = 0.9f; /** * Friction applied to fling animations. Since the stack must land on one of the sides of the @@ -118,8 +119,11 @@ public class StackAnimationController extends /** Whether or not the stack's start position has been set. */ private boolean mStackMovedToStartPosition = false; - /** The most recent position in which the stack was resting on the edge of the screen. */ - @Nullable private PointF mRestingStackPosition; + /** + * The stack's most recent position along the edge of the screen. This is saved when the last + * bubble is removed, so that the stack can be restored in its previous position. + */ + private PointF mRestingStackPosition; /** The height of the most recently visible IME. */ private float mImeHeight = 0f; @@ -465,7 +469,6 @@ public class StackAnimationController extends .addEndListener((animation, canceled, endValue, endVelocity) -> { if (!canceled) { - mRestingStackPosition = new PointF(); mRestingStackPosition.set(mStackPosition); springFirstBubbleWithStackFollowing(property, spring, endVelocity, @@ -501,8 +504,10 @@ public class StackAnimationController extends /** * Animates the stack either away from the newly visible IME, or back to its original position * due to the IME going away. + * + * @return The destination Y value of the stack due to the IME movement. */ - public void animateForImeVisibility(boolean imeVisible) { + public float animateForImeVisibility(boolean imeVisible) { final float maxBubbleY = getAllowableStackPositionRegion().bottom; float destinationY = Float.MIN_VALUE; @@ -523,12 +528,14 @@ public class StackAnimationController extends springFirstBubbleWithStackFollowing( DynamicAnimation.TRANSLATION_Y, getSpringForce(DynamicAnimation.TRANSLATION_Y, /* view */ null) - .setStiffness(SpringForce.STIFFNESS_LOW), + .setStiffness(IME_ANIMATION_STIFFNESS), /* startVel */ 0f, destinationY); notifyFloatingCoordinatorStackAnimatingTo(mStackPosition.x, destinationY); } + + return destinationY; } /** @@ -583,7 +590,7 @@ public class StackAnimationController extends - mBubblePaddingTop - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePaddingTop : 0f) - Math.max( - insets.getSystemWindowInsetBottom(), + insets.getStableInsetBottom(), insets.getDisplayCutout() != null ? insets.getDisplayCutout().getSafeInsetBottom() : 0); @@ -853,7 +860,12 @@ public class StackAnimationController extends public void setStackPosition(PointF pos) { Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y)); mStackPosition.set(pos.x, pos.y); - mRestingStackPosition = mStackPosition; + + if (mRestingStackPosition == null) { + mRestingStackPosition = new PointF(); + } + + mRestingStackPosition.set(mStackPosition); // If we're not the active controller, we don't want to physically move the bubble views. if (isActiveController()) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java index 27c9e9895324..ac97d8aab326 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -25,8 +25,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; @@ -54,7 +54,7 @@ public interface BubbleModule { ShadeController shadeController, BubbleData data, ConfigurationController configurationController, - NotificationInterruptStateProvider interruptionStateProvider, + NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager, diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 50bd1ade1e22..5e1ed5892e2e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -253,17 +253,21 @@ class ControlsControllerImpl @Inject constructor ( } override fun error(message: String) { - val loadData = Favorites.getControlsForComponent(componentName).let { - controls -> + executor.execute { + val loadData = Favorites.getControlsForComponent(componentName) + .let { controls -> val keys = controls.map { it.controlId } createLoadDataObject( - controls.map { createRemovedStatus(componentName, it, false) }, - keys, - true + controls.map { + createRemovedStatus(componentName, it, false) + }, + keys, + true ) - } + } - dataCallback.accept(loadData) + dataCallback.accept(loadData) + } } } ) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt index c05351795aed..01f906958fc1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt @@ -22,14 +22,20 @@ import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.controller.ControlInfo /** - * This model is used to show all controls separated by zones. + * This model is used to show controls separated by zones. * * The model will sort the controls and zones in the following manner: * * The zones will be sorted in a first seen basis * * The controls in each zone will be sorted in a first seen basis. * - * @property controls List of all controls as returned by loading - * @property initialFavoriteIds sorted ids of favorite controls + * The controls passed should belong to the same structure, as an instance of this model will be + * created for each structure. + * + * The list of favorite ids can contain ids for controls not passed to this model. Those will be + * filtered out. + * + * @property controls List of controls as returned by loading + * @property initialFavoriteIds sorted ids of favorite controls. * @property noZoneString text to use as header for all controls that have blank or `null` zone. */ class AllModel( @@ -50,7 +56,10 @@ class AllModel( } } - private val favoriteIds = initialFavoriteIds.toMutableList() + private val favoriteIds = run { + val ids = controls.mapTo(HashSet()) { it.control.controlId } + initialFavoriteIds.filter { it in ids }.toMutableList() + } override val elements: List<ElementWrapper> = createWrappers(controls) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index c21f7241180b..179e9fb02797 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -42,7 +42,6 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit * @param onlyFavorites set to true to only display favorites instead of all controls */ class ControlAdapter( - private val layoutInflater: LayoutInflater, private val elevation: Float ) : RecyclerView.Adapter<Holder>() { @@ -60,6 +59,7 @@ class ControlAdapter( private var model: ControlsModel? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { + val layoutInflater = LayoutInflater.from(parent.context) return when (viewType) { TYPE_CONTROL -> { ControlHolder( diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 471f9d30b08b..04715abe5f99 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -19,20 +19,24 @@ package com.android.systemui.controls.management import android.app.Activity import android.content.ComponentName import android.content.Intent +import android.graphics.drawable.Drawable import android.os.Bundle -import android.view.LayoutInflater +import android.text.TextUtils import android.view.View import android.view.ViewStub import android.widget.Button +import android.widget.ImageView import android.widget.TextView -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsControllerImpl +import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.PageIndicator import com.android.systemui.settings.CurrentUserTracker +import java.text.Collator import java.util.concurrent.Executor import java.util.function.Consumer import javax.inject.Inject @@ -40,6 +44,7 @@ import javax.inject.Inject class ControlsFavoritingActivity @Inject constructor( @Main private val executor: Executor, private val controller: ControlsControllerImpl, + private val listingController: ControlsListingController, broadcastDispatcher: BroadcastDispatcher ) : Activity() { @@ -48,12 +53,18 @@ class ControlsFavoritingActivity @Inject constructor( const val EXTRA_APP = "extra_app_label" } - private lateinit var recyclerViewAll: RecyclerView - private lateinit var adapterAll: ControlAdapter - private lateinit var statusText: TextView - private var model: ControlsModel? = null private var component: ComponentName? = null - private var structureName: CharSequence = "" + private var appName: CharSequence? = null + + private lateinit var structurePager: ViewPager2 + private lateinit var statusText: TextView + private lateinit var titleView: TextView + private lateinit var iconView: ImageView + private lateinit var iconFrame: View + private lateinit var pageIndicator: PageIndicator + private var listOfStructures = emptyList<StructureContainer>() + + private lateinit var comparator: Comparator<StructureContainer> private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = controller.currentUserId @@ -66,96 +77,141 @@ class ControlsFavoritingActivity @Inject constructor( } } + private val listingCallback = object : ControlsListingController.ControlsListingCallback { + private var icon: Drawable? = null + + override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { + val newIcon = serviceInfos.firstOrNull { it.componentName == component }?.loadIcon() + if (icon == newIcon) return + icon = newIcon + executor.execute { + if (icon != null) { + iconView.setImageDrawable(icon) + } + iconFrame.visibility = if (icon != null) View.VISIBLE else View.GONE + } + } + } + override fun onBackPressed() { finish() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.controls_management) - requireViewById<ViewStub>(R.id.stub).apply { - layoutResource = R.layout.controls_management_favorites - inflate() - } - - val app = intent.getCharSequenceExtra(EXTRA_APP) + val collator = Collator.getInstance(resources.configuration.locales[0]) + comparator = compareBy(collator) { it.structureName } + appName = intent.getCharSequenceExtra(EXTRA_APP) component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) - statusText = requireViewById(R.id.status_message) - setUpRecyclerView() + bindViews() - requireViewById<TextView>(R.id.title).text = app?.let { it } - ?: resources.getText(R.string.controls_favorite_default_title) - requireViewById<TextView>(R.id.subtitle).text = - resources.getText(R.string.controls_favorite_subtitle) + setUpPager() - requireViewById<Button>(R.id.other_apps).apply { - visibility = View.VISIBLE - setOnClickListener { - this@ControlsFavoritingActivity.onBackPressed() - } - } + loadControls() - requireViewById<Button>(R.id.done).setOnClickListener { - if (component == null) return@setOnClickListener - val favoritesForStorage = model?.favorites?.map { - it.build() - } - if (favoritesForStorage != null) { - controller.replaceFavoritesForStructure(StructureInfo(component!!, structureName, - favoritesForStorage)) - finishAffinity() - } - } + listingController.addCallback(listingCallback) + currentUserTracker.startTracking() + } + + private fun loadControls() { component?.let { statusText.text = resources.getText(com.android.internal.R.string.loading) + val emptyZoneString = resources.getText( + R.string.controls_favorite_other_zone_header) controller.loadForComponent(it, Consumer { data -> val allControls = data.allControls val favoriteKeys = data.favoritesIds val error = data.errorOnLoad - val structures = allControls.fold(hashSetOf<CharSequence>()) { - s, c -> - s.add(c.control.structure ?: "") - s - } - // TODO add multi structure switching support + val controlsByStructure = allControls.groupBy { it.control.structure ?: "" } + listOfStructures = controlsByStructure.map { + StructureContainer(it.key, AllModel(it.value, favoriteKeys, emptyZoneString)) + }.sortedWith(comparator) executor.execute { - val emptyZoneString = resources.getText( - R.string.controls_favorite_other_zone_header) - val model = AllModel(allControls, favoriteKeys, emptyZoneString) - adapterAll.changeModel(model) - this.model = model + structurePager.adapter = StructureAdapter(listOfStructures) if (error) { statusText.text = resources.getText(R.string.controls_favorite_load_error) } else { statusText.visibility = View.GONE } + pageIndicator.setNumPages(listOfStructures.size) + pageIndicator.setLocation(0f) + pageIndicator.visibility = + if (listOfStructures.size > 1) View.VISIBLE else View.GONE } }) } + } - currentUserTracker.startTracking() + private fun setUpPager() { + structurePager.apply { + adapter = StructureAdapter(emptyList()) + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + val name = listOfStructures[position].structureName + titleView.text = if (!TextUtils.isEmpty(name)) name else appName + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + super.onPageScrolled(position, positionOffset, positionOffsetPixels) + pageIndicator.setLocation(position + positionOffset) + } + }) + } + } + + private fun bindViews() { + setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_favorites + inflate() + } + + statusText = requireViewById(R.id.status_message) + pageIndicator = requireViewById(R.id.structure_page_indicator) + + titleView = requireViewById<TextView>(R.id.title).apply { + text = appName ?: resources.getText(R.string.controls_favorite_default_title) + } + requireViewById<TextView>(R.id.subtitle).text = + resources.getText(R.string.controls_favorite_subtitle) + iconView = requireViewById(com.android.internal.R.id.icon) + iconFrame = requireViewById(R.id.icon_frame) + structurePager = requireViewById<ViewPager2>(R.id.structure_pager) + bindButtons() } - private fun setUpRecyclerView() { - val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin) - val itemDecorator = MarginItemDecorator(margin, margin) - val layoutInflater = LayoutInflater.from(applicationContext) - val elevation = resources.getFloat(R.dimen.control_card_elevation) - - adapterAll = ControlAdapter(layoutInflater, elevation) - recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply { - adapter = adapterAll - layoutManager = GridLayoutManager(applicationContext, 2).apply { - spanSizeLookup = adapterAll.spanSizeLookup + private fun bindButtons() { + requireViewById<Button>(R.id.other_apps).apply { + visibility = View.VISIBLE + setOnClickListener { + this@ControlsFavoritingActivity.onBackPressed() } - addItemDecoration(itemDecorator) + } + + requireViewById<Button>(R.id.done).setOnClickListener { + if (component == null) return@setOnClickListener + listOfStructures.forEach { + val favoritesForStorage = it.model.favorites.map { it.build() } + controller.replaceFavoritesForStructure(StructureInfo(component!!, it.structureName, + favoritesForStorage)) + } + + finishAffinity() } } override fun onDestroy() { currentUserTracker.stopTracking() + listingController.removeCallback(listingCallback) super.onDestroy() } } + +data class StructureContainer(val structureName: CharSequence, val model: ControlsModel) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt new file mode 100644 index 000000000000..4289274cb3e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt @@ -0,0 +1,43 @@ +/* + * 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.controls.management + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import com.android.systemui.qs.PageIndicator + +/** + * Page indicator for management screens. + * + * Adds RTL support to [PageIndicator]. To be used with [ViewPager2]. + */ +class ManagementPageIndicator( + context: Context, + attrs: AttributeSet +) : PageIndicator(context, attrs) { + + override fun setLocation(location: Float) { + // Location doesn't know about RTL + if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { + val numPages = childCount + super.setLocation(numPages - 1 - location) + } else { + super.setLocation(location) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt new file mode 100644 index 000000000000..cb67454195ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt @@ -0,0 +1,73 @@ +/* + * 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.controls.management + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R + +class StructureAdapter( + private val models: List<StructureContainer> +) : RecyclerView.Adapter<StructureAdapter.StructureHolder>() { + + override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder { + val layoutInflater = LayoutInflater.from(parent.context) + return StructureHolder( + layoutInflater.inflate(R.layout.controls_structure_page, parent, false) + ) + } + + override fun getItemCount() = models.size + + override fun onBindViewHolder(holder: StructureHolder, index: Int) { + holder.bind(models[index].model) + } + + class StructureHolder(view: View) : RecyclerView.ViewHolder(view) { + + private val recyclerView: RecyclerView + private val controlAdapter: ControlAdapter + + init { + recyclerView = itemView.requireViewById<RecyclerView>(R.id.listAll) + val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation) + controlAdapter = ControlAdapter(elevation) + setUpRecyclerView() + } + + fun bind(model: ControlsModel) { + controlAdapter.changeModel(model) + } + + private fun setUpRecyclerView() { + val margin = itemView.context.resources + .getDimensionPixelSize(R.dimen.controls_card_margin) + val itemDecorator = MarginItemDecorator(margin, margin) + + recyclerView.apply { + this.adapter = controlAdapter + layoutManager = GridLayoutManager(recyclerView.context, 2).apply { + spanSizeLookup = controlAdapter.spanSizeLookup + } + addItemDecoration(itemDecorator) + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt new file mode 100644 index 000000000000..2494fd119670 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt @@ -0,0 +1,102 @@ +/* + * 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.controls.ui + +import android.app.AlertDialog +import android.app.Dialog +import android.content.DialogInterface +import android.service.controls.actions.BooleanAction +import android.service.controls.actions.CommandAction +import android.service.controls.actions.ControlAction +import android.service.controls.actions.FloatAction +import android.service.controls.actions.ModeAction +import android.text.InputType +import android.util.Log +import android.view.WindowManager +import android.widget.CheckBox +import android.widget.EditText + +import com.android.systemui.R + +/** + * Creates all dialogs for challengeValues that can occur from a call to + * {@link ControlsProviderService#performControlAction}. The types of challenge + * responses are listed in {@link ControlAction.ResponseResult}. + */ +object ChallengeDialogs { + + fun createPinDialog(cvh: ControlViewHolder): Dialog? { + val lastAction = cvh.lastAction + if (lastAction == null) { + Log.e(ControlsUiController.TAG, + "PIN Dialog attempted but no last action is set. Will not show") + return null + } + val builder = AlertDialog.Builder( + cvh.context, + android.R.style.Theme_DeviceDefault_Dialog_Alert + ).apply { + setTitle(R.string.controls_pin_verify) + setView(R.layout.controls_dialog_pin) + setPositiveButton( + android.R.string.ok, + DialogInterface.OnClickListener { dialog, _ -> + if (dialog is Dialog) { + dialog.requireViewById<EditText>(R.id.controls_pin_input) + val pin = dialog.requireViewById<EditText>(R.id.controls_pin_input) + .getText().toString() + cvh.action(addChallengeValue(lastAction, pin)) + dialog.dismiss() + } + }) + setNegativeButton( + android.R.string.cancel, + DialogInterface.OnClickListener { dialog, _ -> dialog.cancel() } + ) + } + return builder.create().apply { + getWindow().apply { + setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) + setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) + } + setOnShowListener(DialogInterface.OnShowListener { _ -> + val editText = requireViewById<EditText>(R.id.controls_pin_input) + requireViewById<CheckBox>(R.id.controls_pin_use_alpha).setOnClickListener { v -> + if ((v as CheckBox).isChecked) { + editText.setInputType( + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD) + } else { + editText.setInputType( + InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD) + } + } + editText.requestFocus() + }) + } + } + + private fun addChallengeValue(action: ControlAction, challengeValue: String): ControlAction { + val id = action.getTemplateId() + return when (action) { + is BooleanAction -> BooleanAction(id, action.getNewState(), challengeValue) + is FloatAction -> FloatAction(id, action.getNewValue(), challengeValue) + is CommandAction -> CommandAction(id, challengeValue) + is ModeAction -> ModeAction(id, action.getNewMode(), challengeValue) + else -> throw IllegalStateException("'action' is not a known type: $action") + } + } +} 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 d56428dbcdbe..b1b98bc7d044 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -27,7 +27,6 @@ import android.service.controls.templates.ControlTemplate import android.service.controls.templates.TemperatureControlTemplate import android.service.controls.templates.ToggleRangeTemplate import android.service.controls.templates.ToggleTemplate -import android.util.Log import android.view.View import android.view.ViewGroup import android.widget.ImageView @@ -57,6 +56,7 @@ class ControlViewHolder( lateinit var cws: ControlWithState var cancelUpdate: Runnable? = null var behavior: Behavior? = null + var lastAction: ControlAction? = null init { val ld = layout.getBackground() as LayerDrawable @@ -98,7 +98,6 @@ class ControlViewHolder( fun actionResponse(@ControlAction.ResponseResult response: Int) { // TODO: b/150931809 - handle response codes - Log.d(ControlsUiController.TAG, "Received response code: $response") } fun setTransientStatus(tempStatus: String) { @@ -115,6 +114,7 @@ class ControlViewHolder( } fun action(action: ControlAction) { + lastAction = action controlsController.action(cws.componentName, cws.ci, action) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index eaf57ff208e5..826618a51d8f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -16,18 +16,15 @@ package com.android.systemui.controls.ui -import android.accounts.Account -import android.accounts.AccountManager +import android.app.Dialog import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.ServiceConnection import android.content.SharedPreferences import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable -import android.os.IBinder import android.service.controls.Control -import android.service.controls.TokenProvider +import android.service.controls.actions.ControlAction import android.util.Log import android.view.ContextThemeWrapper import android.view.LayoutInflater @@ -49,8 +46,8 @@ import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsProviderSelectorActivity import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.R import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.R import dagger.Lazy @@ -59,68 +56,6 @@ import java.text.Collator import javax.inject.Inject import javax.inject.Singleton -// TEMP CODE for MOCK -private const val TOKEN = "https://www.googleapis.com/auth/assistant" -private const val SCOPE = "oauth2:" + TOKEN -private var tokenProviderConnection: TokenProviderConnection? = null -class TokenProviderConnection( - val cc: ControlsController, - val context: Context, - val structure: StructureInfo? -) : ServiceConnection { - private var mTokenProvider: TokenProvider? = null - - override fun onServiceConnected(cName: ComponentName, binder: IBinder) { - Thread({ - Log.i(ControlsUiController.TAG, "TokenProviderConnection connected") - mTokenProvider = TokenProvider.Stub.asInterface(binder) - - val mLastAccountName = mTokenProvider?.getAccountName() - - if (mLastAccountName == null || mLastAccountName.isEmpty()) { - Log.e(ControlsUiController.TAG, "NO ACCOUNT IS SET. Open HomeMock app") - } else { - mTokenProvider?.setAuthToken(getAuthToken(mLastAccountName)) - structure?.let { - cc.subscribeToFavorites(it) - } - } - }, "TokenProviderThread").start() - } - - override fun onServiceDisconnected(cName: ComponentName) { - mTokenProvider = null - } - - fun getAuthToken(accountName: String): String? { - val am = AccountManager.get(context) - val accounts = am.getAccountsByType("com.google") - if (accounts == null || accounts.size == 0) { - Log.w(ControlsUiController.TAG, "No com.google accounts found") - return null - } - - var account: Account? = null - for (a in accounts) { - if (a.name.equals(accountName)) { - account = a - break - } - } - - if (account == null) { - account = accounts[0] - } - - try { - return am.blockingGetAuthToken(account!!, SCOPE, true) - } catch (e: Throwable) { - Log.e(ControlsUiController.TAG, "Error getting auth token", e) - return null - } - } -} - private data class ControlKey(val componentName: ComponentName, val controlId: String) @Singleton @@ -152,7 +87,7 @@ class ControlsUiControllerImpl @Inject constructor ( private lateinit var parent: ViewGroup private lateinit var lastItems: List<SelectionItem> private var popup: ListPopupWindow? = null - + private var activeDialog: Dialog? = null private val addControlsItem: SelectionItem init { @@ -213,21 +148,10 @@ class ControlsUiControllerImpl @Inject constructor ( ControlKey(selectedStructure.componentName, it.ci.controlId) } listingCallback = createCallback(::showControlsView) + controlsController.get().subscribeToFavorites(selectedStructure) } controlsListingController.get().addCallback(listingCallback) - - // Temp code to pass auth - tokenProviderConnection = TokenProviderConnection(controlsController.get(), context, - selectedStructure) - - val serviceIntent = Intent() - serviceIntent.setComponent(ComponentName("com.android.systemui.home.mock", - "com.android.systemui.home.mock.AuthService")) - if (!context.bindService(serviceIntent, tokenProviderConnection!!, - Context.BIND_AUTO_CREATE)) { - controlsController.get().subscribeToFavorites(selectedStructure) - } } private fun showInitialSetupView(items: List<SelectionItem>) { @@ -388,10 +312,9 @@ class ControlsUiControllerImpl @Inject constructor ( override fun hide() { Log.d(ControlsUiController.TAG, "hide()") popup?.dismiss() + activeDialog?.dismiss() controlsController.get().unsubscribe() - context.unbindService(tokenProviderConnection) - tokenProviderConnection = null parent.removeAllViews() controlsById.clear() @@ -418,7 +341,15 @@ class ControlsUiControllerImpl @Inject constructor ( override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { val key = ControlKey(componentName, controlId) uiExecutor.execute { - controlViewsById.get(key)?.actionResponse(response) + controlViewsById.get(key)?.let { cvh -> + when (response) { + ControlAction.RESPONSE_CHALLENGE_PIN -> { + activeDialog = ChallengeDialogs.createPinDialog(cvh) + activeDialog?.show() + } + else -> cvh.actionResponse(response) + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 3a4b273e1c98..6c502d273a1c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -90,7 +90,7 @@ public class DependencyProvider { /** */ @Provides - public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) { + public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) { return new AmbientDisplayConfiguration(context); } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 24f505d5a395..786ad2c7d82a 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1574,7 +1574,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private ControlsUiController mControlsUiController; private ViewGroup mControlsView; - private ViewGroup mContainerView; ActionsDialog(Context context, MyAdapter adapter, GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils, @@ -1671,7 +1670,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls); mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view); mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); - ((View) mGlobalActionsLayout.getParent()).setOnClickListener(view -> dismiss()); mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() { @Override public boolean dispatchPopulateAccessibilityEvent( @@ -1684,6 +1682,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mGlobalActionsLayout.setRotationListener(this::onRotate); mGlobalActionsLayout.setAdapter(mAdapter); + View globalActionsParent = (View) mGlobalActionsLayout.getParent(); + globalActionsParent.setOnClickListener(v -> dismiss()); + + // add fall-through dismiss handling to root view + View rootView = findViewById(com.android.systemui.R.id.global_actions_grid_root); + if (rootView != null) { + rootView.setOnClickListener(v -> dismiss()); + } + if (shouldUsePanel()) { initializePanel(); } @@ -1692,14 +1699,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mScrimAlpha = ScrimController.BUSY_SCRIM_ALPHA; } getWindow().setBackgroundDrawable(mBackgroundDrawable); - - if (mControlsView != null) { - mContainerView = findViewById(com.android.systemui.R.id.global_actions_container); - mContainerView.setOnTouchListener((v, e) -> { - dismiss(); - return true; - }); - } } private void fixNavBarClipping() { diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java b/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java new file mode 100644 index 000000000000..622fa658f1b0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java @@ -0,0 +1,43 @@ +/* + * 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.globalactions; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ScrollView; + +/** + * When measured, this view sets the minimum height of its first child to be equal to its own + * target height. + * + * This ensures fall-through click handlers can be placed on this view's child component. + */ +public class MinHeightScrollView extends ScrollView { + public MinHeightScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + View firstChild = getChildAt(0); + if (firstChild != null) { + firstChild.setMinimumHeight(MeasureSpec.getSize(heightMeasureSpec)); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index c6eecf260dac..04d53627d0c6 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -129,217 +129,221 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } }; - private DisplayImeController.ImePositionProcessor mImePositionProcessor = - new DisplayImeController.ImePositionProcessor() { - /** - * These are the y positions of the top of the IME surface when it is hidden and - * when it is shown respectively. These are NOT necessarily the top of the visible - * IME itself. - */ - private int mHiddenTop = 0; - private int mShownTop = 0; - - // The following are target states (what we are curretly animating towards). - /** - * {@code true} if, at the end of the animation, the split task positions should be - * adjusted by height of the IME. This happens when the secondary split is the IME - * target. - */ - private boolean mTargetAdjusted = false; - /** - * {@code true} if, at the end of the animation, the IME should be shown/visible - * regardless of what has focus. - */ - private boolean mTargetShown = false; - - // The following are the current (most recent) states set during animation - /** - * {@code true} if the secondary split has IME focus. - */ - private boolean mSecondaryHasFocus = false; - /** The dimming currently applied to the primary/secondary splits. */ - private float mLastPrimaryDim = 0.f; - private float mLastSecondaryDim = 0.f; - /** The most recent y position of the top of the IME surface */ - private int mLastAdjustTop = -1; - - // The following are states reached last time an animation fully completed. - /** {@code true} if the IME was shown/visible by the last-completed animation. */ - private boolean mImeWasShown = false; - /** - * {@code true} if the split positions were adjusted by the last-completed - * animation. - */ - private boolean mAdjusted = false; - - /** - * When some aspect of split-screen needs to animate independent from the IME, - * this will be non-null and control split animation. - */ - @Nullable - private ValueAnimator mAnimation = null; - - private boolean getSecondaryHasFocus(int displayId) { - try { - IWindowContainer imeSplit = ActivityTaskManager.getTaskOrganizerController() - .getImeTarget(displayId); - return imeSplit != null - && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder()); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to get IME target", e); - } - return false; - } + private class DividerImeController implements DisplayImeController.ImePositionProcessor { + /** + * These are the y positions of the top of the IME surface when it is hidden and when it is + * shown respectively. These are NOT necessarily the top of the visible IME itself. + */ + private int mHiddenTop = 0; + private int mShownTop = 0; + + // The following are target states (what we are curretly animating towards). + /** + * {@code true} if, at the end of the animation, the split task positions should be + * adjusted by height of the IME. This happens when the secondary split is the IME target. + */ + private boolean mTargetAdjusted = false; + /** + * {@code true} if, at the end of the animation, the IME should be shown/visible + * regardless of what has focus. + */ + private boolean mTargetShown = false; + private float mTargetPrimaryDim = 0.f; + private float mTargetSecondaryDim = 0.f; + + // The following are the current (most recent) states set during animation + /** {@code true} if the secondary split has IME focus. */ + private boolean mSecondaryHasFocus = false; + /** The dimming currently applied to the primary/secondary splits. */ + private float mLastPrimaryDim = 0.f; + private float mLastSecondaryDim = 0.f; + /** The most recent y position of the top of the IME surface */ + private int mLastAdjustTop = -1; + + // The following are states reached last time an animation fully completed. + /** {@code true} if the IME was shown/visible by the last-completed animation. */ + private boolean mImeWasShown = false; + /** {@code true} if the split positions were adjusted by the last-completed animation. */ + private boolean mAdjusted = false; + + /** + * When some aspect of split-screen needs to animate independent from the IME, + * this will be non-null and control split animation. + */ + @Nullable + private ValueAnimator mAnimation = null; + + private boolean getSecondaryHasFocus(int displayId) { + try { + IWindowContainer imeSplit = ActivityTaskManager.getTaskOrganizerController() + .getImeTarget(displayId); + return imeSplit != null + && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder()); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to get IME target", e); + } + return false; + } - @Override - public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, - boolean imeShouldShow, SurfaceControl.Transaction t) { - mSecondaryHasFocus = getSecondaryHasFocus(displayId); - mTargetAdjusted = imeShouldShow && mSecondaryHasFocus - && !mSplitLayout.mDisplayLayout.isLandscape(); - mHiddenTop = hiddenTop; - mShownTop = shownTop; - mTargetShown = imeShouldShow; - if (mLastAdjustTop < 0) { - mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop; - } - if (mAnimation != null || (mImeWasShown && imeShouldShow - && mTargetAdjusted != mAdjusted)) { - // We need to animate adjustment independently of the IME position, so - // start our own animation to drive adjustment. This happens when a - // different split's editor has gained focus while the IME is still visible. - startAsyncAnimation(); - } - // Reposition the server's secondary split position so that it evaluates - // insets properly. - WindowContainerTransaction wct = new WindowContainerTransaction(); - if (mTargetAdjusted) { - mSplitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop); - wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary); - // "Freeze" the configuration size so that the app doesn't get a config - // or relaunch. This is required because normally nav-bar contributes - // to configuration bounds (via nondecorframe). - Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration - .windowConfiguration.getAppBounds()); - adjustAppBounds.offset(0, mSplitLayout.mAdjustedSecondary.top - - mSplitLayout.mSecondary.top); - wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds); - wct.setScreenSizeDp(mSplits.mSecondary.token, - mSplits.mSecondary.configuration.screenWidthDp, - mSplits.mSecondary.configuration.screenHeightDp); - } else { - wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary); - wct.setAppBounds(mSplits.mSecondary.token, null); - wct.setScreenSizeDp(mSplits.mSecondary.token, - SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); - } - try { - ActivityTaskManager.getTaskOrganizerController() - .applyContainerTransaction(wct, null /* organizer */); - } catch (RemoteException e) { - } - - // Update all the adjusted-for-ime states - mView.setAdjustedForIme(mTargetShown, mTargetShown - ? DisplayImeController.ANIMATION_DURATION_SHOW_MS - : DisplayImeController.ANIMATION_DURATION_HIDE_MS); - setAdjustedForIme(mTargetShown); - } + @Override + public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, + boolean imeShouldShow, SurfaceControl.Transaction t) { + if (!inSplitMode()) { + return; + } + final boolean splitIsVisible = !mView.isHidden(); + mSecondaryHasFocus = getSecondaryHasFocus(displayId); + mTargetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus + && !mSplitLayout.mDisplayLayout.isLandscape(); + mHiddenTop = hiddenTop; + mShownTop = shownTop; + mTargetShown = imeShouldShow; + if (mLastAdjustTop < 0) { + mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop; + } + mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible) + ? ADJUSTED_NONFOCUS_DIM : 0.f; + mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible) + ? ADJUSTED_NONFOCUS_DIM : 0.f; + if (mAnimation != null || (mImeWasShown && imeShouldShow + && mTargetAdjusted != mAdjusted)) { + // We need to animate adjustment independently of the IME position, so + // start our own animation to drive adjustment. This happens when a + // different split's editor has gained focus while the IME is still visible. + startAsyncAnimation(); + } + if (splitIsVisible) { + // If split is hidden, we don't want to trigger any relayouts that would cause the + // divider to show again. + updateImeAdjustState(); + } + } - @Override - public void onImePositionChanged(int displayId, int imeTop, - SurfaceControl.Transaction t) { - if (mAnimation != null) { - // Not synchronized with IME anymore, so return. - return; - } - final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop); - final float progress = mTargetShown ? fraction : 1.f - fraction; - onProgress(progress, t); - } + private void updateImeAdjustState() { + // Reposition the server's secondary split position so that it evaluates + // insets properly. + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (mTargetAdjusted) { + mSplitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop); + wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary); + // "Freeze" the configuration size so that the app doesn't get a config + // or relaunch. This is required because normally nav-bar contributes + // to configuration bounds (via nondecorframe). + Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration + .windowConfiguration.getAppBounds()); + adjustAppBounds.offset(0, mSplitLayout.mAdjustedSecondary.top + - mSplitLayout.mSecondary.top); + wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds); + wct.setScreenSizeDp(mSplits.mSecondary.token, + mSplits.mSecondary.configuration.screenWidthDp, + mSplits.mSecondary.configuration.screenHeightDp); + } else { + wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary); + wct.setAppBounds(mSplits.mSecondary.token, null); + wct.setScreenSizeDp(mSplits.mSecondary.token, + SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); + } + try { + ActivityTaskManager.getTaskOrganizerController() + .applyContainerTransaction(wct, null /* organizer */); + } catch (RemoteException e) { + } - @Override - public void onImeEndPositioning(int displayId, boolean cancelled, - SurfaceControl.Transaction t) { - if (mAnimation != null) { - // Not synchronized with IME anymore, so return. - return; - } - onEnd(cancelled, t); - } + // Update all the adjusted-for-ime states + mView.setAdjustedForIme(mTargetShown, mTargetShown + ? DisplayImeController.ANIMATION_DURATION_SHOW_MS + : DisplayImeController.ANIMATION_DURATION_HIDE_MS); + setAdjustedForIme(mTargetShown); + } - private void onProgress(float progress, SurfaceControl.Transaction t) { - if (mTargetAdjusted != mAdjusted) { - final float fraction = mTargetAdjusted ? progress : 1.f - progress; - mLastAdjustTop = - (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop); - mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop); - mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary, - mSplitLayout.mAdjustedSecondary); - } - final float invProg = 1.f - progress; - final float targetPrimaryDim = (mSecondaryHasFocus && mTargetShown) - ? ADJUSTED_NONFOCUS_DIM : 0.f; - final float targetSecondaryDim = (!mSecondaryHasFocus && mTargetShown) - ? ADJUSTED_NONFOCUS_DIM : 0.f; - mView.setResizeDimLayer(t, true /* primary */, - mLastPrimaryDim * invProg + progress * targetPrimaryDim); - mView.setResizeDimLayer(t, false /* primary */, - mLastSecondaryDim * invProg + progress * targetSecondaryDim); - } + @Override + public void onImePositionChanged(int displayId, int imeTop, + SurfaceControl.Transaction t) { + if (mAnimation != null || !inSplitMode()) { + // Not synchronized with IME anymore, so return. + return; + } + final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop); + final float progress = mTargetShown ? fraction : 1.f - fraction; + onProgress(progress, t); + } - private void onEnd(boolean cancelled, SurfaceControl.Transaction t) { - if (!cancelled) { - onProgress(1.f, t); - mAdjusted = mTargetAdjusted; - mImeWasShown = mTargetShown; - mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop; - mLastPrimaryDim = - (mSecondaryHasFocus && mTargetShown) ? ADJUSTED_NONFOCUS_DIM : 0.f; - mLastSecondaryDim = - (!mSecondaryHasFocus && mTargetShown) ? ADJUSTED_NONFOCUS_DIM : 0.f; - } - } + @Override + public void onImeEndPositioning(int displayId, boolean cancelled, + SurfaceControl.Transaction t) { + if (mAnimation != null || !inSplitMode()) { + // Not synchronized with IME anymore, so return. + return; + } + onEnd(cancelled, t); + } + + private void onProgress(float progress, SurfaceControl.Transaction t) { + if (mTargetAdjusted != mAdjusted) { + final float fraction = mTargetAdjusted ? progress : 1.f - progress; + mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop); + mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop); + mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary, + mSplitLayout.mAdjustedSecondary); + } + final float invProg = 1.f - progress; + mView.setResizeDimLayer(t, true /* primary */, + mLastPrimaryDim * invProg + progress * mTargetPrimaryDim); + mView.setResizeDimLayer(t, false /* primary */, + mLastSecondaryDim * invProg + progress * mTargetSecondaryDim); + } + + private void onEnd(boolean cancelled, SurfaceControl.Transaction t) { + if (!cancelled) { + onProgress(1.f, t); + mAdjusted = mTargetAdjusted; + mImeWasShown = mTargetShown; + mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop; + mLastPrimaryDim = mTargetPrimaryDim; + mLastSecondaryDim = mTargetSecondaryDim; + } + } + + private void startAsyncAnimation() { + if (mAnimation != null) { + mAnimation.cancel(); + } + mAnimation = ValueAnimator.ofFloat(0.f, 1.f); + mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS); + if (mTargetAdjusted != mAdjusted) { + final float fraction = + ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop); + final float progress = mTargetAdjusted ? fraction : 1.f - fraction; + mAnimation.setCurrentFraction(progress); + } - private void startAsyncAnimation() { - if (mAnimation != null) { - mAnimation.cancel(); - } - mAnimation = ValueAnimator.ofFloat(0.f, 1.f); - mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS); - if (mTargetAdjusted != mAdjusted) { - final float fraction = - ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop); - final float progress = mTargetAdjusted ? fraction : 1.f - fraction; - mAnimation.setCurrentFraction(progress); - } - - mAnimation.addUpdateListener(animation -> { - SurfaceControl.Transaction t = mTransactionPool.acquire(); - float value = (float) animation.getAnimatedValue(); - onProgress(value, t); - t.apply(); - mTransactionPool.release(t); - }); - mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR); - mAnimation.addListener(new AnimatorListenerAdapter() { - private boolean mCancel = false; - @Override - public void onAnimationCancel(Animator animation) { - mCancel = true; - } - @Override - public void onAnimationEnd(Animator animation) { - SurfaceControl.Transaction t = mTransactionPool.acquire(); - onEnd(mCancel, t); - t.apply(); - mTransactionPool.release(t); - mAnimation = null; - } - }); - mAnimation.start(); + mAnimation.addUpdateListener(animation -> { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + float value = (float) animation.getAnimatedValue(); + onProgress(value, t); + t.apply(); + mTransactionPool.release(t); + }); + mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR); + mAnimation.addListener(new AnimatorListenerAdapter() { + private boolean mCancel = false; + @Override + public void onAnimationCancel(Animator animation) { + mCancel = true; } - }; + @Override + public void onAnimationEnd(Animator animation) { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + onEnd(mCancel, t); + t.apply(); + mTransactionPool.release(t); + mAnimation = null; + } + }); + mAnimation.start(); + } + } + private final DividerImeController mImePositionProcessor = new DividerImeController(); public Divider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy, DisplayController displayController, SystemWindows systemWindows, @@ -513,44 +517,45 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } } - private void setHomeStackResizable(boolean resizable) { - if (mHomeStackResizable == resizable) { - return; - } - mHomeStackResizable = resizable; - if (!inSplitMode()) { - return; - } - WindowManagerProxy.applyHomeTasksMinimized(mSplitLayout, mSplits.mSecondary.token); - } - - private void updateMinimizedDockedStack(final boolean minimized, final long animDuration, - final boolean isHomeStackResizable) { - setHomeStackResizable(isHomeStackResizable); - if (animDuration > 0) { - mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable); - } else { - mView.setMinimizedDockStack(minimized, isHomeStackResizable); - } - updateTouchable(); - } - /** Switch to minimized state if appropriate */ public void setMinimized(final boolean minimized) { mHandler.post(() -> { - if (!inSplitMode()) { - return; - } - if (mMinimized == minimized) { - return; + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (setHomeMinimized(minimized, mHomeStackResizable, wct)) { + WindowManagerProxy.applyContainerTransaction(wct); } - mMinimized = minimized; - WindowManagerProxy.applyPrimaryFocusable(mSplits, !mMinimized); - mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable); - updateTouchable(); }); } + private boolean setHomeMinimized(final boolean minimized, boolean homeStackResizable, + WindowContainerTransaction wct) { + boolean transact = false; + + // Update minimized state + if (mMinimized != minimized) { + mMinimized = minimized; + wct.setFocusable(mSplits.mPrimary.token, !mMinimized); + transact = true; + } + + // Update home-stack resizability + if (mHomeStackResizable != homeStackResizable) { + mHomeStackResizable = homeStackResizable; + if (inSplitMode()) { + WindowManagerProxy.applyHomeTasksMinimized( + mSplitLayout, mSplits.mSecondary.token, wct); + transact = true; + } + } + + // Sync state to DividerView if it exists. + if (mView != null) { + mView.setMinimizedDockStack(minimized, getAnimDuration(), homeStackResizable); + } + updateTouchable(); + return transact; + } + void setAdjustedForIme(boolean adjustedForIme) { if (mAdjustedForIme == adjustedForIme) { return; @@ -646,46 +651,30 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } void ensureMinimizedSplit() { - final boolean wasMinimized = mMinimized; - mMinimized = true; - setHomeStackResizable(mSplits.mSecondary.isResizable()); - WindowManagerProxy.applyPrimaryFocusable(mSplits, false /* focusable */); + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (setHomeMinimized(true, mSplits.mSecondary.isResizable(), wct)) { + WindowManagerProxy.applyContainerTransaction(wct); + } if (!inSplitMode()) { // Wasn't in split-mode yet, so enter now. if (DEBUG) { Log.d(TAG, " entering split mode with minimized=true"); } updateVisibility(true /* visible */); - } else if (!wasMinimized) { - if (DEBUG) { - Log.d(TAG, " in split mode, but minimizing "); - } - // Was already in split-mode, update just minimized state. - updateMinimizedDockedStack(mMinimized, getAnimDuration(), - mHomeStackResizable); } } void ensureNormalSplit() { - if (mMinimized) { - WindowManagerProxy.applyPrimaryFocusable(mSplits, true /* focusable */); + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (setHomeMinimized(false /* minimized */, mHomeStackResizable, wct)) { + WindowManagerProxy.applyContainerTransaction(wct); } if (!inSplitMode()) { // Wasn't in split-mode, so enter now. if (DEBUG) { Log.d(TAG, " enter split mode unminimized "); } - mMinimized = false; updateVisibility(true /* visible */); } - if (mMinimized) { - // Was in minimized state, so leave that. - if (DEBUG) { - Log.d(TAG, " in split mode already, but unminimizing "); - } - mMinimized = false; - updateMinimizedDockedStack(mMinimized, getAnimDuration(), - mHomeStackResizable); - } } } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 477cbb7c7ad0..4114bb9d055d 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -165,6 +165,10 @@ public class DividerView extends FrameLayout implements OnTouchListener, // The view is removed or in the process of been removed from the system. private boolean mRemoved; + // Whether the surface for this view has been hidden regardless of actual visibility. This is + // used interact with keyguard. + private boolean mSurfaceHidden = false; + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -414,6 +418,10 @@ public class DividerView extends FrameLayout implements OnTouchListener, /** Unlike setVisible, this directly hides the surface without changing view visibility. */ void setHidden(boolean hidden) { + if (mSurfaceHidden == hidden) { + return; + } + mSurfaceHidden = hidden; post(() -> { final SurfaceControl sc = getWindowSurfaceControl(); if (sc == null) { @@ -430,6 +438,10 @@ public class DividerView extends FrameLayout implements OnTouchListener, }); } + boolean isHidden() { + return mSurfaceHidden; + } + public boolean startDragging(boolean animate, boolean touching) { cancelFlingAnimation(); if (touching) { @@ -1071,7 +1083,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, void setResizeDimLayer(Transaction t, boolean primary, float alpha) { SurfaceControl dim = primary ? mTiles.mPrimaryDim : mTiles.mSecondaryDim; - if (alpha <= 0.f) { + if (alpha <= 0.001f) { t.hide(dim); } else { t.setAlpha(dim, alpha); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java index 3020a25dfa47..729df3887915 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java @@ -88,6 +88,9 @@ public class DividerWindowManager { } public void setTouchable(boolean touchable) { + if (mView == null) { + return; + } boolean changed = false; if (!touchable && (mLp.flags & FLAG_NOT_TOUCHABLE) == 0) { mLp.flags |= FLAG_NOT_TOUCHABLE; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java index 167c33abac6e..fea57a320d04 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.graphics.Rect; @@ -137,17 +138,13 @@ public class WindowManagerProxy { return resizable; } - static void applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent) { - applyHomeTasksMinimized(layout, parent, null /* transaction */); - } - /** * Assign a fixed override-bounds to home tasks that reflect their geometry while the primary * split is minimized. This actually "sticks out" of the secondary split area, but when in * minimized mode, the secondary split gets a 'negative' crop to expose it. */ static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent, - WindowContainerTransaction t) { + @NonNull WindowContainerTransaction wct) { // Resize the home/recents stacks to the larger minimized-state size final Rect homeBounds; final ArrayList<IWindowContainer> homeStacks = new ArrayList<>(); @@ -158,19 +155,9 @@ public class WindowManagerProxy { homeBounds = new Rect(0, 0, layout.mDisplayLayout.width(), layout.mDisplayLayout.height()); } - WindowContainerTransaction wct = t != null ? t : new WindowContainerTransaction(); for (int i = homeStacks.size() - 1; i >= 0; --i) { wct.setBounds(homeStacks.get(i), homeBounds); } - if (t != null) { - return isHomeResizable; - } - try { - ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct, - null /* organizer */); - } catch (RemoteException e) { - Log.e(TAG, "Failed to resize home stacks ", e); - } return isHomeResizable; } @@ -301,10 +288,8 @@ public class WindowManagerProxy { } } - static void applyPrimaryFocusable(SplitScreenTaskOrganizer splits, boolean focusable) { + static void applyContainerTransaction(WindowContainerTransaction wct) { try { - WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setFocusable(splits.mPrimary.token, focusable); ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct, null /* organizer */); } catch (RemoteException e) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt index 88888d10e283..269a7a59f1b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.systemui.statusbar.notification.interruption +package com.android.systemui.statusbar.notification import android.content.Context import android.media.MediaMetadata @@ -24,7 +24,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.KeyguardBypassController diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java index b5725029450d..df21f0b21ec1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.interruption; +package com.android.systemui.statusbar.notification; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; @@ -27,9 +27,6 @@ import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -42,7 +39,7 @@ public class NotificationAlertingManager { private final NotificationRemoteInputManager mRemoteInputManager; private final VisualStabilityManager mVisualStabilityManager; private final StatusBarStateController mStatusBarStateController; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final NotificationListener mNotificationListener; private HeadsUpManager mHeadsUpManager; @@ -55,13 +52,13 @@ public class NotificationAlertingManager { NotificationRemoteInputManager remoteInputManager, VisualStabilityManager visualStabilityManager, StatusBarStateController statusBarStateController, - NotificationInterruptStateProvider notificationInterruptionStateProvider, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationListener notificationListener, HeadsUpManager headsUpManager) { mRemoteInputManager = remoteInputManager; mVisualStabilityManager = visualStabilityManager; mStatusBarStateController = statusBarStateController; - mNotificationInterruptStateProvider = notificationInterruptionStateProvider; + mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mNotificationListener = notificationListener; mHeadsUpManager = headsUpManager; @@ -97,7 +94,7 @@ public class NotificationAlertingManager { if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) { // Possible for shouldHeadsUp to change between the inflation starting and ending. // If it does and we no longer need to heads up, we should free the view. - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { + if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { mHeadsUpManager.showNotification(entry); if (!mStatusBarStateController.isDozing()) { // Mark as seen immediately @@ -112,7 +109,7 @@ public class NotificationAlertingManager { private void updateAlertState(NotificationEntry entry) { boolean alertAgain = alertAgain(entry, entry.getSbn().getNotification()); // includes check for whether this notification should be filtered: - boolean shouldAlert = mNotificationInterruptStateProvider.shouldHeadsUp(entry); + boolean shouldAlert = mNotificationInterruptionStateProvider.shouldHeadsUp(entry); final boolean wasAlerting = mHeadsUpManager.isAlerting(entry.getKey()); if (wasAlerting) { if (shouldAlert) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java index 46d50441c06b..bbf2dde80040 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java @@ -14,35 +14,33 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.interruption; +package com.android.systemui.statusbar.notification; import static com.android.systemui.statusbar.StatusBarState.SHADE; import android.app.NotificationManager; -import android.content.ContentResolver; +import android.content.Context; import android.database.ContentObserver; import android.hardware.display.AmbientDisplayConfiguration; -import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; +import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.Dependency; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.HeadsUpManager; -import java.util.ArrayList; -import java.util.List; - import javax.inject.Inject; import javax.inject.Singleton; @@ -50,84 +48,120 @@ import javax.inject.Singleton; * Provides heads-up and pulsing state for notification entries. */ @Singleton -public class NotificationInterruptStateProviderImpl implements NotificationInterruptStateProvider { +public class NotificationInterruptionStateProvider { + private static final String TAG = "InterruptionStateProvider"; - private static final boolean DEBUG = true; //false; + private static final boolean DEBUG = false; private static final boolean DEBUG_HEADS_UP = true; private static final boolean ENABLE_HEADS_UP = true; private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; - private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>(); private final StatusBarStateController mStatusBarStateController; private final NotificationFilter mNotificationFilter; - private final ContentResolver mContentResolver; + private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; + + private final Context mContext; private final PowerManager mPowerManager; private final IDreamManager mDreamManager; - private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; private final BatteryController mBatteryController; - private final ContentObserver mHeadsUpObserver; + + private NotificationPresenter mPresenter; private HeadsUpManager mHeadsUpManager; + private HeadsUpSuppressor mHeadsUpSuppressor; + private ContentObserver mHeadsUpObserver; @VisibleForTesting protected boolean mUseHeadsUp = false; + private boolean mDisableNotificationAlerts; @Inject - public NotificationInterruptStateProviderImpl( - ContentResolver contentResolver, + public NotificationInterruptionStateProvider(Context context, NotificationFilter filter, + StatusBarStateController stateController, BatteryController batteryController) { + this(context, + (PowerManager) context.getSystemService(Context.POWER_SERVICE), + IDreamManager.Stub.asInterface( + ServiceManager.checkService(DreamService.DREAM_SERVICE)), + new AmbientDisplayConfiguration(context), + filter, + batteryController, + stateController); + } + + @VisibleForTesting + protected NotificationInterruptionStateProvider( + Context context, PowerManager powerManager, IDreamManager dreamManager, AmbientDisplayConfiguration ambientDisplayConfiguration, NotificationFilter notificationFilter, BatteryController batteryController, - StatusBarStateController statusBarStateController, - HeadsUpManager headsUpManager, - @Main Handler mainHandler) { - mContentResolver = contentResolver; + StatusBarStateController statusBarStateController) { + mContext = context; mPowerManager = powerManager; mDreamManager = dreamManager; mBatteryController = batteryController; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mNotificationFilter = notificationFilter; mStatusBarStateController = statusBarStateController; - mHeadsUpManager = headsUpManager; - mHeadsUpObserver = new ContentObserver(mainHandler) { - @Override - public void onChange(boolean selfChange) { - boolean wasUsing = mUseHeadsUp; - mUseHeadsUp = ENABLE_HEADS_UP - && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( - mContentResolver, - Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, - Settings.Global.HEADS_UP_OFF); - Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); - if (wasUsing != mUseHeadsUp) { - if (!mUseHeadsUp) { - Log.d(TAG, "dismissing any existing heads up notification on " - + "disable event"); - mHeadsUpManager.releaseAllImmediately(); + } + + /** Sets up late-binding dependencies for this component. */ + public void setUpWithPresenter( + NotificationPresenter notificationPresenter, + HeadsUpManager headsUpManager, + HeadsUpSuppressor headsUpSuppressor) { + setUpWithPresenter(notificationPresenter, headsUpManager, headsUpSuppressor, + new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) { + @Override + public void onChange(boolean selfChange) { + boolean wasUsing = mUseHeadsUp; + mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts + && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, + Settings.Global.HEADS_UP_OFF); + Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); + if (wasUsing != mUseHeadsUp) { + if (!mUseHeadsUp) { + Log.d(TAG, + "dismissing any existing heads up notification on disable" + + " event"); + mHeadsUpManager.releaseAllImmediately(); + } + } } - } - } - }; + }); + } + + /** Sets up late-binding dependencies for this component. */ + public void setUpWithPresenter( + NotificationPresenter notificationPresenter, + HeadsUpManager headsUpManager, + HeadsUpSuppressor headsUpSuppressor, + ContentObserver observer) { + mPresenter = notificationPresenter; + mHeadsUpManager = headsUpManager; + mHeadsUpSuppressor = headsUpSuppressor; + mHeadsUpObserver = observer; if (ENABLE_HEADS_UP) { - mContentResolver.registerContentObserver( + mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true, mHeadsUpObserver); - mContentResolver.registerContentObserver( + mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, mHeadsUpObserver); } mHeadsUpObserver.onChange(true); // set up } - @Override - public void addSuppressor(NotificationInterruptSuppressor suppressor) { - mSuppressors.add(suppressor); - } - - @Override + /** + * Whether the notification should appear as a bubble with a fly-out on top of the screen. + * + * @param entry the entry to check + * @return true if the entry should bubble up, false otherwise + */ public boolean shouldBubbleUp(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); @@ -167,8 +201,12 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return true; } - - @Override + /** + * Whether the notification should peek in from the top and alert the user. + * + * @param entry the entry to check + * @return true if the entry should heads up, false otherwise + */ public boolean shouldHeadsUp(NotificationEntry entry) { if (mStatusBarStateController.isDozing()) { return shouldHeadsUpWhenDozing(entry); @@ -177,17 +215,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } } - /** - * When an entry was added, should we launch its fullscreen intent? Examples are Alarms or - * incoming calls. - */ - @Override - public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) { - return entry.getSbn().getNotification().fullScreenIntent != null - && (!shouldHeadsUp(entry) - || mStatusBarStateController.getState() == StatusBarState.KEYGUARD); - } - private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) { StatusBarNotification sbn = entry.getSbn(); @@ -244,15 +271,13 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } - for (int i = 0; i < mSuppressors.size(); i++) { - if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) { - if (DEBUG_HEADS_UP) { - Log.d(TAG, "No heads up: aborted by suppressor: " - + mSuppressors.get(i).getName() + " sbnKey=" + sbn.getKey()); - } - return false; + if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) { + if (DEBUG_HEADS_UP) { + Log.d(TAG, "No heads up: aborted by suppressor: " + sbn.getKey()); } + return false; } + return true; } @@ -300,7 +325,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } return false; } - return true; + return true; } /** @@ -309,7 +334,8 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter * @param entry the entry to check * @return true if these checks pass, false if the notification should not alert */ - private boolean canAlertCommon(NotificationEntry entry) { + @VisibleForTesting + public boolean canAlertCommon(NotificationEntry entry) { StatusBarNotification sbn = entry.getSbn(); if (mNotificationFilter.shouldFilterOut(entry)) { @@ -326,16 +352,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } return false; } - - for (int i = 0; i < mSuppressors.size(); i++) { - if (mSuppressors.get(i).suppressInterruptions(entry)) { - if (DEBUG_HEADS_UP) { - Log.d(TAG, "No alerting: aborted by suppressor: " - + mSuppressors.get(i).getName() + " sbnKey=" + sbn.getKey()); - } - return false; - } - } return true; } @@ -345,17 +361,15 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter * @param entry the entry to check * @return true if these checks pass, false if the notification should not alert */ - private boolean canAlertAwakeCommon(NotificationEntry entry) { + @VisibleForTesting + public boolean canAlertAwakeCommon(NotificationEntry entry) { StatusBarNotification sbn = entry.getSbn(); - for (int i = 0; i < mSuppressors.size(); i++) { - if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) { - if (DEBUG_HEADS_UP) { - Log.d(TAG, "No alerting: aborted by suppressor: " - + mSuppressors.get(i).getName() + " sbnKey=" + sbn.getKey()); - } - return false; + if (mPresenter.isDeviceInVrMode()) { + if (DEBUG_HEADS_UP) { + Log.d(TAG, "No alerting: no huns or vr mode"); } + return false; } if (isSnoozedPackage(sbn)) { @@ -378,4 +392,54 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter private boolean isSnoozedPackage(StatusBarNotification sbn) { return mHeadsUpManager.isSnoozed(sbn.getPackageName()); } + + /** Sets whether to disable all alerts. */ + public void setDisableNotificationAlerts(boolean disableNotificationAlerts) { + mDisableNotificationAlerts = disableNotificationAlerts; + mHeadsUpObserver.onChange(true); + } + + /** Whether all alerts are disabled. */ + @VisibleForTesting + public boolean areNotificationAlertsDisabled() { + return mDisableNotificationAlerts; + } + + /** Whether HUNs should be used. */ + @VisibleForTesting + public boolean getUseHeadsUp() { + return mUseHeadsUp; + } + + protected NotificationPresenter getPresenter() { + return mPresenter; + } + + /** + * When an entry was added, should we launch its fullscreen intent? Examples are Alarms or + * incoming calls. + * + * @param entry the entry that was added + * @return {@code true} if we should launch the full screen intent + */ + public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) { + return entry.getSbn().getNotification().fullScreenIntent != null + && (!shouldHeadsUp(entry) + || mStatusBarStateController.getState() == StatusBarState.KEYGUARD); + } + + /** A component which can suppress heads-up notifications due to the overall state of the UI. */ + public interface HeadsUpSuppressor { + /** + * Returns false if the provided notification is ineligible for heads-up according to this + * component. + * + * @param entry entry of the notification that might be heads upped + * @param sbn notification that might be heads upped + * @return false if the notification can not be heads upped + */ + boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn); + + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 27476964b9af..8a23e3796e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification import android.animation.ObjectAnimator +import android.content.Context import android.util.FloatProperty import com.android.systemui.Interpolators import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -25,10 +26,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.PanelExpansionListener -import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import javax.inject.Inject @@ -36,14 +37,15 @@ import javax.inject.Singleton @Singleton class NotificationWakeUpCoordinator @Inject constructor( - private val mHeadsUpManager: HeadsUpManager, - private val statusBarStateController: StatusBarStateController, - private val bypassController: KeyguardBypassController, - private val dozeParameters: DozeParameters -) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener { + private val mHeadsUpManagerPhone: HeadsUpManagerPhone, + private val statusBarStateController: StatusBarStateController, + private val bypassController: KeyguardBypassController, + private val dozeParameters: DozeParameters) + : OnHeadsUpChangedListener, StatusBarStateController.StateListener, + PanelExpansionListener { - private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>( - "notificationVisibility") { + private val mNotificationVisibility + = object : FloatProperty<NotificationWakeUpCoordinator>("notificationVisibility") { override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) { coordinator.setVisibilityAmount(value) @@ -76,10 +78,10 @@ class NotificationWakeUpCoordinator @Inject constructor( field = value willWakeUp = false if (value) { - if (mNotificationsVisible && !mNotificationsVisibleForExpansion && - !bypassController.bypassEnabled) { + if (mNotificationsVisible && !mNotificationsVisibleForExpansion + && !bypassController.bypassEnabled) { // We're waking up while pulsing, let's make sure the animation looks nice - mStackScroller.wakeUpFromPulse() + mStackScroller.wakeUpFromPulse(); } if (bypassController.bypassEnabled && !mNotificationsVisible) { // Let's make sure our huns become visible once we are waking up in case @@ -98,7 +100,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } private var collapsedEnoughToHide: Boolean = false - lateinit var iconAreaController: NotificationIconAreaController + lateinit var iconAreaController : NotificationIconAreaController var pulsing: Boolean = false set(value) { @@ -130,8 +132,8 @@ class NotificationWakeUpCoordinator @Inject constructor( var canShow = pulsing if (bypassController.bypassEnabled) { // We also allow pulsing on the lock screen! - canShow = canShow || (wakingUp || willWakeUp || fullyAwake) && - statusBarStateController.state == StatusBarState.KEYGUARD + canShow = canShow || (wakingUp || willWakeUp || fullyAwake) + && statusBarStateController.state == StatusBarState.KEYGUARD // We want to hide the notifications when collapsed too much if (collapsedEnoughToHide) { canShow = false @@ -141,7 +143,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } init { - mHeadsUpManager.addListener(this) + mHeadsUpManagerPhone.addListener(this) statusBarStateController.addCallback(this) addListener(object : WakeUpListener { override fun onFullyHiddenChanged(isFullyHidden: Boolean) { @@ -153,7 +155,7 @@ class NotificationWakeUpCoordinator @Inject constructor( increaseSpeed = false) } } - }) + }); } fun setStackScroller(stackScroller: NotificationStackScrollLayout) { @@ -176,55 +178,46 @@ class NotificationWakeUpCoordinator @Inject constructor( * @param animate should this change be animated * @param increaseSpeed should the speed be increased of the animation */ - fun setNotificationsVisibleForExpansion( - visible: Boolean, - animate: Boolean, - increaseSpeed: Boolean - ) { + fun setNotificationsVisibleForExpansion(visible: Boolean, animate: Boolean, + increaseSpeed: Boolean) { mNotificationsVisibleForExpansion = visible updateNotificationVisibility(animate, increaseSpeed) if (!visible && mNotificationsVisible) { // If we stopped expanding and we're still visible because we had a pulse that hasn't // times out, let's release them all to make sure were not stuck in a state where // notifications are visible - mHeadsUpManager.releaseAllImmediately() + mHeadsUpManagerPhone.releaseAllImmediately() } } fun addListener(listener: WakeUpListener) { - wakeUpListeners.add(listener) + wakeUpListeners.add(listener); } fun removeListener(listener: WakeUpListener) { - wakeUpListeners.remove(listener) + wakeUpListeners.remove(listener); } - private fun updateNotificationVisibility( - animate: Boolean, - increaseSpeed: Boolean - ) { + private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) { // TODO: handle Lockscreen wakeup for bypass when we're not pulsing anymore - var visible = mNotificationsVisibleForExpansion || mHeadsUpManager.hasNotifications() + var visible = mNotificationsVisibleForExpansion || mHeadsUpManagerPhone.hasNotifications() visible = visible && canShowPulsingHuns if (!visible && mNotificationsVisible && (wakingUp || willWakeUp) && mDozeAmount != 0.0f) { // let's not make notifications invisible while waking up, otherwise the animation // is strange - return + return; } setNotificationsVisible(visible, animate, increaseSpeed) } - private fun setNotificationsVisible( - visible: Boolean, - animate: Boolean, - increaseSpeed: Boolean - ) { + private fun setNotificationsVisible(visible: Boolean, animate: Boolean, + increaseSpeed: Boolean) { if (mNotificationsVisible == visible) { return } mNotificationsVisible = visible - mVisibilityAnimator?.cancel() + mVisibilityAnimator?.cancel(); if (animate) { notifyAnimationStart(visible) startVisibilityAnimation(increaseSpeed) @@ -237,8 +230,8 @@ class NotificationWakeUpCoordinator @Inject constructor( if (updateDozeAmountIfBypass()) { return } - if (linear != 1.0f && linear != 0.0f && - (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) { + if (linear != 1.0f && linear != 0.0f + && (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) { // Let's notify the scroller that an animation started notifyAnimationStart(mLinearDozeAmount == 1.0f) } @@ -252,17 +245,17 @@ class NotificationWakeUpCoordinator @Inject constructor( mStackScroller.setDozeAmount(mDozeAmount) updateHideAmount() if (changed && linear == 0.0f) { - setNotificationsVisible(visible = false, animate = false, increaseSpeed = false) + setNotificationsVisible(visible = false, animate = false, increaseSpeed = false); setNotificationsVisibleForExpansion(visible = false, animate = false, increaseSpeed = false) } } override fun onStateChanged(newState: Int) { - updateDozeAmountIfBypass() + updateDozeAmountIfBypass(); if (bypassController.bypassEnabled && - newState == StatusBarState.KEYGUARD && state == StatusBarState.SHADE_LOCKED && - (!statusBarStateController.isDozing || shouldAnimateVisibility())) { + newState == StatusBarState.KEYGUARD && state == StatusBarState.SHADE_LOCKED + && (!statusBarStateController.isDozing || shouldAnimateVisibility())) { // We're leaving shade locked. Let's animate the notifications away setNotificationsVisible(visible = true, increaseSpeed = false, animate = false) setNotificationsVisible(visible = false, increaseSpeed = false, animate = true) @@ -273,23 +266,23 @@ class NotificationWakeUpCoordinator @Inject constructor( override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) { val collapsedEnough = expansion <= 0.9f if (collapsedEnough != this.collapsedEnoughToHide) { - val couldShowPulsingHuns = canShowPulsingHuns + val couldShowPulsingHuns = canShowPulsingHuns; this.collapsedEnoughToHide = collapsedEnough if (couldShowPulsingHuns && !canShowPulsingHuns) { updateNotificationVisibility(animate = true, increaseSpeed = true) - mHeadsUpManager.releaseAllImmediately() + mHeadsUpManagerPhone.releaseAllImmediately() } } } private fun updateDozeAmountIfBypass(): Boolean { if (bypassController.bypassEnabled) { - var amount = 1.0f - if (statusBarStateController.state == StatusBarState.SHADE || - statusBarStateController.state == StatusBarState.SHADE_LOCKED) { - amount = 0.0f + var amount = 1.0f; + if (statusBarStateController.state == StatusBarState.SHADE + || statusBarStateController.state == StatusBarState.SHADE_LOCKED) { + amount = 0.0f; } - setDozeAmount(amount, amount) + setDozeAmount(amount, amount) return true } return false @@ -307,7 +300,7 @@ class NotificationWakeUpCoordinator @Inject constructor( visibilityAnimator.setInterpolator(Interpolators.LINEAR) var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong() if (increaseSpeed) { - duration = (duration.toFloat() / 1.5F).toLong() + duration = (duration.toFloat() / 1.5F).toLong(); } visibilityAnimator.setDuration(duration) visibilityAnimator.start() @@ -318,7 +311,7 @@ class NotificationWakeUpCoordinator @Inject constructor( mLinearVisibilityAmount = visibilityAmount mVisibilityAmount = mVisibilityInterpolator.getInterpolation( visibilityAmount) - handleAnimationFinished() + handleAnimationFinished(); updateHideAmount() } @@ -329,7 +322,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } } - fun getWakeUpHeight(): Float { + fun getWakeUpHeight() : Float { return mStackScroller.wakeUpHeight } @@ -337,7 +330,7 @@ class NotificationWakeUpCoordinator @Inject constructor( val linearAmount = Math.min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount) val amount = Math.min(1.0f - mVisibilityAmount, mDozeAmount) mStackScroller.setHideAmount(linearAmount, amount) - notificationsFullyHidden = linearAmount == 1.0f + notificationsFullyHidden = linearAmount == 1.0f; } private fun notifyAnimationStart(awake: Boolean) { @@ -368,7 +361,7 @@ class NotificationWakeUpCoordinator @Inject constructor( // if we animate, we see the shelf briefly visible. Instead we fully animate // the notification and its background out animate = false - } else if (!wakingUp && !willWakeUp) { + } else if (!wakingUp && !willWakeUp){ // TODO: look that this is done properly and not by anyone else entry.setHeadsUpAnimatingAway(true) mEntrySetToClearWhenFinished.add(entry) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 4beeedecfdf5..e8a62e48e75e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -37,8 +37,8 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationClicker; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotifBindPipeline; @@ -66,7 +66,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private static final String TAG = "NotificationViewManager"; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final Context mContext; private final NotifBindPipeline mNotifBindPipeline; @@ -97,7 +97,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { StatusBarStateController statusBarStateController, NotificationGroupManager notificationGroupManager, NotificationGutsManager notificationGutsManager, - NotificationInterruptStateProvider notificationInterruptionStateProvider, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, Provider<RowInflaterTask> rowInflaterTaskProvider, ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) { mContext = context; @@ -106,7 +106,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { mMessagingUtil = notificationMessagingUtil; mNotificationRemoteInputManager = notificationRemoteInputManager; mNotificationLockscreenUserManager = notificationLockscreenUserManager; - mNotificationInterruptStateProvider = notificationInterruptionStateProvider; + mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mRowInflaterTaskProvider = rowInflaterTaskProvider; mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder; } @@ -243,7 +243,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); params.setUseLowPriority(entry.isAmbient()); - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { + if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); } //TODO: Replace this API with RowContentBindParams directly diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index cd6affdaffac..3c0ac7ef53fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -31,8 +31,10 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController; +import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; @@ -42,9 +44,6 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -57,7 +56,6 @@ import java.util.concurrent.Executor; import javax.inject.Singleton; -import dagger.Binds; import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -127,7 +125,7 @@ public interface NotificationsModule { NotificationRemoteInputManager remoteInputManager, VisualStabilityManager visualStabilityManager, StatusBarStateController statusBarStateController, - NotificationInterruptStateProvider notificationInterruptStateProvider, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationListener notificationListener, HeadsUpManager headsUpManager) { return new NotificationAlertingManager( @@ -135,7 +133,7 @@ public interface NotificationsModule { remoteInputManager, visualStabilityManager, statusBarStateController, - notificationInterruptStateProvider, + notificationInterruptionStateProvider, notificationListener, headsUpManager); } @@ -201,9 +199,4 @@ public interface NotificationsModule { NotificationEntryManager entryManager) { return featureFlags.isNewNotifPipelineRenderingEnabled() ? pipeline.get() : entryManager; } - - /** */ - @Binds - NotificationInterruptStateProvider bindNotificationInterruptStateProvider( - NotificationInterruptStateProviderImpl notificationInterruptStateProviderImpl); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java deleted file mode 100644 index 3292a8fcdb50..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.interruption; - -import com.android.systemui.statusbar.notification.collection.NotificationEntry; - -/** - * Provides bubble-up and heads-up state for notification entries. - * - * When a notification is heads-up when dozing, this is also called "pulsing." - */ -public interface NotificationInterruptStateProvider { - /** - * If the device is awake (not dozing): - * Whether the notification should peek in from the top and alert the user. - * - * If the device is dozing: - * Whether the notification should show the ambient view of the notification ("pulse"). - * - * @param entry the entry to check - * @return true if the entry should heads up, false otherwise - */ - boolean shouldHeadsUp(NotificationEntry entry); - - /** - * Whether the notification should appear as a bubble with a fly-out on top of the screen. - * - * @param entry the entry to check - * @return true if the entry should bubble up, false otherwise - */ - boolean shouldBubbleUp(NotificationEntry entry); - - /** - * Whether to launch the entry's full screen intent when the entry is added. - * - * @param entry the entry that was added - * @return {@code true} if we should launch the full screen intent - */ - boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry); - - /** - * Add a component that can suppress visual interruptions. - */ - void addSuppressor(NotificationInterruptSuppressor suppressor); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptSuppressor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptSuppressor.java deleted file mode 100644 index c19f8bd1994a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptSuppressor.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.interruption; - -import com.android.systemui.statusbar.notification.collection.NotificationEntry; - -/** A component which can suppress visual interruptions of notifications such as heads-up and - * bubble-up. - */ -public interface NotificationInterruptSuppressor { - /** - * A unique name to identify this suppressor. - */ - default String getName() { - return this.getClass().getName(); - } - - /** - * Returns true if the provided notification is, when the device is awake, ineligible for - * heads-up according to this component. - * - * @param entry entry of the notification that might heads-up - * @return true if the heads up interruption should be suppressed when the device is awake - */ - default boolean suppressAwakeHeadsUp(NotificationEntry entry) { - return false; - } - - /** - * Returns true if the provided notification is, when the device is awake, ineligible for - * heads-up or bubble-up according to this component. - * - * @param entry entry of the notification that might heads-up or bubble-up - * @return true if interruptions should be suppressed when the device is awake - */ - default boolean suppressAwakeInterruptions(NotificationEntry entry) { - return false; - } - - /** - * Returns true if the provided notification is, regardless of awake/dozing state, - * ineligible for heads-up or bubble-up according to this component. - * - * @param entry entry of the notification that might heads-up or bubble-up - * @return true if interruptions should be suppressed - */ - default boolean suppressInterruptions(NotificationEntry entry) { - return false; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 287ede48fa06..b3a62d8a4753 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -185,14 +185,15 @@ import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; +import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; +import com.android.systemui.statusbar.notification.NotificationAlertingManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -403,9 +404,10 @@ public class StatusBar extends SystemUI implements DemoMode, private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final NotificationViewHierarchyManager mViewHierarchyManager; private final KeyguardViewMediator mKeyguardViewMediator; - protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private final NotificationAlertingManager mNotificationAlertingManager; // for disabling the status bar private int mDisabled1 = 0; @@ -619,9 +621,10 @@ public class StatusBar extends SystemUI implements DemoMode, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationInterruptStateProvider notificationInterruptStateProvider, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, + NotificationAlertingManager notificationAlertingManager, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @UiBackground Executor uiBgExecutor, @@ -698,9 +701,10 @@ public class StatusBar extends SystemUI implements DemoMode, mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler; mGutsManager = notificationGutsManager; mNotificationLogger = notificationLogger; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; + mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mViewHierarchyManager = notificationViewHierarchyManager; mKeyguardViewMediator = keyguardViewMediator; + mNotificationAlertingManager = notificationAlertingManager; mDisplayMetrics = displayMetrics; mMetricsLogger = metricsLogger; mUiBgExecutor = uiBgExecutor; @@ -1234,9 +1238,9 @@ public class StatusBar extends SystemUI implements DemoMode, mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController, mHeadsUpManager, mNotificationShadeWindowView, mStackScroller, mDozeScrimController, mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController, - mKeyguardStateController, mKeyguardIndicationController, - this /* statusBar */, mShadeController, mCommandQueue, mInitController, - mNotificationInterruptStateProvider); + mNotificationAlertingManager, mKeyguardStateController, + mKeyguardIndicationController, + this /* statusBar */, mShadeController, mCommandQueue, mInitController); mNotificationShelf.setOnActivatedListener(mPresenter); mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController); @@ -1585,9 +1589,8 @@ public class StatusBar extends SystemUI implements DemoMode, } if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { - if (areNotificationAlertsDisabled()) { - mHeadsUpManager.releaseAllImmediately(); - } + mNotificationInterruptionStateProvider.setDisableNotificationAlerts( + (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0); } if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) { @@ -1602,10 +1605,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - boolean areNotificationAlertsDisabled() { - return (mDisabled1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; - } - protected H createHandler() { return new StatusBar.H(); } @@ -2333,7 +2332,7 @@ public class StatusBar extends SystemUI implements DemoMode, void checkBarModes() { if (mDemoMode) return; - if (mNotificationShadeWindowViewController != null) { + if (mNotificationShadeWindowViewController != null && getStatusBarTransitions() != null) { checkBarMode(mStatusBarMode, mStatusBarWindowState, getStatusBarTransitions()); } mNavigationBarController.checkNavBarModes(mDisplayId); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 53fa2630a9c3..e1a20b6ac5d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -68,12 +68,12 @@ import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.HeadsUpUtil; @@ -108,7 +108,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final NotifCollection mNotifCollection; private final FeatureFlags mFeatureFlags; private final StatusBarStateController mStatusBarStateController; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final MetricsLogger mMetricsLogger; private final Context mContext; private final NotificationPanelViewController mNotificationPanel; @@ -142,7 +142,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit NotificationLockscreenUserManager lockscreenUserManager, ShadeController shadeController, StatusBar statusBar, KeyguardStateController keyguardStateController, - NotificationInterruptStateProvider notificationInterruptStateProvider, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils, Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor, ActivityIntentHelper activityIntentHelper, BubbleController bubbleController, @@ -167,7 +167,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mActivityStarter = activityStarter; mEntryManager = entryManager; mStatusBarStateController = statusBarStateController; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; + mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mMetricsLogger = metricsLogger; mAssistManagerLazy = assistManagerLazy; mGroupManager = groupManager; @@ -436,7 +436,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } private void handleFullScreenIntent(NotificationEntry entry) { - if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) { + if (mNotificationInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) { if (shouldSuppressFullScreenIntent(entry)) { if (DEBUG) { Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.getKey()); @@ -603,7 +603,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final ActivityIntentHelper mActivityIntentHelper; private final BubbleController mBubbleController; private NotificationPanelViewController mNotificationPanelViewController; - private NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final ShadeController mShadeController; private NotificationPresenter mNotificationPresenter; private ActivityLaunchAnimator mActivityLaunchAnimator; @@ -626,7 +626,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit NotificationGroupManager groupManager, NotificationLockscreenUserManager lockscreenUserManager, KeyguardStateController keyguardStateController, - NotificationInterruptStateProvider notificationInterruptStateProvider, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils, @Main Handler mainThreadHandler, @@ -654,7 +654,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mGroupManager = groupManager; mLockscreenUserManager = lockscreenUserManager; mKeyguardStateController = keyguardStateController; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; + mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mMetricsLogger = metricsLogger; mLockPatternUtils = lockPatternUtils; mMainThreadHandler = mainThreadHandler; @@ -712,7 +712,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mShadeController, mStatusBar, mKeyguardStateController, - mNotificationInterruptStateProvider, + mNotificationInterruptionStateProvider, mMetricsLogger, mLockPatternUtils, mMainThreadHandler, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 79cea91b8612..30d6b5079166 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -60,13 +60,13 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -98,6 +98,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class); private final NotificationEntryManager mEntryManager = Dependency.get(NotificationEntryManager.class); + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = + Dependency.get(NotificationInterruptionStateProvider.class); private final NotificationMediaManager mMediaManager = Dependency.get(NotificationMediaManager.class); private final VisualStabilityManager mVisualStabilityManager = @@ -138,13 +140,13 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, ScrimController scrimController, ActivityLaunchAnimator activityLaunchAnimator, DynamicPrivacyController dynamicPrivacyController, + NotificationAlertingManager notificationAlertingManager, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, StatusBar statusBar, ShadeController shadeController, CommandQueue commandQueue, - InitController initController, - NotificationInterruptStateProvider notificationInterruptStateProvider) { + InitController initController) { mContext = context; mKeyguardStateController = keyguardStateController; mNotificationPanel = panel; @@ -214,7 +216,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mEntryManager.addNotificationLifetimeExtender(mGutsManager); mEntryManager.addNotificationLifetimeExtenders( remoteInputManager.getLifetimeExtenders()); - notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor); + mNotificationInterruptionStateProvider.setUpWithPresenter( + this, mHeadsUpManager, this::canHeadsUp); mLockscreenUserManager.setUpWithPresenter(this); mMediaManager.setUpWithPresenter(this); mVisualStabilityManager.setUpWithPresenter(this); @@ -333,6 +336,39 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, return mEntryManager.hasActiveNotifications(); } + public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) { + if (mStatusBar.isOccluded()) { + boolean devicePublic = mLockscreenUserManager. + isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); + boolean userPublic = devicePublic + || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); + boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); + if (userPublic && needsRedaction) { + // TODO(b/135046837): we can probably relax this with dynamic privacy + return false; + } + } + + if (!mCommandQueue.panelsEnabled()) { + if (DEBUG) { + Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey()); + } + return false; + } + + if (sbn.getNotification().fullScreenIntent != null) { + if (mAccessibilityManager.isTouchExplorationEnabled()) { + if (DEBUG) Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey()); + return false; + } else { + // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent + return !mKeyguardStateController.isShowing() + || mStatusBar.isOccluded(); + } + } + return true; + } + @Override public void onUserSwitched(int newUserId) { // Begin old BaseStatusBar.userSwitched @@ -471,66 +507,4 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } } }; - - private final NotificationInterruptSuppressor mInterruptSuppressor = - new NotificationInterruptSuppressor() { - @Override - public String getName() { - return TAG; - } - - @Override - public boolean suppressAwakeHeadsUp(NotificationEntry entry) { - final StatusBarNotification sbn = entry.getSbn(); - if (mStatusBar.isOccluded()) { - boolean devicePublic = mLockscreenUserManager - .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); - boolean userPublic = devicePublic - || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); - boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); - if (userPublic && needsRedaction) { - // TODO(b/135046837): we can probably relax this with dynamic privacy - return true; - } - } - - if (!mCommandQueue.panelsEnabled()) { - if (DEBUG) { - Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey()); - } - return true; - } - - if (sbn.getNotification().fullScreenIntent != null) { - // we don't allow head-up on the lockscreen (unless there's a - // "showWhenLocked" activity currently showing) if - // the potential HUN has a fullscreen intent - if (mKeyguardStateController.isShowing() && !mStatusBar.isOccluded()) { - if (DEBUG) { - Log.d(TAG, "No heads up: entry has fullscreen intent on lockscreen " - + sbn.getKey()); - } - return true; - } - - if (mAccessibilityManager.isTouchExplorationEnabled()) { - if (DEBUG) { - Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey()); - } - return true; - } - } - return false; - } - - @Override - public boolean suppressAwakeInterruptions(NotificationEntry entry) { - return isDeviceInVrMode(); - } - - @Override - public boolean suppressInterruptions(NotificationEntry entry) { - return mStatusBar.areNotificationAlertsDisabled(); - } - }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 824e0f0d4295..eec8d50f00de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -56,12 +56,13 @@ import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.NotificationAlertingManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -138,9 +139,10 @@ public interface StatusBarPhoneModule { RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationInterruptStateProvider notificationInterruptStateProvider, + NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, + NotificationAlertingManager notificationAlertingManager, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @UiBackground Executor uiBgExecutor, @@ -216,9 +218,10 @@ public interface StatusBarPhoneModule { remoteInputQuickSettingsDisabler, notificationGutsManager, notificationLogger, - notificationInterruptStateProvider, + notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, + notificationAlertingManager, displayMetrics, metricsLogger, uiBgExecutor, diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt index 2276ba19e432..812a1e4bc121 100644 --- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt @@ -358,7 +358,7 @@ abstract class MagnetizedObject<T : Any>( targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf cancelAnimations() magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!) - animateStuckToTarget(targetObjectIsInMagneticFieldOf!!, velX, velY, false) + animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false) vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) } else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 977d0bbd0004..742e652a7189 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -45,11 +45,7 @@ import android.app.IActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.content.res.Resources; -import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.face.FaceManager; -import android.os.Handler; -import android.os.PowerManager; -import android.service.dreams.IDreamManager; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -65,12 +61,14 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -229,17 +227,15 @@ public class BubbleControllerTest extends SysuiTestCase { mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); - TestableNotificationInterruptStateProviderImpl interruptionStateProvider = - new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), - mock(PowerManager.class), - mock(IDreamManager.class), - mock(AmbientDisplayConfiguration.class), + TestableNotificationInterruptionStateProvider interruptionStateProvider = + new TestableNotificationInterruptionStateProvider(mContext, mock(NotificationFilter.class), mock(StatusBarStateController.class), - mock(BatteryController.class), - mock(HeadsUpManager.class), - mock(Handler.class) - ); + mock(BatteryController.class)); + interruptionStateProvider.setUpWithPresenter( + mock(NotificationPresenter.class), + mock(HeadsUpManager.class), + mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); mBubbleData = new BubbleData(mContext); when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false); mBubbleController = new TestableBubbleController( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 7fc83dac0280..22ef3f34cb4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -41,11 +41,7 @@ import android.app.IActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.content.res.Resources; -import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.face.FaceManager; -import android.os.Handler; -import android.os.PowerManager; -import android.service.dreams.IDreamManager; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -61,10 +57,12 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; @@ -218,17 +216,15 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); - TestableNotificationInterruptStateProviderImpl interruptionStateProvider = - new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), - mock(PowerManager.class), - mock(IDreamManager.class), - mock(AmbientDisplayConfiguration.class), + TestableNotificationInterruptionStateProvider interruptionStateProvider = + new TestableNotificationInterruptionStateProvider(mContext, mock(NotificationFilter.class), mock(StatusBarStateController.class), - mock(BatteryController.class), - mock(HeadsUpManager.class), - mock(Handler.class) - ); + mock(BatteryController.class)); + interruptionStateProvider.setUpWithPresenter( + mock(NotificationPresenter.class), + mock(HeadsUpManager.class), + mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); mBubbleData = new BubbleData(mContext); when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true); mBubbleController = new TestableBubbleController( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java index d3d90c408468..de1fb41ddbbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java @@ -23,8 +23,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; @@ -44,7 +44,7 @@ public class TestableBubbleController extends BubbleController { ShadeController shadeController, BubbleData data, ConfigurationController configurationController, - NotificationInterruptStateProvider interruptionStateProvider, + NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController, NotificationLockscreenUserManager lockscreenUserManager, NotificationGroupManager groupManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptStateProviderImpl.java deleted file mode 100644 index 17dc76b38a56..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptStateProviderImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.bubbles; - -import android.content.ContentResolver; -import android.hardware.display.AmbientDisplayConfiguration; -import android.os.Handler; -import android.os.PowerManager; -import android.service.dreams.IDreamManager; - -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.notification.NotificationFilter; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.HeadsUpManager; - -public class TestableNotificationInterruptStateProviderImpl - extends NotificationInterruptStateProviderImpl { - - TestableNotificationInterruptStateProviderImpl( - ContentResolver contentResolver, - PowerManager powerManager, - IDreamManager dreamManager, - AmbientDisplayConfiguration ambientDisplayConfiguration, - NotificationFilter filter, - StatusBarStateController statusBarStateController, - BatteryController batteryController, - HeadsUpManager headsUpManager, - Handler mainHandler) { - super(contentResolver, - powerManager, - dreamManager, - ambientDisplayConfiguration, - filter, - batteryController, - statusBarStateController, - headsUpManager, - mainHandler); - mUseHeadsUp = true; - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java new file mode 100644 index 000000000000..5d192b2071b5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java @@ -0,0 +1,35 @@ +/* + * 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.bubbles; + +import android.content.Context; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.policy.BatteryController; + +public class TestableNotificationInterruptionStateProvider + extends NotificationInterruptionStateProvider { + + TestableNotificationInterruptionStateProvider(Context context, + NotificationFilter filter, StatusBarStateController controller, + BatteryController batteryController) { + super(context, filter, controller, batteryController); + mUseHeadsUp = true; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index 183dde87bdf7..a8c4851585db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -340,6 +340,8 @@ class ControlsControllerImplTest : SysuiTestCase() { controlLoadCallbackCaptor.value.error("") + delayableExecutor.runAllReady() + assertTrue(loaded) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java index f9c62e1ab604..1693e7f4df7f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.statusbar.notification.interruption; +package com.android.systemui.statusbar; import static android.app.Notification.FLAG_BUBBLE; @@ -30,14 +30,15 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.graphics.drawable.Icon; import android.hardware.display.AmbientDisplayConfiguration; -import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.service.dreams.IDreamManager; @@ -49,6 +50,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.policy.BatteryController; @@ -66,7 +68,7 @@ import org.mockito.MockitoAnnotations; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { +public class NotificationInterruptionStateProviderTest extends SysuiTestCase { @Mock PowerManager mPowerManager; @@ -79,36 +81,38 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @Mock StatusBarStateController mStatusBarStateController; @Mock + NotificationPresenter mPresenter; + @Mock HeadsUpManager mHeadsUpManager; @Mock - BatteryController mBatteryController; + NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor; @Mock - Handler mMockHandler; + BatteryController mBatteryController; - private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider; + private NotificationInterruptionStateProvider mNotifInterruptionStateProvider; @Before public void setup() { MockitoAnnotations.initMocks(this); mNotifInterruptionStateProvider = - new NotificationInterruptStateProviderImpl( - mContext.getContentResolver(), + new TestableNotificationInterruptionStateProvider(mContext, mPowerManager, mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter, - mBatteryController, mStatusBarStateController, - mHeadsUpManager, - mMockHandler); + mBatteryController); - mNotifInterruptionStateProvider.mUseHeadsUp = true; + mNotifInterruptionStateProvider.setUpWithPresenter( + mPresenter, + mHeadsUpManager, + mHeadsUpSuppressor); } /** * Sets up the state such that any requests to - * {@link NotificationInterruptStateProviderImpl#canAlertCommon(NotificationEntry)} will + * {@link NotificationInterruptionStateProvider#canAlertCommon(NotificationEntry)} will * pass as long its provided NotificationEntry fulfills group suppression check. */ private void ensureStateForAlertCommon() { @@ -117,16 +121,17 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { /** * Sets up the state such that any requests to - * {@link NotificationInterruptStateProviderImpl#canAlertAwakeCommon(NotificationEntry)} will + * {@link NotificationInterruptionStateProvider#canAlertAwakeCommon(NotificationEntry)} will * pass as long its provided NotificationEntry fulfills launch fullscreen check. */ private void ensureStateForAlertAwakeCommon() { + when(mPresenter.isDeviceInVrMode()).thenReturn(false); when(mHeadsUpManager.isSnoozed(any())).thenReturn(false); } /** * Sets up the state such that any requests to - * {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will + * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will * pass as long its provided NotificationEntry fulfills importance & DND checks. */ private void ensureStateForHeadsUpWhenAwake() throws RemoteException { @@ -136,11 +141,12 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { when(mStatusBarStateController.isDozing()).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); when(mPowerManager.isScreenOn()).thenReturn(true); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true); } /** * Sets up the state such that any requests to - * {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will + * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will * pass as long its provided NotificationEntry fulfills importance & DND checks. */ private void ensureStateForHeadsUpWhenDozing() { @@ -152,7 +158,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { /** * Sets up the state such that any requests to - * {@link NotificationInterruptStateProviderImpl#shouldBubbleUp(NotificationEntry)} will + * {@link NotificationInterruptionStateProvider#shouldBubbleUp(NotificationEntry)} will * pass as long its provided NotificationEntry fulfills importance & bubble checks. */ private void ensureStateForBubbleUp() { @@ -160,53 +166,75 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { ensureStateForAlertAwakeCommon(); } + /** + * Ensure that the disabled state is set correctly. + */ @Test - public void testDefaultSuppressorDoesNotSuppress() { - // GIVEN a suppressor without any overrides - final NotificationInterruptSuppressor defaultSuppressor = - new NotificationInterruptSuppressor() { - @Override - public String getName() { - return "defaultSuppressor"; - } - }; + public void testDisableNotificationAlerts() { + // Enabled by default + assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse(); - NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); + // Disable alerts + mNotifInterruptionStateProvider.setDisableNotificationAlerts(true); + assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isTrue(); - // THEN this suppressor doesn't suppress anything by default - assertThat(defaultSuppressor.suppressAwakeHeadsUp(entry)).isFalse(); - assertThat(defaultSuppressor.suppressAwakeInterruptions(entry)).isFalse(); - assertThat(defaultSuppressor.suppressInterruptions(entry)).isFalse(); + // Enable alerts + mNotifInterruptionStateProvider.setDisableNotificationAlerts(false); + assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse(); } + /** + * Ensure that the disabled alert state effects whether HUNs are enabled. + */ @Test - public void testShouldHeadsUpAwake() throws RemoteException { - ensureStateForHeadsUpWhenAwake(); + public void testHunSettingsChange_enabled_butAlertsDisabled() { + // Set up but without a mock change observer + mNotifInterruptionStateProvider.setUpWithPresenter( + mPresenter, + mHeadsUpManager, + mHeadsUpSuppressor); - NotificationEntry entry = createNotification(IMPORTANCE_HIGH); - assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue(); + // HUNs enabled by default + assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isTrue(); + + // Set alerts disabled + mNotifInterruptionStateProvider.setDisableNotificationAlerts(true); + + // No more HUNs + assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse(); } + /** + * Alerts can happen. + */ @Test - public void testShouldNotHeadsUpAwake_flteredOut() throws RemoteException { - // GIVEN state for "heads up when awake" is true - ensureStateForHeadsUpWhenAwake(); + public void testCanAlertCommon_true() { + ensureStateForAlertCommon(); - // WHEN this entry should be filtered out - NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); - when(mNotificationFilter.shouldFilterOut(entry)).thenReturn(true); + NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); + assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isTrue(); + } - // THEN we shouldn't heads up this entry - assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); + /** + * Filtered out notifications don't alert. + */ + @Test + public void testCanAlertCommon_false_filteredOut() { + ensureStateForAlertCommon(); + when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true); + + NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); + assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse(); } + /** + * Grouped notifications have different alerting behaviours, sometimes the alert for a + * grouped notification may be suppressed {@link android.app.Notification#GROUP_ALERT_CHILDREN}. + */ @Test - public void testShouldNotHeadsUp_suppressedForGroups() throws RemoteException { - // GIVEN state for "heads up when awake" is true - ensureStateForHeadsUpWhenAwake(); + public void testCanAlertCommon_false_suppressedForGroups() { + ensureStateForAlertCommon(); - // WHEN the alert for a grouped notification is suppressed - // see {@link android.app.Notification#GROUP_ALERT_CHILDREN} NotificationEntry entry = new NotificationEntryBuilder() .setPkg("a") .setOpPkg("a") @@ -219,40 +247,40 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .setImportance(IMPORTANCE_DEFAULT) .build(); - // THEN this entry shouldn't HUN - assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); + assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse(); } + /** + * HUNs while dozing can happen. + */ @Test - public void testShouldHeadsUpWhenDozing() { + public void testShouldHeadsUpWhenDozing_true() { ensureStateForHeadsUpWhenDozing(); NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue(); } + /** + * Ambient display can show HUNs for new notifications, this may be disabled. + */ @Test - public void testShouldNotHeadsUpWhenDozing_pulseDisabled() { - // GIVEN state for "heads up when dozing" is true + public void testShouldHeadsUpWhenDozing_false_pulseDisabled() { ensureStateForHeadsUpWhenDozing(); - - // WHEN pulsing (HUNs when dozing) is disabled when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(false); - // THEN this entry shouldn't HUN NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); } + /** + * If the device is not in ambient display or sleeping then we don't HUN. + */ @Test - public void testShouldNotHeadsUpWhenDozing_notDozing() { - // GIVEN state for "heads up when dozing" is true + public void testShouldHeadsUpWhenDozing_false_notDozing() { ensureStateForHeadsUpWhenDozing(); - - // WHEN we're not dozing (in ambient display or sleeping) when(mStatusBarStateController.isDozing()).thenReturn(false); - // THEN this entry shouldn't HUN NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); } @@ -262,7 +290,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}. */ @Test - public void testShouldNotHeadsUpWhenDozing_suppressingAmbient() { + public void testShouldHeadsUpWhenDozing_false_suppressingAmbient() { ensureStateForHeadsUpWhenDozing(); NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); @@ -273,18 +301,23 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); } + /** + * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_DEFAULT} don't + * get to pulse. + */ @Test - public void testShouldNotHeadsUpWhenDozing_lessImportant() { + public void testShouldHeadsUpWhenDozing_false_lessImportant() { ensureStateForHeadsUpWhenDozing(); - // Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_DEFAULT} don't - // get to pulse NotificationEntry entry = createNotification(IMPORTANCE_LOW); assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); } + /** + * Heads up can happen. + */ @Test - public void testShouldHeadsUp() throws RemoteException { + public void testShouldHeadsUp_true() throws RemoteException { ensureStateForHeadsUpWhenAwake(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -292,11 +325,38 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } /** + * Heads up notifications can be disabled in general. + */ + @Test + public void testShouldHeadsUp_false_noHunsAllowed() throws RemoteException { + ensureStateForHeadsUpWhenAwake(); + + // Set alerts disabled, this should cause heads up to be false + mNotifInterruptionStateProvider.setDisableNotificationAlerts(true); + assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse(); + + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); + } + + /** + * If the device is dozing, we don't show as heads up. + */ + @Test + public void testShouldHeadsUp_false_dozing() throws RemoteException { + ensureStateForHeadsUpWhenAwake(); + when(mStatusBarStateController.isDozing()).thenReturn(true); + + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); + } + + /** * If the notification is a bubble, and the user is not on AOD / lockscreen, then * the bubble is shown rather than the heads up. */ @Test - public void testShouldNotHeadsUp_bubble() throws RemoteException { + public void testShouldHeadsUp_false_bubble() throws RemoteException { ensureStateForHeadsUpWhenAwake(); // Bubble bit only applies to interruption when we're in the shade @@ -309,7 +369,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * If we're not allowed to alert in general, we shouldn't be shown as heads up. */ @Test - public void testShouldNotHeadsUp_filtered() throws RemoteException { + public void testShouldHeadsUp_false_alertCommonFalse() throws RemoteException { ensureStateForHeadsUpWhenAwake(); // Make canAlertCommon false by saying it's filtered out when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true); @@ -323,7 +383,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}. */ @Test - public void testShouldNotHeadsUp_suppressPeek() throws RemoteException { + public void testShouldHeadsUp_false_suppressPeek() throws RemoteException { ensureStateForHeadsUpWhenAwake(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -339,7 +399,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * to show as a heads up. */ @Test - public void testShouldNotHeadsUp_lessImportant() throws RemoteException { + public void testShouldHeadsUp_false_lessImportant() throws RemoteException { ensureStateForHeadsUpWhenAwake(); NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); @@ -350,7 +410,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * If the device is not in use then we shouldn't be shown as heads up. */ @Test - public void testShouldNotHeadsUp_deviceNotInUse() throws RemoteException { + public void testShouldHeadsUp_false_deviceNotInUse() throws RemoteException { ensureStateForHeadsUpWhenAwake(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -364,58 +424,61 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); } + /** + * If something wants to suppress this heads up, then it shouldn't be shown as a heads up. + */ @Test - public void testShouldNotHeadsUp_headsUpSuppressed() throws RemoteException { + public void testShouldHeadsUp_false_suppressed() throws RemoteException { ensureStateForHeadsUpWhenAwake(); - - // If a suppressor is suppressing heads up, then it shouldn't be shown as a heads up. - mNotifInterruptionStateProvider.addSuppressor(mSuppressAwakeHeadsUp); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(false); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); + verify(mHeadsUpSuppressor).canHeadsUp(any(), any()); } + /** + * On screen alerts don't happen when the device is in VR Mode. + */ @Test - public void testShouldNotHeadsUpAwake_awakeInterruptsSuppressed() throws RemoteException { - ensureStateForHeadsUpWhenAwake(); - - // If a suppressor is suppressing heads up, then it shouldn't be shown as a heads up. - mNotifInterruptionStateProvider.addSuppressor(mSuppressAwakeInterruptions); + public void testCanAlertAwakeCommon__false_vrMode() { + ensureStateForAlertAwakeCommon(); + when(mPresenter.isDeviceInVrMode()).thenReturn(true); - NotificationEntry entry = createNotification(IMPORTANCE_HIGH); - assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); + NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); + assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse(); } /** * On screen alerts don't happen when the notification is snoozed. */ @Test - public void testShouldNotHeadsUp_snoozedPackage() { - NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); + public void testCanAlertAwakeCommon_false_snoozedPackage() { ensureStateForAlertAwakeCommon(); + when(mHeadsUpManager.isSnoozed(any())).thenReturn(true); - when(mHeadsUpManager.isSnoozed(entry.getSbn().getPackageName())).thenReturn(true); - - assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); + NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); + assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse(); } - + /** + * On screen alerts don't happen when that package has just launched fullscreen. + */ @Test - public void testShouldNotHeadsUp_justLaunchedFullscreen() { + public void testCanAlertAwakeCommon_false_justLaunchedFullscreen() { ensureStateForAlertAwakeCommon(); - // On screen alerts don't happen when that package has just launched fullscreen. NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); entry.notifyFullScreenIntentLaunched(); - assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); + assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse(); } /** * Bubbles can happen. */ @Test - public void testShouldBubbleUp() { + public void testShouldBubbleUp_true() { ensureStateForBubbleUp(); assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue(); } @@ -424,7 +487,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * If the notification doesn't have permission to bubble, it shouldn't bubble. */ @Test - public void shouldNotBubbleUp_notAllowedToBubble() { + public void shouldBubbleUp_false_notAllowedToBubble() { ensureStateForBubbleUp(); NotificationEntry entry = createBubble(); @@ -439,7 +502,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * If the notification isn't a bubble, it should definitely not show as a bubble. */ @Test - public void shouldNotBubbleUp_notABubble() { + public void shouldBubbleUp_false_notABubble() { ensureStateForBubbleUp(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -454,7 +517,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * If the notification doesn't have bubble metadata, it shouldn't bubble. */ @Test - public void shouldNotBubbleUp_invalidMetadata() { + public void shouldBubbleUp_false_invalidMetadata() { ensureStateForBubbleUp(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -466,18 +529,24 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse(); } + /** + * If the notification can't heads up in general, it shouldn't bubble. + */ @Test - public void shouldNotBubbleUp_suppressedInterruptions() { + public void shouldBubbleUp_false_alertAwakeCommonFalse() { ensureStateForBubbleUp(); - // If the notification can't heads up in general, it shouldn't bubble. - mNotifInterruptionStateProvider.addSuppressor(mSuppressInterruptions); + // Make alert common return false by pretending we're in VR mode + when(mPresenter.isDeviceInVrMode()).thenReturn(true); assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse(); } + /** + * If the notification can't heads up in general, it shouldn't bubble. + */ @Test - public void shouldNotBubbleUp_filteredOut() { + public void shouldBubbleUp_false_alertCommonFalse() { ensureStateForBubbleUp(); // Make canAlertCommon false by saying it's filtered out @@ -523,45 +592,20 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .build(); } - private final NotificationInterruptSuppressor - mSuppressAwakeHeadsUp = - new NotificationInterruptSuppressor() { - @Override - public String getName() { - return "suppressAwakeHeadsUp"; - } - - @Override - public boolean suppressAwakeHeadsUp(NotificationEntry entry) { - return true; - } - }; - - private final NotificationInterruptSuppressor - mSuppressAwakeInterruptions = - new NotificationInterruptSuppressor() { - @Override - public String getName() { - return "suppressAwakeInterruptions"; - } - - @Override - public boolean suppressAwakeInterruptions(NotificationEntry entry) { - return true; - } - }; - - private final NotificationInterruptSuppressor - mSuppressInterruptions = - new NotificationInterruptSuppressor() { - @Override - public String getName() { - return "suppressInterruptions"; - } - - @Override - public boolean suppressInterruptions(NotificationEntry entry) { - return true; + /** + * Testable class overriding constructor. + */ + public static class TestableNotificationInterruptionStateProvider extends + NotificationInterruptionStateProvider { + + TestableNotificationInterruptionStateProvider(Context context, + PowerManager powerManager, IDreamManager dreamManager, + AmbientDisplayConfiguration ambientDisplayConfiguration, + NotificationFilter notificationFilter, + StatusBarStateController statusBarStateController, + BatteryController batteryController) { + super(context, powerManager, dreamManager, ambientDisplayConfiguration, + notificationFilter, batteryController, statusBarStateController); } - }; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index a21a047d9a70..5d0349dbbb60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -56,12 +56,12 @@ import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger; import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; @@ -108,7 +108,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { @Mock private NotificationEntryListener mEntryListener; @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback; @Mock private HeadsUpManager mHeadsUpManager; - @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider; + @Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private NotificationGutsManager mGutsManager; @Mock private NotificationRemoteInputManager mRemoteInputManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index b9c5b7c02b1c..1e4df272b02b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -67,10 +67,10 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -183,7 +183,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class), mock(NotificationLockscreenUserManager.class), mKeyguardStateController, - mock(NotificationInterruptStateProvider.class), mock(MetricsLogger.class), + mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class), mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor, mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags, mNotifPipeline, mNotifCollection) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 318e9b87fa70..b9d2d229cd69 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -16,9 +16,8 @@ package com.android.systemui.statusbar.phone; import static android.view.Display.DEFAULT_DISPLAY; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; @@ -36,7 +35,6 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.FakeMetricsLogger; -import com.android.systemui.ForegroundServiceNotificationListener; import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -50,12 +48,12 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -64,7 +62,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import java.util.ArrayList; @@ -75,9 +72,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { private StatusBarNotificationPresenter mStatusBarNotificationPresenter; - private NotificationInterruptStateProvider mNotificationInterruptStateProvider = - mock(NotificationInterruptStateProvider.class); - private NotificationInterruptSuppressor mInterruptSuppressor; private CommandQueue mCommandQueue; private FakeMetricsLogger mMetricsLogger; private ShadeController mShadeController = mock(ShadeController.class); @@ -101,11 +95,11 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mDependency.injectMockDependency(NotificationViewHierarchyManager.class); mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class); mDependency.injectMockDependency(NotificationLockscreenUserManager.class); + mDependency.injectMockDependency(NotificationInterruptionStateProvider.class); mDependency.injectMockDependency(NotificationMediaManager.class); mDependency.injectMockDependency(VisualStabilityManager.class); mDependency.injectMockDependency(NotificationGutsManager.class); mDependency.injectMockDependency(NotificationShadeWindowController.class); - mDependency.injectMockDependency(ForegroundServiceNotificationListener.class); NotificationEntryManager entryManager = mDependency.injectMockDependency(NotificationEntryManager.class); when(entryManager.getActiveNotificationsForCurrentUser()).thenReturn(new ArrayList<>()); @@ -113,25 +107,18 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { NotificationShadeWindowView notificationShadeWindowView = mock(NotificationShadeWindowView.class); when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources()); - mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(mContext, mock(NotificationPanelViewController.class), mock(HeadsUpManagerPhone.class), notificationShadeWindowView, mock(NotificationListContainerViewGroup.class), mock(DozeScrimController.class), mock(ScrimController.class), mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class), - mock(KeyguardStateController.class), + mock(NotificationAlertingManager.class), mock(KeyguardStateController.class), mock(KeyguardIndicationController.class), mStatusBar, - mock(ShadeControllerImpl.class), mCommandQueue, mInitController, - mNotificationInterruptStateProvider); - mInitController.executePostInitTasks(); - ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor = - ArgumentCaptor.forClass(NotificationInterruptSuppressor.class); - verify(mNotificationInterruptStateProvider).addSuppressor(suppressorCaptor.capture()); - mInterruptSuppressor = suppressorCaptor.getValue(); + mock(ShadeControllerImpl.class), mCommandQueue, mInitController); } @Test - public void testSuppressHeadsUp_disabledStatusBar() { + public void testHeadsUp_disabledStatusBar() { Notification n = new Notification.Builder(getContext(), "a").build(); NotificationEntry entry = new NotificationEntryBuilder() .setPkg("a") @@ -143,12 +130,12 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { false /* animate */); TestableLooper.get(this).processAllMessages(); - assertTrue("The panel should suppress heads up while disabled", - mInterruptSuppressor.suppressAwakeHeadsUp(entry)); + assertFalse("The panel shouldn't allow heads up while disabled", + mStatusBarNotificationPresenter.canHeadsUp(entry, entry.getSbn())); } @Test - public void testSuppressHeadsUp_disabledNotificationShade() { + public void testHeadsUp_disabledNotificationShade() { Notification n = new Notification.Builder(getContext(), "a").build(); NotificationEntry entry = new NotificationEntryBuilder() .setPkg("a") @@ -160,39 +147,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { false /* animate */); TestableLooper.get(this).processAllMessages(); - assertTrue("The panel should suppress interruptions while notification shade " - + "disabled", - mInterruptSuppressor.suppressAwakeHeadsUp(entry)); - } - - @Test - public void testSuppressInterruptions_vrMode() { - Notification n = new Notification.Builder(getContext(), "a").build(); - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setNotification(n) - .build(); - mStatusBarNotificationPresenter.mVrMode = true; - - assertTrue("Vr mode should suppress interruptions", - mInterruptSuppressor.suppressAwakeInterruptions(entry)); - } - - @Test - public void testSuppressInterruptions_statusBarAlertsDisabled() { - Notification n = new Notification.Builder(getContext(), "a").build(); - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setNotification(n) - .build(); - when(mStatusBar.areNotificationAlertsDisabled()).thenReturn(true); - - assertTrue("StatusBar alerts disabled shouldn't allow interruptions", - mInterruptSuppressor.suppressInterruptions(entry)); + assertFalse("The panel shouldn't allow heads up while notitifcation shade disabled", + mStatusBarNotificationPresenter.canHeadsUp(entry, entry.getSbn())); } @Test @@ -216,3 +172,4 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } } } + diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index d9f4d4b8f758..0d7734e13621 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -42,7 +42,7 @@ import android.app.Notification; import android.app.StatusBarManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; -import android.content.ContentResolver; +import android.content.Context; import android.content.IntentFilter; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.fingerprint.FingerprintManager; @@ -109,17 +109,18 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; @@ -128,7 +129,6 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; @@ -160,7 +160,7 @@ public class StatusBarTest extends SysuiTestCase { private StatusBar mStatusBar; private FakeMetricsLogger mMetricsLogger; private PowerManager mPowerManager; - private TestableNotificationInterruptStateProviderImpl mNotificationInterruptStateProvider; + private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider; @Mock private NotificationsController mNotificationsController; @Mock private LightBarController mLightBarController; @@ -178,6 +178,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private DozeScrimController mDozeScrimController; @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; @Mock private BiometricUnlockController mBiometricUnlockController; + @Mock private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor; @Mock private VisualStabilityManager mVisualStabilityManager; @Mock private NotificationListener mNotificationListener; @Mock private KeyguardViewMediator mKeyguardViewMediator; @@ -190,9 +191,10 @@ public class StatusBarTest extends SysuiTestCase { @Mock private StatusBarNotificationPresenter mNotificationPresenter; @Mock private NotificationEntryListener mEntryListener; @Mock private NotificationFilter mNotificationFilter; - @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration; + @Mock private NotificationAlertingManager mNotificationAlertingManager; @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration; @Mock private NotificationShadeWindowView mNotificationShadeWindowView; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private AssistManager mAssistManager; @@ -260,12 +262,10 @@ public class StatusBarTest extends SysuiTestCase { mPowerManager = new PowerManager(mContext, powerManagerService, thermalService, Handler.createAsync(Looper.myLooper())); - mNotificationInterruptStateProvider = - new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), - mPowerManager, + mNotificationInterruptionStateProvider = + new TestableNotificationInterruptionStateProvider(mContext, mPowerManager, mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter, - mStatusBarStateController, mBatteryController, mHeadsUpManager, - new Handler(TestableLooper.get(this).getLooper())); + mStatusBarStateController, mBatteryController); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); @@ -298,6 +298,9 @@ public class StatusBarTest extends SysuiTestCase { return null; }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any()); + mNotificationInterruptionStateProvider.setUpWithPresenter(mNotificationPresenter, + mHeadsUpManager, mHeadsUpSuppressor); + when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); WakefulnessLifecycle wakefulnessLifecycle = new WakefulnessLifecycle(); @@ -344,9 +347,10 @@ public class StatusBarTest extends SysuiTestCase { ), mNotificationGutsManager, notificationLogger, - mNotificationInterruptStateProvider, + mNotificationInterruptionStateProvider, mNotificationViewHierarchyManager, mKeyguardViewMediator, + mNotificationAlertingManager, new DisplayMetrics(), mMetricsLogger, mUiBgExecutor, @@ -557,6 +561,7 @@ public class StatusBarTest extends SysuiTestCase { when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true); Notification n = new Notification.Builder(getContext(), "a") .setGroup("a") @@ -572,7 +577,7 @@ public class StatusBarTest extends SysuiTestCase { .setImportance(IMPORTANCE_HIGH) .build(); - assertTrue(mNotificationInterruptStateProvider.shouldHeadsUp(entry)); + assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry)); } @Test @@ -581,6 +586,7 @@ public class StatusBarTest extends SysuiTestCase { when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true); Notification n = new Notification.Builder(getContext(), "a") .setGroup("a") @@ -596,7 +602,7 @@ public class StatusBarTest extends SysuiTestCase { .setImportance(IMPORTANCE_HIGH) .build(); - assertFalse(mNotificationInterruptStateProvider.shouldHeadsUp(entry)); + assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry)); } @Test @@ -605,6 +611,7 @@ public class StatusBarTest extends SysuiTestCase { when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true); Notification n = new Notification.Builder(getContext(), "a").build(); @@ -617,7 +624,7 @@ public class StatusBarTest extends SysuiTestCase { .setSuppressedVisualEffects(SUPPRESSED_EFFECT_PEEK) .build(); - assertFalse(mNotificationInterruptStateProvider.shouldHeadsUp(entry)); + assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry)); } @Test @@ -626,6 +633,7 @@ public class StatusBarTest extends SysuiTestCase { when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true); Notification n = new Notification.Builder(getContext(), "a").build(); @@ -637,7 +645,7 @@ public class StatusBarTest extends SysuiTestCase { .setImportance(IMPORTANCE_HIGH) .build(); - assertTrue(mNotificationInterruptStateProvider.shouldHeadsUp(entry)); + assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry)); } @Test @@ -863,21 +871,19 @@ public class StatusBarTest extends SysuiTestCase { verify(mDozeServiceHost).setDozeSuppressed(false); } - public static class TestableNotificationInterruptStateProviderImpl extends - NotificationInterruptStateProviderImpl { + public static class TestableNotificationInterruptionStateProvider extends + NotificationInterruptionStateProvider { - TestableNotificationInterruptStateProviderImpl( - ContentResolver contentResolver, + TestableNotificationInterruptionStateProvider( + Context context, PowerManager powerManager, IDreamManager dreamManager, AmbientDisplayConfiguration ambientDisplayConfiguration, NotificationFilter filter, StatusBarStateController controller, - BatteryController batteryController, - HeadsUpManager headsUpManager, - Handler mainHandler) { - super(contentResolver, powerManager, dreamManager, ambientDisplayConfiguration, filter, - batteryController, controller, headsUpManager, mainHandler); + BatteryController batteryController) { + super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter, + batteryController, controller); mUseHeadsUp = true; } } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl index a55419383380..b4e3ba46791c 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl +++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl @@ -35,4 +35,5 @@ oneway interface ITetheringEventCallback void onConfigurationChanged(in TetheringConfigurationParcel config); void onTetherStatesChanged(in TetherStatesParcel states); void onTetherClientsChanged(in List<TetheredClient> clients); + void onOffloadStatusChanged(int status); } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl index c064aa4d9a61..253eacbd23e7 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl @@ -31,4 +31,5 @@ parcelable TetheringCallbackStartedParcel { TetheringConfigurationParcel config; TetherStatesParcel states; List<TetheredClient> tetheredClients; -}
\ No newline at end of file + int offloadStatus; +} diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index fd9f7137c85d..7f831ced7bec 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -18,6 +18,7 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -34,6 +35,8 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -172,6 +175,23 @@ public class TetheringManager { public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, value = { + TETHER_HARDWARE_OFFLOAD_STOPPED, + TETHER_HARDWARE_OFFLOAD_STARTED, + TETHER_HARDWARE_OFFLOAD_FAILED, + }) + public @interface TetherOffloadStatus { + } + + /** Tethering offload status is stopped. */ + public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; + /** Tethering offload status is started. */ + public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; + /** Fail to start tethering offload. */ + public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; + /** * Create a TetheringManager object for interacting with the tethering service. * @@ -378,6 +398,9 @@ public class TetheringManager { @Override public void onTetherClientsChanged(List<TetheredClient> clients) { } + @Override + public void onOffloadStatusChanged(int status) { } + public void waitForStarted() { mWaitForCallback.block(DEFAULT_TIMEOUT_MS); throwIfPermissionFailure(mError); @@ -802,6 +825,14 @@ public class TetheringManager { * @param clients The new set of tethered clients; the collection is not ordered. */ public void onClientsChanged(@NonNull Collection<TetheredClient> clients) {} + + /** + * Called when tethering offload status changes. + * + * <p>This will be called immediately after the callback is registered. + * @param status The offload status. + */ + public void onOffloadStatusChanged(@TetherOffloadStatus int status) {} } /** @@ -925,6 +956,7 @@ public class TetheringManager { maybeSendTetherableIfacesChangedCallback(parcel.states); maybeSendTetheredIfacesChangedCallback(parcel.states); callback.onClientsChanged(parcel.tetheredClients); + callback.onOffloadStatusChanged(parcel.offloadStatus); }); } @@ -960,6 +992,11 @@ public class TetheringManager { public void onTetherClientsChanged(final List<TetheredClient> clients) { executor.execute(() -> callback.onClientsChanged(clients)); } + + @Override + public void onOffloadStatusChanged(final int status) { + executor.execute(() -> callback.onOffloadStatusChanged(status)); + } }; getConnector(c -> c.registerTetheringEventCallback(remoteCallback, callerPkg)); mTetheringEventCallbacks.put(callback, remoteCallback); @@ -1131,6 +1168,25 @@ public class TetheringManager { public boolean isTetheringSupported() { final String callerPkg = mContext.getOpPackageName(); + return isTetheringSupported(callerPkg); + } + + /** + * Check if the device allows for tethering. It may be disabled via {@code ro.tether.denied} + * system property, Settings.TETHER_SUPPORTED or due to device configuration. This is useful + * for system components that query this API on behalf of an app. In particular, Bluetooth + * has @UnsupportedAppUsage calls that will let apps turn on bluetooth tethering if they have + * the right permissions, but such an app needs to know whether it can (permissions as well + * as support from the device) turn on tethering in the first place to show the appropriate UI. + * + * @param callerPkg The caller package name, if it is not matching the calling uid, + * SecurityException would be thrown. + * @return a boolean - {@code true} indicating Tethering is supported. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public boolean isTetheringSupported(@NonNull final String callerPkg) { + final RequestDispatcher dispatcher = new RequestDispatcher(); final int ret = dispatcher.waitForResult((connector, listener) -> { try { diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index ca74430f706c..f89da849ea91 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -44,6 +44,9 @@ import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; +import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED; +import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED; +import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED; import static android.net.util.TetheringMessageBase.BASE_MASTER; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; @@ -220,6 +223,7 @@ public class Tethering { private final UserRestrictionActionListener mTetheringRestriction; private final ActiveDataSubIdListener mActiveDataSubIdListener; private final ConnectedClientsTracker mConnectedClientsTracker; + private final TetheringThreadExecutor mExecutor; private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID; // All the usage of mTetheringEventCallback should run in the same thread. private ITetheringEventCallback mTetheringEventCallback = null; @@ -236,6 +240,7 @@ public class Tethering { private TetherStatesParcel mTetherStatesParcel; private boolean mDataSaverEnabled = false; private String mWifiP2pTetherInterface = null; + private int mOffloadStatus = TETHER_HARDWARE_OFFLOAD_STOPPED; @GuardedBy("mPublicSync") private EthernetManager.TetheredInterfaceRequest mEthernetIfaceRequest; @@ -296,8 +301,8 @@ public class Tethering { final UserManager userManager = (UserManager) mContext.getSystemService( Context.USER_SERVICE); mTetheringRestriction = new UserRestrictionActionListener(userManager, this); - final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler); - mActiveDataSubIdListener = new ActiveDataSubIdListener(executor); + mExecutor = new TetheringThreadExecutor(mHandler); + mActiveDataSubIdListener = new ActiveDataSubIdListener(mExecutor); // Load tethering configuration. updateConfiguration(); @@ -315,9 +320,7 @@ public class Tethering { final WifiManager wifiManager = getWifiManager(); if (wifiManager != null) { - wifiManager.registerSoftApCallback( - mHandler::post /* executor */, - new TetheringSoftApCallback()); + wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback()); } } @@ -606,14 +609,17 @@ public class Tethering { Context.ETHERNET_SERVICE); synchronized (mPublicSync) { if (enable) { + if (mEthernetCallback != null) return TETHER_ERROR_NO_ERROR; + mEthernetCallback = new EthernetCallback(); - mEthernetIfaceRequest = em.requestTetheredInterface(mEthernetCallback); + mEthernetIfaceRequest = em.requestTetheredInterface(mExecutor, mEthernetCallback); } else { - if (mConfiguredEthernetIface != null) { - stopEthernetTetheringLocked(); + stopEthernetTetheringLocked(); + if (mEthernetCallback != null) { mEthernetIfaceRequest.release(); + mEthernetCallback = null; + mEthernetIfaceRequest = null; } - mEthernetCallback = null; } } return TETHER_ERROR_NO_ERROR; @@ -1899,12 +1905,15 @@ public class Tethering { // OffloadController implementation. class OffloadWrapper { public void start() { - mOffloadController.start(); + final int status = mOffloadController.start() ? TETHER_HARDWARE_OFFLOAD_STARTED + : TETHER_HARDWARE_OFFLOAD_FAILED; + updateOffloadStatus(status); sendOffloadExemptPrefixes(); } public void stop() { mOffloadController.stop(); + updateOffloadStatus(TETHER_HARDWARE_OFFLOAD_STOPPED); } public void updateUpstreamNetworkState(UpstreamNetworkState ns) { @@ -1965,6 +1974,13 @@ public class Tethering { mOffloadController.setLocalPrefixes(localPrefixes); } + + private void updateOffloadStatus(final int newStatus) { + if (newStatus == mOffloadStatus) return; + + mOffloadStatus = newStatus; + reportOffloadStatusChanged(mOffloadStatus); + } } } @@ -1999,6 +2015,7 @@ public class Tethering { parcel.tetheredClients = hasListPermission ? mConnectedClientsTracker.getLastTetheredClients() : Collections.emptyList(); + parcel.offloadStatus = mOffloadStatus; try { callback.onCallbackStarted(parcel); } catch (RemoteException e) { @@ -2093,6 +2110,21 @@ public class Tethering { } } + private void reportOffloadStatusChanged(final int status) { + final int length = mTetheringEventCallbacks.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + try { + mTetheringEventCallbacks.getBroadcastItem(i).onOffloadStatusChanged(status); + } catch (RemoteException e) { + // Not really very much to do here. + } + } + } finally { + mTetheringEventCallbacks.finishBroadcast(); + } + } + void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { // Binder.java closes the resource for us. @SuppressWarnings("resource") diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index f2074bd46886..af7ad662b8a5 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -28,11 +28,15 @@ import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED; import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY; import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER; import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER; +import static android.net.TetheringManager.TETHERING_ETHERNET; import static android.net.TetheringManager.TETHERING_NCM; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; +import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED; +import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED; +import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; @@ -75,6 +79,8 @@ import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; +import android.net.EthernetManager; +import android.net.EthernetManager.TetheredInterfaceRequest; import android.net.INetd; import android.net.ITetheringEventCallback; import android.net.InetAddresses; @@ -166,6 +172,7 @@ public class TetheringTest { @Mock private Context mContext; @Mock private NetworkStatsManager mStatsManager; @Mock private OffloadHardwareInterface mOffloadHardwareInterface; + @Mock private OffloadHardwareInterface.ForwardedStats mForwardedStats; @Mock private Resources mResources; @Mock private TelephonyManager mTelephonyManager; @Mock private UsbManager mUsbManager; @@ -180,6 +187,7 @@ public class TetheringTest { @Mock private UserManager mUserManager; @Mock private NetworkRequest mNetworkRequest; @Mock private ConnectivityManager mCm; + @Mock private EthernetManager mEm; private final MockIpServerDependencies mIpServerDependencies = spy(new MockIpServerDependencies()); @@ -232,6 +240,7 @@ public class TetheringTest { if (Context.USER_SERVICE.equals(name)) return mUserManager; if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager; if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; + if (Context.ETHERNET_SERVICE.equals(name)) return mEm; return super.getSystemService(name); } @@ -458,6 +467,9 @@ public class TetheringTest { mInterfaceConfiguration.flags = new String[0]; when(mRouterAdvertisementDaemon.start()) .thenReturn(true); + initOffloadConfiguration(true /* offloadConfig */, true /* offloadControl */, + 0 /* defaultDisabled */); + when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats); mServiceContext = new TestContext(mContext); when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null); @@ -1131,6 +1143,7 @@ public class TetheringTest { private final ArrayList<TetheringConfigurationParcel> mTetheringConfigs = new ArrayList<>(); private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>(); + private final ArrayList<Integer> mOffloadStatus = new ArrayList<>(); // This function will remove the recorded callbacks, so it must be called once for // each callback. If this is called after multiple callback, the order matters. @@ -1166,6 +1179,11 @@ public class TetheringTest { assertNoConfigChangeCallback(); } + public void expectOffloadStatusChanged(final int expectedStatus) { + assertOffloadStatusChangedCallback(); + assertEquals(mOffloadStatus.remove(0), new Integer(expectedStatus)); + } + public TetherStatesParcel pollTetherStatesChanged() { assertStateChangeCallback(); return mTetherStates.remove(0); @@ -1192,10 +1210,16 @@ public class TetheringTest { } @Override + public void onOffloadStatusChanged(final int status) { + mOffloadStatus.add(status); + } + + @Override public void onCallbackStarted(TetheringCallbackStartedParcel parcel) { mActualUpstreams.add(parcel.upstreamNetwork); mTetheringConfigs.add(parcel.config); mTetherStates.add(parcel.states); + mOffloadStatus.add(parcel.offloadStatus); } @Override @@ -1217,6 +1241,10 @@ public class TetheringTest { assertFalse(mTetherStates.isEmpty()); } + public void assertOffloadStatusChangedCallback() { + assertFalse(mOffloadStatus.isEmpty()); + } + public void assertNoCallback() { assertNoUpstreamChangeCallback(); assertNoConfigChangeCallback(); @@ -1265,6 +1293,7 @@ public class TetheringTest { mTethering.getTetheringConfiguration().toStableParcelable()); TetherStatesParcel tetherState = callback.pollTetherStatesChanged(); assertTetherStatesNotNullButEmpty(tetherState); + callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED); // 2. Enable wifi tethering. UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState(); when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState); @@ -1282,6 +1311,7 @@ public class TetheringTest { tetherState = callback.pollTetherStatesChanged(); assertArrayEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME}); callback.expectUpstreamChanged(upstreamState.network); + callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED); // 3. Register second callback. mTethering.registerTetheringEventCallback(callback2); @@ -1291,6 +1321,7 @@ public class TetheringTest { mTethering.getTetheringConfiguration().toStableParcelable()); tetherState = callback2.pollTetherStatesChanged(); assertEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME}); + callback2.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED); // 4. Unregister first callback and disable wifi tethering mTethering.unregisterTetheringEventCallback(callback); @@ -1302,10 +1333,59 @@ public class TetheringTest { assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME}); mLooper.dispatchAll(); callback2.expectUpstreamChanged(new Network[] {null}); + callback2.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED); callback.assertNoCallback(); } @Test + public void testReportFailCallbackIfOffloadNotSupported() throws Exception { + final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState(); + TestTetheringEventCallback callback = new TestTetheringEventCallback(); + mTethering.registerTetheringEventCallback(callback); + mLooper.dispatchAll(); + callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED); + + // 1. Offload fail if no OffloadConfig. + initOffloadConfiguration(false /* offloadConfig */, true /* offloadControl */, + 0 /* defaultDisabled */); + runUsbTethering(upstreamState); + callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED); + runStopUSBTethering(); + callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED); + reset(mUsbManager); + // 2. Offload fail if no OffloadControl. + initOffloadConfiguration(true /* offloadConfig */, false /* offloadControl */, + 0 /* defaultDisabled */); + runUsbTethering(upstreamState); + callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED); + runStopUSBTethering(); + callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED); + reset(mUsbManager); + // 3. Offload fail if disabled by settings. + initOffloadConfiguration(true /* offloadConfig */, true /* offloadControl */, + 1 /* defaultDisabled */); + runUsbTethering(upstreamState); + callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED); + runStopUSBTethering(); + callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED); + } + + private void runStopUSBTethering() { + mTethering.stopTethering(TETHERING_USB); + mLooper.dispatchAll(); + mTethering.interfaceRemoved(TEST_USB_IFNAME); + mLooper.dispatchAll(); + } + + private void initOffloadConfiguration(final boolean offloadConfig, + final boolean offloadControl, final int defaultDisabled) { + when(mOffloadHardwareInterface.initOffloadConfig()).thenReturn(offloadConfig); + when(mOffloadHardwareInterface.initOffloadControl(any())).thenReturn(offloadControl); + when(mOffloadHardwareInterface.getDefaultTetherOffloadDisabled()).thenReturn( + defaultDisabled); + } + + @Test public void testMultiSimAware() throws Exception { final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration(); assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.activeDataSubId); @@ -1316,6 +1396,24 @@ public class TetheringTest { assertEquals(fakeSubId, newConfig.activeDataSubId); } + @Test + public void testNoDuplicatedEthernetRequest() throws Exception { + final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class); + when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest); + mTethering.startTethering(createTetheringRquestParcel(TETHERING_ETHERNET), null); + mLooper.dispatchAll(); + verify(mEm, times(1)).requestTetheredInterface(any(), any()); + mTethering.startTethering(createTetheringRquestParcel(TETHERING_ETHERNET), null); + mLooper.dispatchAll(); + verifyNoMoreInteractions(mEm); + mTethering.stopTethering(TETHERING_ETHERNET); + mLooper.dispatchAll(); + verify(mockRequest, times(1)).release(); + mTethering.stopTethering(TETHERING_ETHERNET); + mLooper.dispatchAll(); + verifyNoMoreInteractions(mEm); + } + private void workingWifiP2pGroupOwner( boolean emulateInterfaceStatusChanged) throws Exception { if (emulateInterfaceStatusChanged) { diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 14154339ac4a..7dedad7f2fea 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -167,14 +167,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { @Override public String toString() { - return "{callingPackage=" + callingPackage + " binder=" + binder - + " callback=" + callback + return "{callingPackage=" + pii(callingPackage) + " callerUid=" + callerUid + " binder=" + + binder + " callback=" + callback + " onSubscriptionsChangedListenererCallback=" + onSubscriptionsChangedListenerCallback + " onOpportunisticSubscriptionsChangedListenererCallback=" - + onOpportunisticSubscriptionsChangedListenerCallback - + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId - + " events=" + Integer.toHexString(events) + "}"; + + onOpportunisticSubscriptionsChangedListenerCallback + " subId=" + subId + + " phoneId=" + phoneId + " events=" + Integer.toHexString(events) + "}"; } } @@ -598,9 +597,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { int callerUserId = UserHandle.getCallingUserId(); mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); if (VDBG) { - log("listen oscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId() - + " callerUserId=" + callerUserId + " callback=" + callback - + " callback.asBinder=" + callback.asBinder()); + log("listen oscl: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid() + + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId + + " callback=" + callback + " callback.asBinder=" + callback.asBinder()); } synchronized (mRecords) { @@ -652,9 +651,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { int callerUserId = UserHandle.getCallingUserId(); mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); if (VDBG) { - log("listen ooscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId() - + " callerUserId=" + callerUserId + " callback=" + callback - + " callback.asBinder=" + callback.asBinder()); + log("listen ooscl: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid() + + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId + + " callback=" + callback + " callback.asBinder=" + callback.asBinder()); } synchronized (mRecords) { @@ -769,9 +768,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { IPhoneStateListener callback, int events, boolean notifyNow, int subId) { int callerUserId = UserHandle.getCallingUserId(); mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); - String str = "listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events) - + " notifyNow=" + notifyNow + " subId=" + subId + " myUserId=" - + UserHandle.myUserId() + " callerUserId=" + callerUserId; + String str = "listen: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid() + + " events=0x" + Integer.toHexString(events) + " notifyNow=" + notifyNow + " subId=" + + subId + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId; mListenLog.log(str); if (VDBG) { log(str); @@ -2957,4 +2956,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (info == null) return INVALID_SIM_SLOT_INDEX; return info.getSimSlotIndex(); } + + /** + * On certain build types, we should redact information by default. UID information will be + * preserved in the same log line, so no debugging capability is lost in full bug reports. + * However, privacy-constrained bug report types (e.g. connectivity) cannot display raw + * package names on user builds as it's considered an information leak. + */ + private static String pii(String packageName) { + return Build.IS_DEBUGGABLE ? packageName : "***"; + } } diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java index a09aa64476c1..df9417ee1301 100644 --- a/services/core/java/com/android/server/am/AppExitInfoTracker.java +++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java @@ -129,7 +129,7 @@ public final class AppExitInfoTracker { private final ProcessMap<AppExitInfoContainer> mData; /** A pool of raw {@link android.app.ApplicationExitInfo} records. */ - @GuardedBy("mService") + @GuardedBy("mLock") private final SynchronizedPool<ApplicationExitInfo> mRawRecordsPool; /** @@ -204,8 +204,7 @@ public final class AppExitInfoTracker { }); } - @GuardedBy("mService") - void scheduleNoteProcessDiedLocked(final ProcessRecord app) { + void scheduleNoteProcessDied(final ProcessRecord app) { if (app == null || app.info == null) { return; } @@ -214,11 +213,9 @@ public final class AppExitInfoTracker { if (!mAppExitInfoLoaded) { return; } + mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED, obtainRawRecordLocked(app)) + .sendToTarget(); } - // The current thread is holding the global lock, let's extract the info from it - // and schedule the info note task in the kill handler. - mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED, obtainRawRecordLocked(app)) - .sendToTarget(); } void scheduleNoteAppKill(final ProcessRecord app, final @Reason int reason, @@ -227,8 +224,6 @@ public final class AppExitInfoTracker { if (!mAppExitInfoLoaded) { return; } - } - synchronized (mService) { if (app == null || app.info == null) { return; } @@ -247,8 +242,6 @@ public final class AppExitInfoTracker { if (!mAppExitInfoLoaded) { return; } - } - synchronized (mService) { ProcessRecord app; synchronized (mService.mPidsSelfLocked) { app = mService.mPidsSelfLocked.get(pid); @@ -823,7 +816,7 @@ public final class AppExitInfoTracker { } @VisibleForTesting - @GuardedBy("mService") + @GuardedBy("mLock") ApplicationExitInfo obtainRawRecordLocked(ProcessRecord app) { ApplicationExitInfo info = mRawRecordsPool.acquire(); if (info == null) { @@ -850,7 +843,7 @@ public final class AppExitInfoTracker { } @VisibleForTesting - @GuardedBy("mService") + @GuardedBy("mLock") void recycleRawRecordLocked(ApplicationExitInfo info) { info.setProcessName(null); info.setDescription(null); @@ -1135,8 +1128,6 @@ public final class AppExitInfoTracker { ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj; synchronized (mLock) { handleNoteProcessDiedLocked(raw); - } - synchronized (mService) { recycleRawRecordLocked(raw); } } @@ -1145,8 +1136,6 @@ public final class AppExitInfoTracker { ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj; synchronized (mLock) { handleNoteAppKillLocked(raw); - } - synchronized (mService) { recycleRawRecordLocked(raw); } } diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index 7cc2e8eb2954..1f826b5253bd 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -16,12 +16,6 @@ jji@google.com # Windows & Activities ogunwale@google.com -jjaggi@google.com -racarr@google.com -chaviw@google.com -vishnun@google.com -akulian@google.com -roosa@google.com # Permissions & Packages svetoslavganov@google.com diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 644f0f776387..91348aaea9c4 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2588,8 +2588,13 @@ public final class OomAdjuster { return; } + // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze + if (app.frozen && app.shouldNotFreeze) { + mCachedAppOptimizer.unfreezeAppLocked(app); + } + // Use current adjustment when freezing, set adjustment when unfreezing. - if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen) { + if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen && !app.shouldNotFreeze) { mCachedAppOptimizer.freezeAppAsync(app); } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.frozen) { mCachedAppOptimizer.unfreezeAppLocked(app); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index a9d18e05e350..6b165139aefd 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3773,7 +3773,7 @@ public final class ProcessList { } Watchdog.getInstance().processDied(app.processName, app.pid); - mAppExitInfoTracker.scheduleNoteProcessDiedLocked(app); + mAppExitInfoTracker.scheduleNoteProcessDied(app); } /** diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index bfc2f82d586d..61bede987bae 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -219,6 +219,19 @@ final class CompatConfig { } /** + * Returns whether the change is marked as disabled. + */ + boolean isDisabled(long changeId) { + synchronized (mChanges) { + CompatChange c = mChanges.get(changeId); + if (c == null) { + return false; + } + return c.getDisabled(); + } + } + + /** * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This * restores the default behaviour for the given change and app, once any app processes have been * restarted. diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java index 9e18c74265d7..f5d6e5ac46e5 100644 --- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java +++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java @@ -59,6 +59,7 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild(); boolean finalBuild = mAndroidBuildClassifier.isFinalBuild(); int minTargetSdk = mCompatConfig.minTargetSdkForChangeId(changeId); + boolean disabled = mCompatConfig.isDisabled(changeId); // Allow any override for userdebug or eng builds. if (debuggableBuild) { @@ -83,12 +84,12 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { if (!finalBuild) { return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk); } - // Do not allow overriding non-target sdk gated changes on user builds - if (minTargetSdk == -1) { + // Do not allow overriding default enabled changes on user builds + if (minTargetSdk == -1 && !disabled) { return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk); } // Only allow to opt-in for a targetSdk gated change. - if (applicationInfo.targetSdkVersion < minTargetSdk) { + if (disabled || applicationInfo.targetSdkVersion < minTargetSdk) { return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk); } return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, minTargetSdk); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 968528ca5b29..7c3cab17704f 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -951,18 +951,18 @@ public class Vpn { || isVpnServicePreConsented(context, packageName); } - private int getAppUid(String app, int userHandle) { + private int getAppUid(final String app, final int userHandle) { if (VpnConfig.LEGACY_VPN.equals(app)) { return Process.myUid(); } PackageManager pm = mContext.getPackageManager(); - int result; - try { - result = pm.getPackageUidAsUser(app, userHandle); - } catch (NameNotFoundException e) { - result = -1; - } - return result; + return Binder.withCleanCallingIdentity(() -> { + try { + return pm.getPackageUidAsUser(app, userHandle); + } catch (NameNotFoundException e) { + return -1; + } + }); } private boolean doesPackageTargetAtLeastQ(String packageName) { diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index f773825a7ff8..6da0de13f623 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -52,7 +52,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.UserHandle; import android.provider.Settings; -import android.security.FileIntegrityManager; import android.util.Slog; import android.util.apk.SourceStampVerificationResult; import android.util.apk.SourceStampVerifier; @@ -122,7 +121,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { private final PackageManagerInternal mPackageManagerInternal; private final RuleEvaluationEngine mEvaluationEngine; private final IntegrityFileManager mIntegrityFileManager; - private final FileIntegrityManager mFileIntegrityManager; /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */ public static AppIntegrityManagerServiceImpl create(Context context) { @@ -134,7 +132,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { LocalServices.getService(PackageManagerInternal.class), RuleEvaluationEngine.getRuleEvaluationEngine(), IntegrityFileManager.getInstance(), - (FileIntegrityManager) context.getSystemService(Context.FILE_INTEGRITY_SERVICE), handlerThread.getThreadHandler()); } @@ -144,13 +141,11 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { PackageManagerInternal packageManagerInternal, RuleEvaluationEngine evaluationEngine, IntegrityFileManager integrityFileManager, - FileIntegrityManager fileIntegrityManager, Handler handler) { mContext = context; mPackageManagerInternal = packageManagerInternal; mEvaluationEngine = evaluationEngine; mIntegrityFileManager = integrityFileManager; - mFileIntegrityManager = fileIntegrityManager; mHandler = handler; IntentFilter integrityVerificationFilter = new IntentFilter(); @@ -476,6 +471,8 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { SourceStampVerifier.verify(installationPath.getAbsolutePath()); appInstallMetadata.setIsStampPresent(sourceStampVerificationResult.isPresent()); appInstallMetadata.setIsStampVerified(sourceStampVerificationResult.isVerified()); + // A verified stamp is set to be trusted. + appInstallMetadata.setIsStampTrusted(sourceStampVerificationResult.isVerified()); if (sourceStampVerificationResult.isVerified()) { X509Certificate sourceStampCertificate = (X509Certificate) sourceStampVerificationResult.getCertificate(); @@ -488,16 +485,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { throw new IllegalArgumentException( "Error computing source stamp certificate digest", e); } - // Checks if the source stamp certificate is trusted. - try { - appInstallMetadata.setIsStampTrusted( - mFileIntegrityManager.isApkVeritySupported() - && mFileIntegrityManager.isAppSourceCertificateTrusted( - sourceStampCertificate)); - } catch (CertificateEncodingException e) { - throw new IllegalArgumentException( - "Error checking if source stamp certificate is trusted", e); - } } } diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index ad20d38e9ed4..8eb773a2d0f8 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -116,9 +116,6 @@ public class DataLoaderManagerService extends SystemService { return null; } - // TODO(b/136132412): better way to enable privileged data loaders in tests - boolean checkLoader = - android.os.SystemProperties.getBoolean("incremental.check_loader", false); int numServices = services.size(); for (int i = 0; i < numServices; i++) { ResolveInfo ri = services.get(i); @@ -128,7 +125,7 @@ public class DataLoaderManagerService extends SystemService { // If there's more than one, return the first one found. try { ApplicationInfo ai = pm.getApplicationInfo(resolved.getPackageName(), 0); - if (checkLoader && !ai.isPrivilegedApp()) { + if (!ai.isPrivilegedApp()) { Slog.w(TAG, "Data loader: " + resolved + " is not a privileged app, skipping."); continue; diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 7dd2e5506bb7..83fe5562324f 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -331,7 +331,8 @@ public class StagingManager { } // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device. - private void abortCheckpoint() { + private void abortCheckpoint(String errorMsg) { + Slog.e(TAG, "Aborting checkpoint: " + errorMsg); try { if (supportsCheckpoint() && needsCheckpoint()) { mApexManager.revertActiveSessions(); @@ -504,6 +505,8 @@ public class StagingManager { // mode. If not, we fail all sessions. if (supportsCheckpoint() && !needsCheckpoint()) { // TODO(b/146343545): Persist failure reason across checkpoint reboot + Slog.d(TAG, "Reverting back to safe state. Marking " + session.sessionId + + " as failed."); session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, "Reverting back to safe state"); return; @@ -524,26 +527,29 @@ public class StagingManager { if (hasApex) { if (apexSessionInfo == null) { + String errorMsg = "apexd did not know anything about a staged session supposed to" + + " be activated"; session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "apexd did not know anything about a staged session supposed to be" - + "activated"); - abortCheckpoint(); + errorMsg); + abortCheckpoint(errorMsg); return; } if (isApexSessionFailed(apexSessionInfo)) { + String errorMsg = "APEX activation failed. Check logcat messages from apexd for " + + "more information."; session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "APEX activation failed. Check logcat messages from apexd for " - + "more information."); - abortCheckpoint(); + errorMsg); + abortCheckpoint(errorMsg); return; } if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) { // Apexd did not apply the session for some unknown reason. There is no guarantee // that apexd will install it next time. Safer to proactively mark as failed. + String errorMsg = "Staged session " + session.sessionId + "at boot didn't " + + "activate nor fail. Marking it as failed anyway."; session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "Staged session " + session.sessionId + "at boot didn't " - + "activate nor fail. Marking it as failed anyway."); - abortCheckpoint(); + errorMsg); + abortCheckpoint(errorMsg); return; } snapshotAndRestoreForApexSession(session); @@ -556,7 +562,7 @@ public class StagingManager { installApksInSession(session); } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); - abortCheckpoint(); + abortCheckpoint(e.getMessage()); // If checkpoint is not supported, we have to handle failure for one staged session. if (!hasApex) { diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index feb3e06c172e..84cd4cfd67be 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -60,6 +60,7 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.DumpUtils; import com.android.internal.view.AppearanceRegion; import com.android.server.LocalServices; +import com.android.server.UiThread; import com.android.server.notification.NotificationDelegate; import com.android.server.policy.GlobalActionsProvider; import com.android.server.power.ShutdownThread; @@ -1118,7 +1119,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } private void notifyBarAttachChanged() { - mHandler.post(() -> { + UiThread.getHandler().post(() -> { if (mGlobalActionListener == null) return; mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null); }); diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java index 73bb4bf956c3..948439da4863 100644 --- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java +++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java @@ -20,6 +20,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Binder; import android.util.EventLog; import android.util.Slog; @@ -134,7 +135,12 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { private BufferedInputStream getAltContent(Context c, Intent i) throws IOException { Uri content = getContentFromIntent(i); - return new BufferedInputStream(c.getContentResolver().openInputStream(content)); + Binder.allowBlockingForCurrentThread(); + try { + return new BufferedInputStream(c.getContentResolver().openInputStream(content)); + } finally { + Binder.defaultBlockingForCurrentThread(); + } } private byte[] getCurrentContent() { diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index d7a80bfd7928..68224b59a287 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -27,7 +27,6 @@ import static com.android.server.wm.utils.RegionUtils.forEachRect; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.NonNull; -import android.app.Service; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -127,7 +126,7 @@ final class AccessibilityController { public boolean setWindowsForAccessibilityCallbackLocked(int displayId, WindowsForAccessibilityCallback callback) { if (callback != null) { - final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); + final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId); if (dc == null) { return false; } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 984ae218431c..688c9ae705d7 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1570,10 +1570,16 @@ class ActivityStarter { mService.mUgmInternal.grantUriPermissionFromIntent(mCallingUid, mStartActivity.packageName, mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.mUserId); - mService.getPackageManagerInternalLocked().grantImplicitAccess( - mStartActivity.mUserId, mIntent, - UserHandle.getAppId(mStartActivity.info.applicationInfo.uid), mCallingUid, - true /*direct*/); + if (mStartActivity.resultTo != null && mStartActivity.resultTo.info != null) { + // we need to resolve resultTo to a uid as grantImplicitAccess deals explicitly in UIDs + final PackageManagerInternal pmInternal = + mService.getPackageManagerInternalLocked(); + final int resultToUid = pmInternal.getPackageUidInternal( + mStartActivity.resultTo.info.packageName, 0, mStartActivity.mUserId); + pmInternal.grantImplicitAccess(mStartActivity.mUserId, mIntent, + UserHandle.getAppId(mStartActivity.info.applicationInfo.uid) /*recipient*/, + resultToUid /*visible*/, true /*direct*/); + } if (newTask) { EventLogTags.writeWmCreateTask(mStartActivity.mUserId, mStartActivity.getTask().mTaskId); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 911812bc7410..d3ff912ea327 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -680,12 +680,15 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) { - if (mFontScaleUri.equals(uri)) { - updateFontScaleIfNeeded(userId); - } else if (mHideErrorDialogsUri.equals(uri)) { - synchronized (mGlobalLock) { - updateShouldShowDialogsLocked(getGlobalConfiguration()); + public void onChange(boolean selfChange, Iterable<Uri> uris, int flags, + @UserIdInt int userId) { + for (Uri uri : uris) { + if (mFontScaleUri.equals(uri)) { + updateFontScaleIfNeeded(userId); + } else if (mHideErrorDialogsUri.equals(uri)) { + synchronized (mGlobalLock) { + updateShouldShowDialogsLocked(getGlobalConfiguration()); + } } } } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 58aefdc0e547..ada6d4710967 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -274,7 +274,7 @@ class InsetsSourceProvider { // window crop of the surface controls (including the leash) until the client finishes // drawing the new frame of the new orientation. Although we cannot defer the reparent // operation, it is fine, because reparent won't cause any visual effect. - final SurfaceControl barrier = mWin.getDeferTransactionBarrier(); + final SurfaceControl barrier = mWin.getClientViewRootSurface(); t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber); t.deferTransactionUntil(leash, barrier, frameNumber); } diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java index 024da888248d..8e1c6329f39d 100644 --- a/services/core/java/com/android/server/wm/SeamlessRotator.java +++ b/services/core/java/com/android/server/wm/SeamlessRotator.java @@ -118,9 +118,9 @@ public class SeamlessRotator { finish(t, win); if (win.mWinAnimator.mSurfaceController != null && !timeout) { t.deferTransactionUntil(win.mSurfaceControl, - win.getDeferTransactionBarrier(), win.getFrameNumber()); + win.getClientViewRootSurface(), win.getFrameNumber()); t.deferTransactionUntil(win.mWinAnimator.mSurfaceController.mSurfaceControl, - win.getDeferTransactionBarrier(), win.getFrameNumber()); + win.getClientViewRootSurface(), win.getFrameNumber()); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 27f1ca025a93..386755de9b87 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5660,8 +5660,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mSession.mPid == pid && isNonToastOrStarting() && isVisibleNow(); } - SurfaceControl getDeferTransactionBarrier() { - return mWinAnimator.getDeferTransactionBarrier(); + SurfaceControl getClientViewRootSurface() { + return mWinAnimator.getClientViewRootSurface(); } @Override diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 81d0e3e88c1a..79dc5366445e 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -383,8 +383,9 @@ class WindowStateAnimator { // Make sure to reparent any children of the new surface back to the preserved // surface before destroying it. if (mSurfaceController != null && mPendingDestroySurface != null) { - mPostDrawTransaction.reparentChildren(mSurfaceController.mSurfaceControl, - mPendingDestroySurface.mSurfaceControl).apply(); + mPostDrawTransaction.reparentChildren( + mSurfaceController.getClientViewRootSurface(), + mPendingDestroySurface.mSurfaceControl).apply(); } destroySurfaceLocked(); mSurfaceDestroyDeferred = true; @@ -413,9 +414,9 @@ class WindowStateAnimator { // child layers need to be reparented to the new surface to make this // transparent to the app. if (mWin.mActivityRecord == null || mWin.mActivityRecord.isRelaunching() == false) { - mPostDrawTransaction.reparentChildren(mPendingDestroySurface.mSurfaceControl, - mSurfaceController.mSurfaceControl) - .apply(); + mPostDrawTransaction.reparentChildren( + mPendingDestroySurface.getClientViewRootSurface(), + mSurfaceController.mSurfaceControl).apply(); } } } @@ -875,7 +876,7 @@ class WindowStateAnimator { if (mSurfaceResized && (mAttrType == TYPE_BASE_APPLICATION) && (task != null) && (task.getMainWindowSizeChangeTransaction() != null)) { - mSurfaceController.deferTransactionUntil(mWin.getDeferTransactionBarrier(), + mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(), mWin.getFrameNumber()); SurfaceControl.mergeToGlobalTransaction(task.getMainWindowSizeChangeTransaction()); task.setMainWindowSizeChangeTransaction(null); @@ -1012,7 +1013,7 @@ class WindowStateAnimator { // the WS position is reset (so the stack position is shown) at the same // time that the buffer size changes. setOffsetPositionForStackResize(false); - mSurfaceController.deferTransactionUntil(mWin.getDeferTransactionBarrier(), + mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(), mWin.getFrameNumber()); } else { final ActivityStack stack = mWin.getRootTask(); @@ -1043,7 +1044,7 @@ class WindowStateAnimator { // comes in at the new size (normally position and crop are unfrozen). // deferTransactionUntil accomplishes this for us. if (wasForceScaled && !mForceScaleUntilResize) { - mSurfaceController.deferTransactionUntil(mWin.getDeferTransactionBarrier(), + mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(), mWin.getFrameNumber()); mSurfaceController.forceScaleableInTransaction(false); } @@ -1288,8 +1289,9 @@ class WindowStateAnimator { if (mPendingDestroySurface != null && mDestroyPreservedSurfaceUponRedraw) { final SurfaceControl pendingSurfaceControl = mPendingDestroySurface.mSurfaceControl; mPostDrawTransaction.reparent(pendingSurfaceControl, null); - mPostDrawTransaction.reparentChildren(pendingSurfaceControl, - mSurfaceController.mSurfaceControl); + mPostDrawTransaction.reparentChildren( + mPendingDestroySurface.getClientViewRootSurface(), + mSurfaceController.mSurfaceControl); } SurfaceControl.mergeToGlobalTransaction(mPostDrawTransaction); @@ -1521,10 +1523,10 @@ class WindowStateAnimator { mOffsetPositionForStackResize = offsetPositionForStackResize; } - SurfaceControl getDeferTransactionBarrier() { + SurfaceControl getClientViewRootSurface() { if (!hasSurface()) { return null; } - return mSurfaceController.getDeferTransactionBarrier(); + return mSurfaceController.getClientViewRootSurface(); } } diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 383c0d9ab3d4..d7c97b922d2d 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -533,7 +533,15 @@ class WindowSurfaceController { return mSurfaceH; } - SurfaceControl getDeferTransactionBarrier() { + /** + * Returns the Surface which the client-framework ViewRootImpl will be using. + * This is either the WSA SurfaceControl or it's BLAST child surface. + * This has too main uses: + * 1. This is the Surface the client will add children to, we use this to make + * sure we don't reparent the BLAST surface itself when calling reparentChildren + * 2. We use this as the barrier Surface for some deferTransaction operations. + */ + SurfaceControl getClientViewRootSurface() { if (mBLASTSurfaceControl != null) { return mBLASTSurfaceControl; } diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index 4075da6d8159..28613e101b7c 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -31,7 +31,7 @@ public: BinderIncrementalService(const sp<IServiceManager>& sm); static BinderIncrementalService* start(); - static const char16_t* getServiceName() { return u"incremental_service"; } + static const char16_t* getServiceName() { return u"incremental"; } status_t dump(int fd, const Vector<String16>& args) final; void onSystemReady(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java index 4722b39cc652..e5ec1f76c554 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java @@ -36,6 +36,7 @@ import com.android.server.appop.AppOpsService; import com.android.server.testables.TestableDeviceConfig; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -267,9 +268,7 @@ public final class CachedAppOptimizerTest { @Test public void useFreeze_doesNotListenToDeviceConfigChanges() throws InterruptedException { - if (!mCachedAppOptimizerUnderTest.isFreezerSupported()) { - return; - } + Assume.assumeTrue(mCachedAppOptimizerUnderTest.isFreezerSupported()); assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isEqualTo( CachedAppOptimizer.DEFAULT_USE_FREEZER); diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java index 16291b256c44..425c7247e50f 100644 --- a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java @@ -176,7 +176,8 @@ public class OverrideValidatorImplTest { CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext) .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1) .addTargetSdkChangeWithId(TARGET_SDK, 2) - .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3).build(); + .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3) + .addDisabledChangeWithId(4).build(); IOverrideValidator overrideValidator = config.getOverrideValidator(); when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) .thenReturn(ApplicationInfoBuilder.create() @@ -190,6 +191,8 @@ public class OverrideValidatorImplTest { overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); OverrideAllowedState stateTargetSdkAfterChange = overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + OverrideAllowedState stateDisabledChange = + overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME); assertThat(stateTargetSdkLessChange) .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_BEFORE)); @@ -197,6 +200,8 @@ public class OverrideValidatorImplTest { .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK)); assertThat(stateTargetSdkAfterChange) .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER)); + assertThat(stateDisabledChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, -1)); } @Test @@ -343,21 +348,22 @@ public class OverrideValidatorImplTest { } @Test - public void getOverrideAllowedState_finalBuildDisabledChangeDebugApp_rejectOverride() + public void getOverrideAllowedState_finalBuildDisabledChangeDebugApp_allowOverride() throws Exception { CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) - .addDisabledChangeWithId(1).build(); + .addDisabledChangeWithId(1).build(); IOverrideValidator overrideValidator = config.getOverrideValidator(); when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) .thenReturn(ApplicationInfoBuilder.create() .withPackageName(PACKAGE_NAME) + .withTargetSdk(TARGET_SDK) .debuggable().build()); OverrideAllowedState allowedState = overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); assertThat(allowedState) - .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1)); + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, -1)); } @Test 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 e2b63e2bb9b7..3dd150479ddc 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -62,7 +62,6 @@ import android.net.Uri; import android.os.Handler; import android.os.Message; import android.provider.Settings; -import android.security.FileIntegrityManager; import androidx.test.InstrumentationRegistry; @@ -136,7 +135,6 @@ public class AppIntegrityManagerServiceImplTest { @Mock RuleEvaluationEngine mRuleEvaluationEngine; @Mock IntegrityFileManager mIntegrityFileManager; @Mock Handler mHandler; - FileIntegrityManager mFileIntegrityManager; private final Context mRealContext = InstrumentationRegistry.getTargetContext(); @@ -165,16 +163,12 @@ public class AppIntegrityManagerServiceImplTest { Files.copy(inputStream, mTestApkSourceStamp.toPath(), REPLACE_EXISTING); } - mFileIntegrityManager = - (FileIntegrityManager) - mRealContext.getSystemService(Context.FILE_INTEGRITY_SERVICE); mService = new AppIntegrityManagerServiceImpl( mMockContext, mPackageManagerInternal, mRuleEvaluationEngine, mIntegrityFileManager, - mFileIntegrityManager, mHandler); mSpyPackageManager = spy(mRealContext.getPackageManager()); @@ -379,7 +373,7 @@ public class AppIntegrityManagerServiceImplTest { AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); assertTrue(appInstallMetadata.isStampPresent()); assertTrue(appInstallMetadata.isStampVerified()); - assertFalse(appInstallMetadata.isStampTrusted()); + assertTrue(appInstallMetadata.isStampTrusted()); assertEquals(SOURCE_STAMP_CERTIFICATE_HASH, appInstallMetadata.getStampCertificateHash()); } diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java index fe75266011ed..34bac5de4c43 100644 --- a/telephony/java/android/telephony/ImsManager.java +++ b/telephony/java/android/telephony/ImsManager.java @@ -34,8 +34,9 @@ public class ImsManager { private Context mContext; /** - * <p>Broadcast Action: Indicates that an IMS operation was rejected by the network due to it - * not being authorized on the network. + * <p>Broadcast Action: Indicates that a previously allowed IMS operation was rejected by the + * network due to the network returning a "forbidden" response. This may be due to a + * provisioning change from the network. * May include the {@link SubscriptionManager#EXTRA_SUBSCRIPTION_INDEX} extra to also specify * which subscription the operation was rejected for. * <p class="note"> diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 40def40c67a9..3c40e35c03e5 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -54,6 +54,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; @@ -11648,11 +11649,9 @@ public class TelephonyManager { } /** - * Override the file path for testing OTA emergency number database in a file partition. + * Override the file path for OTA emergency number database in a file partition. * - * @param otaFilePath The test OTA emergency number database file path; - * if "RESET", recover the original database file partition. - * Format: <root file folder>@<file path> + * @param otaParcelFileDescriptor parcelable file descriptor for OTA emergency number database. * * <p> Requires permission: * {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION} @@ -11662,16 +11661,42 @@ public class TelephonyManager { @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) @SystemApi @TestApi - public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String otaFilePath) { + public void updateOtaEmergencyNumberDbFilePath( + @NonNull ParcelFileDescriptor otaParcelFileDescriptor) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - telephony.updateTestOtaEmergencyNumberDbFilePath(otaFilePath); + telephony.updateOtaEmergencyNumberDbFilePath(otaParcelFileDescriptor); } else { throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { - Log.e(TAG, "notifyOtaEmergencyNumberDatabaseInstalled RemoteException", ex); + Log.e(TAG, "updateOtaEmergencyNumberDbFilePath RemoteException", ex); + ex.rethrowAsRuntimeException(); + } + } + + /** + * Reset the file path to default for OTA emergency number database in a file partition. + * + * <p> Requires permission: + * {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION} + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) + @SystemApi + @TestApi + public void resetOtaEmergencyNumberDbFilePath() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.resetOtaEmergencyNumberDbFilePath(); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Log.e(TAG, "resetOtaEmergencyNumberDbFilePath RemoteException", ex); ex.rethrowAsRuntimeException(); } } diff --git a/telephony/java/android/telephony/ims/ImsUtListener.java b/telephony/java/android/telephony/ims/ImsUtListener.java index bc12404461c7..460a032ce7e0 100644 --- a/telephony/java/android/telephony/ims/ImsUtListener.java +++ b/telephony/java/android/telephony/ims/ImsUtListener.java @@ -49,7 +49,8 @@ public class ImsUtListener { * {@link ImsSsInfo#CLIR_STATUS_TEMPORARILY_RESTRICTED}, and * {@link ImsSsInfo#CLIR_STATUS_TEMPORARILY_ALLOWED}. * @deprecated Use {@link #onLineIdentificationSupplementaryServiceResponse(int, ImsSsInfo)} - * instead. + * instead, this key has been added for backwards compatibility with older proprietary + * implementations only and is being phased out. */ @Deprecated public static final String BUNDLE_KEY_CLIR = "queryClir"; @@ -60,7 +61,8 @@ public class ImsUtListener { * response. The value will be an instance of {@link ImsSsInfo}, which contains the response to * the query. * @deprecated Use {@link #onLineIdentificationSupplementaryServiceResponse(int, ImsSsInfo)} - * instead. + * instead, this key has been added for backwards compatibility with older proprietary + * implementations only and is being phased out. */ @Deprecated public static final String BUNDLE_KEY_SSINFO = "imsSsInfo"; @@ -123,7 +125,7 @@ public class ImsUtListener { try { mServiceInterface.lineIdentificationSupplementaryServiceResponse(id, configuration); } catch (RemoteException e) { - Log.w(LOG_TAG, "onLineIdentificationSupplementaryServicesResponse: remote exception"); + e.rethrowFromSystemServer(); } } diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 037084608d26..00fa94201297 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -305,13 +305,13 @@ public class ProvisioningManager { /** * An integer key associated with the carrier configured expiration time in seconds for - * RCS presence published offline availability in RCS presence. + * published offline availability in RCS presence provided, which is provided to the network. * <p> * Value is in Integer format. * @see #setProvisioningIntValue(int, int) * @see #getProvisioningIntValue(int) */ - public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; + public static final int KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC = 16; /** * An integer key associated with whether or not capability discovery is provisioned for this @@ -326,8 +326,10 @@ public class ProvisioningManager { public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; /** - * An integer key associated with the period of time the capability information of each contact - * is cached on the device. + * An integer key associated with the period of time in seconds the capability information of + * each contact is cached on the device. + * <p> + * Seconds are used because this is usually measured in the span of days. * <p> * Value is in Integer format. * @see #setProvisioningIntValue(int, int) @@ -337,7 +339,8 @@ public class ProvisioningManager { /** * An integer key associated with the period of time in seconds that the availability - * information of a contact is cached on the device. + * information of a contact is cached on the device, which is based on the carrier provisioning + * configuration from the network. * <p> * Value is in Integer format. * @see #setProvisioningIntValue(int, int) @@ -347,7 +350,8 @@ public class ProvisioningManager { /** * An integer key associated with the carrier configured interval in seconds expected between - * successive capability polling attempts. + * successive capability polling attempts, which is based on the carrier provisioning + * configuration from the network. * <p> * Value is in Integer format. * @see #setProvisioningIntValue(int, int) @@ -357,7 +361,7 @@ public class ProvisioningManager { /** * An integer key representing the minimum time allowed between two consecutive presence publish - * messages from the device. + * messages from the device in milliseconds. * <p> * Value is in Integer format. * @see #setProvisioningIntValue(int, int) @@ -378,7 +382,7 @@ public class ProvisioningManager { /** * An integer associated with the expiration timer used during the SIP subscription of a * Request Contained List (RCL), which is used to retrieve the RCS capabilities of the contact - * book. + * book. This timer value is sent in seconds to the network. * <p> * Value is in Integer format. * @see #setProvisioningIntValue(int, int) @@ -470,7 +474,8 @@ public class ProvisioningManager { public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32; /** - * Registration retry Base Time value in seconds. + * Registration retry Base Time value in seconds, which is based off of the carrier + * configuration. * Value is in Integer format. * @see #setProvisioningIntValue(int, int) * @see #getProvisioningIntValue(int) @@ -478,7 +483,8 @@ public class ProvisioningManager { public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33; /** - * Registration retry Max Time value in seconds. + * Registration retry Max Time value in seconds, which is based off of the carrier + * configuration. * Value is in Integer format. * @see #setProvisioningIntValue(int, int) * @see #getProvisioningIntValue(int) diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 58e9b7008050..30306c7f9dea 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -24,12 +24,10 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.Context; -import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.provider.Telephony; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsRcsController; import android.telephony.ims.aidl.IRcsUceControllerCallback; @@ -138,7 +136,7 @@ public class RcsUceAdapter { * UCE. * @hide */ - public static final int PUBLISH_STATE_200_OK = 1; + public static final int PUBLISH_STATE_OK = 1; /** * The hasn't published its capabilities since boot or hasn't gotten any publish response yet. @@ -178,7 +176,7 @@ public class RcsUceAdapter { /**@hide*/ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "PUBLISH_STATE_", value = { - PUBLISH_STATE_200_OK, + PUBLISH_STATE_OK, PUBLISH_STATE_NOT_PUBLISHED, PUBLISH_STATE_VOLTE_PROVISION_ERROR, PUBLISH_STATE_RCS_PROVISION_ERROR, @@ -305,7 +303,7 @@ public class RcsUceAdapter { * Gets the last publish result from the UCE service if the device is using an RCS presence * server. * @return The last publish result from the UCE service. If the device is using SIP OPTIONS, - * this method will return {@link #PUBLISH_STATE_200_OK} as well. + * this method will return {@link #PUBLISH_STATE_OK} as well. * @throws ImsException if the subscription associated with this instance of * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not * available. This can happen if the ImsService has crashed, for example, or if the subscription diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index 96f77d809183..d0cec52dfc86 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -270,11 +270,12 @@ public class ImsConfig { /** * Requested expiration for Published Offline availability. * Value is in Integer format. - * @deprecated use {@link ProvisioningManager#KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC}. + * @deprecated use + * {@link ProvisioningManager#KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC}. */ @Deprecated public static final int PUBLISH_TIMER_EXTENDED = - ProvisioningManager.KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC; + ProvisioningManager.KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC; /** * diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index af5089f8e0d9..dcf339c1be2f 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -22,6 +22,7 @@ import android.content.IntentSender; import android.os.Bundle; import android.os.IBinder; import android.os.Messenger; +import android.os.ParcelFileDescriptor; import android.os.ResultReceiver; import android.os.WorkSource; import android.net.NetworkStats; @@ -2121,9 +2122,14 @@ interface ITelephony { void notifyOtaEmergencyNumberDbInstalled(); /** - * Override the file partition name for testing OTA emergency number database. + * Override a customized file partition name for OTA emergency number database. */ - void updateTestOtaEmergencyNumberDbFilePath(String otaFilePath); + void updateOtaEmergencyNumberDbFilePath(in ParcelFileDescriptor otaParcelFileDescriptor); + + /** + * Reset file partition to default for OTA emergency number database. + */ + void resetOtaEmergencyNumberDbFilePath(); /** * Enable or disable a logical modem stack associated with the slotIndex. diff --git a/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java new file mode 100644 index 000000000000..2fbfeba47b13 --- /dev/null +++ b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.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 com.android.server; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteCallback; +import android.service.watchdog.ExplicitHealthCheckService; +import android.service.watchdog.IExplicitHealthCheckService; + +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; + +public class ExplicitHealthCheckServiceTest { + + private ExplicitHealthCheckService mExplicitHealthCheckService; + private static final String PACKAGE_NAME = "com.test.package"; + + @Before + public void setup() throws Exception { + mExplicitHealthCheckService = spy(ExplicitHealthCheckService.class); + } + + /** + * Test to verify that the correct information is sent in the callback when a package has + * passed an explicit health check. + */ + @Test + public void testNotifyHealthCheckPassed() throws Exception { + IBinder binder = mExplicitHealthCheckService.onBind(new Intent()); + CountDownLatch countDownLatch = new CountDownLatch(1); + RemoteCallback callback = new RemoteCallback(result -> { + assertThat(result.get(ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE)) + .isEqualTo(PACKAGE_NAME); + countDownLatch.countDown(); + }); + IExplicitHealthCheckService.Stub.asInterface(binder).setCallback(callback); + mExplicitHealthCheckService.notifyHealthCheckPassed(PACKAGE_NAME); + countDownLatch.await(); + } +} diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java index ca4ba63142a2..7a60cc105a26 100644 --- a/tests/net/common/java/android/net/CaptivePortalTest.java +++ b/tests/net/common/java/android/net/CaptivePortalTest.java @@ -18,19 +18,26 @@ package android.net; import static org.junit.Assert.assertEquals; +import android.os.Build; import android.os.RemoteException; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class CaptivePortalTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + private static final int DEFAULT_TIMEOUT_MS = 5000; private static final String TEST_PACKAGE_NAME = "com.google.android.test"; @@ -84,6 +91,7 @@ public class CaptivePortalTest { assertEquals(result.mCode, CaptivePortal.APP_RETURN_WANTED_AS_IS); } + @IgnoreUpTo(Build.VERSION_CODES.Q) @Test public void testReevaluateNetwork() { final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.reevaluateNetwork()); diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java index 06c6301d66f3..99dac1439b34 100644 --- a/tests/net/common/java/android/net/LinkAddressTest.java +++ b/tests/net/common/java/android/net/LinkAddressTest.java @@ -28,8 +28,8 @@ import static android.system.OsConstants.RT_SCOPE_SITE; import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static com.android.testutils.MiscAssertsKt.assertEqualBothWays; +import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals; import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay; -import static com.android.testutils.ParcelUtilsKt.assertParcelSane; import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; import static org.junit.Assert.assertEquals; @@ -38,11 +38,17 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.os.Build; import android.os.SystemClock; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,6 +63,8 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class LinkAddressTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); private static final String V4 = "192.0.2.1"; private static final String V6 = "2001:db8::1"; @@ -318,15 +326,29 @@ public class LinkAddressTest { l = new LinkAddress(V6_ADDRESS, 64, 123, 456); assertParcelingIsLossless(l); - l = new LinkAddress(V6_ADDRESS, 64, 123, 456, - 1L, 3600000L); - assertParcelingIsLossless(l); l = new LinkAddress(V4 + "/28", IFA_F_PERMANENT, RT_SCOPE_LINK); - assertParcelSane(l, 6); + assertParcelingIsLossless(l); } - @Test + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testLifetimeParceling() { + final LinkAddress l = new LinkAddress(V6_ADDRESS, 64, 123, 456, 1L, 3600000L); + assertParcelingIsLossless(l); + } + + @Test @IgnoreAfter(Build.VERSION_CODES.Q) + public void testFieldCount_Q() { + assertFieldCountEquals(4, LinkAddress.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testFieldCount() { + // Make sure any new field is covered by the above parceling tests when changing this number + assertFieldCountEquals(6, LinkAddress.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) public void testDeprecationTime() { try { new LinkAddress(V6_ADDRESS, 64, 0, 456, @@ -347,7 +369,7 @@ public class LinkAddressTest { } catch (IllegalArgumentException expected) { } } - @Test + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) public void testExpirationTime() { try { new LinkAddress(V6_ADDRESS, 64, 0, 456, @@ -366,10 +388,13 @@ public class LinkAddressTest { public void testGetFlags() { LinkAddress l = new LinkAddress(V6_ADDRESS, 64, 123, RT_SCOPE_HOST); assertEquals(123, l.getFlags()); + } + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testGetFlags_Deprecation() { // Test if deprecated bit was added/remove automatically based on the provided deprecation // time - l = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_HOST, + LinkAddress l = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_HOST, 1L, LinkAddress.LIFETIME_PERMANENT); // Check if the flag is added automatically. assertTrue((l.getFlags() & IFA_F_DEPRECATED) != 0); @@ -458,8 +483,11 @@ public class LinkAddressTest { (IFA_F_TEMPORARY|IFA_F_TENTATIVE|IFA_F_OPTIMISTIC), RT_SCOPE_UNIVERSE); assertGlobalPreferred(l, "v6,global,tempaddr+optimistic"); + } - l = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testIsGlobalPreferred_DeprecatedInFuture() { + final LinkAddress l = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_UNIVERSE, SystemClock.elapsedRealtime() + 100000, SystemClock.elapsedRealtime() + 200000); // Although the deprecated bit is set, but the deprecation time is in the future, test diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index 9256c57ab4b9..70542b5d3c65 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -16,16 +16,15 @@ package android.net.wifi; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; +import android.net.wifi.WifiAnnotations.ChannelWidth; +import android.net.wifi.WifiAnnotations.WifiStandard; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -313,17 +312,6 @@ public class ScanResult implements Parcelable { */ public static final int WIFI_STANDARD_11AX = 6; - /** @hide */ - @IntDef(prefix = { "WIFI_STANDARD_" }, value = { - WIFI_STANDARD_UNKNOWN, - WIFI_STANDARD_LEGACY, - WIFI_STANDARD_11N, - WIFI_STANDARD_11AC, - WIFI_STANDARD_11AX - }) - @Retention(RetentionPolicy.SOURCE) - public @interface WifiStandard{} - /** * AP wifi standard. */ @@ -368,7 +356,7 @@ public class ScanResult implements Parcelable { * {@link #CHANNEL_WIDTH_80MHZ}, {@link #CHANNEL_WIDTH_160MHZ} * or {@link #CHANNEL_WIDTH_80MHZ_PLUS_MHZ}. */ - public int channelWidth; + public @ChannelWidth int channelWidth; /** * Not used if the AP bandwidth is 20 MHz diff --git a/wifi/java/android/net/wifi/WifiAnnotations.java b/wifi/java/android/net/wifi/WifiAnnotations.java index 05e5b1d45684..acda7e06c95d 100644 --- a/wifi/java/android/net/wifi/WifiAnnotations.java +++ b/wifi/java/android/net/wifi/WifiAnnotations.java @@ -61,6 +61,26 @@ public final class WifiAnnotations { @Retention(RetentionPolicy.SOURCE) public @interface Bandwidth {} + @IntDef(prefix = { "CHANNEL_WIDTH_" }, value = { + ScanResult.CHANNEL_WIDTH_20MHZ, + ScanResult.CHANNEL_WIDTH_40MHZ, + ScanResult.CHANNEL_WIDTH_80MHZ, + ScanResult.CHANNEL_WIDTH_160MHZ, + ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ChannelWidth{} + + @IntDef(prefix = { "WIFI_STANDARD_" }, value = { + ScanResult.WIFI_STANDARD_UNKNOWN, + ScanResult.WIFI_STANDARD_LEGACY, + ScanResult.WIFI_STANDARD_11N, + ScanResult.WIFI_STANDARD_11AC, + ScanResult.WIFI_STANDARD_11AX, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface WifiStandard{} + @IntDef(prefix = { "PROTOCOL_" }, value = { ScanResult.PROTOCOL_NONE, ScanResult.PROTOCOL_WPA, diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 5a7bf4b15f1a..ceb2907e7a93 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -503,6 +503,8 @@ public class WifiConfiguration implements Parcelable { break; case SECURITY_TYPE_EAP_SUITE_B: allowedProtocols.set(WifiConfiguration.Protocol.RSN); + allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP); + allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X); allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192); allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256); allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256); diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 142854a9e41b..70c5e72e4e0c 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -103,7 +103,7 @@ public class WifiInfo implements Parcelable { /** * Wi-Fi standard for the connection */ - private @ScanResult.WifiStandard int mWifiStandard; + private @WifiAnnotations.WifiStandard int mWifiStandard; /** * The unit in which links speeds are expressed. @@ -518,7 +518,7 @@ public class WifiInfo implements Parcelable { * Sets the Wi-Fi standard * @hide */ - public void setWifiStandard(@ScanResult.WifiStandard int wifiStandard) { + public void setWifiStandard(@WifiAnnotations.WifiStandard int wifiStandard) { mWifiStandard = wifiStandard; } @@ -526,7 +526,7 @@ public class WifiInfo implements Parcelable { * Get connection Wi-Fi standard * @return the connection Wi-Fi standard */ - public @ScanResult.WifiStandard int getWifiStandard() { + public @WifiAnnotations.WifiStandard int getWifiStandard() { return mWifiStandard; } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 9703fa61ea23..ff6229684b87 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -2563,7 +2563,7 @@ public class WifiManager { * valid values from {@link ScanResult}'s {@code WIFI_STANDARD_} * @return {@code true} if supported, {@code false} otherwise. */ - public boolean isWifiStandardSupported(@ScanResult.WifiStandard int standard) { + public boolean isWifiStandardSupported(@WifiAnnotations.WifiStandard int standard) { try { return mService.isWifiStandardSupported(standard); } catch (RemoteException e) { diff --git a/wifi/java/android/net/wifi/nl80211/DeviceWiphyCapabilities.java b/wifi/java/android/net/wifi/nl80211/DeviceWiphyCapabilities.java index a045aad9f64c..bb0cc975a3db 100644 --- a/wifi/java/android/net/wifi/nl80211/DeviceWiphyCapabilities.java +++ b/wifi/java/android/net/wifi/nl80211/DeviceWiphyCapabilities.java @@ -19,6 +19,8 @@ package android.net.wifi.nl80211; import android.annotation.NonNull; import android.annotation.SystemApi; import android.net.wifi.ScanResult; +import android.net.wifi.WifiAnnotations.ChannelWidth; +import android.net.wifi.WifiAnnotations.WifiStandard; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -28,6 +30,9 @@ import java.util.Objects; /** * DeviceWiphyCapabilities for wificond * + * Contains the WiFi physical layer attributes and capabilities of the device. + * It is used to collect these attributes from the device driver via wificond. + * * @hide */ @SystemApi @@ -61,7 +66,7 @@ public final class DeviceWiphyCapabilities implements Parcelable { * valid values from {@link ScanResult}'s {@code WIFI_STANDARD_} * @return {@code true} if supported, {@code false} otherwise. */ - public boolean isWifiStandardSupported(int standard) { + public boolean isWifiStandardSupported(@WifiStandard int standard) { switch (standard) { case ScanResult.WIFI_STANDARD_LEGACY: return true; @@ -84,7 +89,7 @@ public final class DeviceWiphyCapabilities implements Parcelable { * valid values from {@link ScanResult}'s {@code WIFI_STANDARD_} * @param support {@code true} if supported, {@code false} otherwise. */ - public void setWifiStandardSupport(int standard, boolean support) { + public void setWifiStandardSupport(@WifiStandard int standard, boolean support) { switch (standard) { case ScanResult.WIFI_STANDARD_11N: m80211nSupported = support; @@ -107,7 +112,7 @@ public final class DeviceWiphyCapabilities implements Parcelable { * * @return {@code true} if supported, {@code false} otherwise. */ - public boolean isChannelWidthSupported(int chWidth) { + public boolean isChannelWidthSupported(@ChannelWidth int chWidth) { switch (chWidth) { case ScanResult.CHANNEL_WIDTH_20MHZ: return true; @@ -131,8 +136,10 @@ public final class DeviceWiphyCapabilities implements Parcelable { * @param chWidth valid values are {@link ScanResult#CHANNEL_WIDTH_160MHZ} and * {@link ScanResult#CHANNEL_WIDTH_80MHZ_PLUS_MHZ} * @param support {@code true} if supported, {@code false} otherwise. + * + * @hide */ - public void setChannelWidthSupported(int chWidth, boolean support) { + public void setChannelWidthSupported(@ChannelWidth int chWidth, boolean support) { switch (chWidth) { case ScanResult.CHANNEL_WIDTH_160MHZ: mChannelWidth160MhzSupported = support; @@ -159,6 +166,8 @@ public final class DeviceWiphyCapabilities implements Parcelable { * Set maximum number of transmit spatial streams * * @param streams number of spatial streams + * + * @hide */ public void setMaxNumberTxSpatialStreams(int streams) { mMaxNumberTxSpatialStreams = streams; @@ -177,6 +186,8 @@ public final class DeviceWiphyCapabilities implements Parcelable { * Set maximum number of receive spatial streams * * @param streams number of streams + * + * @hide */ public void setMaxNumberRxSpatialStreams(int streams) { mMaxNumberRxSpatialStreams = streams; diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java index b5c74d1d01b1..4c22d5d6dc7e 100644 --- a/wifi/tests/src/android/net/wifi/ScanResultTest.java +++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java @@ -42,7 +42,7 @@ public class ScanResultTest { public static final int TEST_LEVEL = -56; public static final int TEST_FREQUENCY = 2412; public static final long TEST_TSF = 04660l; - public static final @ScanResult.WifiStandard int TEST_WIFI_STANDARD = + public static final @WifiAnnotations.WifiStandard int TEST_WIFI_STANDARD = ScanResult.WIFI_STANDARD_11AC; /** diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 047a64b25733..91c74f3c2e3e 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -456,6 +456,8 @@ public class WifiConfigurationTest { config.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B); assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)); + assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)); + assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)); assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.GCMP_256)); assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256)); assertTrue(config.allowedGroupManagementCiphers |