diff options
144 files changed, 3417 insertions, 1457 deletions
diff --git a/api/current.txt b/api/current.txt index db175427139c..e6b44f1bc169 100644 --- a/api/current.txt +++ b/api/current.txt @@ -102,7 +102,7 @@ package android { field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT"; field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS"; field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY"; - field public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"; + field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"; field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR"; field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG"; field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS"; @@ -15389,8 +15389,8 @@ package android.graphics.drawable { method public boolean setState(@NonNull int[]); method public void setTint(@ColorInt int); method public void setTintList(@Nullable android.content.res.ColorStateList); - method @Deprecated public void setTintMode(@NonNull android.graphics.PorterDuff.Mode); - method public void setTintMode(@NonNull android.graphics.BlendMode); + method @Deprecated public void setTintMode(@Nullable android.graphics.PorterDuff.Mode); + method public void setTintMode(@Nullable android.graphics.BlendMode); method public boolean setVisible(boolean, boolean); method public void unscheduleSelf(@NonNull Runnable); } @@ -17058,6 +17058,7 @@ package android.hardware.camera2 { public class CaptureFailure { method public long getFrameNumber(); + method @Nullable public String getPhysicalCameraId(); method public int getReason(); method @NonNull public android.hardware.camera2.CaptureRequest getRequest(); method public int getSequenceId(); @@ -28792,11 +28793,11 @@ package android.net { } public final class IpPrefix implements android.os.Parcelable { - method public boolean contains(java.net.InetAddress); + method public boolean contains(@NonNull java.net.InetAddress); method public int describeContents(); - method public java.net.InetAddress getAddress(); - method public int getPrefixLength(); - method public byte[] getRawAddress(); + method @NonNull public java.net.InetAddress getAddress(); + method @IntRange(from=0, to=128) public int getPrefixLength(); + method @NonNull public byte[] getRawAddress(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR; } @@ -28869,7 +28870,7 @@ package android.net { method public int describeContents(); method public java.net.InetAddress getAddress(); method public int getFlags(); - method public int getPrefixLength(); + method @IntRange(from=0, to=128) public int getPrefixLength(); method public int getScope(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkAddress> CREATOR; @@ -29139,9 +29140,9 @@ package android.net { public final class RouteInfo implements android.os.Parcelable { method public int describeContents(); - method public android.net.IpPrefix getDestination(); - method public java.net.InetAddress getGateway(); - method public String getInterface(); + method @NonNull public android.net.IpPrefix getDestination(); + method @Nullable public java.net.InetAddress getGateway(); + method @Nullable public String getInterface(); method public boolean hasGateway(); method public boolean isDefaultRoute(); method public boolean matches(java.net.InetAddress); @@ -42534,6 +42535,7 @@ package android.system { method public static int getpid(); method public static int getppid(); method public static java.net.SocketAddress getsockname(java.io.FileDescriptor) throws android.system.ErrnoException; + method @NonNull public static android.system.StructTimeval getsockoptTimeval(@NonNull java.io.FileDescriptor, int, int) throws android.system.ErrnoException; method public static int gettid(); method public static int getuid(); method public static byte[] getxattr(String, String) throws android.system.ErrnoException; @@ -42584,6 +42586,7 @@ package android.system { method @Deprecated public static void setgid(int) throws android.system.ErrnoException; method public static int setsid() throws android.system.ErrnoException; method public static void setsockoptInt(java.io.FileDescriptor, int, int, int) throws android.system.ErrnoException; + method public static void setsockoptTimeval(@NonNull java.io.FileDescriptor, int, int, @NonNull android.system.StructTimeval) throws android.system.ErrnoException; method @Deprecated public static void setuid(int) throws android.system.ErrnoException; method public static void setxattr(String, String, byte[], int) throws android.system.ErrnoException; method public static void shutdown(java.io.FileDescriptor, int) throws android.system.ErrnoException; @@ -42789,6 +42792,10 @@ package android.system { field public static final int F_SETOWN; field public static final int F_UNLCK; field public static final int F_WRLCK; + field public static final int ICMP6_ECHO_REPLY; + field public static final int ICMP6_ECHO_REQUEST; + field public static final int ICMP_ECHO; + field public static final int ICMP_ECHOREPLY; field public static final int IFA_F_DADFAILED; field public static final int IFA_F_DEPRECATED; field public static final int IFA_F_HOMEADDRESS; @@ -43161,6 +43168,13 @@ package android.system { field public final long tv_sec; } + public final class StructTimeval { + method @NonNull public static android.system.StructTimeval fromMillis(long); + method public long toMillis(); + field public final long tv_sec; + field public final long tv_usec; + } + public final class StructUtsname { ctor public StructUtsname(String, String, String, String, String); field public final String machine; diff --git a/api/system-current.txt b/api/system-current.txt index 8ee1e0ab5867..ba6e348d375d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1258,9 +1258,9 @@ package android.bluetooth { public final class BluetoothDevice implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean getSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); @@ -1269,7 +1269,6 @@ package android.bluetooth { field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; - field public static final String EXTRA_SILENCE_ENABLED = "android.bluetooth.device.extra.SILENCE_ENABLED"; field public static final int METADATA_COMPANION_APP = 4; // 0x4 field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 @@ -1458,14 +1457,14 @@ package android.content.om { public final class OverlayInfo implements android.os.Parcelable { method public int describeContents(); + method @Nullable public String getCategory(); + method @NonNull public String getPackageName(); + method @Nullable public String getTargetOverlayableName(); + method @Nullable public String getTargetPackageName(); + method public int getUserId(); method public boolean isEnabled(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR; - field public final String category; - field public final String packageName; - field public final String targetOverlayableName; - field public final String targetPackageName; - field public final int userId; } public class OverlayManager { @@ -4061,7 +4060,7 @@ package android.net { } public final class IpPrefix implements android.os.Parcelable { - ctor public IpPrefix(@NonNull java.net.InetAddress, int); + ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); ctor public IpPrefix(@NonNull String); } @@ -4082,8 +4081,8 @@ package android.net { } public class LinkAddress implements android.os.Parcelable { - ctor public LinkAddress(java.net.InetAddress, int, int, int); - ctor public LinkAddress(@NonNull java.net.InetAddress, int); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); ctor public LinkAddress(@NonNull String); ctor public LinkAddress(@NonNull String, int, int); method public boolean isGlobalPreferred(); @@ -4264,8 +4263,8 @@ package android.net.apf { public final class ApfCapabilities implements android.os.Parcelable { ctor public ApfCapabilities(int, int, int); method public int describeContents(); - method public static boolean getApfDrop8023Frames(@NonNull android.content.Context); - method @NonNull public static int[] getApfEthTypeBlackList(@NonNull android.content.Context); + method public static boolean getApfDrop8023Frames(); + method @NonNull public static int[] getApfEtherTypeBlackList(); method public boolean hasDataAccess(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR; diff --git a/api/test-current.txt b/api/test-current.txt index 7762baabc272..84e641e0145e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -680,9 +680,11 @@ package android.content.pm { field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; field public static final int FLAG_PERMISSION_HIDDEN = 1024; // 0x400 + field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4 field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40 field public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8 field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80 + field public static final int FLAG_PERMISSION_SYSTEM_FIXED = 16; // 0x10 field public static final int FLAG_PERMISSION_USER_FIXED = 2; // 0x2 field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 @@ -1239,7 +1241,7 @@ package android.net { } public final class IpPrefix implements android.os.Parcelable { - ctor public IpPrefix(@NonNull java.net.InetAddress, int); + ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); ctor public IpPrefix(@NonNull String); } @@ -1248,8 +1250,8 @@ package android.net { } public class LinkAddress implements android.os.Parcelable { - ctor public LinkAddress(java.net.InetAddress, int, int, int); - ctor public LinkAddress(@NonNull java.net.InetAddress, int); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); ctor public LinkAddress(@NonNull String); ctor public LinkAddress(@NonNull String, int, int); method public boolean isGlobalPreferred(); @@ -1358,8 +1360,8 @@ package android.net.apf { public final class ApfCapabilities implements android.os.Parcelable { ctor public ApfCapabilities(int, int, int); method public int describeContents(); - method public static boolean getApfDrop8023Frames(@NonNull android.content.Context); - method @NonNull public static int[] getApfEthTypeBlackList(@NonNull android.content.Context); + method public static boolean getApfDrop8023Frames(); + method @NonNull public static int[] getApfEtherTypeBlackList(); method public boolean hasDataAccess(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 023371d3b443..80b6349ca284 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4037,7 +4037,7 @@ public class ActivityManager { * continues running even if the process is killed and restarted. To remove the watch, * use {@link #clearWatchHeapLimit()}. * - * <p>This API only work if the calling process has been marked as + * <p>This API only works if the calling process has been marked as * {@link ApplicationInfo#FLAG_DEBUGGABLE} or this is running on a debuggable * (userdebug or eng) build.</p> * diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2441672fb048..2a603d270993 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -6937,9 +6937,6 @@ public final class ActivityThread extends ClientTransactionHandler { // If feature is disabled, we don't need to install if (!DEPRECATE_DATA_COLUMNS) return; - // If app is modern enough, we don't need to install - if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) return; - // Install interception and make sure it sticks! Os def = null; do { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 4b0c05f323cd..ddae34c64358 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -5440,13 +5440,14 @@ public class DevicePolicyManager { } /** - * Called by a device or profile owner to set whether auto time is required. If auto time is - * required, no user will be able set the date and time and network date and time will be used. + * Called by a device owner, or alternatively a profile owner from Android 8.0 (API level 26) or + * higher, to set whether auto time is required. If auto time is required, no user will be able + * set the date and time and network date and time will be used. * <p> * Note: if auto time is required the user can still manually set the time zone. * <p> - * The calling device admin must be a device or profile owner. If it is not, a security - * exception will be thrown. + * The calling device admin must be a device owner, or alternatively a profile owner from + * Android 8.0 (API level 26) or higher. If it is not, a security exception will be thrown. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param required Whether auto time is set required or not. @@ -10789,8 +10790,8 @@ public class DevicePolicyManager { } /** - * Returns whether the device is being used as a managed kiosk, as defined in the CDD. As of - * this release, these requirements are as follows: + * Returns whether the device is being used as a managed kiosk. These requirements are as + * follows: * <ul> * <li>The device is in Lock Task (therefore there is also a Device Owner app on the * device)</li> @@ -10829,11 +10830,11 @@ public class DevicePolicyManager { } /** - * Returns whether the device is being used as an unattended managed kiosk, as defined in the - * CDD. As of this release, these requirements are as follows: + * Returns whether the device is being used as an unattended managed kiosk. These requirements + * are as follows: * <ul> - * <li>The device is being used as a managed kiosk, as defined in the CDD and verified at - * {@link #isManagedKiosk()}</li> + * <li>The device is being used as a managed kiosk, as defined at {@link + * #isManagedKiosk()}</li> * <li>The device has not received user input for at least 30 minutes</li> * </ul> * diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index fa2c9f8a52ae..204d7e3ceca6 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -535,7 +535,6 @@ public final class BluetoothDevice implements Parcelable { /** * Intent to broadcast silence mode changed. * Alway contains the extra field {@link #EXTRA_DEVICE} - * Alway contains the extra field {@link #EXTRA_SILENCE_ENABLED} * * @hide */ @@ -545,16 +544,6 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; /** - * Used as an extra field in {@link #ACTION_SILENCE_MODE_CHANGED} intent, - * contains whether device is in silence mode as boolean. - * - * @hide - */ - @SystemApi - public static final String EXTRA_SILENCE_ENABLED = - "android.bluetooth.device.extra.SILENCE_ENABLED"; - - /** * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent. * * @hide @@ -1615,7 +1604,8 @@ public final class BluetoothDevice implements Parcelable { } /** - * Set the Bluetooth device silence mode. + * Sets whether the {@link BluetoothDevice} enters silence mode. Audio will not + * be routed to the {@link BluetoothDevice} if set to {@code true}. * * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice} * is an active device (for A2DP or HFP), the active device for that profile @@ -1635,6 +1625,7 @@ public final class BluetoothDevice implements Parcelable { * * @param silence true to enter silence mode, false to exit * @return true on success, false on error. + * @throws IllegalStateException if Bluetooth is not turned ON. * @hide */ @SystemApi @@ -1642,12 +1633,9 @@ public final class BluetoothDevice implements Parcelable { public boolean setSilenceMode(boolean silence) { final IBluetooth service = sService; if (service == null) { - return false; + throw new IllegalStateException("Bluetooth is not turned ON"); } try { - if (getSilenceMode() == silence) { - return true; - } return service.setSilenceMode(this, silence); } catch (RemoteException e) { Log.e(TAG, "setSilenceMode fail", e); @@ -1656,24 +1644,25 @@ public final class BluetoothDevice implements Parcelable { } /** - * Get the device silence mode status + * Check whether the {@link BluetoothDevice} is in silence mode * * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. * * @return true on device in silence mode, otherwise false. + * @throws IllegalStateException if Bluetooth is not turned ON. * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean getSilenceMode() { + public boolean isInSilenceMode() { final IBluetooth service = sService; if (service == null) { - return false; + throw new IllegalStateException("Bluetooth is not turned ON"); } try { return service.getSilenceMode(this); } catch (RemoteException e) { - Log.e(TAG, "getSilenceMode fail", e); + Log.e(TAG, "isInSilenceMode fail", e); return false; } } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index fa85f0ae1670..791c55196ecf 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1313,6 +1313,12 @@ public abstract class ContentResolver implements ContentInterface { public final @Nullable ParcelFileDescriptor openFileDescriptor(@NonNull Uri uri, @NonNull String mode, @Nullable CancellationSignal cancellationSignal) throws FileNotFoundException { + try { + if (mWrapped != null) return mWrapped.openFile(uri, mode, cancellationSignal); + } catch (RemoteException e) { + return null; + } + AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode, cancellationSignal); if (afd == null) { return null; @@ -1455,6 +1461,12 @@ public abstract class ContentResolver implements ContentInterface { Preconditions.checkNotNull(uri, "uri"); Preconditions.checkNotNull(mode, "mode"); + try { + if (mWrapped != null) return mWrapped.openAssetFile(uri, mode, cancellationSignal); + } catch (RemoteException e) { + return null; + } + String scheme = uri.getScheme(); if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { if (!"r".equals(mode)) { @@ -1634,6 +1646,12 @@ public abstract class ContentResolver implements ContentInterface { Preconditions.checkNotNull(uri, "uri"); Preconditions.checkNotNull(mimeType, "mimeType"); + try { + if (mWrapped != null) return mWrapped.openTypedAssetFile(uri, mimeType, opts, cancellationSignal); + } catch (RemoteException e) { + return null; + } + IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { throw new FileNotFoundException("No content provider: " + uri); @@ -3525,12 +3543,7 @@ public abstract class ContentResolver implements ContentInterface { */ public @NonNull Bitmap loadThumbnail(@NonNull Uri uri, @NonNull Size size, @Nullable CancellationSignal signal) throws IOException { - Objects.requireNonNull(uri); - Objects.requireNonNull(size); - - try (ContentProviderClient client = acquireContentProviderClient(uri)) { - return loadThumbnail(client, uri, size, signal, ImageDecoder.ALLOCATOR_SOFTWARE); - } + return loadThumbnail(this, uri, size, signal, ImageDecoder.ALLOCATOR_SOFTWARE); } /** {@hide} */ diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index b8c6d87acd5c..597c08392d04 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.UserIdInt; import android.os.Parcel; import android.os.Parcelable; @@ -135,7 +136,6 @@ public final class OverlayInfo implements Parcelable { * * @hide */ - @SystemApi public final String packageName; /** @@ -143,7 +143,6 @@ public final class OverlayInfo implements Parcelable { * * @hide */ - @SystemApi public final String targetPackageName; /** @@ -151,7 +150,6 @@ public final class OverlayInfo implements Parcelable { * * @hide */ - @SystemApi public final String targetOverlayableName; /** @@ -159,7 +157,6 @@ public final class OverlayInfo implements Parcelable { * * @hide */ - @SystemApi public final String category; /** @@ -178,7 +175,6 @@ public final class OverlayInfo implements Parcelable { * User handle for which this overlay applies * @hide */ - @SystemApi public final int userId; /** @@ -243,6 +239,56 @@ public final class OverlayInfo implements Parcelable { ensureValidState(); } + /** + * Returns package name of the current overlay. + * @hide + */ + @SystemApi + @NonNull + public String getPackageName() { + return packageName; + } + + /** + * Returns the target package name of the current overlay. + * @hide + */ + @SystemApi + @Nullable + public String getTargetPackageName() { + return targetPackageName; + } + + /** + * Returns the category of the current overlay. + * @hide\ + */ + @SystemApi + @Nullable + public String getCategory() { + return category; + } + + /** + * Returns user handle for which this overlay applies to. + * @hide + */ + @SystemApi + @UserIdInt + public int getUserId() { + return userId; + } + + /** + * Returns name of the target overlayable declaration. + * @hide + */ + @SystemApi + @Nullable + public String getTargetOverlayableName() { + return targetOverlayableName; + } + private void ensureValidState() { if (packageName == null) { throw new IllegalArgumentException("packageName must not be null"); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8f7df2517580..de10bb00bfdf 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2998,6 +2998,7 @@ public abstract class PackageManager { * @hide */ @SystemApi + @TestApi public static final int FLAG_PERMISSION_POLICY_FIXED = 1 << 2; /** @@ -3021,6 +3022,7 @@ public abstract class PackageManager { * @hide */ @SystemApi + @TestApi public static final int FLAG_PERMISSION_SYSTEM_FIXED = 1 << 4; /** diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java index cd2bc5f943e7..20ca4a338f01 100644 --- a/core/java/android/hardware/camera2/CaptureFailure.java +++ b/core/java/android/hardware/camera2/CaptureFailure.java @@ -17,6 +17,7 @@ package android.hardware.camera2; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -61,17 +62,19 @@ public class CaptureFailure { private final boolean mDropped; private final int mSequenceId; private final long mFrameNumber; + private final String mErrorPhysicalCameraId; /** * @hide */ public CaptureFailure(CaptureRequest request, int reason, - boolean dropped, int sequenceId, long frameNumber) { + boolean dropped, int sequenceId, long frameNumber, String errorPhysicalCameraId) { mRequest = request; mReason = reason; mDropped = dropped; mSequenceId = sequenceId; mFrameNumber = frameNumber; + mErrorPhysicalCameraId = errorPhysicalCameraId; } /** @@ -155,4 +158,17 @@ public class CaptureFailure { public int getSequenceId() { return mSequenceId; } + + /** + * The physical camera device ID in case the capture failure comes from a {@link CaptureRequest} + * with configured physical camera streams for a logical camera. + * + * @return String The physical camera device ID of the respective failing output. + * {@code null} in case the capture request has no associated physical camera device. + * @see CaptureRequest.Builder#setPhysicalCameraKey + * @see android.hardware.camera2.params.OutputConfiguration#setPhysicalCameraId + */ + public @Nullable String getPhysicalCameraId() { + return mErrorPhysicalCameraId; + } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 57b608f0fd84..fc12b090b2f3 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -2232,6 +2232,7 @@ public class CameraDeviceImpl extends CameraDevice final int requestId = resultExtras.getRequestId(); final int subsequenceId = resultExtras.getSubsequenceId(); final long frameNumber = resultExtras.getFrameNumber(); + final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId(); final CaptureCallbackHolder holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); @@ -2287,7 +2288,8 @@ public class CameraDeviceImpl extends CameraDevice reason, /*dropped*/ mayHaveBuffers, requestId, - frameNumber); + frameNumber, + errorPhysicalCameraId); failureDispatch = new Runnable() { @Override diff --git a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java index edc3d91eaf12..1ff5bd562f2e 100644 --- a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java +++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java @@ -29,6 +29,7 @@ public class CaptureResultExtras implements Parcelable { private long frameNumber; private int partialResultCount; private int errorStreamId; + private String errorPhysicalCameraId; public static final @android.annotation.NonNull Parcelable.Creator<CaptureResultExtras> CREATOR = new Parcelable.Creator<CaptureResultExtras>() { @@ -49,7 +50,8 @@ public class CaptureResultExtras implements Parcelable { public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId, int precaptureTriggerId, long frameNumber, - int partialResultCount, int errorStreamId) { + int partialResultCount, int errorStreamId, + String errorPhysicalCameraId) { this.requestId = requestId; this.subsequenceId = subsequenceId; this.afTriggerId = afTriggerId; @@ -57,6 +59,7 @@ public class CaptureResultExtras implements Parcelable { this.frameNumber = frameNumber; this.partialResultCount = partialResultCount; this.errorStreamId = errorStreamId; + this.errorPhysicalCameraId = errorPhysicalCameraId; } @Override @@ -73,6 +76,12 @@ public class CaptureResultExtras implements Parcelable { dest.writeLong(frameNumber); dest.writeInt(partialResultCount); dest.writeInt(errorStreamId); + if ((errorPhysicalCameraId != null) && !errorPhysicalCameraId.isEmpty()) { + dest.writeBoolean(true); + dest.writeString(errorPhysicalCameraId); + } else { + dest.writeBoolean(false); + } } public void readFromParcel(Parcel in) { @@ -83,6 +92,14 @@ public class CaptureResultExtras implements Parcelable { frameNumber = in.readLong(); partialResultCount = in.readInt(); errorStreamId = in.readInt(); + boolean errorPhysicalCameraIdPresent = in.readBoolean(); + if (errorPhysicalCameraIdPresent) { + errorPhysicalCameraId = in.readString(); + } + } + + public String getErrorPhysicalCameraId() { + return errorPhysicalCameraId; } public int getRequestId() { diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index aff09f2a45f2..908ed0913ffc 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -109,11 +109,11 @@ public class LegacyCameraDevice implements AutoCloseable { } if (holder == null) { return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, - ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE); + ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null); } return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(), /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(), - /*partialResultCount*/1, errorStreamId); + /*partialResultCount*/1, errorStreamId, null); } /** diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java index 402bffdc2a97..8cfe6df678c7 100644 --- a/core/java/android/net/IpPrefix.java +++ b/core/java/android/net/IpPrefix.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -71,7 +72,7 @@ public final class IpPrefix implements Parcelable { * * @hide */ - public IpPrefix(@NonNull byte[] address, int prefixLength) { + public IpPrefix(@NonNull byte[] address, @IntRange(from = 0, to = 128) int prefixLength) { this.address = address.clone(); this.prefixLength = prefixLength; checkAndMaskAddressAndPrefixLength(); @@ -88,7 +89,7 @@ public final class IpPrefix implements Parcelable { */ @SystemApi @TestApi - public IpPrefix(@NonNull InetAddress address, int prefixLength) { + public IpPrefix(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength) { // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array, // which is unnecessary because getAddress() already returns a clone. this.address = address.getAddress(); @@ -150,13 +151,13 @@ public final class IpPrefix implements Parcelable { * * @return the address in the form of a byte array. */ - public InetAddress getAddress() { + public @NonNull InetAddress getAddress() { try { return InetAddress.getByAddress(address); } catch (UnknownHostException e) { // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte // array is the wrong length, but we check that in the constructor. - return null; + throw new IllegalArgumentException("Address is invalid"); } } @@ -166,7 +167,7 @@ public final class IpPrefix implements Parcelable { * * @return the address in the form of a byte array. */ - public byte[] getRawAddress() { + public @NonNull byte[] getRawAddress() { return address.clone(); } @@ -175,6 +176,7 @@ public final class IpPrefix implements Parcelable { * * @return the prefix length. */ + @IntRange(from = 0, to = 128) public int getPrefixLength() { return prefixLength; } @@ -183,10 +185,10 @@ public final class IpPrefix implements Parcelable { * Determines whether the prefix contains the specified address. * * @param address An {@link InetAddress} to test. - * @return {@code true} if the prefix covers the given address. + * @return {@code true} if the prefix covers the given address. {@code false} otherwise. */ - public boolean contains(InetAddress address) { - byte[] addrBytes = (address == null) ? null : address.getAddress(); + public boolean contains(@NonNull InetAddress address) { + byte[] addrBytes = address.getAddress(); if (addrBytes == null || addrBytes.length != this.address.length) { return false; } @@ -201,7 +203,7 @@ public final class IpPrefix implements Parcelable { * @param otherPrefix the prefix to test * @hide */ - public boolean containsPrefix(IpPrefix otherPrefix) { + public boolean containsPrefix(@NonNull IpPrefix otherPrefix) { if (otherPrefix.getPrefixLength() < prefixLength) return false; final byte[] otherAddress = otherPrefix.getRawAddress(); NetworkUtils.maskRawAddress(otherAddress, prefixLength); diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index 333603f3a0f2..93dd2e4d7717 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -25,6 +25,7 @@ import static android.system.OsConstants.RT_SCOPE_LINK; import static android.system.OsConstants.RT_SCOPE_SITE; import static android.system.OsConstants.RT_SCOPE_UNIVERSE; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -170,7 +171,7 @@ public class LinkAddress implements Parcelable { * Constructs a new {@code LinkAddress} from an {@code InetAddress} and prefix length, with * the specified flags and scope. Flags and scope are not checked for validity. * @param address The IP address. - * @param prefixLength The prefix length. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). * @param flags A bitmask of {@code IFA_F_*} values representing properties of the address. * @param scope An integer defining the scope in which the address is unique (e.g., * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}). @@ -178,7 +179,8 @@ public class LinkAddress implements Parcelable { */ @SystemApi @TestApi - public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) { + public LinkAddress(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength, + int flags, int scope) { init(address, prefixLength, flags, scope); } @@ -186,12 +188,13 @@ public class LinkAddress implements Parcelable { * Constructs a new {@code LinkAddress} from an {@code InetAddress} and a prefix length. * The flags are set to zero and the scope is determined from the address. * @param address The IP address. - * @param prefixLength The prefix length. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). * @hide */ @SystemApi @TestApi - public LinkAddress(@NonNull InetAddress address, int prefixLength) { + public LinkAddress(@NonNull InetAddress address, + @IntRange(from = 0, to = 128) int prefixLength) { this(address, prefixLength, 0, 0); this.scope = scopeForUnicastAddress(address); } @@ -202,7 +205,7 @@ public class LinkAddress implements Parcelable { * @param interfaceAddress The interface address. * @hide */ - public LinkAddress(InterfaceAddress interfaceAddress) { + public LinkAddress(@NonNull InterfaceAddress interfaceAddress) { this(interfaceAddress.getAddress(), interfaceAddress.getNetworkPrefixLength()); } @@ -306,6 +309,7 @@ public class LinkAddress implements Parcelable { /** * Returns the prefix length of this {@code LinkAddress}. */ + @IntRange(from = 0, to = 128) public int getPrefixLength() { return prefixLength; } @@ -316,6 +320,7 @@ public class LinkAddress implements Parcelable { * @hide */ @UnsupportedAppUsage + @IntRange(from = 0, to = 128) public int getNetworkPrefixLength() { return getPrefixLength(); } diff --git a/core/java/android/net/NetworkCapabilities.aidl b/core/java/android/net/NetworkCapabilities.aidl index cd7d71cad978..01d328605de4 100644 --- a/core/java/android/net/NetworkCapabilities.aidl +++ b/core/java/android/net/NetworkCapabilities.aidl @@ -17,5 +17,5 @@ package android.net; -parcelable NetworkCapabilities; +@JavaOnlyStableParcelable parcelable NetworkCapabilities; diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index b0239c839348..52d3fc48a3a5 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -16,6 +16,8 @@ package android.net; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -24,6 +26,8 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -51,20 +55,32 @@ import java.util.Objects; * (IPv4 or IPv6). */ public final class RouteInfo implements Parcelable { + /** @hide */ + @IntDef(value = { + RTN_UNICAST, + RTN_UNREACHABLE, + RTN_THROW, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RouteType {} + /** * The IP destination address for this route. */ + @NonNull private final IpPrefix mDestination; /** * The gateway address for this route. */ @UnsupportedAppUsage + @Nullable private final InetAddress mGateway; /** * The interface for this route. */ + @Nullable private final String mInterface; @@ -108,13 +124,14 @@ public final class RouteInfo implements Parcelable { * @param destination the destination prefix * @param gateway the IP address to route packets through * @param iface the interface name to send packets on + * @param type the type of this route * * @hide */ @SystemApi @TestApi public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, - @Nullable String iface, int type) { + @Nullable String iface, @RouteType int type) { switch (type) { case RTN_UNICAST: case RTN_UNREACHABLE: @@ -173,10 +190,24 @@ public final class RouteInfo implements Parcelable { } /** - * @hide + * Constructs a {@code RouteInfo} object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route <code>0.0.0.0</code> + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route <code>::/0</code> if gateway is an instance of {@link Inet6Address}. + * <p> + * Destination and gateway may not both be null. + * + * @param destination the destination address and prefix in an {@link IpPrefix} + * @param gateway the {@link InetAddress} to route packets through + * @param iface the interface name to send packets on + * + * @hide */ @UnsupportedAppUsage - public RouteInfo(IpPrefix destination, InetAddress gateway, String iface) { + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface) { this(destination, gateway, iface, RTN_UNICAST); } @@ -184,7 +215,8 @@ public final class RouteInfo implements Parcelable { * @hide */ @UnsupportedAppUsage - public RouteInfo(LinkAddress destination, InetAddress gateway, String iface) { + public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway, + @Nullable String iface) { this(destination == null ? null : new IpPrefix(destination.getAddress(), destination.getPrefixLength()), gateway, iface); @@ -205,7 +237,7 @@ public final class RouteInfo implements Parcelable { * * @hide */ - public RouteInfo(IpPrefix destination, InetAddress gateway) { + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway) { this(destination, gateway, null); } @@ -215,7 +247,7 @@ public final class RouteInfo implements Parcelable { * TODO: Remove this. */ @UnsupportedAppUsage - public RouteInfo(LinkAddress destination, InetAddress gateway) { + public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway) { this(destination, gateway, null); } @@ -227,7 +259,7 @@ public final class RouteInfo implements Parcelable { * @hide */ @UnsupportedAppUsage - public RouteInfo(InetAddress gateway) { + public RouteInfo(@NonNull InetAddress gateway) { this((IpPrefix) null, gateway, null); } @@ -239,35 +271,36 @@ public final class RouteInfo implements Parcelable { * * @hide */ - public RouteInfo(IpPrefix destination) { + public RouteInfo(@NonNull IpPrefix destination) { this(destination, null, null); } /** * @hide */ - public RouteInfo(LinkAddress destination) { + public RouteInfo(@NonNull LinkAddress destination) { this(destination, null, null); } /** * @hide */ - public RouteInfo(IpPrefix destination, int type) { + public RouteInfo(@NonNull IpPrefix destination, @RouteType int type) { this(destination, null, null, type); } /** * @hide */ - public static RouteInfo makeHostRoute(InetAddress host, String iface) { + public static RouteInfo makeHostRoute(@NonNull InetAddress host, @Nullable String iface) { return makeHostRoute(host, null, iface); } /** * @hide */ - public static RouteInfo makeHostRoute(InetAddress host, InetAddress gateway, String iface) { + public static RouteInfo makeHostRoute(@Nullable InetAddress host, @Nullable InetAddress gateway, + @Nullable String iface) { if (host == null) return null; if (host instanceof Inet4Address) { @@ -290,6 +323,7 @@ public final class RouteInfo implements Parcelable { * * @return {@link IpPrefix} specifying the destination. This is never {@code null}. */ + @NonNull public IpPrefix getDestination() { return mDestination; } @@ -298,6 +332,7 @@ public final class RouteInfo implements Parcelable { * TODO: Convert callers to use IpPrefix and then remove. * @hide */ + @NonNull public LinkAddress getDestinationLinkAddress() { return new LinkAddress(mDestination.getAddress(), mDestination.getPrefixLength()); } @@ -308,6 +343,7 @@ public final class RouteInfo implements Parcelable { * @return {@link InetAddress} specifying the gateway or next hop. This may be * {@code null} for a directly-connected route." */ + @Nullable public InetAddress getGateway() { return mGateway; } @@ -317,6 +353,7 @@ public final class RouteInfo implements Parcelable { * * @return The name of the interface used for this route. */ + @Nullable public String getInterface() { return mInterface; } @@ -330,6 +367,7 @@ public final class RouteInfo implements Parcelable { */ @TestApi @SystemApi + @RouteType public int getType() { return mType; } @@ -401,6 +439,7 @@ public final class RouteInfo implements Parcelable { * @hide */ @UnsupportedAppUsage + @Nullable public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) { if ((routes == null) || (dest == null)) return null; diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java index 17a03c7c8933..4dd2ace59c62 100644 --- a/core/java/android/net/apf/ApfCapabilities.java +++ b/core/java/android/net/apf/ApfCapabilities.java @@ -19,14 +19,17 @@ package android.net.apf; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.content.Context; +import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.R; /** - * APF program support capabilities. + * APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible + * way to drop unwanted network packets to save power. + * + * See documentation at hardware/google/apf/apf.h * * This class is immutable. * @hide @@ -104,10 +107,11 @@ public final class ApfCapabilities implements Parcelable { } /** - * Returns true if the APF interpreter advertises support for the data buffer access opcodes - * LDDW and STDW. + * Determines whether the APF interpreter advertises support for the data buffer access opcodes + * LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and + * STDW (STore Data Word) support is present from APFv4 on. * - * Full LDDW and STDW support is present from APFv4 on. + * @return {@code true} if the IWifiStaIface#readApfPacketFilterData is supported. */ public boolean hasDataAccess() { return apfVersionSupported >= 4; @@ -116,14 +120,14 @@ public final class ApfCapabilities implements Parcelable { /** * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames. */ - public static boolean getApfDrop8023Frames(@NonNull Context context) { - return context.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); + public static boolean getApfDrop8023Frames() { + return Resources.getSystem().getBoolean(R.bool.config_apfDrop802_3Frames); } /** * @return An array of blacklisted EtherType, packets with EtherTypes within it will be dropped. */ - public static @NonNull int[] getApfEthTypeBlackList(@NonNull Context context) { - return context.getResources().getIntArray(R.array.config_apfEthTypeBlackList); + public static @NonNull int[] getApfEtherTypeBlackList() { + return Resources.getSystem().getIntArray(R.array.config_apfEthTypeBlackList); } } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 7feeeee0f790..bda6ed19d3c1 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -31,6 +31,7 @@ import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.AppGlobals; import android.content.ClipData; +import android.content.ContentInterface; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentUris; @@ -884,9 +885,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#openFileDescriptor(Uri, String)} - * to gain access. This value will always be {@code NULL} - * for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -1714,11 +1713,9 @@ public final class MediaStore { url = cr.insert(EXTERNAL_CONTENT_URI, values); if (source != null) { - OutputStream imageOut = cr.openOutputStream(url); - try { - source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut); - } finally { - imageOut.close(); + try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream( + cr.openFile(url, "w", null))) { + source.compress(Bitmap.CompressFormat.JPEG, 50, out); } long id = ContentUris.parseId(url); @@ -1959,9 +1956,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#loadThumbnail} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -2442,9 +2437,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#openFileDescriptor(Uri, String)} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -2734,9 +2727,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#loadThumbnail} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -2823,9 +2814,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#loadThumbnail} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -3193,9 +3182,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#openFileDescriptor(Uri, String)} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java index 077d12de4d59..d7baa1086b2f 100644 --- a/core/java/android/text/format/Formatter.java +++ b/core/java/android/text/format/Formatter.java @@ -92,10 +92,15 @@ public final class Formatter { * @return formatted string with the number */ public static String formatFileSize(@Nullable Context context, long sizeBytes) { + return formatFileSize(context, sizeBytes, FLAG_SI_UNITS); + } + + /** @hide */ + public static String formatFileSize(@Nullable Context context, long sizeBytes, int flags) { if (context == null) { return ""; } - final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SI_UNITS); + final BytesResult res = formatBytes(context.getResources(), sizeBytes, flags); return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix, res.value, res.units)); } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index c794a69d3680..8fbbcf4b88c6 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -16,11 +16,20 @@ package android.view; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; + import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; import android.os.Handler; import android.os.Message; +import android.os.SystemClock; +import android.util.StatsLog; /** * Detects various gestures and events using the supplied {@link MotionEvent}s. @@ -251,8 +260,12 @@ public class GestureDetector { private boolean mAlwaysInTapRegion; private boolean mAlwaysInBiggerTapRegion; private boolean mIgnoreNextUpEvent; + // Whether a classification has been recorded by statsd for the current event stream. Reset on + // ACTION_DOWN. + private boolean mHasRecordedClassification; private MotionEvent mCurrentDownEvent; + private MotionEvent mCurrentMotionEvent; private MotionEvent mPreviousUpEvent; /** @@ -297,6 +310,7 @@ public class GestureDetector { break; case LONG_PRESS: + recordGestureClassification(msg.arg1); dispatchLongPress(); break; @@ -304,6 +318,8 @@ public class GestureDetector { // If the user's finger is still down, do not count it as a tap if (mDoubleTapListener != null) { if (!mStillDown) { + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); } else { mDeferConfirmSingleTap = true; @@ -501,6 +517,11 @@ public class GestureDetector { final int action = ev.getAction(); + if (mCurrentMotionEvent != null) { + mCurrentMotionEvent.recycle(); + } + mCurrentMotionEvent = MotionEvent.obtain(ev); + if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } @@ -569,6 +590,8 @@ public class GestureDetector { && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { // This is a second tap mIsDoubleTapping = true; + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); // Give a callback with the first tap of the double-tap handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); // Give a callback with down event of the double-tap @@ -590,11 +613,17 @@ public class GestureDetector { mStillDown = true; mInLongPress = false; mDeferConfirmSingleTap = false; + mHasRecordedClassification = false; if (mIsLongpressEnabled) { mHandler.removeMessages(LONG_PRESS); - mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() - + ViewConfiguration.getLongPressTimeout()); + mHandler.sendMessageAtTime( + mHandler.obtainMessage( + LONG_PRESS, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS, + 0 /* arg2 */), + mCurrentDownEvent.getDownTime() + + ViewConfiguration.getLongPressTimeout()); } mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); @@ -613,6 +642,8 @@ public class GestureDetector { final float scrollY = mLastFocusY - focusY; if (mIsDoubleTapping) { // Give the move events of the double-tap + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mAlwaysInTapRegion) { final int deltaX = (int) (focusX - mDownFocusX); @@ -635,8 +666,12 @@ public class GestureDetector { // reschedule long press with a modified timeout. mHandler.removeMessages(LONG_PRESS); final long longPressTimeout = ViewConfiguration.getLongPressTimeout(); - mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime() - + (long) (longPressTimeout * multiplier)); + mHandler.sendMessageAtTime( + mHandler.obtainMessage( + LONG_PRESS, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS, + 0 /* arg2 */), + ev.getDownTime() + (long) (longPressTimeout * multiplier)); } // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll // until the gesture is resolved. @@ -646,6 +681,8 @@ public class GestureDetector { } if (distance > slopSquare) { + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL); handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; @@ -659,6 +696,7 @@ public class GestureDetector { mAlwaysInBiggerTapRegion = false; } } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { + recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL); handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; @@ -667,7 +705,11 @@ public class GestureDetector { motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; if (deepPress && hasPendingLongPress) { mHandler.removeMessages(LONG_PRESS); - mHandler.sendEmptyMessage(LONG_PRESS); + mHandler.sendMessage( + mHandler.obtainMessage( + LONG_PRESS, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS, + 0 /* arg2 */)); } break; @@ -676,11 +718,15 @@ public class GestureDetector { MotionEvent currentUpEvent = MotionEvent.obtain(ev); if (mIsDoubleTapping) { // Finally, give the up event of the double-tap + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mInLongPress) { mHandler.removeMessages(TAP); mInLongPress = false; } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) { + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); handled = mListener.onSingleTapUp(ev); if (mDeferConfirmSingleTap && mDoubleTapListener != null) { mDoubleTapListener.onSingleTapConfirmed(ev); @@ -821,4 +867,26 @@ public class GestureDetector { mInLongPress = true; mListener.onLongPress(mCurrentDownEvent); } + + private void recordGestureClassification(int classification) { + if (mHasRecordedClassification + || classification + == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) { + // Only record the first classification for an event stream. + return; + } + if (mCurrentDownEvent == null || mCurrentMotionEvent == null) { + // If the complete event stream wasn't seen, don't record anything. + mHasRecordedClassification = true; + return; + } + StatsLog.write( + StatsLog.TOUCH_GESTURE_CLASSIFIED, + getClass().getName(), + classification, + (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()), + (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(), + mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY())); + mHasRecordedClassification = true; + } } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 13b0cc038fce..b76f2a175346 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -17,7 +17,10 @@ package android.view; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; import static android.view.WindowInsets.Type.SIZE; +import static android.view.WindowInsets.Type.SYSTEM_GESTURES; import static android.view.WindowInsets.Type.indexOf; import android.annotation.IntDef; @@ -55,6 +58,12 @@ public class InsetsState implements Parcelable { TYPE_SIDE_BAR_1, TYPE_SIDE_BAR_2, TYPE_SIDE_BAR_3, + TYPE_TOP_GESTURES, + TYPE_BOTTOM_GESTURES, + TYPE_LEFT_GESTURES, + TYPE_RIGHT_GESTURES, + TYPE_TOP_TAPPABLE_ELEMENT, + TYPE_BOTTOM_TAPPABLE_ELEMENT, TYPE_IME }) public @interface InternalInsetType {} @@ -73,8 +82,16 @@ public class InsetsState implements Parcelable { public static final int TYPE_SIDE_BAR_2 = 2; public static final int TYPE_SIDE_BAR_3 = 3; + public static final int TYPE_TOP_GESTURES = 4; + public static final int TYPE_BOTTOM_GESTURES = 5; + public static final int TYPE_LEFT_GESTURES = 6; + public static final int TYPE_RIGHT_GESTURES = 7; + public static final int TYPE_TOP_TAPPABLE_ELEMENT = 8; + public static final int TYPE_BOTTOM_TAPPABLE_ELEMENT = 9; + /** Input method window. */ - public static final int TYPE_IME = 4; + public static final int TYPE_IME = 10; + static final int LAST_TYPE = TYPE_IME; // Derived types @@ -137,17 +154,6 @@ public class InsetsState implements Parcelable { && legacyContentInsets != null && legacyStableInsets != null) { WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets); WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets); - - // TODO: set system gesture insets based on actual system gesture area. - typeInsetsMap[Type.indexOf(Type.systemGestures())] = Insets.of(legacyContentInsets); - typeInsetsMap[Type.indexOf(Type.mandatorySystemGestures())] = - Insets.of(legacyContentInsets); - typeInsetsMap[Type.indexOf(Type.tappableElement())] = Insets.of(legacyContentInsets); - - typeMaxInsetsMap[Type.indexOf(Type.systemGestures())] = Insets.of(legacyStableInsets); - typeMaxInsetsMap[Type.indexOf(Type.mandatorySystemGestures())] = - Insets.of(legacyStableInsets); - typeMaxInsetsMap[Type.indexOf(Type.tappableElement())] = Insets.of(legacyStableInsets); } for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { InsetsSource source = mSources.get(type); @@ -159,7 +165,9 @@ public class InsetsState implements Parcelable { && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR); boolean skipIme = source.getType() == TYPE_IME && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0; - if (skipSystemBars || skipIme) { + boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && (toPublicType(type) & Type.compatSystemInsets()) != 0; + if (skipSystemBars || skipIme || skipLegacyTypes) { typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible(); continue; } @@ -183,7 +191,25 @@ public class InsetsState implements Parcelable { @Nullable boolean[] typeVisibilityMap) { Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility); - int index = indexOf(toPublicType(source.getType())); + int type = toPublicType(source.getType()); + processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap, + insets, type); + + if (type == MANDATORY_SYSTEM_GESTURES) { + // Mandatory system gestures are also system gestures. + // TODO: find a way to express this more generally. One option would be to define + // Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the + // ability to set systemGestureInsets() independently from + // mandatorySystemGestureInsets() in the Builder. + processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap, + insets, SYSTEM_GESTURES); + } + } + + private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, + @InsetSide @Nullable SparseIntArray typeSideMap, + @Nullable boolean[] typeVisibilityMap, Insets insets, int type) { + int index = indexOf(type); Insets existing = typeInsetsMap[index]; if (existing == null) { typeInsetsMap[index] = insets; @@ -300,6 +326,15 @@ public class InsetsState implements Parcelable { return Type.SIDE_BARS; case TYPE_IME: return Type.IME; + case TYPE_TOP_GESTURES: + case TYPE_BOTTOM_GESTURES: + return Type.MANDATORY_SYSTEM_GESTURES; + case TYPE_LEFT_GESTURES: + case TYPE_RIGHT_GESTURES: + return Type.SYSTEM_GESTURES; + case TYPE_TOP_TAPPABLE_ELEMENT: + case TYPE_BOTTOM_TAPPABLE_ELEMENT: + return Type.TAPPABLE_ELEMENT; default: throw new IllegalArgumentException("Unknown type: " + type); } @@ -336,10 +371,20 @@ public class InsetsState implements Parcelable { return "TYPE_SIDE_BAR_2"; case TYPE_SIDE_BAR_3: return "TYPE_SIDE_BAR_3"; - case TYPE_IME: - return "TYPE_IME"; + case TYPE_TOP_GESTURES: + return "TYPE_TOP_GESTURES"; + case TYPE_BOTTOM_GESTURES: + return "TYPE_BOTTOM_GESTURES"; + case TYPE_LEFT_GESTURES: + return "TYPE_LEFT_GESTURES"; + case TYPE_RIGHT_GESTURES: + return "TYPE_RIGHT_GESTURES"; + case TYPE_TOP_TAPPABLE_ELEMENT: + return "TYPE_TOP_TAPPABLE_ELEMENT"; + case TYPE_BOTTOM_TAPPABLE_ELEMENT: + return "TYPE_BOTTOM_TAPPABLE_ELEMENT"; default: - return "TYPE_UNKNOWN"; + return "TYPE_UNKNOWN_" + type; } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 24f4c1431214..65fe87fa8ca0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -17,6 +17,10 @@ package android.view; import static android.content.res.Resources.ID_NULL; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; @@ -96,6 +100,7 @@ import android.util.Property; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.StateSet; +import android.util.StatsLog; import android.util.SuperNotCalledException; import android.util.TypedValue; import android.view.AccessibilityIterators.CharacterTextSegmentIterator; @@ -4062,11 +4067,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, }, formatToHexString = true) /* @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769414) public int mPrivateFlags; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768943) int mPrivateFlags2; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 129147060) int mPrivateFlags3; private int mPrivateFlags4; @@ -14571,7 +14576,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (clickable) { setPressed(true, x, y); } - checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); + checkForLongClick( + ViewConfiguration.getLongPressTimeout(), + x, + y, + // This is not a touch gesture -- do not classify it as one. + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION); return true; } } @@ -15312,7 +15322,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mHasPerformedLongPress = false; if (!clickable) { - checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); + checkForLongClick( + ViewConfiguration.getLongPressTimeout(), + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); break; } @@ -15336,7 +15350,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); - checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); + checkForLongClick( + ViewConfiguration.getLongPressTimeout(), + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } break; @@ -15373,7 +15391,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * ambiguousMultiplier); // Subtract the time already spent delay -= event.getEventTime() - event.getDownTime(); - checkForLongClick(delay, x, y); + checkForLongClick( + delay, + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } touchSlop *= ambiguousMultiplier; } @@ -15395,7 +15417,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (deepPress && hasPendingLongPressCallback()) { // process the long click action immediately removeLongPressCallback(); - checkForLongClick(0 /* send immediately */, x, y); + checkForLongClick( + 0 /* send immediately */, + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS); } break; @@ -26143,7 +26169,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - private void checkForLongClick(long delay, float x, float y) { + private void checkForLongClick(long delay, float x, float y, int classification) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) { mHasPerformedLongPress = false; @@ -26153,6 +26179,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); mPendingCheckForLongPress.rememberPressedState(); + mPendingCheckForLongPress.setClassification(classification); postDelayed(mPendingCheckForLongPress, delay); } } @@ -27710,11 +27737,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private float mX; private float mY; private boolean mOriginalPressedState; + /** + * The classification of the long click being checked: one of the + * StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants. + */ + private int mClassification; @Override public void run() { if ((mOriginalPressedState == isPressed()) && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { + recordGestureClassification(mClassification); if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } @@ -27733,6 +27766,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void rememberPressedState() { mOriginalPressedState = isPressed(); } + + public void setClassification(int classification) { + mClassification = classification; + } } private final class CheckForTap implements Runnable { @@ -27745,17 +27782,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPressed(true, x, y); final long delay = ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(); - checkForLongClick(delay, x, y); + checkForLongClick(delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } } private final class PerformClick implements Runnable { @Override public void run() { + recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); performClickInternal(); } } + /** Records a classification for the current event stream. */ + private void recordGestureClassification(int classification) { + if (classification == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) { + return; + } + // To avoid negatively impacting View performance, the latency and displacement metrics + // are omitted. + StatsLog.write(StatsLog.TOUCH_GESTURE_CLASSIFIED, getClass().getName(), classification); + } + /** * This method returns a ViewPropertyAnimator object, which can be used to animate * specific properties on this View. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 49166ade34ce..ca6496903590 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1916,16 +1916,10 @@ public final class ViewRootImpl implements ViewParent, } contentInsets = ensureInsetsNonNegative(contentInsets, "content"); stableInsets = ensureInsetsNonNegative(stableInsets, "stable"); - if (sNewInsetsMode != NEW_INSETS_MODE_NONE) { - mLastWindowInsets = mInsetsController.calculateInsets( - mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeSystemBars, displayCutout, - contentInsets, stableInsets, mWindowAttributes.softInputMode); - } else { - mLastWindowInsets = new WindowInsets(contentInsets, stableInsets, - mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeSystemBars, displayCutout); - } + mLastWindowInsets = mInsetsController.calculateInsets( + mContext.getResources().getConfiguration().isScreenRound(), + mAttachInfo.mAlwaysConsumeSystemBars, displayCutout, + contentInsets, stableInsets, mWindowAttributes.softInputMode); } return mLastWindowInsets; } @@ -3986,7 +3980,7 @@ public final class ViewRootImpl implements ViewParent, void systemGestureExclusionChanged() { final List<Rect> rectsForWindowManager = mGestureExclusionTracker.computeChangedRects(); - if (rectsForWindowManager != null) { + if (rectsForWindowManager != null && mView != null) { try { mWindowSession.reportSystemGestureExclusionChanged(mWindow, rectsForWindowManager); } catch (RemoteException e) { diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index ffa769a424a9..2d292ef7b25c 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -29,9 +29,6 @@ import static android.view.WindowInsets.Type.TOP_BAR; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.compatSystemInsets; import static android.view.WindowInsets.Type.indexOf; -import static android.view.WindowInsets.Type.mandatorySystemGestures; -import static android.view.WindowInsets.Type.systemGestures; -import static android.view.WindowInsets.Type.tappableElement; import android.annotation.IntDef; import android.annotation.IntRange; @@ -225,10 +222,6 @@ public final class WindowInsets { } Insets[] typeInsetMap = new Insets[SIZE]; assignCompatInsets(typeInsetMap, insets); - // TODO: set system gesture insets based on actual system gesture area. - typeInsetMap[indexOf(systemGestures())] = Insets.of(insets); - typeInsetMap[indexOf(mandatorySystemGestures())] = Insets.of(insets); - typeInsetMap[indexOf(tappableElement())] = Insets.of(insets); return typeInsetMap; } diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 4413585535f2..678a25223ef5 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -321,45 +321,6 @@ public final class WebViewFactory { } } - /** - * If the ApplicationInfo provided is for a stub WebView, fix up the object to include the - * required values from the donor package. If the ApplicationInfo is for a full WebView, - * leave it alone. Throws MissingWebViewPackageException if the donor is missing. - */ - private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm) - throws MissingWebViewPackageException { - String donorPackageName = null; - if (ai.metaData != null) { - donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage"); - } - if (donorPackageName != null) { - PackageInfo donorPackage; - try { - donorPackage = pm.getPackageInfo( - donorPackageName, - PackageManager.GET_SHARED_LIBRARY_FILES - | PackageManager.MATCH_DEBUG_TRIAGED_MISSING - | PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_FACTORY_ONLY); - } catch (PackageManager.NameNotFoundException e) { - throw new MissingWebViewPackageException("Failed to find donor package: " + - donorPackageName); - } - ApplicationInfo donorInfo = donorPackage.applicationInfo; - - // Replace the stub's code locations with the donor's. - ai.sourceDir = donorInfo.sourceDir; - ai.splitSourceDirs = donorInfo.splitSourceDirs; - ai.nativeLibraryDir = donorInfo.nativeLibraryDir; - ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir; - - // Copy the donor's primary and secondary ABIs, since the stub doesn't have native code - // and so they are unset. - ai.primaryCpuAbi = donorInfo.primaryCpuAbi; - ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi; - } - } - @UnsupportedAppUsage private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException { Application initialApplication = AppGlobals.getInitialApplication(); @@ -411,7 +372,6 @@ public final class WebViewFactory { verifyPackageInfo(response.packageInfo, newPackageInfo); ApplicationInfo ai = newPackageInfo.applicationInfo; - fixupStubApplicationInfo(ai, pm); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "initialApplication.createApplicationContext"); @@ -494,18 +454,14 @@ public final class WebViewFactory { */ public static int onWebViewProviderChanged(PackageInfo packageInfo) { int startedRelroProcesses = 0; - ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo); try { - fixupStubApplicationInfo(packageInfo.applicationInfo, - AppGlobals.getInitialApplication().getPackageManager()); - startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo); } catch (Throwable t) { // Log and discard errors at this stage as we must not crash the system server. Log.e(LOGTAG, "error preparing webview native library", t); } - WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo); + WebViewZygote.onWebViewProviderChanged(packageInfo); return startedRelroProcesses; } diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index 09aa066549cb..62f54b943e11 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -16,8 +16,6 @@ package android.webkit; -import android.app.LoadedApk; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.os.AsyncTask; import android.os.Build; @@ -29,10 +27,6 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - /** @hide */ public class WebViewZygote { private static final String LOGTAG = "WebViewZygote"; @@ -56,13 +50,6 @@ public class WebViewZygote { private static PackageInfo sPackage; /** - * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from - * #onWebViewProviderChanged(). - */ - @GuardedBy("sLock") - private static ApplicationInfo sPackageOriginalAppInfo; - - /** * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote * will not be started. */ @@ -110,11 +97,9 @@ public class WebViewZygote { } } - public static void onWebViewProviderChanged(PackageInfo packageInfo, - ApplicationInfo originalAppInfo) { + static void onWebViewProviderChanged(PackageInfo packageInfo) { synchronized (sLock) { sPackage = packageInfo; - sPackageOriginalAppInfo = originalAppInfo; // If multi-process is not enabled, then do not start the zygote service. if (!sMultiprocessEnabled) { @@ -165,34 +150,7 @@ public class WebViewZygote { Process.FIRST_ISOLATED_UID, Integer.MAX_VALUE); // TODO(b/123615476) deal with user-id ranges properly ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress()); - - if (sPackageOriginalAppInfo.sourceDir.equals(sPackage.applicationInfo.sourceDir)) { - // No stub WebView is involved here, so we can preload the package the "clean" way - // using the ApplicationInfo. - sZygote.preloadApp(sPackage.applicationInfo, abi); - } else { - // Legacy path to support the stub WebView. - // Reuse the logic from LoadedApk to determine the correct paths and pass them to - // the zygote as strings. - final List<String> zipPaths = new ArrayList<>(10); - final List<String> libPaths = new ArrayList<>(10); - LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths); - final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths); - final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) : - TextUtils.join(File.pathSeparator, zipPaths); - - String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo); - - // Use the original ApplicationInfo to determine what the original classpath would - // have been to use as a cache key. - LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null); - final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) : - TextUtils.join(File.pathSeparator, zipPaths); - - Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath); - sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey, - Build.SUPPORTED_ABIS[0]); - } + sZygote.preloadApp(sPackage.applicationInfo, abi); } catch (Exception e) { Log.e(LOGTAG, "Error connecting to webview zygote", e); stopZygoteLocked(); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index f14b50dcd04d..07d28eba793d 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -24,7 +24,6 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.IntDef; -import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.ActivityManager; import android.app.prediction.AppPredictionContext; @@ -197,6 +196,11 @@ public class ChooserActivity extends ResolverActivity { private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2; private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3; private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4; + private static final int LIST_VIEW_UPDATE_MESSAGE = 5; + + private static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250; + + private boolean mListViewDataChanged = false; @Retention(SOURCE) @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT}) @@ -213,10 +217,13 @@ public class ChooserActivity extends ResolverActivity { private final Handler mChooserHandler = new Handler() { @Override public void handleMessage(Message msg) { + if (mChooserListAdapter == null || isDestroyed()) { + return; + } + switch (msg.what) { case CHOOSER_TARGET_SERVICE_RESULT: if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT"); - if (isDestroyed()) break; final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; if (!mServiceConnections.contains(sri.connection)) { Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection @@ -240,17 +247,22 @@ public class ChooserActivity extends ResolverActivity { if (DEBUG) { Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services"); } - if (mChooserListAdapter == null || isDestroyed()) { - break; - } + unbindRemainingServices(); sendVoiceChoicesIfNeeded(); mChooserListAdapter.completeServiceTargetLoading(); break; + case LIST_VIEW_UPDATE_MESSAGE: + if (DEBUG) { + Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; "); + } + + mChooserListAdapter.refreshListView(); + break; + case SHORTCUT_MANAGER_SHARE_TARGET_RESULT: if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT"); - if (isDestroyed()) break; final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj; if (resultInfo.resultTargets != null) { mChooserListAdapter.addServiceResults(resultInfo.originalTarget, @@ -829,6 +841,7 @@ public class ChooserActivity extends ResolverActivity { mRefinementResultReceiver = null; } unbindRemainingServices(); + mChooserHandler.removeMessages(LIST_VIEW_UPDATE_MESSAGE); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT); if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { @@ -1872,6 +1885,23 @@ public class ChooserActivity extends ResolverActivity { } } + @Override + public void notifyDataSetChanged() { + if (!mListViewDataChanged) { + mChooserHandler.sendEmptyMessageDelayed(LIST_VIEW_UPDATE_MESSAGE, + LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); + mListViewDataChanged = true; + } + } + + private void refreshListView() { + if (mListViewDataChanged) { + super.notifyDataSetChanged(); + } + mListViewDataChanged = false; + } + + private void createPlaceHolders() { mServiceTargets.clear(); for (int i = 0; i < MAX_SERVICE_TARGETS; i++) { @@ -1893,7 +1923,7 @@ public class ChooserActivity extends ResolverActivity { } if (mServiceTargets != null) { - if (getDisplayInfoCount() == 0) { + if (getDisplayResolveInfoCount() == 0) { // b/109676071: When packages change, onListRebuilt() is called before // ResolverActivity.mDisplayList is re-populated; pruning now would cause the // list to disappear briefly, so instead we detect this case (the @@ -1906,12 +1936,14 @@ public class ChooserActivity extends ResolverActivity { if (DEBUG) { Log.d(TAG, "querying direct share targets from ShortcutManager"); } + queryDirectShareTargets(this); } if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) { if (DEBUG) { Log.d(TAG, "List built querying services"); } + queryTargetServices(this); } } @@ -2007,7 +2039,7 @@ public class ChooserActivity extends ResolverActivity { offset += callerTargetCount; return filtered ? super.getItem(position - offset) - : getDisplayInfoAt(position - offset); + : getDisplayResolveInfo(position - offset); } public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 9f9e083f1fb1..f47469a0e1b3 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1230,7 +1230,7 @@ public class ResolverActivity extends Activity { final ImageView iconView = findViewById(R.id.icon); final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem(); if (iconView != null && iconInfo != null) { - new LoadIconIntoViewTask(iconInfo, iconView).execute(); + new LoadIconTask(iconInfo, iconView).execute(); } } @@ -1871,14 +1871,6 @@ public class ResolverActivity extends Activity { return mDisplayList.size(); } - public int getDisplayInfoCount() { - return mDisplayList.size(); - } - - public DisplayResolveInfo getDisplayInfoAt(int index) { - return mDisplayList.get(index); - } - @Nullable public TargetInfo getItem(int position) { if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { @@ -1966,9 +1958,10 @@ public class ResolverActivity extends Activity { if (info instanceof DisplayResolveInfo && !((DisplayResolveInfo) info).hasDisplayIcon()) { - new LoadAdapterIconTask((DisplayResolveInfo) info).execute(); + new LoadIconTask((DisplayResolveInfo) info, holder.icon).execute(); + } else { + holder.icon.setImageDrawable(info.getDisplayIcon()); } - holder.icon.setImageDrawable(info.getDisplayIcon()); } } @@ -2087,13 +2080,15 @@ public class ResolverActivity extends Activity { } - abstract class LoadIconTask extends AsyncTask<Void, Void, Drawable> { + class LoadIconTask extends AsyncTask<Void, Void, Drawable> { protected final DisplayResolveInfo mDisplayResolveInfo; private final ResolveInfo mResolveInfo; + private final ImageView mTargetView; - public LoadIconTask(DisplayResolveInfo dri) { + LoadIconTask(DisplayResolveInfo dri, ImageView target) { mDisplayResolveInfo = dri; mResolveInfo = dri.getResolveInfo(); + mTargetView = target; } @Override @@ -2103,37 +2098,12 @@ public class ResolverActivity extends Activity { @Override protected void onPostExecute(Drawable d) { - mDisplayResolveInfo.setDisplayIcon(d); - } - } - - class LoadAdapterIconTask extends LoadIconTask { - public LoadAdapterIconTask(DisplayResolveInfo dri) { - super(dri); - } - - @Override - protected void onPostExecute(Drawable d) { - super.onPostExecute(d); if (mProfileView != null && mAdapter.getOtherProfile() == mDisplayResolveInfo) { bindProfileView(); + } else { + mDisplayResolveInfo.setDisplayIcon(d); + mTargetView.setImageDrawable(d); } - mAdapter.notifyDataSetChanged(); - } - } - - class LoadIconIntoViewTask extends LoadIconTask { - private final ImageView mTargetView; - - public LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target) { - super(dri); - mTargetView = target; - } - - @Override - protected void onPostExecute(Drawable d) { - super.onPostExecute(d); - mTargetView.setImageDrawable(d); } } diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index dfb6c0817043..9a9c9d14154b 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -48,6 +48,7 @@ import "frameworks/base/core/proto/android/service/notification.proto"; import "frameworks/base/core/proto/android/service/package.proto"; import "frameworks/base/core/proto/android/service/print.proto"; import "frameworks/base/core/proto/android/service/procstats.proto"; +import "frameworks/base/core/proto/android/service/restricted_image.proto"; import "frameworks/base/core/proto/android/service/usb.proto"; import "frameworks/base/core/proto/android/util/event_log_tags.proto"; import "frameworks/base/core/proto/android/util/log.proto"; @@ -314,6 +315,12 @@ message IncidentProto { (section).args = "role --proto" ]; + optional android.service.restricted_image.RestrictedImagesDumpProto restricted_images = 3025 [ + (section).type = SECTION_DUMPSYS, + (section).userdebug_and_eng_only = true, + (section).args = "incidentcompanion --restricted_image" + ]; + // Reserved for OEMs. extensions 50000 to 100000; } diff --git a/core/proto/android/service/restricted_image.proto b/core/proto/android/service/restricted_image.proto new file mode 100644 index 000000000000..4a33d478fbde --- /dev/null +++ b/core/proto/android/service/restricted_image.proto @@ -0,0 +1,58 @@ +/* + * 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. + */ + +syntax = "proto2"; +package android.service.restricted_image; + +option java_multiple_files = true; +option java_outer_classname = "RestrictedImage"; + +import "frameworks/base/core/proto/android/privacy.proto"; + +// Restricted Image proto is for collecting images from the user with their +// permission for the purpose of debugging photos. +message RestrictedImagesDumpProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + repeated RestrictedImageSetProto sets = 1; +} + +message RestrictedImageSetProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + // Name of the service producing the data. + optional string category = 1; + + // The images + repeated RestrictedImageProto images = 2; + + // Additional metadata + optional bytes metadata = 3; +} + +message RestrictedImageProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + // Type of image data + optional string mime_type = 1; + + // The image data + optional bytes image_data = 2; + + // Metadata about the image. Typically this has another proto schema, + // but it is undefined exactly what that is in AOSP code. + optional bytes metadata = 3; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ba7a93f52939..ab86c42ac12f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1014,6 +1014,9 @@ call with the option to redirect the call to a different number or abort the call altogether. <p>Protection level: dangerous + + @deprecated Applications should use {@link android.telecom.CallRedirectionService} instead + of the {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL} broadcast. --> <permission android:name="android.permission.PROCESS_OUTGOING_CALLS" android:permissionGroup="android.permission-group.UNDEFINED" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 77ce8c382fad..5cbe0038f871 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3241,6 +3241,12 @@ Only applies if the device display is not square. --> <bool name="config_navBarCanMove">true</bool> + <!-- Controls whether the navigation bar lets through taps. --> + <bool name="config_navBarTapThrough">false</bool> + + <!-- Controls the size of the back gesture inset. --> + <dimen name="config_backGestureInset">0dp</dimen> + <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows. These values are in DPs and will be converted to pixel sizes internally. --> <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string> @@ -3983,6 +3989,9 @@ <!-- Whether or not to enable automatic heap dumps for the system server on debuggable builds. --> <bool name="config_debugEnableAutomaticSystemServerHeapDumps">false</bool> + <!-- Trigger a heap dump if the system server pss usage exceeds this threshold. 400 MB --> + <integer name="config_debugSystemServerPssThresholdBytes">419430400</integer> + <!-- See DropBoxManagerService. The minimum period in milliseconds between broadcasts for entries with low priority dropbox tags. --> @@ -4003,4 +4012,6 @@ <item>system_server_wtf</item> </string-array> + <!-- Which binder services to include in incident reports containing restricted images. --> + <string-array name="config_restrictedImagesServices" translatable="false"/> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index feecd020b087..c646fefad9d6 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -47,6 +47,9 @@ <dimen name="navigation_bar_height_landscape">48dp</dimen> <!-- Width of the navigation bar when it is placed vertically on the screen --> <dimen name="navigation_bar_width">48dp</dimen> + <!-- How much we expand the touchable region of the status bar below the notch to catch touches + that just start below the notch. --> + <dimen name="display_cutout_touchable_region_size">12dp</dimen> <!-- EXPERIMENT BEGIN --> <!-- Height of the bottom navigation bar frame; this is different than navigation_bar_height diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 03259ed2c0c6..4320bf41f467 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4325,10 +4325,10 @@ <!-- Activity starter --> <!-- Toast message for blocking background activity starts feature running in permissive mode --> - <string name="activity_starter_block_bg_activity_starts_permissive">This background activity start from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> will be blocked in future Q builds. See go/q-bg-block.</string> + <string name="activity_starter_block_bg_activity_starts_permissive">This background activity start from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> will be blocked in future Q builds. See g.co/dev/bgblock.</string> <!-- Toast message for blocking background activity starts feature running in enforcing mode --> - <string name="activity_starter_block_bg_activity_starts_enforcing">Background activity start from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> blocked. See go/q-bg-block. </string> + <string name="activity_starter_block_bg_activity_starts_enforcing">Background activity start from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> blocked. See g.co/dev/bgblock. </string> <!-- Keyguard strings --> <!-- Message shown in pattern unlock after some number of unsuccessful attempts --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 35fd88e64d5b..a29a4f8845f5 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1750,6 +1750,7 @@ <java-symbol type="dimen" name="navigation_bar_height_landscape_car_mode" /> <java-symbol type="dimen" name="navigation_bar_width_car_mode" /> <java-symbol type="dimen" name="status_bar_height" /> + <java-symbol type="dimen" name="display_cutout_touchable_region_size" /> <java-symbol type="dimen" name="quick_qs_offset_height" /> <java-symbol type="dimen" name="quick_qs_total_height" /> <java-symbol type="drawable" name="ic_jog_dial_sound_off" /> @@ -2844,6 +2845,8 @@ <java-symbol type="integer" name="config_navBarOpacityMode" /> <java-symbol type="integer" name="config_navBarInteractionMode" /> <java-symbol type="bool" name="config_navBarCanMove" /> + <java-symbol type="bool" name="config_navBarTapThrough" /> + <java-symbol type="dimen" name="config_backGestureInset" /> <java-symbol type="color" name="system_bar_background_semi_transparent" /> <!-- EditText suggestion popup. --> @@ -3230,6 +3233,7 @@ <java-symbol type="bool" name="config_batterymeterDualTone" /> <java-symbol type="bool" name="config_debugEnableAutomaticSystemServerHeapDumps" /> + <java-symbol type="integer" name="config_debugSystemServerPssThresholdBytes" /> <!-- Accessibility Shortcut --> <java-symbol type="string" name="accessibility_shortcut_warning_dialog_title" /> @@ -3686,6 +3690,7 @@ <java-symbol type="integer" name="config_attentionApiTimeout" /> <java-symbol type="string" name="config_incidentReportApproverPackage" /> + <java-symbol type="array" name="config_restrictedImagesServices" /> <!-- Display White-Balance --> <java-symbol type="integer" name="config_displayWhiteBalanceBrightnessSensorRate" /> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 3c8794f7c5fa..ed198e60902b 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -258,6 +258,7 @@ applications that come with the platform <permission name="android.permission.ACTIVITY_EMBEDDING"/> <permission name="android.permission.FORCE_STOP_PACKAGES"/> <permission name="android.permission.GET_APP_OPS_STATS"/> + <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/> <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/> <permission name="android.permission.INSTALL_PACKAGES"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index e4142a933f0f..adc04fb03e2a 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -688,20 +688,20 @@ public abstract class Drawable { * {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint. * </p> * - * @param tintMode A Porter-Duff blending mode + * @param tintMode A Porter-Duff blending mode to apply to the drawable, a value of null sets + * the default Porter-Diff blending mode value + * of {@link PorterDuff.Mode#SRC_IN} * @see #setTint(int) * @see #setTintList(ColorStateList) * * @deprecated use {@link #setTintMode(BlendMode)} instead */ @Deprecated - public void setTintMode(@NonNull PorterDuff.Mode tintMode) { + public void setTintMode(@Nullable PorterDuff.Mode tintMode) { if (!mSetTintModeInvoked) { mSetTintModeInvoked = true; - BlendMode mode = BlendMode.fromValue(tintMode.nativeInt); - if (mode != null) { - setTintMode(mode); - } + BlendMode mode = tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null; + setTintMode(mode != null ? mode : Drawable.DEFAULT_BLEND_MODE); mSetTintModeInvoked = false; } } @@ -716,17 +716,16 @@ public abstract class Drawable { * {@link #setColorFilter(ColorFilter)} * </p> * - * @param blendMode + * @param blendMode BlendMode to apply to the drawable, a value of null sets the default + * blend mode value of {@link BlendMode#SRC_IN} * @see #setTint(int) * @see #setTintList(ColorStateList) */ - public void setTintMode(@NonNull BlendMode blendMode) { + public void setTintMode(@Nullable BlendMode blendMode) { if (!mSetBlendModeInvoked) { mSetBlendModeInvoked = true; PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode); - if (mode != null) { - setTintMode(mode); - } + setTintMode(mode != null ? mode : Drawable.DEFAULT_TINT_MODE); mSetBlendModeInvoked = false; } } diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 6cce31943d03..b76e49ce94a0 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -209,8 +209,8 @@ void RenderThread::requireVkContext() { mVkManager->initialize(); GrContextOptions options; initGrContextOptions(options); - // TODO: get a string describing the SPIR-V compiler version and use it here - cacheManager().configureContext(&options, nullptr, 0); + auto vkDriverVersion = mVkManager->getDriverVersion(); + cacheManager().configureContext(&options, &vkDriverVersion, sizeof(vkDriverVersion)); sk_sp<GrContext> grContext = mVkManager->createContext(options); LOG_ALWAYS_FATAL_IF(!grContext.get()); setGrContext(grContext); @@ -408,12 +408,13 @@ bool RenderThread::isCurrent() { } void RenderThread::preload() { - std::thread eglInitThread([]() { - //TODO: don't load EGL drivers for Vulkan, when HW bitmap uploader is refactored. - eglGetDisplay(EGL_DEFAULT_DISPLAY); - }); - eglInitThread.detach(); - if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { + // EGL driver is always preloaded only if HWUI renders with GL. + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { + std::thread eglInitThread([]() { + eglGetDisplay(EGL_DEFAULT_DISPLAY); + }); + eglInitThread.detach(); + } else { requireVkContext(); } HardwareBitmapUploader::initialize(); diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index c92909898652..ac62ff47f50a 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -170,6 +170,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe VkPhysicalDeviceProperties physDeviceProperties; mGetPhysicalDeviceProperties(mPhysicalDevice, &physDeviceProperties); LOG_ALWAYS_FATAL_IF(physDeviceProperties.apiVersion < VK_MAKE_VERSION(1, 1, 0)); + mDriverVersion = physDeviceProperties.driverVersion; // query to get the initial queue props size uint32_t queueCount; diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index c2d18029c731..54333f326d4f 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -82,6 +82,8 @@ public: sk_sp<GrContext> createContext(const GrContextOptions& options); + uint32_t getDriverVersion() const { return mDriverVersion; } + private: friend class VulkanSurface; // Sets up the VkInstance and VkDevice objects. Also fills out the passed in @@ -178,6 +180,7 @@ private: }; SwapBehavior mSwapBehavior = SwapBehavior::Discard; GrVkExtensions mExtensions; + uint32_t mDriverVersion = 0; }; } /* namespace renderthread */ diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index a3eee0a67d56..bf1063d0907f 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -557,7 +557,7 @@ public final class AudioAttributes implements Parcelable { private int mContentType = CONTENT_TYPE_UNKNOWN; private int mSource = MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID; private int mFlags = 0x0; - private boolean mMuteHapticChannels = false; + private boolean mMuteHapticChannels = true; private HashSet<String> mTags = new HashSet<String>(); private Bundle mBundle; @@ -888,7 +888,7 @@ public final class AudioAttributes implements Parcelable { /** * Specifying if haptic should be muted or not when playing audio-haptic coupled data. - * By default, haptic channels are enabled. + * By default, haptic channels are disabled. * @param muted true to force muting haptic channels. * @return the same Builder instance. */ diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml index 44e0a659212a..a5286364dc26 100644 --- a/packages/CaptivePortalLogin/AndroidManifest.xml +++ b/packages/CaptivePortalLogin/AndroidManifest.xml @@ -18,7 +18,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.captiveportallogin" - android:versionCode="11" + android:versionCode="200000000" android:versionName="Q-initial"> <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" /> diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml index 395eac1d2ccb..2fe9d21b0489 100644 --- a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml +++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml @@ -18,8 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" - android:visibility="gone"> + android:fitsSystemWindows="true"> <LinearLayout android:id="@+id/container" diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml index 383aba4e400a..5c915b874dde 100644 --- a/packages/CarSystemUI/res/layout/notification_center_activity.xml +++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml @@ -19,7 +19,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/notification_view" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@color/notification_shade_background_color"> <View android:id="@+id/glass_pane" diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml index 0594dce2ba22..9a074ddd7506 100644 --- a/packages/CarSystemUI/res/layout/super_status_bar.xml +++ b/packages/CarSystemUI/res/layout/super_status_bar.xml @@ -64,7 +64,6 @@ <include layout="@layout/car_top_navigation_bar" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" /> </LinearLayout> @@ -80,6 +79,13 @@ android:layout_height="match_parent" android:visibility="invisible"/> + <include layout="@layout/notification_center_activity" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginBottom="112dp" + android:visibility="invisible" + /> + <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_in_front" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index c9f9dead6e81..d946fbc9520a 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -21,6 +21,7 @@ <string name="config_systemUIFactoryComponent" translatable="false"> com.android.systemui.CarSystemUIFactory </string> + <bool name="config_enableFullscreenUserSwitcher">true</bool> <!-- configure which system ui bars should be displayed --> @@ -28,30 +29,12 @@ <bool name="config_enableRightNavigationBar">false</bool> <bool name="config_enableBottomNavigationBar">true</bool> - <!-- SystemUI Services: The classes of the stuff to start. This is duplicated from core - SystemUi b/c it can't be overlayed at this level for now - --> - <string-array name="config_systemUIServiceComponents" translatable="false"> - <item>com.android.systemui.Dependency$DependencyCreator</item> - <item>com.android.systemui.util.NotificationChannels</item> - <item>com.android.systemui.notifications.NotificationsUI</item> - <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item> - <item>com.android.systemui.keyguard.KeyguardViewMediator</item> - <item>com.android.systemui.recents.Recents</item> - <item>com.android.systemui.volume.VolumeUI</item> - <item>com.android.systemui.stackdivider.Divider</item> - <item>com.android.systemui.SystemBars</item> - <item>com.android.systemui.usb.StorageNotification</item> - <item>com.android.systemui.power.PowerUI</item> - <item>com.android.systemui.media.RingtonePlayer</item> - <item>com.android.systemui.keyboard.KeyboardUI</item> - <item>com.android.systemui.pip.PipUI</item> - <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> - <item>@string/config_systemUIVendorServiceComponent</item> - <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> - <item>com.android.systemui.LatencyTester</item> - <item>com.android.systemui.globalactions.GlobalActionsComponent</item> - <item>com.android.systemui.ScreenDecorations</item> - <item>com.android.systemui.SliceBroadcastRelayHandler</item> - </string-array> + <bool name="config_hideNavWhenKeyguardBouncerShown">true</bool> + <bool name="config_enablePersistentDockedActivity">false</bool> + <string name="config_persistentDockedActivityIntentUri" translatable="false"></string> + + <!-- How many icons may be shown at once in the system bar. Includes any + slots that may be reused for things like IME control. --> + <integer name="config_maxNotificationIcons">0</integer> + </resources> diff --git a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java deleted file mode 100644 index 2e2f3b7bea11..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java +++ /dev/null @@ -1,469 +0,0 @@ -/* - * 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.notifications; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.car.Car; -import android.car.CarNotConnectedException; -import android.car.drivingstate.CarUxRestrictionsManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.ServiceConnection; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.os.IBinder; -import android.os.ServiceManager; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.WindowManager; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.android.car.notification.CarNotificationListener; -import com.android.car.notification.CarNotificationView; -import com.android.car.notification.CarUxRestrictionManagerWrapper; -import com.android.car.notification.NotificationClickHandlerFactory; -import com.android.car.notification.NotificationViewController; -import com.android.car.notification.PreprocessingManager; -import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.SystemUI; -import com.android.systemui.statusbar.FlingAnimationUtils; -import com.android.systemui.statusbar.car.CarStatusBar; -import com.android.systemui.statusbar.policy.ConfigurationController; - -/** - * Standalone SystemUI for displaying Notifications that have been designed to be used in the car - */ -public class NotificationsUI extends SystemUI - implements ConfigurationController.ConfigurationListener { - - private static final String TAG = "NotificationsUI"; - // used to calculate how fast to open or close the window - private static final float DEFAULT_FLING_VELOCITY = 0; - // max time a fling animation takes - private static final float FLING_ANIMATION_MAX_TIME = 0.5f; - // acceleration rate for the fling animation - private static final float FLING_SPEED_UP_FACTOR = 0.6f; - private CarNotificationListener mCarNotificationListener; - private CarUxRestrictionsManager mCarUxRestrictionsManager; - private NotificationClickHandlerFactory mClickHandlerFactory; - private Car mCar; - private ViewGroup mCarNotificationWindow; - private NotificationViewController mNotificationViewController; - private boolean mIsShowing; - private boolean mIsTracking; - private boolean mNotificationListAtBottom; - private boolean mNotificationListAtBottomAtTimeOfTouch; - private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper = - new CarUxRestrictionManagerWrapper(); - // Used in the Notification panel touch listener - private GestureDetector mGestureDetector; - // Used in scrollable content of the notifications - private GestureDetector mScrollUpDetector; - private View mContent; - private View.OnTouchListener mOnTouchListener; - private FlingAnimationUtils mFlingAnimationUtils; - private static int sSettleOpenPercentage; - private static int sSettleClosePercentage; - private CarStatusBar mCarStatusBar; - - /** - * Inits the window that hosts the notifications and establishes the connections - * to the car related services. - */ - @Override - public void start() { - sSettleOpenPercentage = mContext.getResources().getInteger( - R.integer.notification_settle_open_percentage); - sSettleClosePercentage = mContext.getResources().getInteger( - R.integer.notification_settle_close_percentage); - WindowManager windowManager = - (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mFlingAnimationUtils = new FlingAnimationUtils(mContext, - FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR); - mCarNotificationListener = new CarNotificationListener(); - // create a notification click handler that closes the notification ui if the an activity - // is launched successfully - mClickHandlerFactory = new NotificationClickHandlerFactory( - IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)), - launchResult -> { - if (launchResult == ActivityManager.START_TASK_TO_FRONT - || launchResult == ActivityManager.START_SUCCESS) { - closeCarNotifications(DEFAULT_FLING_VELOCITY); - } - }); - mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper, - mClickHandlerFactory); - mCar = Car.createCar(mContext, mCarConnectionListener); - mCar.connect(); - NotificationGestureListener gestureListener = new NotificationGestureListener(); - mGestureDetector = new GestureDetector(mContext, gestureListener); - mScrollUpDetector = new GestureDetector(mContext, new ScrollUpDetector()); - mOnTouchListener = new NotificationPanelTouchListener(); - mCarNotificationWindow = (ViewGroup) View.inflate(new ContextThemeWrapper(mContext, - R.style.Theme_Notification), - R.layout.navigation_bar_window, null); - mCarNotificationWindow - .setBackgroundColor(mContext.getColor(R.color.notification_shade_background_color)); - inflateNotificationContent(); - - WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - layoutParams.setTitle("Car Notification Window"); - // start in the hidden state - mCarNotificationWindow.setVisibility(View.GONE); - windowManager.addView(mCarNotificationWindow, layoutParams); - - // Add this object to the SystemUI component registry such that the status bar - // can get a reference to it. - putComponent(NotificationsUI.class, this); - Dependency.get(ConfigurationController.class).addCallback(this); - } - - @SuppressLint("ClickableViewAccessibility") - private void inflateNotificationContent() { - if (mNotificationViewController != null) { - mNotificationViewController.disable(); - } - mCarNotificationWindow.removeAllViews(); - - mContent = View.inflate(new ContextThemeWrapper(mContext, - com.android.car.notification.R.style.Theme_Notification), - R.layout.notification_center_activity, - mCarNotificationWindow); - // set the click handler such that we can dismiss the UI when a notification is clicked - CarNotificationView noteView = mCarNotificationWindow.findViewById(R.id.notification_view); - noteView.setClickHandlerFactory(mClickHandlerFactory); - - mContent.setOnTouchListener(mOnTouchListener); - // set initial translation after size is calculated - mContent.getViewTreeObserver().addOnGlobalLayoutListener( - new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - mContent.getViewTreeObserver().removeOnGlobalLayoutListener(this); - if (!mIsShowing && !mIsTracking) { - mContent.setTranslationY(mContent.getHeight() * -1); - } - } - }); - - RecyclerView notificationList = mCarNotificationWindow.findViewById(R.id.notifications); - // register a scroll listener so we can figure out if we are at the bottom of the - // list of notifications - notificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - if (!notificationList.canScrollVertically(1)) { - mNotificationListAtBottom = true; - return; - } - mNotificationListAtBottom = false; - mNotificationListAtBottomAtTimeOfTouch = false; - } - }); - // add a touch listener such that when the user scrolls up and they are at the bottom - // of the list we can start the closing of the view. - notificationList.setOnTouchListener(new NotificationListTouchListener()); - - // There's a view installed at a higher z-order such that we can intercept the ACTION_DOWN - // to set the initial click state. - mCarNotificationWindow.findViewById(R.id.glass_pane).setOnTouchListener((v, event) -> { - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - mNotificationListAtBottomAtTimeOfTouch = false; - } - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom; - // register the down event with the gesture detectors so then know where the down - // started. This is needed because at this point we don't know which listener - // is going to handle scroll and fling events. - mGestureDetector.onTouchEvent(event); - mScrollUpDetector.onTouchEvent(event); - } - return false; - }); - - mNotificationViewController = new NotificationViewController( - mCarNotificationWindow - .findViewById(com.android.car.notification.R.id.notification_view), - PreprocessingManager.getInstance(mContext), - mCarNotificationListener, - mCarUxRestrictionManagerWrapper); - mNotificationViewController.enable(); - } - - // allows for day night switch - @Override - public void onConfigChanged(Configuration newConfig) { - inflateNotificationContent(); - } - - public void setStatusBar(CarStatusBar carStatusBar) { - mCarStatusBar = carStatusBar; - } - - public View.OnTouchListener getDragDownListener() { - return mOnTouchListener; - } - - /** - * This listener is attached to the notification list UI to intercept gestures if the user - * is scrolling up when the notification list is at the bottom - */ - private class ScrollUpDetector extends GestureDetector.SimpleOnGestureListener { - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - return distanceY > 0; - } - - @Override - public boolean onSingleTapUp(MotionEvent motionEvent) { - closeCarNotifications(DEFAULT_FLING_VELOCITY); - return false; - } - } - - private class NotificationListTouchListener implements View.OnTouchListener { - - @Override - public boolean onTouch(View v, MotionEvent event) { - // reset mNotificationListAtBottomAtTimeOfTouch here since the "glass pane" will not - // get the up event - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - mNotificationListAtBottomAtTimeOfTouch = false; - } - boolean wasScrolledUp = mScrollUpDetector.onTouchEvent(event); - - if (mIsTracking - || (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom - && wasScrolledUp)) { - mOnTouchListener.onTouch(v, event); - // touch event should not be propagated further - return true; - } - return false; - } - } - - /** - * Touch listener installed on the notification panel. It is also used by the Nav and StatusBar - */ - private class NotificationPanelTouchListener implements View.OnTouchListener { - - @Override - public boolean onTouch(View v, MotionEvent event) { - boolean consumed = mGestureDetector.onTouchEvent(event); - if (consumed) { - return true; - } - if (!mIsTracking || event.getActionMasked() != MotionEvent.ACTION_UP) { - return false; - } - - float percentFromBottom = - Math.abs(mContent.getTranslationY() / mContent.getHeight()) * 100; - if (mIsShowing) { - if (percentFromBottom < sSettleOpenPercentage) { - // panel started to close but did not cross minimum threshold thus we open - // it back up - openCarNotifications(DEFAULT_FLING_VELOCITY); - return true; - } - // panel was lifted more than the threshold thus we close it the rest of the way - closeCarNotifications(DEFAULT_FLING_VELOCITY); - return true; - } - - if (percentFromBottom > sSettleClosePercentage) { - // panel was only peeked at thus close it back up - closeCarNotifications(DEFAULT_FLING_VELOCITY); - return true; - } - // panel has been open more than threshold thus open it the rest of the way - openCarNotifications(DEFAULT_FLING_VELOCITY); - return true; - - } - } - - /** - * Listener called by mGestureDetector. This will be initiated from the - * NotificationPanelTouchListener - */ - private class NotificationGestureListener extends GestureDetector.SimpleOnGestureListener { - private static final int SWIPE_UP_MIN_DISTANCE = 75; - private static final int SWIPE_DOWN_MIN_DISTANCE = 25; - private static final int SWIPE_MAX_OFF_PATH = 75; - private static final int SWIPE_THRESHOLD_VELOCITY = 200; - - @Override - public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, - float distanceY) { - if (mCarStatusBar == null || !mCarStatusBar.getIsUserSetup()) { - return true; - } - boolean isDown = event1.getY() - event2.getY() < 0; - // CarStatusBar and NavigationBar are identical so avoid the touch if it - // starts from NavigationBar to open. - if (event1.getRawY() > mCarNotificationWindow.getHeight() && isDown - && mCarNotificationWindow.getVisibility() == View.GONE) { - mIsTracking = false; - return true; - } - mIsTracking = true; - mCarNotificationWindow.setVisibility(View.VISIBLE); - - mContent.setTranslationY(Math.min(mContent.getTranslationY() - distanceY, 0)); - if (mContent.getTranslationY() == 0) { - mIsTracking = false; - } - return true; - } - - @Override - public boolean onFling(MotionEvent event1, MotionEvent event2, - float velocityX, float velocityY) { - if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH - || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { - // swipe was not vertical or was not fast enough - return false; - } - - boolean isUp = velocityY < 0; - float distanceDelta = Math.abs(event1.getY() - event2.getY()); - if (isUp && distanceDelta > SWIPE_UP_MIN_DISTANCE) { - // fling up - mIsTracking = false; - closeCarNotifications(Math.abs(velocityY)); - return true; - - } else if (!isUp && distanceDelta > SWIPE_DOWN_MIN_DISTANCE - && (event1.getRawY() < mCarNotificationWindow.getHeight() - || mCarNotificationWindow.getVisibility() == View.VISIBLE)) { - // fling down - mIsTracking = false; - openCarNotifications(velocityY); - return true; - } - - return false; - } - } - - /** - * Connection callback to establish UX Restrictions - */ - private ServiceConnection mCarConnectionListener = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - try { - mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager( - Car.CAR_UX_RESTRICTION_SERVICE); - mCarUxRestrictionManagerWrapper - .setCarUxRestrictionsManager(mCarUxRestrictionsManager); - PreprocessingManager preprocessingManager = PreprocessingManager.getInstance( - mContext); - preprocessingManager - .setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper); - } catch (CarNotConnectedException e) { - Log.e(TAG, "Car not connected in CarConnectionListener", e); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Log.e(TAG, "Car service disconnected unexpectedly"); - } - }; - - /** - * Toggles the visibility of the notifications - */ - public void toggleShowingCarNotifications() { - if (mCarNotificationWindow.getVisibility() == View.VISIBLE) { - closeCarNotifications(DEFAULT_FLING_VELOCITY); - return; - } - openCarNotifications(DEFAULT_FLING_VELOCITY); - } - - /** - * Hides the notifications - */ - public void closeCarNotifications(float velocityY) { - float closedTranslation = mContent.getHeight() * -1; - ValueAnimator animator = - ValueAnimator.ofFloat(mContent.getTranslationY(), closedTranslation); - animator.addUpdateListener( - animation -> mContent.setTranslationY((Float) animation.getAnimatedValue())); - mFlingAnimationUtils.apply( - animator, mContent.getTranslationY(), closedTranslation, velocityY); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mCarNotificationWindow.setVisibility(View.GONE); - } - }); - animator.start(); - mNotificationViewController.disable(); - mIsShowing = false; - mIsTracking = false; - RecyclerView notificationListView = mCarNotificationWindow.findViewById(R.id.notifications); - notificationListView.scrollToPosition(0); - } - - /** - * Sets the notifications to visible - */ - public void openCarNotifications(float velocityY) { - if (mCarStatusBar == null || !mCarStatusBar.getIsUserSetup()) { - return; - } - mCarNotificationWindow.setVisibility(View.VISIBLE); - - ValueAnimator animator = ValueAnimator.ofFloat(mContent.getTranslationY(), 0); - animator.addUpdateListener( - animation -> mContent.setTranslationY((Float) animation.getAnimatedValue())); - mFlingAnimationUtils.apply(animator, mContent.getTranslationY(), 0, velocityY); - animator.start(); - - mNotificationViewController.enable(); - mIsShowing = true; - mIsTracking = false; - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java index afefa1b1fa56..a0f2367c65a4 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java @@ -38,6 +38,7 @@ class CarNavigationBarView extends LinearLayout { private CarStatusBar mCarStatusBar; private Context mContext; private View mLockScreenButtons; + // used to wire in open/close gestures for notifications private OnTouchListener mStatusBarWindowTouchListener; @@ -65,26 +66,45 @@ class CarNavigationBarView extends LinearLayout { mDarkIconManager.setShouldLog(true); Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager); } + // needs to be clickable so that it will receive ACTION_MOVE events + setClickable(true); } + // Used to forward touch events even if the touch was initiated from a child component @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mStatusBarWindowTouchListener == null) { - return false; + if (mStatusBarWindowTouchListener != null) { + // forward touch events to the status bar window so it can add a drag down + // windows if required (Notification shade) + mStatusBarWindowTouchListener.onTouch(this, ev); } - // forward touch events to the status bar window so it can add a drag down - // windows if required (Notification shade) - mStatusBarWindowTouchListener.onTouch(this, ev); - return false; + return super.onInterceptTouchEvent(ev); } + void setStatusBar(CarStatusBar carStatusBar) { mCarStatusBar = carStatusBar; - mStatusBarWindowTouchListener = carStatusBar.getStatusBarWindowTouchListener(); + } + + /** + * Set a touch listener that will be called from onInterceptTouchEvent and onTouchEvent + * + * @param statusBarWindowTouchListener The listener to call from touch and intercept touch + */ + void setStatusBarWindowTouchListener(OnTouchListener statusBarWindowTouchListener) { + mStatusBarWindowTouchListener = statusBarWindowTouchListener; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mStatusBarWindowTouchListener != null) { + mStatusBarWindowTouchListener.onTouch(this, event); + } + return super.onTouchEvent(event); } protected void onNotificationsClick(View v) { - mCarStatusBar.toggleCarNotifications(); + mCarStatusBar.togglePanel(); } /** 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 abe9be87fe7b..9bcc1ab90e47 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -16,17 +16,29 @@ package com.android.systemui.statusbar.car; +import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.car.drivingstate.CarDrivingStateEvent; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.util.Log; +import android.view.GestureDetector; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.car.notification.CarNotificationListener; +import com.android.car.notification.CarNotificationView; +import com.android.car.notification.CarUxRestrictionManagerWrapper; +import com.android.car.notification.NotificationClickHandlerFactory; +import com.android.car.notification.NotificationViewController; +import com.android.car.notification.PreprocessingManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.BatteryMeterView; import com.android.systemui.CarSystemUIFactory; @@ -37,7 +49,6 @@ import com.android.systemui.SystemUIFactory; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.notifications.NotificationsUI; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.car.CarQSFragment; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -70,7 +81,6 @@ public class CarStatusBar extends StatusBar implements private BatteryMeterView mBatteryMeterView; private Drawable mNotificationPanelBackground; - private ConnectedDeviceSignalController mConnectedDeviceSignalController; private ViewGroup mNavigationBarWindow; private ViewGroup mLeftNavigationBarWindow; private ViewGroup mRightNavigationBarWindow; @@ -90,6 +100,17 @@ public class CarStatusBar extends StatusBar implements private DrivingStateHelper mDrivingStateHelper; private SwitchToGuestTimer mSwitchToGuestTimer; + // The container for the notifications. + private CarNotificationView mNotificationView; + private RecyclerView mNotificationList; + // The state of if the notification list is currently showing the bottom. + private boolean mNotificationListAtBottom; + // Was the notification list at the bottom when the user first touched the screen + private boolean mNotificationListAtBottomAtTimeOfTouch; + // To be attached to the navigation bars such that they can close the notification panel if + // it's open. + private View.OnTouchListener mNavBarNotificationTouchListener; + @Override public void start() { // get the provisioned state before calling the parent class since it's that flow that @@ -223,7 +244,6 @@ public class CarStatusBar extends StatusBar implements * Switch to the keyguard applicable content contained in the nav bars */ private void updateNavBarForKeyguardContent() { - getComponent(NotificationsUI.class).closeCarNotifications(0); if (mNavigationBarView != null) { mNavigationBarView.showKeyguardButtons(); } @@ -255,17 +275,150 @@ public class CarStatusBar extends StatusBar implements // when a device has connected by bluetooth. mBatteryMeterView.setVisibility(View.GONE); }); - addTemperatureViewToController(mStatusBarWindow); + + connectNotificationsUI(); + } + + /** + * Attach the notification listeners and controllers to the UI as well as build all the + * touch listeners needed for opening and closing the notification panel + */ + private void connectNotificationsUI() { + // Attached to the status bar to detect pull down of the notification shade. + GestureDetector openGestureDetector = new GestureDetector(mContext, + new OpenNotificationGestureListener() { + @Override + protected void openNotification() { + animateExpandNotificationsPanel(); + } + }); + // Attached to the notification ui to detect close request of the notification shade. + GestureDetector closeGestureDetector = new GestureDetector(mContext, + new CloseNotificationGestureListener() { + @Override + protected void close() { + animateCollapsePanels(); + } + }); + // Attached to the NavBars to close the notification shade + GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext, + new NavBarCloseNotificationGestureListener() { + @Override + protected void close() { + animateCollapsePanels(); + } + }); + mNavBarNotificationTouchListener = + (v, event) -> navBarCloseNotificationGestureDetector.onTouchEvent(event); + // The following are the ui elements that the user would call the status bar. // This will set the status bar so it they can make call backs. CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar); topBar.setStatusBar(this); - CarNavigationBarView qsTopBar = mStatusBarWindow.findViewById(R.id.qs_car_top_bar); - qsTopBar.setStatusBar(this); - getComponent(NotificationsUI.class).setStatusBar(this); + topBar.setStatusBarWindowTouchListener((v1, event1) -> + openGestureDetector.onTouchEvent(event1)); + + NotificationClickHandlerFactory clickHandlerFactory = new NotificationClickHandlerFactory( + mBarService, + launchResult -> { + if (launchResult == ActivityManager.START_TASK_TO_FRONT + || launchResult == ActivityManager.START_SUCCESS) { + animateCollapsePanels(); + } + }); + CarNotificationListener carNotificationListener = new CarNotificationListener(); + CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper = + new CarUxRestrictionManagerWrapper(); + carNotificationListener.registerAsSystemService(mContext, carUxRestrictionManagerWrapper, + clickHandlerFactory); + + mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view); + View glassPane = mStatusBarWindow.findViewById(R.id.glass_pane); + mNotificationView.setClickHandlerFactory(clickHandlerFactory); + + // The glass pane is used to view touch events before passed to the notification list. + // This allows us to initialize gesture listeners and detect when to close the notifications + glassPane.setOnTouchListener((v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + mNotificationListAtBottomAtTimeOfTouch = false; + } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom; + // Pass the down event to gesture detector so that it knows where the touch event + // started. + closeGestureDetector.onTouchEvent(event); + } + return false; + }); + mNotificationList = mNotificationView.findViewById(R.id.notifications); + mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + if (!mNotificationList.canScrollVertically(1)) { + mNotificationListAtBottom = true; + return; + } + mNotificationListAtBottom = false; + mNotificationListAtBottomAtTimeOfTouch = false; + } + }); + mNotificationList.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + boolean handled = false; + if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) { + handled = closeGestureDetector.onTouchEvent(event); + } + // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after + // the event has been passed to the closeGestureDetector above, such that the + // closeGestureDetector sees the up event before the state has changed. + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + mNotificationListAtBottomAtTimeOfTouch = false; + } + return handled; + } + }); + + NotificationViewController mNotificationViewController = new NotificationViewController( + mNotificationView, + PreprocessingManager.getInstance(mContext), + carNotificationListener, + carUxRestrictionManagerWrapper); + mNotificationViewController.enable(); } @Override + public void animateExpandNotificationsPanel() { + if (!mCommandQueue.panelsEnabled() || !mUserSetup) { + return; + } + // scroll to top + mNotificationList.scrollToPosition(0); + mStatusBarWindowController.setPanelVisible(true); + mNotificationView.setVisibility(View.VISIBLE); + // let the status bar know that the panel is open + setPanelExpanded(true); + } + + @Override + public void animateCollapsePanels(int flags, boolean force, boolean delayed, + float speedUpFactor) { + super.animateCollapsePanels(flags, force, delayed, speedUpFactor); + if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) { + return; + } + mStatusBarWindowController.setStatusBarFocusable(false); + mStatusBarWindow.cancelExpandHelper(); + mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor); + mStatusBarWindowController.setPanelVisible(false); + mNotificationView.setVisibility(View.INVISIBLE); + // let the status bar know that the panel is cloased + setPanelExpanded(false); + } + + + @Override protected QS createDefaultQSFragment() { return new CarQSFragment(); } @@ -338,8 +491,8 @@ public class CarStatusBar extends StatusBar implements lp.setTitle("CarNavigationBar"); lp.windowAnimations = 0; mWindowManager.addView(mNavigationBarWindow, lp); - mNavigationBarWindow.setOnTouchListener(getStatusBarWindowTouchListener()); } + if (mShowLeft) { int width = mContext.getResources().getDimensionPixelSize( R.dimen.car_left_navigation_bar_width); @@ -389,6 +542,7 @@ public class CarStatusBar extends StatusBar implements } mNavigationBarView.setStatusBar(this); addTemperatureViewToController(mNavigationBarView); + mNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener); } private void buildLeft(int layout) { @@ -400,6 +554,7 @@ public class CarStatusBar extends StatusBar implements } mLeftNavigationBarView.setStatusBar(this); addTemperatureViewToController(mLeftNavigationBarView); + mLeftNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener); } @@ -412,6 +567,7 @@ public class CarStatusBar extends StatusBar implements } mRightNavigationBarView.setStatusBar(this); addTemperatureViewToController(mRightNavigationBarView); + mRightNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener); } @Override @@ -435,8 +591,6 @@ public class CarStatusBar extends StatusBar implements pw.println(mCarBatteryController); pw.print(" mBatteryMeterView="); pw.println(mBatteryMeterView); - pw.print(" mConnectedDeviceSignalController="); - pw.println(mConnectedDeviceSignalController); pw.print(" mNavigationBarView="); pw.println(mNavigationBarView); @@ -456,11 +610,6 @@ public class CarStatusBar extends StatusBar implements } } - @Override - protected View.OnTouchListener getStatusBarWindowTouchListener() { - // Gets the car specific notification touch listener - return getComponent(NotificationsUI.class).getDragDownListener(); - } @Override public void showBatteryView() { @@ -585,15 +734,6 @@ public class CarStatusBar extends StatusBar implements true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */); } - @Override - public void animateExpandNotificationsPanel() { - // 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 removing all heads- - // up notifications. - mHeadsUpManager.releaseAllImmediately(); - super.animateExpandNotificationsPanel(); - } - /** * Ensures that relevant child views are appropriately recreated when the device's density * changes. @@ -620,12 +760,73 @@ public class CarStatusBar extends StatusBar implements return mUserSetup; } - public void toggleCarNotifications() { - getComponent(NotificationsUI.class).toggleShowingCarNotifications(); + + // TODO: add settle down/up logic + private static final int SWIPE_UP_MIN_DISTANCE = 75; + private static final int SWIPE_DOWN_MIN_DISTANCE = 25; + private static final int SWIPE_MAX_OFF_PATH = 75; + private static final int SWIPE_THRESHOLD_VELOCITY = 200; + // Only responsible for open hooks. Since once the panel opens it covers all elements + // there is no need to merge with close. + private abstract class OpenNotificationGestureListener extends + GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onFling(MotionEvent event1, MotionEvent event2, + float velocityX, float velocityY) { + if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH + || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { + // swipe was not vertical or was not fast enough + return false; + } + boolean isDown = velocityY > 0; + float distanceDelta = Math.abs(event1.getY() - event2.getY()); + if (isDown && distanceDelta > SWIPE_DOWN_MIN_DISTANCE) { + openNotification(); + return true; + } + + return false; + } + protected abstract void openNotification(); } - @Override - public void maybeEscalateHeadsUp() { - // Never send full screen intent in car. + // to be installed on the open panel notification panel + private abstract class CloseNotificationGestureListener extends + GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onFling(MotionEvent event1, MotionEvent event2, + float velocityX, float velocityY) { + if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH + || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { + // swipe was not vertical or was not fast enough + return false; + } + boolean isUp = velocityY < 0; + float distanceDelta = Math.abs(event1.getY() - event2.getY()); + if (isUp && distanceDelta > SWIPE_UP_MIN_DISTANCE) { + close(); + return true; + } + return false; + } + protected abstract void close(); + } + + // to be installed on the nav bars + private abstract class NavBarCloseNotificationGestureListener extends + CloseNotificationGestureListener { + @Override + public boolean onSingleTapUp(MotionEvent e) { + close(); + return super.onSingleTapUp(e); + } + + @Override + public void onLongPress(MotionEvent e) { + close(); + super.onLongPress(e); + } } } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java index f896cf1bf10c..0a167d9acf98 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java @@ -38,7 +38,9 @@ public class FullscreenUserSwitcher { public FullscreenUserSwitcher(CarStatusBar statusBar, ViewStub containerStub, Context context) { mStatusBar = statusBar; mParent = containerStub.inflate(); - mParent.setVisibility(View.VISIBLE); + // Hide the user grid by default. It will only be made visible by clicking on a cancel + // button in a bouncer. + hide(); View container = mParent.findViewById(R.id.container); // Initialize user grid. @@ -49,10 +51,6 @@ public class FullscreenUserSwitcher { mUserGridView.buildAdapter(); mUserGridView.setUserSelectionListener(this::onUserSelected); - // Hide the user grid by default. It will only be made visible by clicking on a cancel - // button in a bouncer. - hide(); - mShortAnimDuration = container.getResources() .getInteger(android.R.integer.config_shortAnimTime); } @@ -61,21 +59,21 @@ public class FullscreenUserSwitcher { * Makes user grid visible. */ public void show() { - mUserGridView.setVisibility(View.VISIBLE); + mParent.setVisibility(View.VISIBLE); } /** * Hides the user grid. */ public void hide() { - mUserGridView.setVisibility(View.INVISIBLE); + mParent.setVisibility(View.INVISIBLE); } /** * @return {@code true} if user grid is visible, {@code false} otherwise. */ public boolean isVisible() { - return mUserGridView.getVisibility() == View.VISIBLE; + return mParent.getVisibility() == View.VISIBLE; } /** diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml index d40975801ff8..45a59a32ec17 100644 --- a/packages/ExtServices/AndroidManifest.xml +++ b/packages/ExtServices/AndroidManifest.xml @@ -17,7 +17,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" package="android.ext.services" - android:versionCode="1" + android:versionCode="200000000" android:versionName="1" coreApp="true"> diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index 9b60dc3e952a..b4588e01dc79 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -17,10 +17,18 @@ */ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.networkstack" - android:sharedUserId="android.uid.networkstack"> + package="com.android.networkstack" + android:sharedUserId="android.uid.networkstack" + android:versionCode="200000000" + android:versionName="29 system image" +> + <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" /> + <!-- Permissions must be defined here, and not in the base manifest, as the network stack + running in the system server process does not need any permission, and having privileged + permissions added would cause crashes on startup unless they are also added to the + privileged permissions whitelist for that package. --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> @@ -30,7 +38,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> - + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <!-- Signature permission defined in NetworkStackStub --> <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" /> <application> diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 7a06af41f951..c1f178a7f5f1 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -1389,8 +1389,8 @@ public class IpClient extends StateMachine { apfConfig.apfCapabilities = mConfiguration.mApfCapabilities; apfConfig.multicastFilter = mMulticastFiltering; // Get the Configuration for ApfFilter from Context - apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames(mContext); - apfConfig.ethTypeBlackList = ApfCapabilities.getApfEthTypeBlackList(mContext); + apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames(); + apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList(); mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback); // TODO: investigate the effects of any multicast filtering racing/interfering with the // rest of this IP configuration startup. diff --git a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java index fedb8d110197..670563cf391e 100644 --- a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java +++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java @@ -17,6 +17,7 @@ package android.net.util; import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.SparseArray; import java.io.FileDescriptor; @@ -81,4 +82,19 @@ public class NetworkStackUtils { } return false; } + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or has no non-null + * value. + * @return the corresponding value, or defaultValue if none exists. + */ + @Nullable + public static String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name, + @Nullable String defaultValue) { + // TODO: Link to DeviceConfig API once it is ready. + return defaultValue; + } } diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 19e9108d2fc8..63f057caa26e 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -35,7 +35,9 @@ import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkStackConnector; +import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkCapabilities; import android.net.PrivateDnsConfigParcel; import android.net.dhcp.DhcpServer; import android.net.dhcp.DhcpServingParams; @@ -307,9 +309,9 @@ public class NetworkStackService extends Service { } @Override - public void notifyNetworkConnected() { + public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) { checkNetworkStackCallingPermission(); - mNm.notifyNetworkConnected(); + mNm.notifyNetworkConnected(lp, nc); } @Override @@ -319,15 +321,15 @@ public class NetworkStackService extends Service { } @Override - public void notifyLinkPropertiesChanged() { + public void notifyLinkPropertiesChanged(LinkProperties lp) { checkNetworkStackCallingPermission(); - mNm.notifyLinkPropertiesChanged(); + mNm.notifyLinkPropertiesChanged(lp); } @Override - public void notifyNetworkCapabilitiesChanged() { + public void notifyNetworkCapabilitiesChanged(NetworkCapabilities nc) { checkNetworkStackCallingPermission(); - mNm.notifyNetworkCapabilitiesChanged(); + mNm.notifyNetworkCapabilitiesChanged(nc); } } } diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index f3476ed1566f..c000fc6b721d 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -78,6 +78,7 @@ import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.RingBufferIndices; @@ -221,19 +222,31 @@ public class NetworkMonitor extends StateMachine { * Message to self indicating captive portal detection is completed. * obj = CaptivePortalProbeResult for detection result; */ - public static final int CMD_PROBE_COMPLETE = 16; + private static final int CMD_PROBE_COMPLETE = 16; /** * ConnectivityService notifies NetworkMonitor of DNS query responses event. * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query. */ - public static final int EVENT_DNS_NOTIFICATION = 17; + private static final int EVENT_DNS_NOTIFICATION = 17; /** * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and * NetworkMonitor should ignore the https probe. */ - public static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18; + private static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18; + + /** + * ConnectivityService notifies NetworkMonitor of changed LinkProperties. + * obj = new LinkProperties. + */ + private static final int EVENT_LINK_PROPERTIES_CHANGED = 19; + + /** + * ConnectivityService notifies NetworkMonitor of changed NetworkCapabilities. + * obj = new NetworkCapabilities. + */ + private static final int EVENT_NETWORK_CAPABILITIES_CHANGED = 20; // Start mReevaluateDelayMs at this value and double. private static final int INITIAL_REEVALUATE_DELAY_MS = 1000; @@ -379,10 +392,8 @@ public class NetworkMonitor extends StateMachine { mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold(); mDataStallEvaluationType = getDataStallEvalutionType(); - // mLinkProperties and mNetworkCapbilities must never be null or we will NPE. - // Provide empty objects in case we are started and the network disconnects before - // we can ever fetch them. - // TODO: Delete ASAP. + // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null, + // even before notifyNetworkConnected. mLinkProperties = new LinkProperties(); mNetworkCapabilities = new NetworkCapabilities(null); } @@ -434,8 +445,16 @@ public class NetworkMonitor extends StateMachine { /** * Send a notification to NetworkMonitor indicating that the network is now connected. */ - public void notifyNetworkConnected() { - sendMessage(CMD_NETWORK_CONNECTED); + public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) { + sendMessage(CMD_NETWORK_CONNECTED, new Pair<>( + new LinkProperties(lp), new NetworkCapabilities(nc))); + } + + private void updateConnectedNetworkAttributes(Message connectedMsg) { + final Pair<LinkProperties, NetworkCapabilities> attrs = + (Pair<LinkProperties, NetworkCapabilities>) connectedMsg.obj; + mLinkProperties = attrs.first; + mNetworkCapabilities = attrs.second; } /** @@ -448,37 +467,15 @@ public class NetworkMonitor extends StateMachine { /** * Send a notification to NetworkMonitor indicating that link properties have changed. */ - public void notifyLinkPropertiesChanged() { - getHandler().post(() -> { - updateLinkProperties(); - }); - } - - private void updateLinkProperties() { - final LinkProperties lp = mCm.getLinkProperties(mNetwork); - // If null, we should soon get a message that the network was disconnected, and will stop. - if (lp != null) { - // TODO: send LinkProperties parceled in notifyLinkPropertiesChanged() and start(). - mLinkProperties = lp; - } + public void notifyLinkPropertiesChanged(final LinkProperties lp) { + sendMessage(EVENT_LINK_PROPERTIES_CHANGED, new LinkProperties(lp)); } /** * Send a notification to NetworkMonitor indicating that network capabilities have changed. */ - public void notifyNetworkCapabilitiesChanged() { - getHandler().post(() -> { - updateNetworkCapabilities(); - }); - } - - private void updateNetworkCapabilities() { - final NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork); - // If null, we should soon get a message that the network was disconnected, and will stop. - if (nc != null) { - // TODO: send NetworkCapabilities parceled in notifyNetworkCapsChanged() and start(). - mNetworkCapabilities = nc; - } + public void notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc) { + sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(nc)); } /** @@ -547,16 +544,10 @@ public class NetworkMonitor extends StateMachine { // does not entail any real state (hence no enter() or exit() routines). private class DefaultState extends State { @Override - public void enter() { - // TODO: have those passed parceled in start() and remove this - updateLinkProperties(); - updateNetworkCapabilities(); - } - - @Override public boolean processMessage(Message message) { switch (message.what) { case CMD_NETWORK_CONNECTED: + updateConnectedNetworkAttributes(message); logNetworkEvent(NetworkEvent.NETWORK_CONNECTED); transitionTo(mEvaluatingState); return HANDLED; @@ -660,6 +651,12 @@ public class NetworkMonitor extends StateMachine { case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: mAcceptPartialConnectivity = true; break; + case EVENT_LINK_PROPERTIES_CHANGED: + mLinkProperties = (LinkProperties) message.obj; + break; + case EVENT_NETWORK_CAPABILITIES_CHANGED: + mNetworkCapabilities = (NetworkCapabilities) message.obj; + break; default: break; } @@ -684,6 +681,7 @@ public class NetworkMonitor extends StateMachine { public boolean processMessage(Message message) { switch (message.what) { case CMD_NETWORK_CONNECTED: + updateConnectedNetworkAttributes(message); transitionTo(mValidatedState); break; case CMD_EVALUATE_PRIVATE_DNS: diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java index 4d4ceed9cb52..b4eeefd5ecae 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java @@ -72,6 +72,10 @@ public class IpMemoryStoreDatabase { public static final String COLNAME_ASSIGNEDV4ADDRESS = "assignedV4Address"; public static final String COLTYPE_ASSIGNEDV4ADDRESS = "INTEGER"; + public static final String COLNAME_ASSIGNEDV4ADDRESSEXPIRY = "assignedV4AddressExpiry"; + // The lease expiry timestamp in uint of milliseconds + public static final String COLTYPE_ASSIGNEDV4ADDRESSEXPIRY = "BIGINT"; + // Please note that the group hint is only a *hint*, hence its name. The client can offer // this information to nudge the grouping in the decision it thinks is right, but it can't // decide for the memory store what is the same L3 network. @@ -86,13 +90,14 @@ public class IpMemoryStoreDatabase { public static final String COLTYPE_MTU = "INTEGER DEFAULT -1"; public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " - + TABLENAME + " (" - + COLNAME_L2KEY + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, " - + COLNAME_EXPIRYDATE + " " + COLTYPE_EXPIRYDATE + ", " - + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", " - + COLNAME_GROUPHINT + " " + COLTYPE_GROUPHINT + ", " - + COLNAME_DNSADDRESSES + " " + COLTYPE_DNSADDRESSES + ", " - + COLNAME_MTU + " " + COLTYPE_MTU + ")"; + + TABLENAME + " (" + + COLNAME_L2KEY + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, " + + COLNAME_EXPIRYDATE + " " + COLTYPE_EXPIRYDATE + ", " + + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", " + + COLNAME_ASSIGNEDV4ADDRESSEXPIRY + " " + COLTYPE_ASSIGNEDV4ADDRESSEXPIRY + ", " + + COLNAME_GROUPHINT + " " + COLTYPE_GROUPHINT + ", " + + COLNAME_DNSADDRESSES + " " + COLTYPE_DNSADDRESSES + ", " + + COLNAME_MTU + " " + COLTYPE_MTU + ")"; public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME; } @@ -134,7 +139,7 @@ public class IpMemoryStoreDatabase { /** The SQLite DB helper */ public static class DbHelper extends SQLiteOpenHelper { // Update this whenever changing the schema. - private static final int SCHEMA_VERSION = 2; + private static final int SCHEMA_VERSION = 3; private static final String DATABASE_FILENAME = "IpMemoryStore.db"; public DbHelper(@NonNull final Context context) { @@ -153,10 +158,27 @@ public class IpMemoryStoreDatabase { @Override public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { - // No upgrade supported yet. - db.execSQL(NetworkAttributesContract.DROP_TABLE); - db.execSQL(PrivateDataContract.DROP_TABLE); - onCreate(db); + try { + if (oldVersion < 2) { + // upgrade from version 1 to version 2 + // since we starts from version 2, do nothing here + } + + if (oldVersion < 3) { + // upgrade from version 2 to version 3 + final String sqlUpgradeAddressExpiry = "alter table" + + " " + NetworkAttributesContract.TABLENAME + " ADD" + + " " + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY + + " " + NetworkAttributesContract.COLTYPE_ASSIGNEDV4ADDRESSEXPIRY; + db.execSQL(sqlUpgradeAddressExpiry); + } + } catch (SQLiteException e) { + Log.e(TAG, "Could not upgrade to the new version", e); + // create database with new version + db.execSQL(NetworkAttributesContract.DROP_TABLE); + db.execSQL(PrivateDataContract.DROP_TABLE); + onCreate(db); + } } /** Called when the database is downgraded */ @@ -204,6 +226,10 @@ public class IpMemoryStoreDatabase { values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, inet4AddressToIntHTH(attributes.assignedV4Address)); } + if (null != attributes.assignedV4AddressExpiry) { + values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY, + attributes.assignedV4AddressExpiry); + } if (null != attributes.groupHint) { values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); } @@ -251,6 +277,8 @@ public class IpMemoryStoreDatabase { final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); final int assignedV4AddressInt = getInt(cursor, NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); + final long assignedV4AddressExpiry = getLong(cursor, + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY, 0); final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); final byte[] dnsAddressesBlob = getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); @@ -258,6 +286,9 @@ public class IpMemoryStoreDatabase { if (0 != assignedV4AddressInt) { builder.setAssignedV4Address(intToInet4AddressHTH(assignedV4AddressInt)); } + if (0 != assignedV4AddressExpiry) { + builder.setAssignedV4AddressExpiry(assignedV4AddressExpiry); + } builder.setGroupHint(groupHint); if (null != dnsAddressesBlob) { builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java index d732c4e81d83..6665aae65d90 100644 --- a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -26,6 +26,8 @@ import static android.provider.Settings.Global.DATA_STALL_EVALUATION_TYPE_DNS; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; @@ -60,6 +62,7 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; +import android.os.RemoteException; import android.os.SystemClock; import android.provider.Settings; import android.telephony.CellSignalStrength; @@ -73,6 +76,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; @@ -107,6 +111,7 @@ public class NetworkMonitorTest { private @Spy Network mNetwork = new Network(TEST_NETID); private @Mock DataStallStatsUtils mDataStallStatsUtils; private @Mock WifiInfo mWifiInfo; + private @Captor ArgumentCaptor<String> mNetworkTestedRedirectUrlCaptor; private static final int TEST_NETID = 4242; @@ -122,7 +127,7 @@ public class NetworkMonitorTest { private static final int HANDLER_TIMEOUT_MS = 1000; - private static final LinkProperties TEST_LINKPROPERTIES = new LinkProperties(); + private static final LinkProperties TEST_LINK_PROPERTIES = new LinkProperties(); private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) @@ -182,10 +187,6 @@ public class NetworkMonitorTest { InetAddresses.parseNumericAddress("192.168.0.0") }).when(mNetwork).getAllByName(any()); - // Default values. Individual tests can override these. - when(mCm.getLinkProperties(any())).thenReturn(TEST_LINKPROPERTIES); - when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); - setMinDataStallEvaluateInterval(500); setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS); setValidDataStallDnsTimeThreshold(500); @@ -195,10 +196,9 @@ public class NetworkMonitorTest { private class WrappedNetworkMonitor extends NetworkMonitor { private long mProbeTime = 0; - WrappedNetworkMonitor(Context context, Network network, IpConnectivityLog logger, - Dependencies deps, DataStallStatsUtils statsUtils) { - super(context, mCallbacks, network, logger, - new SharedLog("test_nm"), deps, statsUtils); + WrappedNetworkMonitor() { + super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, mDependencies, + mDataStallStatsUtils); } @Override @@ -216,33 +216,30 @@ public class NetworkMonitorTest { } } - private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() { - final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( - mContext, mNetwork, mLogger, mDependencies, mDataStallStatsUtils); - when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); + private WrappedNetworkMonitor makeMonitor() { + final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(); nm.start(); waitForIdle(nm.getHandler()); return nm; } - private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() { - final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( - mContext, mNetwork, mLogger, mDependencies, mDataStallStatsUtils); - when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES); - nm.start(); - waitForIdle(nm.getHandler()); + private WrappedNetworkMonitor makeMeteredNetworkMonitor() { + final WrappedNetworkMonitor nm = makeMonitor(); + setNetworkCapabilities(nm, METERED_CAPABILITIES); return nm; } - private NetworkMonitor makeMonitor() { - final NetworkMonitor nm = new NetworkMonitor( - mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, - mDependencies, mDataStallStatsUtils); - nm.start(); - waitForIdle(nm.getHandler()); + private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() { + final WrappedNetworkMonitor nm = makeMonitor(); + setNetworkCapabilities(nm, NOT_METERED_CAPABILITIES); return nm; } + private void setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc) { + nm.notifyNetworkCapabilitiesChanged(nc); + waitForIdle(nm.getHandler()); + } + private void waitForIdle(Handler handler) { final ConditionVariable cv = new ConditionVariable(false); handler.post(cv::open); @@ -256,7 +253,7 @@ public class NetworkMonitorTest { setSslException(mHttpsConnection); setPortal302(mHttpConnection); - assertPortal(makeMonitor().isCaptivePortal()); + runPortalNetworkTest(); } @Test @@ -264,17 +261,7 @@ public class NetworkMonitorTest { setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 500); - assertNotPortal(makeMonitor().isCaptivePortal()); - } - - @Test - public void testIsCaptivePortal_HttpsProbeFailedHttpSuccessNotUsed() throws IOException { - setSslException(mHttpsConnection); - // Even if HTTP returns a 204, do not use the result unless HTTPS succeeded - setStatus(mHttpConnection, 204); - setStatus(mFallbackConnection, 500); - - assertFailed(makeMonitor().isCaptivePortal()); + runNotPortalNetworkTest(); } @Test @@ -283,17 +270,17 @@ public class NetworkMonitorTest { setStatus(mHttpConnection, 500); setPortal302(mFallbackConnection); - assertPortal(makeMonitor().isCaptivePortal()); + runPortalNetworkTest(); } @Test public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws IOException { setSslException(mHttpsConnection); setStatus(mHttpConnection, 500); - setStatus(mFallbackConnection, 204); + setStatus(mFallbackConnection, 500); // Fallback probe did not see portal, HTTPS failed -> inconclusive - assertFailed(makeMonitor().isCaptivePortal()); + runFailedNetworkTest(); } @Test @@ -310,15 +297,15 @@ public class NetworkMonitorTest { // TEST_OTHER_FALLBACK_URL is third when(mRandom.nextInt()).thenReturn(2); - final NetworkMonitor monitor = makeMonitor(); - // First check always uses the first fallback URL: inconclusive - assertFailed(monitor.isCaptivePortal()); + final NetworkMonitor monitor = runNetworkTest(NETWORK_TEST_RESULT_INVALID); + assertNull(mNetworkTestedRedirectUrlCaptor.getValue()); verify(mFallbackConnection, times(1)).getResponseCode(); verify(mOtherFallbackConnection, never()).getResponseCode(); // Second check uses the URL chosen by Random - assertPortal(monitor.isCaptivePortal()); + final CaptivePortalProbeResult result = monitor.isCaptivePortal(); + assertTrue(result.isPortal()); verify(mOtherFallbackConnection, times(1)).getResponseCode(); } @@ -328,7 +315,7 @@ public class NetworkMonitorTest { setStatus(mHttpConnection, 500); setStatus(mFallbackConnection, 404); - assertFailed(makeMonitor().isCaptivePortal()); + runFailedNetworkTest(); verify(mFallbackConnection, times(1)).getResponseCode(); verify(mOtherFallbackConnection, never()).getResponseCode(); } @@ -342,7 +329,7 @@ public class NetworkMonitorTest { setStatus(mHttpConnection, 500); setPortal302(mOtherFallbackConnection); - assertPortal(makeMonitor().isCaptivePortal()); + runPortalNetworkTest(); verify(mOtherFallbackConnection, times(1)).getResponseCode(); verify(mFallbackConnection, never()).getResponseCode(); } @@ -360,12 +347,12 @@ public class NetworkMonitorTest { } @Test - public void testIsCaptivePortal_FallbackSpecIsNotPortal() throws IOException { + public void testIsCaptivePortal_FallbackSpecIsPartial() throws IOException { setupFallbackSpec(); set302(mOtherFallbackConnection, "https://www.google.com/test?q=3"); - // HTTPS failed, fallback spec did not see a portal -> inconclusive - assertFailed(makeMonitor().isCaptivePortal()); + // HTTPS failed, fallback spec went through -> partial connectivity + runPartialConnectivityNetworkTest(); verify(mOtherFallbackConnection, times(1)).getResponseCode(); verify(mFallbackConnection, never()).getResponseCode(); } @@ -375,7 +362,7 @@ public class NetworkMonitorTest { setupFallbackSpec(); set302(mOtherFallbackConnection, "http://login.portal.example.com"); - assertPortal(makeMonitor().isCaptivePortal()); + runPortalNetworkTest(); } @Test @@ -384,20 +371,20 @@ public class NetworkMonitorTest { setSslException(mHttpsConnection); setPortal302(mHttpConnection); - assertNotPortal(makeMonitor().isCaptivePortal()); + runNotPortalNetworkTest(); } @Test public void testIsDataStall_EvaluationDisabled() { setDataStallEvaluationType(0); - WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); assertFalse(wrappedMonitor.isDataStall()); } @Test public void testIsDataStall_EvaluationDnsOnNotMeteredNetwork() { - WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD); assertTrue(wrappedMonitor.isDataStall()); @@ -405,7 +392,7 @@ public class NetworkMonitorTest { @Test public void testIsDataStall_EvaluationDnsOnMeteredNetwork() { - WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); assertFalse(wrappedMonitor.isDataStall()); @@ -416,7 +403,7 @@ public class NetworkMonitorTest { @Test public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() { - WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(wrappedMonitor, 3); assertFalse(wrappedMonitor.isDataStall()); @@ -430,7 +417,7 @@ public class NetworkMonitorTest { // Set the value to larger than the default dns log size. setConsecutiveDnsTimeoutThreshold(51); - wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(wrappedMonitor, 50); assertFalse(wrappedMonitor.isDataStall()); @@ -442,7 +429,7 @@ public class NetworkMonitorTest { @Test public void testIsDataStall_EvaluationDnsWithDnsTimeThreshold() { // Test dns events happened in valid dns time threshold. - WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD); assertFalse(wrappedMonitor.isDataStall()); @@ -451,7 +438,7 @@ public class NetworkMonitorTest { // Test dns events happened before valid dns time threshold. setValidDataStallDnsTimeThreshold(0); - wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD); assertFalse(wrappedMonitor.isDataStall()); @@ -464,24 +451,13 @@ public class NetworkMonitorTest { setSslException(mHttpsConnection); setStatus(mHttpConnection, 500); setStatus(mFallbackConnection, 404); - when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); - final NetworkMonitor nm = makeMonitor(); - nm.notifyNetworkConnected(); - - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) - .notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null); + runFailedNetworkTest(); } @Test public void testNoInternetCapabilityValidated() throws Exception { - when(mCm.getNetworkCapabilities(any())).thenReturn(NO_INTERNET_CAPABILITIES); - - final NetworkMonitor nm = makeMonitor(); - nm.notifyNetworkConnected(); - - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) - .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null); + runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID); verify(mNetwork, never()).openConnection(any()); } @@ -491,7 +467,7 @@ public class NetworkMonitorTest { setPortal302(mHttpConnection); final NetworkMonitor nm = makeMonitor(); - nm.notifyNetworkConnected(); + nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES); verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) .showProvisioningNotification(any(), any()); @@ -522,7 +498,7 @@ public class NetworkMonitorTest { @Test public void testDataStall_StallSuspectedAndSendMetrics() throws IOException { - WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(wrappedMonitor, 5); assertTrue(wrappedMonitor.isDataStall()); @@ -531,7 +507,7 @@ public class NetworkMonitorTest { @Test public void testDataStall_NoStallSuspectedAndSendMetrics() throws IOException { - WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(wrappedMonitor, 3); assertFalse(wrappedMonitor.isDataStall()); @@ -540,7 +516,7 @@ public class NetworkMonitorTest { @Test public void testCollectDataStallMetrics() { - WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor(); when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE); when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC); @@ -578,14 +554,11 @@ public class NetworkMonitorTest { setSslException(mHttpsConnection); setStatus(mHttpConnection, 204); - final NetworkMonitor nm = makeMonitor(); - nm.notifyNetworkConnected(); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) - .notifyNetworkTested(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY, null); + final NetworkMonitor nm = runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY); nm.setAcceptPartialConnectivity(); verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) - .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null); + .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), any()); } @Test @@ -593,12 +566,12 @@ public class NetworkMonitorTest { setStatus(mHttpsConnection, 500); setStatus(mHttpConnection, 204); setStatus(mFallbackConnection, 500); - assertPartialConnectivity(makeMonitor().isCaptivePortal()); + runPartialConnectivityNetworkTest(); setStatus(mHttpsConnection, 500); setStatus(mHttpConnection, 500); setStatus(mFallbackConnection, 204); - assertPartialConnectivity(makeMonitor().isCaptivePortal()); + runPartialConnectivityNetworkTest(); } private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) { @@ -660,26 +633,41 @@ public class NetworkMonitorTest { eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode); } - private void assertPortal(CaptivePortalProbeResult result) { - assertTrue(result.isPortal()); - assertFalse(result.isFailed()); - assertFalse(result.isSuccessful()); + private void runPortalNetworkTest() { + runNetworkTest(NETWORK_TEST_RESULT_INVALID); + assertNotNull(mNetworkTestedRedirectUrlCaptor.getValue()); } - private void assertNotPortal(CaptivePortalProbeResult result) { - assertFalse(result.isPortal()); - assertFalse(result.isFailed()); - assertTrue(result.isSuccessful()); + private void runNotPortalNetworkTest() { + runNetworkTest(NETWORK_TEST_RESULT_VALID); + assertNull(mNetworkTestedRedirectUrlCaptor.getValue()); } - private void assertFailed(CaptivePortalProbeResult result) { - assertFalse(result.isPortal()); - assertTrue(result.isFailed()); - assertFalse(result.isSuccessful()); + private void runFailedNetworkTest() { + runNetworkTest(NETWORK_TEST_RESULT_INVALID); + assertNull(mNetworkTestedRedirectUrlCaptor.getValue()); } - private void assertPartialConnectivity(CaptivePortalProbeResult result) { - assertTrue(result.isPartialConnectivity()); + private void runPartialConnectivityNetworkTest() { + runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY); + assertNull(mNetworkTestedRedirectUrlCaptor.getValue()); + } + + private NetworkMonitor runNetworkTest(int testResult) { + return runNetworkTest(METERED_CAPABILITIES, testResult); + } + + private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) { + final NetworkMonitor monitor = makeMonitor(); + monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc); + try { + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(eq(testResult), mNetworkTestedRedirectUrlCaptor.capture()); + } catch (RemoteException e) { + fail("Unexpected exception: " + e); + } + + return monitor; } private void setSslException(HttpURLConnection connection) throws IOException { diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java index d0e58b817e9d..071ff2635152 100644 --- a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java @@ -228,6 +228,7 @@ public class IpMemoryStoreServiceTest { public void testNetworkAttributes() throws UnknownHostException { final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + na.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000); na.setGroupHint("hint1"); na.setMtu(219); final String l2Key = FAKE_KEYS[0]; @@ -257,6 +258,8 @@ public class IpMemoryStoreServiceTest { + status.resultCode, status.isSuccess()); assertEquals(l2Key, key); assertEquals(attributes.assignedV4Address, attr.assignedV4Address); + assertEquals(attributes.assignedV4AddressExpiry, + attr.assignedV4AddressExpiry); assertEquals(attributes.groupHint, attr.groupHint); assertEquals(attributes.mtu, attr.mtu); assertEquals(attributes2.dnsAddresses, attr.dnsAddresses); @@ -278,7 +281,7 @@ public class IpMemoryStoreServiceTest { // Verify that this test does not miss any new field added later. // If any field is added to NetworkAttributes it must be tested here for storing // and retrieving. - assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) + assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); } diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 62535b635e44..b0e2700a1f30 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -43,14 +43,12 @@ <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.INSTALL_PACKAGE" /> <category android:name="android.intent.category.DEFAULT" /> - <data android:scheme="file" /> <data android:scheme="content" /> <data android:mimeType="application/vnd.android.package-archive" /> </intent-filter> <intent-filter android:priority="1"> <action android:name="android.intent.action.INSTALL_PACKAGE" /> <category android:name="android.intent.category.DEFAULT" /> - <data android:scheme="file" /> <data android:scheme="package" /> <data android:scheme="content" /> </intent-filter> diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp new file mode 100644 index 000000000000..7f4442deecd6 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/Android.bp @@ -0,0 +1,13 @@ +android_library { + name: "SettingsLibAdaptiveIcon", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.annotation_annotation", + "SettingsLibTile" + ], + + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml b/packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml new file mode 100644 index 000000000000..256b8f3ea477 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml new file mode 100644 index 000000000000..76d106a067dd --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<resources> + <color name="homepage_generic_icon_background">#1A73E8</color> + + <color name="bt_outline_color">#1f000000</color> <!-- icon outline color --> +</resources> diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml new file mode 100644 index 000000000000..7f5b58c48abb --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. + --> + +<resources> + <!-- Dashboard foreground image inset (from background edge to foreground edge) --> + <dimen name="dashboard_tile_foreground_image_inset">6dp</dimen> + + <!-- Stroke size of adaptive outline --> + <dimen name="adaptive_outline_stroke">1dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java new file mode 100644 index 000000000000..fc93650a6b85 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 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.settingslib.widget; + +import static androidx.annotation.VisibleForTesting.NONE; + +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.android.settingslib.drawer.Tile; + +/** + * Adaptive icon that can set background color + */ +public class AdaptiveIcon extends LayerDrawable { + + private static final String TAG = "AdaptiveHomepageIcon"; + + @VisibleForTesting(otherwise = NONE) + int mBackgroundColor = -1; + private AdaptiveConstantState mAdaptiveConstantState; + + public AdaptiveIcon(Context context, Drawable foreground) { + super(new Drawable[]{ + new AdaptiveIconShapeDrawable(context.getResources()), + foreground + }); + final int insetPx = context.getResources() + .getDimensionPixelSize(R.dimen.dashboard_tile_foreground_image_inset); + setLayerInset(1 /* index */, insetPx, insetPx, insetPx, insetPx); + mAdaptiveConstantState = new AdaptiveConstantState(context, foreground); + } + + /** + * According {@code tile} metaData to set background color + */ + public void setBackgroundColor(Context context, Tile tile) { + final Bundle metaData = tile.getMetaData(); + try { + if (metaData != null) { + // Load from bg.argb first + int bgColor = metaData.getInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, + 0 /* default */); + // Not found, load from bg.hint + if (bgColor == 0) { + final int colorRes = metaData.getInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, + 0 /* default */); + if (colorRes != 0) { + bgColor = context.getPackageManager() + .getResourcesForApplication(tile.getPackageName()) + .getColor(colorRes, null /* theme */); + } + } + // If found anything, use it. + if (bgColor != 0) { + setBackgroundColor(bgColor); + return; + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to set background color for " + tile.getPackageName()); + } + setBackgroundColor(context.getColor(R.color.homepage_generic_icon_background)); + } + + /** + * Set background color by {@code color} + */ + public void setBackgroundColor(int color) { + mBackgroundColor = color; + getDrawable(0).setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + Log.d(TAG, "Setting background color " + mBackgroundColor); + mAdaptiveConstantState.mColor = color; + } + + @Override + public ConstantState getConstantState() { + return mAdaptiveConstantState; + } + + @VisibleForTesting + static class AdaptiveConstantState extends ConstantState { + Context mContext; + Drawable mDrawable; + int mColor; + + AdaptiveConstantState(Context context, Drawable drawable) { + this.mContext = context; + this.mDrawable = drawable; + } + + @Override + public Drawable newDrawable() { + final AdaptiveIcon + icon = new AdaptiveIcon(mContext, mDrawable); + icon.setBackgroundColor(mColor); + + return icon; + } + + @Override + public int getChangingConfigurations() { + return 0; + } + } +} diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java new file mode 100644 index 000000000000..4d7610cf97b6 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 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.settingslib.widget; + +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.graphics.Path; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.PathShape; +import android.util.AttributeSet; +import android.util.PathParser; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * Draws a filled {@link ShapeDrawable} using the path from {@link AdaptiveIconDrawable}. + */ +public class AdaptiveIconShapeDrawable extends ShapeDrawable { + public AdaptiveIconShapeDrawable() { + super(); + } + + public AdaptiveIconShapeDrawable(Resources resources) { + super(); + init(resources); + } + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + super.inflate(r, parser, attrs, theme); + init(r); + } + + private void init(Resources resources) { + final float pathSize = AdaptiveIconDrawable.MASK_SIZE; + final Path path = new Path(PathParser.createPathFromPathData( + resources.getString(com.android.internal.R.string.config_icon_mask))); + setShape(new PathShape(path, pathSize, pathSize)); + } +} diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java new file mode 100644 index 000000000000..1c65bc248961 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 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.settingslib.widget; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.DrawableWrapper; +import android.util.PathParser; + +import androidx.annotation.VisibleForTesting; + +/** + * Adaptive outline drawable with white plain background color and black outline + */ +public class AdaptiveOutlineDrawable extends DrawableWrapper { + @VisibleForTesting + final Paint mOutlinePaint; + private Path mPath; + private final int mInsetPx; + private final Bitmap mBitmap; + + public AdaptiveOutlineDrawable(Resources resources, Bitmap bitmap) { + super(new AdaptiveIconShapeDrawable(resources)); + + getDrawable().setTint(Color.WHITE); + mPath = new Path(PathParser.createPathFromPathData( + resources.getString(com.android.internal.R.string.config_icon_mask))); + mOutlinePaint = new Paint(); + mOutlinePaint.setColor(resources.getColor(R.color.bt_outline_color, null)); + mOutlinePaint.setStyle(Paint.Style.STROKE); + mOutlinePaint.setStrokeWidth(resources.getDimension(R.dimen.adaptive_outline_stroke)); + mOutlinePaint.setAntiAlias(true); + + mInsetPx = resources + .getDimensionPixelSize(R.dimen.dashboard_tile_foreground_image_inset); + mBitmap = bitmap; + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + final Rect bounds = getBounds(); + final float pathSize = AdaptiveIconDrawable.MASK_SIZE; + + final float scaleX = (bounds.right - bounds.left) / pathSize; + final float scaleY = (bounds.bottom - bounds.top) / pathSize; + + final int count = canvas.save(); + canvas.scale(scaleX, scaleY); + // Draw outline + canvas.drawPath(mPath, mOutlinePaint); + canvas.restoreToCount(count); + + // Draw the foreground icon + canvas.drawBitmap(mBitmap, bounds.left + mInsetPx, bounds.top + mInsetPx, null); + } + + @Override + public int getIntrinsicHeight() { + return mBitmap.getHeight() + 2 * mInsetPx; + } + + @Override + public int getIntrinsicWidth() { + return mBitmap.getWidth() + 2 * mInsetPx; + } +} diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 730e9e134e3d..b532621cd617 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -22,6 +22,7 @@ android_library { "SettingsLibEntityHeaderWidgets", "SettingsLibBarChartPreference", "SettingsLibProgressBar", + "SettingsLibAdaptiveIcon", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp new file mode 100644 index 000000000000..bf16ef317fd8 --- /dev/null +++ b/packages/SettingsLib/Tile/Android.bp @@ -0,0 +1,11 @@ +android_library { + name: "SettingsLibTile", + + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.annotation_annotation", + ], + + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/Tile/AndroidManifest.xml b/packages/SettingsLib/Tile/AndroidManifest.xml new file mode 100644 index 000000000000..b13532e2a5fd --- /dev/null +++ b/packages/SettingsLib/Tile/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.drawer"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DashboardCategory.java index a3dda658bec7..7b062b1ac9a9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DashboardCategory.java @@ -1,11 +1,11 @@ -/** - * Copyright (C) 2015 The Android Open Source Project +/* + * Copyright (C) 2019 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 + * 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, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.settingslib.drawer; import static java.lang.String.CASE_INSENSITIVE_ORDER; @@ -26,6 +25,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +/** + * The category for handle {@link Tile} + */ public class DashboardCategory implements Parcelable { /** @@ -67,18 +69,30 @@ public class DashboardCategory implements Parcelable { return result; } + /** + * Add tile + */ public synchronized void addTile(Tile tile) { mTiles.add(tile); } + /** + * Remove tile + */ public synchronized void removeTile(int n) { mTiles.remove(n); } + /** + * Get size of tile + */ public int getTilesCount() { return mTiles.size(); } + /** + * Get tile + */ public Tile getTile(int n) { return mTiles.get(n); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java index d28b00a7ed39..5108efbdc216 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java @@ -1,11 +1,11 @@ -/** - * Copyright (C) 2015 The Android Open Source Project +/* + * Copyright (C) 2019 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 + * 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, @@ -84,8 +84,8 @@ public class Tile implements Parcelable { mActivityPackage = in.readString(); mActivityName = in.readString(); mIntent = new Intent().setClassName(mActivityPackage, mActivityName); - final int N = in.readInt(); - for (int i = 0; i < N; i++) { + final int number = in.readInt(); + for (int i = 0; i < number; i++) { userHandle.add(UserHandle.CREATOR.createFromParcel(in)); } mCategory = in.readString(); @@ -101,9 +101,9 @@ public class Tile implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(mActivityPackage); dest.writeString(mActivityName); - final int N = userHandle.size(); - dest.writeInt(N); - for (int i = 0; i < N; i++) { + final int size = userHandle.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { userHandle.get(i).writeToParcel(dest, flags); } dest.writeString(mCategory); @@ -151,6 +151,9 @@ public class Tile implements Parcelable { } } + /** + * Check whether title has order. + */ public boolean hasOrder() { return mMetaData.containsKey(META_DATA_KEY_ORDER) && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer; @@ -262,6 +265,9 @@ public class Tile implements Parcelable { } } + /** + * Check whether title has key. + */ public boolean hasKey() { return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT); } @@ -361,9 +367,12 @@ public class Tile implements Parcelable { } }; + /** + * Check whether title is only have primary profile + */ public boolean isPrimaryProfileOnly() { - String profile = mMetaData != null ? - mMetaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL; + String profile = mMetaData != null + ? mMetaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL; profile = (profile != null ? profile : PROFILE_ALL); return TextUtils.equals(profile, PROFILE_PRIMARY); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java index 91892abdfb44..31925ab64ec3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 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. @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.settingslib.drawer; @@ -39,6 +39,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Utils is a helper class that contains profile key, meta data, settings action + * and static methods for get icon or text from uri. + */ public class TileUtils { private static final boolean DEBUG_TIMING = false; diff --git a/packages/SettingsLib/res/drawable/ic_media_device.xml b/packages/SettingsLib/res/drawable/ic_media_device.xml new file mode 100644 index 000000000000..5a6aeb4a8e23 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_media_device.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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:viewportWidth="24" + android:viewportHeight="24" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="#00000000" + android:fillAlpha=".1" + android:pathData="M0 0h24v24H0z" /> + <path + android:fillColor="#00000000" + android:pathData="M0 0h24v24H0z" /> + <path + android:fillColor="#000000" + android:pathData="M21 3H3c-1.1 0-2 0.9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2 -0.9 2-2V5c0-1.1 -0.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z" /> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml index ed3c11cd3ca3..39c55fd1925c 100644 --- a/packages/SettingsLib/res/values/arrays.xml +++ b/packages/SettingsLib/res/values/arrays.xml @@ -603,4 +603,26 @@ <item>3</item><item>3</item> </array> + <!-- Bluetooth icon foreground colors --> + <integer-array name="bt_icon_fg_colors"> + <item>@color/bt_color_icon_1</item> + <item>@color/bt_color_icon_2</item> + <item>@color/bt_color_icon_3</item> + <item>@color/bt_color_icon_4</item> + <item>@color/bt_color_icon_5</item> + <item>@color/bt_color_icon_6</item> + <item>@color/bt_color_icon_7</item> + </integer-array> + + <!-- Bluetooth icon background colors --> + <integer-array name="bt_icon_bg_colors"> + <item>@color/bt_color_bg_1</item> + <item>@color/bt_color_bg_2</item> + <item>@color/bt_color_bg_3</item> + <item>@color/bt_color_bg_4</item> + <item>@color/bt_color_bg_5</item> + <item>@color/bt_color_bg_6</item> + <item>@color/bt_color_bg_7</item> + </integer-array> + </resources> diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml index 66bbb3a6c890..4b91bbb8d8dc 100644 --- a/packages/SettingsLib/res/values/colors.xml +++ b/packages/SettingsLib/res/values/colors.xml @@ -19,4 +19,20 @@ <color name="usage_graph_dots">@*android:color/tertiary_device_default_settings</color> <color name="list_divider_color">#64000000</color> + + <color name="bt_color_icon_1">#48a50e0e</color> <!-- 72% Material Red 900 --> + <color name="bt_color_icon_2">#480d652d</color> <!-- 72% Material Green 900 --> + <color name="bt_color_icon_3">#48e37400</color> <!-- 72% Material Yellow 900 --> + <color name="bt_color_icon_4">#48b06000</color> <!-- 72% Material Orange 900 --> + <color name="bt_color_icon_5">#489c166b</color> <!-- 72% Material Pink 900 --> + <color name="bt_color_icon_6">#48681da8</color> <!-- 72% Material Purple 900 --> + <color name="bt_color_icon_7">#48007b83</color> <!-- 72% Material Cyan 900 --> + + <color name="bt_color_bg_1">#fad2cf</color> <!-- Material Red 100 --> + <color name="bt_color_bg_2">#ceead6</color> <!-- Material Green 100 --> + <color name="bt_color_bg_3">#feefc3</color> <!-- Material Yellow 100 --> + <color name="bt_color_bg_4">#fedfc8</color> <!-- Material Orange 100 --> + <color name="bt_color_bg_5">#fdcfe8</color> <!-- Material Pink 100 --> + <color name="bt_color_bg_6">#e9d2fd</color> <!-- Material Purple 100 --> + <color name="bt_color_bg_7">#cbf0f8</color> <!-- Material Cyan 100 --> </resources> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index a9c5061f6d87..2cb9d4b14aa7 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -91,6 +91,7 @@ <!-- How far to inset the rounded edges --> <dimen name="stat_sys_mobile_signal_circle_inset">0.9dp</dimen> - + <!-- Size of nearby icon --> + <dimen name="bt_nearby_icon_size">24dp</dimen> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index bb8c8a6768ed..867efb408258 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -1,18 +1,30 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; import android.util.Pair; import androidx.annotation.DrawableRes; import com.android.settingslib.R; +import com.android.settingslib.widget.AdaptiveIcon; +import com.android.settingslib.widget.AdaptiveOutlineDrawable; +import java.io.IOException; import java.util.List; public class BluetoothUtils { + private static final String TAG = "BluetoothUtils"; + public static final boolean V = false; // verbose logging public static final boolean D = true; // regular logging @@ -112,4 +124,68 @@ public class BluetoothUtils { public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) { return context.getDrawable(resId); } + + /** + * Get colorful bluetooth icon with description + */ + public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context, + CachedBluetoothDevice cachedDevice) { + final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription( + context, cachedDevice); + final BluetoothDevice bluetoothDevice = cachedDevice.getDevice(); + final boolean untetheredHeadset = bluetoothDevice != null + ? Boolean.parseBoolean(bluetoothDevice.getMetadata( + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)) + : false; + final int iconSize = context.getResources().getDimensionPixelSize( + R.dimen.bt_nearby_icon_size); + final Resources resources = context.getResources(); + + // Deal with untethered headset + if (untetheredHeadset) { + final String uriString = bluetoothDevice != null + ? bluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON) + : null; + final Uri iconUri = uriString != null ? Uri.parse(uriString) : null; + if (iconUri != null) { + try { + final Bitmap bitmap = MediaStore.Images.Media.getBitmap( + context.getContentResolver(), iconUri); + if (bitmap != null) { + final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, + iconSize, false); + bitmap.recycle(); + final AdaptiveOutlineDrawable drawable = new AdaptiveOutlineDrawable( + resources, resizedBitmap); + return new Pair<>(drawable, pair.second); + } + } catch (IOException e) { + Log.e(TAG, "Failed to get drawable for: " + iconUri, e); + } + } + } + + return new Pair<>(buildBtRainbowDrawable(context, + pair.first, cachedDevice.getAddress().hashCode()), pair.second); + } + + /** + * Build Bluetooth device icon with rainbow + */ + public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable, + int hashCode) { + final Resources resources = context.getResources(); + + // Deal with normal headset + final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors); + final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors); + + // get color index based on mac address + final int index = Math.abs(hashCode % iconBgColors.length); + drawable.setColorFilter(iconFgColors[index], PorterDuff.Mode.SRC_ATOP); + final Drawable adaptiveIcon = new AdaptiveIcon(context, drawable); + ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]); + + return adaptiveIcon; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index 3092b9960c7e..2711e3175957 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -18,8 +18,11 @@ package com.android.settingslib.media; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.Log; +import android.util.Pair; +import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; /** @@ -48,9 +51,10 @@ public class BluetoothMediaDevice extends MediaDevice { } @Override - public int getIcon() { - //TODO(b/117129183): This is not final icon for bluetooth device, just for demo. - return com.android.internal.R.drawable.ic_bt_headphones_a2dp; + public Drawable getIcon() { + final Pair<Drawable, String> pair = BluetoothUtils + .getBtRainbowDrawableWithDescription(mContext, mCachedDevice); + return pair.first; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index 95f3d3d0f769..732e8dba3e44 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -16,10 +16,14 @@ package com.android.settingslib.media; import android.content.Context; +import android.graphics.drawable.Drawable; import android.widget.Toast; import androidx.mediarouter.media.MediaRouter; +import com.android.settingslib.R; +import com.android.settingslib.bluetooth.BluetoothUtils; + /** * InfoMediaDevice extends MediaDevice to represents wifi device. */ @@ -46,9 +50,10 @@ public class InfoMediaDevice extends MediaDevice { } @Override - public int getIcon() { - //TODO(b/121083246): This is not final icon for cast device, just for demo. - return com.android.internal.R.drawable.ic_settings_print; + public Drawable getIcon() { + //TODO(b/120669861): Return remote device icon uri once api is ready. + return BluetoothUtils.buildBtRainbowDrawable(mContext, + mContext.getDrawable(R.drawable.ic_media_device), getId().hashCode()); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 9b9e80310c18..53a852069478 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -16,6 +16,7 @@ package com.android.settingslib.media; import android.content.Context; +import android.graphics.drawable.Drawable; import android.text.TextUtils; import androidx.annotation.IntDef; @@ -70,11 +71,11 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { public abstract String getSummary(); /** - * Get resource id of MediaDevice. + * Get icon of MediaDevice. * - * @return resource id of MediaDevice. + * @return drawable of icon. */ - public abstract int getIcon(); + public abstract Drawable getIcon(); /** * Get unique ID that represent MediaDevice diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 8c3fcc077edc..af91c3464194 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -16,10 +16,12 @@ package com.android.settingslib.media; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.Log; import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; +import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; @@ -56,8 +58,9 @@ public class PhoneMediaDevice extends MediaDevice { } @Override - public int getIcon() { - return R.drawable.ic_smartphone; + public Drawable getIcon() { + return BluetoothUtils.buildBtRainbowDrawable(mContext, + mContext.getDrawable(R.drawable.ic_smartphone), getId().hashCode()); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 3acbcd3f6b41..8a88a4c64d0a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -53,6 +53,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import androidx.annotation.NonNull; @@ -65,8 +66,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -197,6 +200,9 @@ public class AccessPoint implements Comparable<AccessPoint> { private final Context mContext; + private WifiManager mWifiManager; + private WifiManager.ActionListener mConnectListener; + private String ssid; private String bssid; private int security; @@ -1068,8 +1074,10 @@ public class AccessPoint implements Comparable<AccessPoint> { /** * Starts the OSU Provisioning flow. */ - public void startOsuProvisioning() { - mContext.getSystemService(WifiManager.class).startSubscriptionProvisioning( + public void startOsuProvisioning(@Nullable WifiManager.ActionListener connectListener) { + mConnectListener = connectListener; + + getWifiManager().startSubscriptionProvisioning( mOsuProvider, mContext.getMainExecutor(), new AccessPointProvisioningCallback() @@ -1539,12 +1547,20 @@ public class AccessPoint implements Comparable<AccessPoint> { return string; } + private WifiManager getWifiManager() { + if (mWifiManager == null) { + mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + } + return mWifiManager; + } + /** * Callbacks relaying changes to the AccessPoint representation. * * <p>All methods are invoked on the Main Thread. */ public interface AccessPointListener { + /** * Indicates a change to the externally visible state of the AccessPoint trigger by an * update of ScanResults, saved configuration state, connection state, or score @@ -1561,7 +1577,6 @@ public class AccessPoint implements Comparable<AccessPoint> { * changed */ @MainThread void onAccessPointChanged(AccessPoint accessPoint); - /** * Indicates the "wifi pie signal level" has changed, retrieved via calls to * {@link AccessPoint#getLevel()}. @@ -1643,11 +1658,46 @@ public class AccessPoint implements Comparable<AccessPoint> { mOsuProvisioningComplete = true; mOsuFailure = null; mOsuStatus = null; + ThreadUtils.postOnMainThread(() -> { if (mAccessPointListener != null) { mAccessPointListener.onAccessPointChanged(AccessPoint.this); } }); + + // Connect to the freshly provisioned network. + WifiManager wifiManager = getWifiManager(); + + PasspointConfiguration passpointConfig = wifiManager + .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(mOsuProvider)) + .get(mOsuProvider); + if (passpointConfig == null) { + Log.e(TAG, "Missing PasspointConfiguration for newly provisioned network!"); + if (mConnectListener != null) { + mConnectListener.onFailure(0); + } + return; + } + + String fqdn = passpointConfig.getHomeSp().getFqdn(); + for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing : + wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) { + WifiConfiguration config = pairing.first; + if (TextUtils.equals(config.FQDN, fqdn)) { + List<ScanResult> homeScans = + pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); + List<ScanResult> roamingScans = + pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); + + AccessPoint connectionAp = + new AccessPoint(mContext, config, homeScans, roamingScans); + wifiManager.connect(connectionAp.getConfig(), mConnectListener); + return; + } + } + if (mConnectListener != null) { + mConnectListener.onFailure(0); + } } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index fdc0fd33e6d7..5f2bc4e1d490 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -84,7 +84,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS; /** Maximum age of scan results to hold onto while actively scanning. **/ - private static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000; + @VisibleForTesting static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000; private static final String TAG = "WifiTracker"; private static final boolean DBG() { @@ -142,6 +142,13 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro */ private boolean mStaleScanResults = true; + /** + * Tracks whether the latest SCAN_RESULTS_AVAILABLE_ACTION contained new scans. If not, then + * we treat the last scan as an aborted scan and increase the eviction timeout window to avoid + * completely flushing the AP list before the next successful scan completes. + */ + private boolean mLastScanSucceeded = true; + // Does not need to be locked as it only updated on the worker thread, with the exception of // during onStart, which occurs before the receiver is registered on the work handler. private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>(); @@ -478,17 +485,22 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } /** - * Remove old scan results from the cache. + * Remove old scan results from the cache. If {@link #mLastScanSucceeded} is false, then + * increase the timeout window to avoid completely flushing the AP list before the next + * successful scan completes. * * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when * {@link #mStaleScanResults} is false. */ private void evictOldScans() { + long evictionTimeoutMillis = mLastScanSucceeded ? MAX_SCAN_RESULT_AGE_MILLIS + : MAX_SCAN_RESULT_AGE_MILLIS * 2; + long nowMs = SystemClock.elapsedRealtime(); for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) { ScanResult result = iter.next(); // result timestamp is in microseconds - if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) { + if (nowMs - result.timestamp / 1000 > evictionTimeoutMillis) { iter.remove(); } } @@ -840,6 +852,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro WifiManager.WIFI_STATE_UNKNOWN)); } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { mStaleScanResults = false; + mLastScanSucceeded = + intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true); fetchScansAndConfigsAndUpdateAccessPoints(); } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 9c8e3f4fe543..8e4027164587 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -41,6 +41,7 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkScoreCache; import android.net.wifi.WifiSsid; import android.net.wifi.hotspot2.OsuProvider; @@ -53,6 +54,7 @@ import android.os.SystemClock; import android.text.SpannableString; import android.text.format.DateUtils; import android.util.ArraySet; +import android.util.Pair; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -72,6 +74,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -95,9 +98,12 @@ public class AccessPointTest { private Context mContext; private WifiInfo mWifiInfo; + @Mock private Context mMockContext; + @Mock private WifiManager mMockWifiManager; @Mock private RssiCurve mockBadgeCurve; @Mock private WifiNetworkScoreCache mockWifiNetworkScoreCache; @Mock private AccessPoint.AccessPointListener mMockAccessPointListener; + @Mock private WifiManager.ActionListener mMockConnectListener; private static final int NETWORK_ID = 123; private static final int DEFAULT_RSSI = -55; @@ -1360,6 +1366,9 @@ public class AccessPointTest { .isEqualTo(mContext.getString(R.string.tap_to_sign_up)); } + /** + * Verifies that the summary of an OSU entry updates based on provisioning status. + */ @Test public void testOsuAccessPointSummary_showsProvisioningUpdates() { AccessPoint osuAccessPoint = new AccessPoint(mContext, createOsuProvider(), @@ -1411,4 +1420,82 @@ public class AccessPointTest { assertThat(osuAccessPoint.getSummary()) .isEqualTo(mContext.getString(R.string.osu_sign_up_complete)); } + + /** + * Verifies that after provisioning through an OSU provider, we connect to the freshly + * provisioned network. + */ + @Test + public void testOsuAccessPoint_connectsAfterProvisioning() { + // Set up mock for WifiManager.getAllMatchingWifiConfigs + WifiConfiguration config = new WifiConfiguration(); + config.FQDN = "fqdn"; + Map<Integer, List<ScanResult>> scanMapping = new HashMap<>(); + scanMapping.put(WifiManager.PASSPOINT_HOME_NETWORK, mScanResults); + Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> configMapPair = + new Pair<>(config, scanMapping); + List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingWifiConfig = + new ArrayList<>(); + matchingWifiConfig.add(configMapPair); + when(mMockWifiManager.getAllMatchingWifiConfigs(any())).thenReturn(matchingWifiConfig); + + // Set up mock for WifiManager.getMatchingPasspointConfigsForOsuProviders + OsuProvider provider = createOsuProvider(); + PasspointConfiguration passpointConfig = new PasspointConfiguration(); + HomeSp homeSp = new HomeSp(); + homeSp.setFqdn("fqdn"); + homeSp.setFriendlyName("Test Provider"); + passpointConfig.setHomeSp(homeSp); + Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>(); + osuProviderConfigMap.put(provider, passpointConfig); + when(mMockWifiManager + .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(provider))) + .thenReturn(osuProviderConfigMap); + + when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); + + AccessPoint osuAccessPoint = new AccessPoint(mMockContext, provider, mScanResults); + osuAccessPoint.setListener(mMockAccessPointListener); + + AccessPoint.AccessPointProvisioningCallback provisioningCallback = + osuAccessPoint.new AccessPointProvisioningCallback(); + provisioningCallback.onProvisioningComplete(); + + verify(mMockWifiManager).connect(any(), any()); + } + + /** + * Verifies that after provisioning through an OSU provider, we call the connect listener's + * onFailure() method if we cannot find the network we just provisioned. + */ + @Test + public void testOsuAccessPoint_noMatchingConfigsAfterProvisioning_callsOnFailure() { + // Set up mock for WifiManager.getAllMatchingWifiConfigs + when(mMockWifiManager.getAllMatchingWifiConfigs(any())).thenReturn(new ArrayList<>()); + + // Set up mock for WifiManager.getMatchingPasspointConfigsForOsuProviders + OsuProvider provider = createOsuProvider(); + PasspointConfiguration passpointConfig = new PasspointConfiguration(); + HomeSp homeSp = new HomeSp(); + homeSp.setFqdn("fqdn"); + homeSp.setFriendlyName("Test Provider"); + passpointConfig.setHomeSp(homeSp); + Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>(); + osuProviderConfigMap.put(provider, passpointConfig); + when(mMockWifiManager + .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(provider))) + .thenReturn(osuProviderConfigMap); + + when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); + + AccessPoint osuAccessPoint = new AccessPoint(mMockContext, provider, mScanResults); + osuAccessPoint.setListener(mMockAccessPointListener); + osuAccessPoint.startOsuProvisioning(mMockConnectListener); + + AccessPoint.AccessPointProvisioningCallback provisioningCallback = + osuAccessPoint.new AccessPointProvisioningCallback(); + provisioningCallback.onProvisioningComplete(); + + verify(mMockConnectListener).onFailure(anyInt()); + } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java index edf414ddf323..683ec8bb5a6c 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java @@ -270,7 +270,7 @@ public class WifiTrackerTest { SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */); } - private static ScanResult buildStaleScanResult() { + private static ScanResult buildScanResultWithTimestamp(long timestampMillis) { return new ScanResult( WifiSsid.createFromAsciiEncoded(SSID_3), BSSID_3, @@ -280,7 +280,7 @@ public class WifiTrackerTest { "", // capabilities RSSI_3, 0, // frequency - 0 /* microsecond timestamp */); + timestampMillis * 1000 /* microsecond timestamp */); } private static WifiConfiguration buildPasspointConfiguration(String fqdn, String friendlyName) { @@ -379,6 +379,12 @@ public class WifiTrackerTest { tracker.mReceiver.onReceive(mContext, i); } + private void sendFailedScanResults(WifiTracker tracker) throws InterruptedException { + Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + i.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false); + tracker.mReceiver.onReceive(mContext, i); + } + private void sendUpdatedScores() throws InterruptedException { Bundle attr1 = new Bundle(); attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve1); @@ -982,8 +988,8 @@ public class WifiTrackerTest { @Test public void onStart_updateScanResults_evictOldScanResult() { - when(mockWifiManager.getScanResults()).thenReturn( - Arrays.asList(buildScanResult1(), buildScanResult2(), buildStaleScanResult())); + when(mockWifiManager.getScanResults()).thenReturn(Arrays.asList( + buildScanResult1(), buildScanResult2(), buildScanResultWithTimestamp(0))); WifiTracker tracker = createMockedWifiTracker(); tracker.forceUpdate(); @@ -995,6 +1001,33 @@ public class WifiTrackerTest { } /** + * Verifies that a failed scan reported on SCAN_RESULTS_AVAILABLE_ACTION should increase the + * ScanResult eviction timeout to twice the default. + */ + @Test + public void failedScan_increasesEvictionTimeout() throws InterruptedException { + when(mockWifiManager.getScanResults()).thenReturn(Arrays.asList( + buildScanResult1(), buildScanResult2(), buildScanResultWithTimestamp( + SystemClock.elapsedRealtime() - WifiTracker.MAX_SCAN_RESULT_AGE_MILLIS))); + WifiTracker tracker = createMockedWifiTracker(); + + sendFailedScanResults(tracker); + + // Failed scan increases timeout window to include the stale scan + assertThat(tracker.getAccessPoints()).hasSize(3); + assertThat(tracker.getAccessPoints().get(0).getBssid()).isEqualTo(BSSID_1); + assertThat(tracker.getAccessPoints().get(1).getBssid()).isEqualTo(BSSID_2); + assertThat(tracker.getAccessPoints().get(2).getBssid()).isEqualTo(BSSID_3); + + sendScanResults(tracker); + + // Successful scan resets the timeout window to remove the stale scan + assertThat(tracker.getAccessPoints()).hasSize(2); + assertThat(tracker.getAccessPoints().get(0).getBssid()).isEqualTo(BSSID_1); + assertThat(tracker.getAccessPoints().get(1).getBssid()).isEqualTo(BSSID_2); + } + + /** * Verifies that updatePasspointAccessPoints will only return AccessPoints whose * isPasspoint() evaluates as true. */ diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index b713e08eb67e..b228cf7c10c6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -15,15 +15,20 @@ */ package com.android.settingslib.bluetooth; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.Pair; +import com.android.settingslib.widget.AdaptiveIcon; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,6 +43,9 @@ public class BluetoothUtilsTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; + private Context mContext; @Before @@ -66,4 +74,16 @@ public class BluetoothUtilsTest { verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_laptop); } + + @Test + public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).thenReturn("false"); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn("1f:aa:bb"); + + assertThat(BluetoothUtils.getBtRainbowDrawableWithDescription( + RuntimeEnvironment.application, + mCachedBluetoothDevice).first).isInstanceOf(AdaptiveIcon.class); + } }
\ No newline at end of file diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java new file mode 100644 index 000000000000..ed6b9b0a135e --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 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.settingslib.widget; + +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Icon; +import android.graphics.drawable.ShapeDrawable; +import android.os.Bundle; + +import com.android.settingslib.R; +import com.android.settingslib.drawer.CategoryKey; +import com.android.settingslib.drawer.Tile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AdaptiveIconTest { + + private Context mContext; + private ActivityInfo mActivityInfo; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mActivityInfo = new ActivityInfo(); + mActivityInfo.packageName = mContext.getPackageName(); + mActivityInfo.name = "class"; + mActivityInfo.metaData = new Bundle(); + } + + @Test + public void createIcon_shouldSetBackgroundAndInset() { + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + + assertThat(icon.getNumberOfLayers()).isEqualTo(2); + assertThat(icon.getDrawable(0)).isInstanceOf(AdaptiveIconShapeDrawable.class); + } + + @Test + public void setBackgroundColor_shouldUpdateColorFilter() { + final AdaptiveIcon icon = + spy(new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK))); + final ShapeDrawable background = mock(ShapeDrawable.class); + when(icon.getDrawable(0)).thenReturn(background); + + icon.setBackgroundColor(Color.BLUE); + + verify(background).setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP); + } + + @Test + public void setBackgroundColor_externalTileWithBackgroundColorRawValue_shouldUpdateIcon() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, 0xff0000); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) + .when(tile).getIcon(mContext); + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + + icon.setBackgroundColor(mContext, tile); + assertThat(icon.mBackgroundColor).isEqualTo(0xff0000); + } + + @Test + public void setBackgroundColor_tileWithoutBackgroundColor_shouldSetDefaultBackgroundColor() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) + .when(tile).getIcon(mContext); + final AdaptiveIcon icon = new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + + icon.setBackgroundColor(mContext, tile); + + assertThat(icon.mBackgroundColor) + .isEqualTo(mContext.getColor(R.color.homepage_generic_icon_background)); + } + + @Test + public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, + R.color.bt_outline_color); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) + .when(tile).getIcon(mContext); + + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + icon.setBackgroundColor(mContext, tile); + + assertThat(icon.mBackgroundColor) + .isEqualTo(mContext.getColor(R.color.bt_outline_color)); + } + + @Test + public void getConstantState_returnCorrectState() { + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + icon.setBackgroundColor(Color.YELLOW); + + final AdaptiveIcon.AdaptiveConstantState state = + (AdaptiveIcon.AdaptiveConstantState) icon.getConstantState(); + + assertThat(state.mColor).isEqualTo(Color.YELLOW); + assertThat(state.mContext).isEqualTo(mContext); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java new file mode 100644 index 000000000000..71d55bc3de2a --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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.settingslib.widget; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.res.Resources; +import android.graphics.Paint; + +import com.android.settingslib.R; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AdaptiveOutlineDrawableTest { + + @Test + public void constructor_initPaint() { + final Resources resources = RuntimeEnvironment.application.getResources(); + final AdaptiveOutlineDrawable drawable = new AdaptiveOutlineDrawable(resources, null); + + assertThat(drawable.mOutlinePaint.getStyle()).isEqualTo(Paint.Style.STROKE); + assertThat(drawable.mOutlinePaint.getStrokeWidth()).isWithin(0.01f).of( + resources.getDimension(R.dimen.adaptive_outline_stroke)); + } + +} diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index d39646b566d8..2a9456dd723c 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -168,6 +168,9 @@ <uses-permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" /> <uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" /> <uses-permission android:name="android.permission.WATCH_APPOPS" /> + <!-- Permission needed to invoke DynamicSystem (AOT) --> + <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" /> + <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> <uses-permission android:name="android.permission.SUSPEND_APPS" /> diff --git a/packages/SystemUI/res/drawable/ic_camera.xml b/packages/SystemUI/res/drawable/ic_camera.xml new file mode 100644 index 000000000000..b330875864d3 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_camera.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 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="17dp" + android:height="17dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFF" + android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/stat_sys_camera.xml b/packages/SystemUI/res/drawable/stat_sys_camera.xml index eb3e9632dd35..c914262571ea 100644 --- a/packages/SystemUI/res/drawable/stat_sys_camera.xml +++ b/packages/SystemUI/res/drawable/stat_sys_camera.xml @@ -18,14 +18,5 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="3dp" - android:insetRight="3dp"> - <vector - android:width="17dp" - android:height="17dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#FFF" - android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/> - </vector> -</inset> + android:insetRight="3dp" + android:drawable="@drawable/ic_camera" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml index d49aff9abc01..3786812db827 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml @@ -39,7 +39,7 @@ android:layout_height="wrap_content" android:paddingEnd="12dp" android:paddingBottom="4dp" - android:textColor="@color/ksh_keyword_color" + android:textColor="?android:attr/textColorPrimary" android:textSize="16sp" android:maxLines="5" android:singleLine="false" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index b82c250e2bd2..290d75b4de9c 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -129,7 +129,6 @@ <!-- Keyboard shortcuts colors --> <color name="ksh_application_group_color">#fff44336</color> - <color name="ksh_keyword_color">#d9000000</color> <color name="ksh_key_item_color">@color/material_grey_600</color> <color name="ksh_key_item_background">@color/material_grey_100</color> diff --git a/packages/SystemUI/res/values/config_car.xml b/packages/SystemUI/res/values/config_car.xml deleted file mode 100644 index 2c549bc8ce2d..000000000000 --- a/packages/SystemUI/res/values/config_car.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 2016, 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. -*/ ---> - -<resources> - <!-- These next two work together, if you enable this first one you need to provide an intent - uri that will be launched into the docked window. --> - <bool name="config_enablePersistentDockedActivity">false</bool> - <string name="config_persistentDockedActivityIntentUri" translatable="false"></string> - - <!-- configure which system ui bars should be displayed --> - <bool name="config_enableLeftNavigationBar">false</bool> - <bool name="config_enableRightNavigationBar">false</bool> - <bool name="config_enableBottomNavigationBar">true</bool> - <bool name="config_hideNavWhenKeyguardBouncerShown">true</bool> -</resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 1a865eef8d24..6eb279affc4f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -23,8 +23,6 @@ <dimen name="navigation_bar_size">@*android:dimen/navigation_bar_height</dimen> <!-- Minimum swipe distance to catch the swipe gestures to invoke assist or switch tasks. --> <dimen name="navigation_bar_min_swipe_distance">48dp</dimen> - <!-- The default distance from a side of the device to start an edge swipe from --> - <dimen name="navigation_bar_default_edge_width">48dp</dimen> <dimen name="navigation_bar_default_edge_height">500dp</dimen> <!-- thickness (height) of the dead zone at the top of the navigation bar, @@ -985,10 +983,6 @@ <!-- How much into a DisplayCutout's bounds we can go, on each side --> <dimen name="display_cutout_margin_consumption">0px</dimen> - <!-- How much we expand the touchable region of the status bar below the notch to catch touches - that just start below the notch. --> - <dimen name="display_cutout_touchable_region_size">12dp</dimen> - <!-- Padding below Ongoing App Ops dialog title --> <dimen name="ongoing_appops_dialog_sep">16dp</dimen> <!--Padding around text items in Ongoing App Ops dialog --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 46f4c8689196..d0c17b7f6738 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -112,4 +112,22 @@ public class QuickStepContract { return context.getResources().getInteger( com.android.internal.R.integer.config_navBarInteractionMode); } + + /** + * @return {@code true} if the navbar can be clicked through + */ + public static boolean isNavBarClickThrough(Context context) { + return context.getResources().getBoolean( + com.android.internal.R.bool.config_navBarTapThrough); + } + + /** + * @return the edge sensitivity width in px + */ + public static int getEdgeSensitivityWidth(Context context) { + return context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.config_backGestureInset); + } + + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index 7f39e474f6c6..7e6ddcfea762 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -128,7 +128,8 @@ public final class KeyboardShortcuts { private KeyCharacterMap mBackupKeyCharacterMap; private KeyboardShortcuts(Context context) { - this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_Light); + this.mContext = new ContextThemeWrapper( + context, android.R.style.Theme_DeviceDefault_Settings); this.mPackageManager = AppGlobals.getPackageManager(); loadResources(context); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index 08a10dc925e3..ac58e681dbbc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -36,7 +36,8 @@ import javax.inject.Singleton; /** */ @Singleton -public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher { +public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, + LightBarTransitionsController.DarkIntensityApplier { private final LightBarTransitionsController mTransitionsController; private final Rect mTintArea = new Rect(); @@ -54,8 +55,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher { mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone); mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone); - mTransitionsController = new LightBarTransitionsController(context, - this::setIconTintInternal); + mTransitionsController = new LightBarTransitionsController(context, this); } public LightBarTransitionsController getTransitionsController() { @@ -104,13 +104,19 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher { applyIconTint(); } - private void setIconTintInternal(float darkIntensity) { + @Override + public void applyDarkIntensity(float darkIntensity) { mDarkIntensity = darkIntensity; mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone); applyIconTint(); } + @Override + public int getTintAnimationDuration() { + return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION; + } + private void applyIconTint() { for (int i = 0; i < mReceivers.size(); i++) { mReceivers.valueAt(i).onDarkChanged(mTintArea, mDarkIntensity, mIconTint); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 9844d8e5a67a..b7a154d67c10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -140,7 +140,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, mHeadsUpInset = mStatusBarHeight + resources.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); mDisplayCutoutTouchableRegionSize = resources.getDimensionPixelSize( - R.dimen.display_cutout_touchable_region_size); + com.android.internal.R.dimen.display_cutout_touchable_region_size); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index b622688a8ac6..d7097309ce20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -16,9 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.statusbar.phone.NavBarTintController.DEFAULT_COLOR_ADAPT_TRANSITION_TIME; -import static com.android.systemui.statusbar.phone.NavBarTintController.MIN_COLOR_ADAPT_TRANSITION_TIME; - import android.animation.ValueAnimator; import android.content.Context; import android.os.Bundle; @@ -52,7 +49,6 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, private final DarkIntensityApplier mApplier; private final KeyguardMonitor mKeyguardMonitor; private final StatusBarStateController mStatusBarStateController; - private NavBarTintController mColorAdaptionController; private boolean mTransitionDeferring; private long mTransitionDeferringStartTime; @@ -118,7 +114,8 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, } if (mTransitionPending && mTintChangePending) { mTintChangePending = false; - animateIconTint(mPendingDarkIntensity, 0 /* delay */, getTintAnimationDuration()); + animateIconTint(mPendingDarkIntensity, 0 /* delay */, + mApplier.getTintAnimationDuration()); } mTransitionPending = false; } @@ -159,15 +156,8 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()), mTransitionDeferringDuration); } else { - animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, getTintAnimationDuration()); - } - } - - public long getTintAnimationDuration() { - if (NavBarTintController.isEnabled(mContext)) { - return Math.max(DEFAULT_COLOR_ADAPT_TRANSITION_TIME, MIN_COLOR_ADAPT_TRANSITION_TIME); + animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, mApplier.getTintAnimationDuration()); } - return DEFAULT_TINT_ANIMATION_DURATION; } public float getCurrentDarkIntensity() { @@ -243,5 +233,6 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, */ public interface DarkIntensityApplier { void applyDarkIntensity(float darkIntensity); + int getTintAnimationDuration(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index d4cec429dc90..8ff6cc9b3d93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.NavBarTintController.DEFAULT_COLOR_ADAPT_TRANSITION_TIME; +import static com.android.systemui.statusbar.phone.NavBarTintController.MIN_COLOR_ADAPT_TRANSITION_TIME; + import android.content.Context; import android.graphics.Rect; import android.os.Handler; @@ -31,7 +34,8 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; import com.android.systemui.R; -public final class NavigationBarTransitions extends BarTransitions { +public final class NavigationBarTransitions extends BarTransitions implements + LightBarTransitionsController.DarkIntensityApplier { private final NavigationBarView mView; private final IStatusBarService mBarService; @@ -59,8 +63,7 @@ public final class NavigationBarTransitions extends BarTransitions { mView = view; mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - mLightTransitionsController = new LightBarTransitionsController(view.getContext(), - this::applyDarkIntensity); + mLightTransitionsController = new LightBarTransitionsController(view.getContext(), this); mAllowAutoDimWallpaperNotVisible = view.getContext().getResources() .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper); @@ -170,4 +173,12 @@ public final class NavigationBarTransitions extends BarTransitions { } mView.onDarkIntensityChange(darkIntensity); } + + @Override + public int getTintAnimationDuration() { + if (NavBarTintController.isEnabled(mView.getContext())) { + return Math.max(DEFAULT_COLOR_ADAPT_TRANSITION_TIME, MIN_COLOR_ADAPT_TRANSITION_TIME); + } + return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java index 7ea72c79501d..47a10547688b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.phone; import android.annotation.IntDef; +import android.content.ComponentCallbacks; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Point; @@ -25,6 +27,8 @@ import android.net.Uri; import android.os.Handler; import android.provider.Settings; +import com.android.systemui.shared.system.QuickStepContract; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -33,7 +37,7 @@ import java.lang.annotation.RetentionPolicy; * prototypes to run in the system. The class will handle communication changes from the settings * app and call back to listeners. */ -public class NavigationPrototypeController extends ContentObserver { +public class NavigationPrototypeController extends ContentObserver implements ComponentCallbacks { private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback"; private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome"; private static final String PROTOTYPE_ENABLED = "prototype_enabled"; @@ -85,9 +89,9 @@ public class NavigationPrototypeController extends ContentObserver { registerObserver(HIDE_HOME_BUTTON_SETTING); registerObserver(GESTURE_MATCH_SETTING); registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING); - registerObserver(EDGE_SENSITIVITY_WIDTH_SETTING); registerObserver(SHOW_HOME_HANDLE_SETTING); registerObserver(ENABLE_ASSISTANT_GESTURE); + mContext.registerComponentCallbacks(this); } /** @@ -95,6 +99,7 @@ public class NavigationPrototypeController extends ContentObserver { */ public void unregister() { mContext.getContentResolver().unregisterContentObserver(this); + mContext.unregisterComponentCallbacks(this); } @Override @@ -115,9 +120,6 @@ public class NavigationPrototypeController extends ContentObserver { } else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) { mListener.onColorAdaptChanged( NavBarTintController.isEnabled(mContext)); - } else if (path.endsWith(EDGE_SENSITIVITY_WIDTH_SETTING)) { - mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(), - getEdgeSensitivityHeight()); } else if (path.endsWith(SHOW_HOME_HANDLE_SETTING)) { mListener.onHomeHandleVisiblilityChanged(showHomeHandle()); } else if (path.endsWith(ENABLE_ASSISTANT_GESTURE)) { @@ -130,8 +132,7 @@ public class NavigationPrototypeController extends ContentObserver { * @return the width for edge swipe */ public int getEdgeSensitivityWidth() { - // TODO: Move into resource - return convertDpToPixel(getGlobalInt(EDGE_SENSITIVITY_WIDTH_SETTING, 48)); + return QuickStepContract.getEdgeSensitivityWidth(mContext); } /** @@ -203,6 +204,18 @@ public class NavigationPrototypeController extends ContentObserver { return (int) (dp * Resources.getSystem().getDisplayMetrics().density); } + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (mListener != null) { + mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(), + getEdgeSensitivityHeight()); + } + } + + @Override + public void onLowMemory() { + } + public interface OnPrototypeChangedListener { void onGestureRemap(@GestureAction int[] actions); void onBackButtonVisibilityChanged(boolean visible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java index 25cb7d0573da..8053ec7e5838 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java @@ -29,7 +29,6 @@ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW; import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM; -import static com.android.systemui.statusbar.phone.NavigationPrototypeController.EDGE_SENSITIVITY_WIDTH_SETTING; import android.annotation.Nullable; import android.content.Context; @@ -264,10 +263,7 @@ public class QuickStepController implements GestureHelper { mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix); mAllowGestureDetection = true; mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed(); - final int defaultRegionThreshold = mContext.getResources() - .getDimensionPixelOffset(R.dimen.navigation_bar_default_edge_width); - mGestureRegionThreshold = convertDpToPixel(getIntGlobalSetting(mContext, - EDGE_SENSITIVITY_WIDTH_SETTING, defaultRegionThreshold)); + mGestureRegionThreshold = QuickStepContract.getEdgeSensitivityWidth(mContext); break; } case MotionEvent.ACTION_MOVE: { @@ -357,7 +353,7 @@ public class QuickStepController implements GestureHelper { if (mCurrentAction != null) { mCurrentAction.endGesture(); } - } else if (QuickStepContract.isGesturalMode(mContext) + } else if (QuickStepContract.isNavBarClickThrough(mContext) && !mClickThroughPressed) { // Enable click through functionality where no gesture has been detected and // not passed the drag slop so inject a touch event at the same location diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml index 545a3cc52193..23d2e7babdcc 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml @@ -26,4 +26,11 @@ <!-- Controls whether the nav bar can move from the bottom to the side in landscape. Only applies if the device display is not square. --> <bool name="config_navBarCanMove">false</bool> -</resources>
\ No newline at end of file + + <!-- Controls whether the navigation bar lets through taps. --> + <bool name="config_navBarTapThrough">true</bool> + + <!-- Controls the size of the back gesture inset. --> + <dimen name="config_backGestureInset">48dp</dimen> + +</resources> diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 904817e1763e..3c69bb73f893 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2552,6 +2552,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled); pw.append(", nonInteractiveUiTimeout=" + userState.mNonInteractiveUiTimeout); pw.append(", interactiveUiTimeout=" + userState.mInteractiveUiTimeout); + pw.append(", installedServiceCount=" + userState.mInstalledServices.size()); if (mUiAutomationManager.isUiAutomationRunningLocked()) { pw.append(", "); mUiAutomationManager.dumpUiAutomationService(fd, pw, args); @@ -2559,7 +2560,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } pw.append("}"); pw.println(); - pw.append(" services:{"); + pw.append(" Bound services:{"); final int serviceCount = userState.mBoundServices.size(); for (int j = 0; j < serviceCount; j++) { if (j > 0) { @@ -2570,6 +2571,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityServiceConnection service = userState.mBoundServices.get(j); service.dump(fd, pw, args); } + pw.println("}"); + pw.append(" Enabled services:{"); + Iterator<ComponentName> it = userState.mEnabledServices.iterator(); + if (it.hasNext()) { + ComponentName componentName = it.next(); + pw.append(componentName.toShortString()); + while (it.hasNext()) { + componentName = it.next(); + pw.append(", "); + pw.append(componentName.toShortString()); + } + } + pw.println("}"); + pw.append(" Binding services:{"); + it = userState.mBindingServices.iterator(); + if (it.hasNext()) { + ComponentName componentName = it.next(); + pw.append(componentName.toShortString()); + while (it.hasNext()) { + componentName = it.next(); + pw.append(", "); + pw.append(componentName.toShortString()); + } + } pw.println("}]"); pw.println(); } @@ -2585,6 +2610,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub pw.append(window.toString()); pw.append(']'); } + pw.println(); } } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1169eeb76801..ed0399f1e883 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -5385,7 +5385,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mDnsResolver, mNMS, factorySerialNumber); // Make sure the network capabilities reflect what the agent info says. - nai.networkCapabilities = mixInCapabilities(nai, nc); + nai.setNetworkCapabilities(mixInCapabilities(nai, nc)); final String extraInfo = networkInfo.getExtraInfo(); final String name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSSID() : extraInfo; @@ -5478,12 +5478,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // Start or stop DNS64 detection and 464xlat according to network state. networkAgent.clatd.update(); notifyIfacesChangedForNetworkStats(); + try { + networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } if (networkAgent.everConnected) { - try { - networkAgent.networkMonitor().notifyLinkPropertiesChanged(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED); } } @@ -5711,7 +5711,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities prevNc; synchronized (nai) { prevNc = nai.networkCapabilities; - nai.networkCapabilities = newNc; + nai.setNetworkCapabilities(newNc); } updateUids(nai, prevNc, newNc); @@ -5726,11 +5726,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // If the requestable capabilities have changed or the score changed, we can't have been // called by rematchNetworkAndRequests, so it's safe to start a rematch. rematchAllNetworksAndRequests(nai, oldScore); - try { - nai.networkMonitor().notifyNetworkCapabilitiesChanged(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } @@ -5989,11 +5984,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (capabilitiesChanged) { - try { - nai.networkMonitor().notifyNetworkCapabilitiesChanged(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } @@ -6402,7 +6392,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (networkAgent.networkMisc.acceptPartialConnectivity) { networkAgent.networkMonitor().setAcceptPartialConnectivity(); } - networkAgent.networkMonitor().notifyNetworkConnected(); + networkAgent.networkMonitor().notifyNetworkConnected( + networkAgent.linkProperties, networkAgent.networkCapabilities); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java index f50364d70bc7..164837ab98dd 100644 --- a/services/core/java/com/android/server/ExplicitHealthCheckController.java +++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java @@ -39,7 +39,9 @@ import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -50,10 +52,22 @@ class ExplicitHealthCheckController { private static final String TAG = "ExplicitHealthCheckController"; private final Object mLock = new Object(); private final Context mContext; - @GuardedBy("mLock") @Nullable private StateCallback mStateCallback; + + // Called everytime the service is connected, so the watchdog can sync it's state with + // the health check service. In practice, should never be null after it has been #setEnabled. + @GuardedBy("mLock") @Nullable private Runnable mOnConnected; + // Called everytime a package passes the health check, so the watchdog is notified of the + // passing check. In practice, should never be null after it has been #setEnabled. + @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer; + // Actual binder object to the explicit health check service. @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService; - @GuardedBy("mLock") @Nullable private ServiceConnection mConnection; + // Cache for packages supporting explicit health checks. This cache should not change while + // the health check service is running. @GuardedBy("mLock") @Nullable private List<String> mSupportedPackages; + // Connection to the explicit health check service, necessary to unbind + @GuardedBy("mLock") @Nullable private ServiceConnection mConnection; + // Bind state of the explicit health check service. + @GuardedBy("mLock") private boolean mEnabled; ExplicitHealthCheckController(Context context) { mContext = context; @@ -61,28 +75,40 @@ class ExplicitHealthCheckController { /** * Requests an explicit health check for {@code packageName}. - * After this request, the callback registered on {@link startService} can receive explicit + * After this request, the callback registered on {@link #setCallbacks} can receive explicit * health check passed results. * * @throws IllegalStateException if the service is not started */ public void request(String packageName) throws RemoteException { synchronized (mLock) { + if (!mEnabled) { + return; + } + enforceServiceReadyLocked(); + + Slog.i(TAG, "Requesting health check for package " + packageName); mRemoteService.request(packageName); } } /** * Cancels all explicit health checks for {@code packageName}. - * After this request, the callback registered on {@link startService} can no longer receive + * After this request, the callback registered on {@link #setCallbacks} can no longer receive * explicit health check passed results. * * @throws IllegalStateException if the service is not started */ public void cancel(String packageName) throws RemoteException { synchronized (mLock) { + if (!mEnabled) { + return; + } + enforceServiceReadyLocked(); + + Slog.i(TAG, "Cancelling health check for package " + packageName); mRemoteService.cancel(packageName); } } @@ -95,13 +121,21 @@ class ExplicitHealthCheckController { */ public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException { synchronized (mLock) { + if (!mEnabled) { + consumer.accept(Collections.emptyList()); + return; + } + enforceServiceReadyLocked(); + if (mSupportedPackages == null) { + Slog.d(TAG, "Getting health check supported packages"); mRemoteService.getSupportedPackages(new RemoteCallback(result -> { mSupportedPackages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES); consumer.accept(mSupportedPackages); })); } else { + Slog.d(TAG, "Getting cached health check supported packages"); consumer.accept(mSupportedPackages); } } @@ -115,95 +149,113 @@ class ExplicitHealthCheckController { */ public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException { synchronized (mLock) { + if (!mEnabled) { + consumer.accept(Collections.emptyList()); + return; + } + enforceServiceReadyLocked(); + + Slog.d(TAG, "Getting health check requested packages"); mRemoteService.getRequestedPackages(new RemoteCallback( result -> consumer.accept( result.getStringArrayList(EXTRA_REQUESTED_PACKAGES)))); } } + /** Enables or disables explicit health checks. */ + public void setEnabled(boolean enabled) { + synchronized (mLock) { + if (enabled == mEnabled) { + return; + } + + Slog.i(TAG, "Setting explicit health checks enabled " + enabled); + mEnabled = enabled; + if (enabled) { + bindService(); + } else { + unbindService(); + } + } + } + /** - * Starts the explicit health check service. - * - * @param stateCallback will receive important state changes changes - * @param passedConsumer will accept packages that pass explicit health checks - * - * @throws IllegalStateException if the service is already started + * Sets callbacks to listen to important events from the controller. + * Should be called at initialization. */ - public void startService(StateCallback stateCallback, Consumer<String> passedConsumer) { + public void setCallbacks(Runnable onConnected, Consumer<String> passedConsumer) { + Preconditions.checkNotNull(onConnected); + Preconditions.checkNotNull(passedConsumer); + mOnConnected = onConnected; + mPassedConsumer = passedConsumer; + } + + /** Binds to the explicit health check service. */ + private void bindService() { synchronized (mLock) { if (mRemoteService != null) { - throw new IllegalStateException("Explicit health check service already started."); + return; + } + ComponentName component = getServiceComponentNameLocked(); + if (component == null) { + Slog.wtf(TAG, "Explicit health check service not found"); + return; } - mStateCallback = stateCallback; + + Intent intent = new Intent(); + intent.setComponent(component); + // TODO: Fix potential race conditions during mConnection state transitions. + // E.g after #onServiceDisconected, the mRemoteService object is invalid until + // we get an #onServiceConnected. mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mLock) { - mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service); - try { - mRemoteService.setCallback(new RemoteCallback(result -> { - String packageName = - result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE); - if (!TextUtils.isEmpty(packageName)) { - passedConsumer.accept(packageName); - } else { - Slog.w(TAG, "Empty package passed explicit health check?"); - } - })); - mStateCallback.onStart(); - Slog.i(TAG, "Explicit health check service is connected " + name); - } catch (RemoteException e) { - Slog.wtf(TAG, "Coud not setCallback on explicit health check service"); - } - } + initState(service); + Slog.i(TAG, "Explicit health check service is connected " + name); } @Override @MainThread public void onServiceDisconnected(ComponentName name) { - resetState(); + // Service crashed or process was killed, #onServiceConnected will be called. + // Don't need to re-bind. Slog.i(TAG, "Explicit health check service is disconnected " + name); } @Override public void onBindingDied(ComponentName name) { - resetState(); + // Application hosting service probably got updated + // Need to re-bind. + synchronized (mLock) { + if (mEnabled) { + unbindService(); + bindService(); + } + } Slog.i(TAG, "Explicit health check service binding is dead " + name); } @Override public void onNullBinding(ComponentName name) { - resetState(); - Slog.i(TAG, "Explicit health check service binding is null " + name); + // Should never happen. Service returned null from #onBind. + Slog.wtf(TAG, "Explicit health check service binding is null?? " + name); } }; - ComponentName component = getServiceComponentNameLocked(); - if (component != null) { - Intent intent = new Intent(); - intent.setComponent(component); - mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, - UserHandle.of(UserHandle.USER_SYSTEM)); - } + Slog.i(TAG, "Binding to explicit health service"); + mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, + UserHandle.of(UserHandle.USER_SYSTEM)); } } - // TODO: Differentiate between expected vs unexpected stop? - /** Callback to receive important {@link ExplicitHealthCheckController} state changes. */ - abstract static class StateCallback { - /** The controller is ready and we can request explicit health checks for packages */ - public void onStart() {} - - /** The controller is not ready and we cannot request explicit health checks for packages */ - public void onStop() {} - } - - /** Stops the explicit health check service. */ - public void stopService() { + /** Unbinds the explicit health check service. */ + private void unbindService() { synchronized (mLock) { if (mRemoteService != null) { + Slog.i(TAG, "Unbinding from explicit health service"); mContext.unbindService(mConnection); + mRemoteService = null; } } } @@ -247,19 +299,41 @@ class ExplicitHealthCheckController { return name; } - private void resetState() { + private void initState(IBinder service) { synchronized (mLock) { - mStateCallback.onStop(); - mStateCallback = null; mSupportedPackages = null; - mRemoteService = null; - mConnection = null; + mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service); + try { + mRemoteService.setCallback(new RemoteCallback(result -> { + String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE); + if (!TextUtils.isEmpty(packageName)) { + synchronized (mLock) { + if (mPassedConsumer == null) { + Slog.w(TAG, "Health check passed for package " + packageName + + "but no consumer registered."); + } else { + mPassedConsumer.accept(packageName); + } + } + } else { + Slog.w(TAG, "Empty package passed explicit health check?"); + } + })); + if (mOnConnected == null) { + Slog.w(TAG, "Health check service connected but no runnable registered."); + } else { + mOnConnected.run(); + } + } catch (RemoteException e) { + Slog.wtf(TAG, "Could not setCallback on explicit health check service"); + } } } @GuardedBy("mLock") private void enforceServiceReadyLocked() { if (mRemoteService == null) { + // TODO: Try to bind to service throw new IllegalStateException("Explicit health check service not ready"); } } diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 660109cf6114..2ba4d975a6b0 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -26,11 +26,12 @@ import android.content.pm.VersionedPackage; import android.os.Environment; import android.os.Handler; import android.os.Looper; +import android.os.RemoteException; import android.os.SystemClock; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; -import android.util.Log; import android.util.Slog; import android.util.Xml; @@ -54,10 +55,12 @@ import java.io.InputStream; import java.lang.annotation.Retention; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.function.Consumer; /** * Monitors the health of packages on the system and notifies interested observers when packages @@ -84,10 +87,10 @@ public class PackageWatchdog { private final Object mLock = new Object(); // System server context private final Context mContext; - // Handler to run package cleanup runnables - private final Handler mTimerHandler; - // Handler for processing IO and observer actions - private final Handler mWorkerHandler; + // Handler to run short running tasks + private final Handler mShortTaskHandler; + // Handler for processing IO and long running tasks + private final Handler mLongTaskHandler; // Contains (observer-name -> observer-handle) that have ever been registered from // previous boots. Observers with all packages expired are periodically pruned. // It is saved to disk on system shutdown and repouplated on startup so it survives reboots. @@ -97,6 +100,12 @@ public class PackageWatchdog { private final AtomicFile mPolicyFile; // Runnable to prune monitored packages that have expired private final Runnable mPackageCleanup; + private final ExplicitHealthCheckController mHealthCheckController; + // Flag to control whether explicit health checks are supported or not + @GuardedBy("mLock") + private boolean mIsHealthCheckEnabled = true; + @GuardedBy("mLock") + private boolean mIsPackagesReady; // Last SystemClock#uptimeMillis a package clean up was executed. // 0 if mPackageCleanup not running. private long mUptimeAtLastRescheduleMs; @@ -104,32 +113,30 @@ public class PackageWatchdog { // 0 if mPackageCleanup not running. private long mDurationAtLastReschedule; - // TODO(b/120598832): Remove redundant context param private PackageWatchdog(Context context) { - mContext = context; - mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"), - "package-watchdog.xml")); - mTimerHandler = new Handler(Looper.myLooper()); - mWorkerHandler = BackgroundThread.getHandler(); - mPackageCleanup = this::rescheduleCleanup; - loadFromFile(); + // Needs to be constructed inline + this(context, new AtomicFile( + new File(new File(Environment.getDataDirectory(), "system"), + "package-watchdog.xml")), + new Handler(Looper.myLooper()), BackgroundThread.getHandler(), + new ExplicitHealthCheckController(context)); } /** - * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers - * and creates package-watchdog.xml in an apps data directory. + * Creates a PackageWatchdog that allows injecting dependencies. */ @VisibleForTesting - PackageWatchdog(Context context, Looper looper) { + PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler, + Handler longTaskHandler, ExplicitHealthCheckController controller) { mContext = context; - mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml")); - mTimerHandler = new Handler(looper); - mWorkerHandler = mTimerHandler; + mPolicyFile = policyFile; + mShortTaskHandler = shortTaskHandler; + mLongTaskHandler = longTaskHandler; mPackageCleanup = this::rescheduleCleanup; + mHealthCheckController = controller; loadFromFile(); } - /** Creates or gets singleton instance of PackageWatchdog. */ public static PackageWatchdog getInstance(Context context) { synchronized (PackageWatchdog.class) { @@ -141,6 +148,20 @@ public class PackageWatchdog { } /** + * Called during boot to notify when packages are ready on the device so we can start + * binding. + */ + public void onPackagesReady() { + synchronized (mLock) { + mIsPackagesReady = true; + mHealthCheckController.setCallbacks(this::updateHealthChecks, + packageName -> onHealthCheckPassed(packageName)); + // Controller is disabled at creation until here where we may enable it + mHealthCheckController.setEnabled(mIsHealthCheckEnabled); + } + } + + /** * Registers {@code observer} to listen for package failures * * <p>Observers are expected to call this on boot. It does not specify any packages but @@ -163,40 +184,63 @@ public class PackageWatchdog { * Starts observing the health of the {@code packages} for {@code observer} and notifies * {@code observer} of any package failures within the monitoring duration. * - * <p>If monitoring a package with {@code withExplicitHealthCheck}, at the end of the monitoring - * duration if {@link #onExplicitHealthCheckPassed} was never called, + * <p>If monitoring a package supporting explicit health check, at the end of the monitoring + * duration if {@link #onHealthCheckPassed} was never called, * {@link PackageHealthObserver#execute} will be called as if the package failed. * * <p>If {@code observer} is already monitoring a package in {@code packageNames}, * the monitoring window of that package will be reset to {@code durationMs} and the health - * check state will be reset to a default depending on {@code withExplictHealthCheck}. + * check state will be reset to a default depending on if the package is contained in + * {@link mPackagesWithExplicitHealthCheckEnabled}. * * @throws IllegalArgumentException if {@code packageNames} is empty * or {@code durationMs} is less than 1 */ - public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames, - long durationMs, boolean withExplicitHealthCheck) { - if (packageNames.isEmpty() || durationMs < 1) { + public void startObservingHealth(PackageHealthObserver observer, List<String> packages, + long durationMs) { + if (packages.isEmpty() || durationMs < 1) { throw new IllegalArgumentException("Observation not started, no packages specified" + "or invalid duration"); } + if (!mIsPackagesReady) { + // TODO: Queue observation requests when packages are not ready + Slog.w(TAG, "Attempt to observe when packages not ready"); + return; + } + + try { + Slog.i(TAG, "Getting packages supporting explicit health check"); + mHealthCheckController.getSupportedPackages(supportedPackages -> + startObservingInner(observer, packages, durationMs, supportedPackages)); + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to fetch supported explicit health check packages"); + } + } + + private void startObservingInner(PackageHealthObserver observer, + List<String> packageNames, long durationMs, + List<String> healthCheckSupportedPackages) { + Slog.i(TAG, "Start observing packages " + packageNames + + ". Explicit health check supported packages " + healthCheckSupportedPackages); List<MonitoredPackage> packages = new ArrayList<>(); for (int i = 0; i < packageNames.size(); i++) { - // When observing packages withExplicitHealthCheck, - // MonitoredPackage#mHasExplicitHealthCheckPassed will be false initially. - packages.add(new MonitoredPackage(packageNames.get(i), durationMs, - !withExplicitHealthCheck)); + String packageName = packageNames.get(i); + boolean shouldEnableHealthCheck = healthCheckSupportedPackages.contains(packageName); + // If we should enable explicit health check for a package, + // MonitoredPackage#mHasHealthCheckPassed will be false + // until PackageWatchdog#onHealthCheckPassed + packages.add(new MonitoredPackage(packageName, durationMs, !shouldEnableHealthCheck)); } synchronized (mLock) { ObserverInternal oldObserver = mAllObservers.get(observer.getName()); if (oldObserver == null) { - Slog.d(TAG, observer.getName() + " started monitoring health of packages " - + packageNames); + Slog.d(TAG, observer.getName() + " started monitoring health " + + "of packages " + packageNames); mAllObservers.put(observer.getName(), new ObserverInternal(observer.getName(), packages)); } else { - Slog.d(TAG, observer.getName() + " added the following packages to monitor " - + packageNames); + Slog.d(TAG, observer.getName() + " added the following " + + "packages to monitor " + packageNames); oldObserver.updatePackages(packages); } } @@ -204,9 +248,97 @@ public class PackageWatchdog { // Always reschedule because we may need to expire packages // earlier than we are already scheduled for rescheduleCleanup(); + updateHealthChecks(); saveToFileAsync(); } + private void requestCheck(String packageName) { + try { + Slog.d(TAG, "Requesting explicit health check for " + packageName); + mHealthCheckController.request(packageName); + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to request explicit health check for " + packageName, e); + } + } + + private void cancelCheck(String packageName) { + try { + Slog.d(TAG, "Cancelling explicit health check for " + packageName); + mHealthCheckController.cancel(packageName); + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to cancel explicit health check for " + packageName, e); + } + } + + private void actOnDifference(Collection<String> collection1, Collection<String> collection2, + Consumer<String> action) { + Iterator<String> iterator = collection1.iterator(); + while (iterator.hasNext()) { + String packageName = iterator.next(); + if (!collection2.contains(packageName)) { + action.accept(packageName); + } + } + } + + private void updateChecksInner(List<String> supportedPackages, + List<String> previousRequestedPackages) { + boolean shouldUpdateFile = false; + + synchronized (mLock) { + Slog.i(TAG, "Updating explicit health checks. Supported packages: " + supportedPackages + + ". Requested packages: " + previousRequestedPackages); + Set<String> newRequestedPackages = new ArraySet<>(); + Iterator<ObserverInternal> oit = mAllObservers.values().iterator(); + while (oit.hasNext()) { + ObserverInternal observer = oit.next(); + Iterator<MonitoredPackage> pit = + observer.mPackages.values().iterator(); + while (pit.hasNext()) { + MonitoredPackage monitoredPackage = pit.next(); + String packageName = monitoredPackage.mName; + if (!monitoredPackage.mHasPassedHealthCheck) { + if (supportedPackages.contains(packageName)) { + newRequestedPackages.add(packageName); + } else { + shouldUpdateFile = true; + monitoredPackage.mHasPassedHealthCheck = true; + } + } + } + } + // TODO: Support ending the binding if newRequestedPackages is empty. + // Will have to re-bind when we #startObservingHealth. + + // Cancel packages no longer requested + actOnDifference(previousRequestedPackages, newRequestedPackages, p -> cancelCheck(p)); + // Request packages not yet requested + actOnDifference(newRequestedPackages, previousRequestedPackages, p -> requestCheck(p)); + } + + if (shouldUpdateFile) { + saveToFileAsync(); + } + } + + private void updateHealthChecks() { + mShortTaskHandler.post(() -> { + try { + Slog.i(TAG, "Updating explicit health checks for all available packages"); + mHealthCheckController.getSupportedPackages(supported -> { + try { + mHealthCheckController.getRequestedPackages( + requested -> updateChecksInner(supported, requested)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get requested health check packages", e); + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get supported health check package", e); + } + }); + } + /** * Unregisters {@code observer} from listening to package failure. * Additionally, this stops observing any packages that may have previously been observed @@ -250,7 +382,7 @@ public class PackageWatchdog { * <p>This method could be called frequently if there is a severe problem on the device. */ public void onPackageFailure(List<VersionedPackage> packages) { - mWorkerHandler.post(() -> { + mLongTaskHandler.post(() -> { synchronized (mLock) { if (mAllObservers.isEmpty()) { return; @@ -286,49 +418,32 @@ public class PackageWatchdog { }); } - /** - * Updates the observers monitoring {@code packageName} that explicit health check has passed. - * - * <p> This update is strictly for registered observers at the time of the call - * Observers that register after this signal will have no knowledge of prior signals and will - * effectively behave as if the explicit health check hasn't passed for {@code packageName}. - * - * <p> {@code packageName} can still be considered failed if reported by - * {@link #onPackageFailure} before the package expires. - * - * <p> Triggered by components outside the system server when they are fully functional after an - * update. - */ - public void onExplicitHealthCheckPassed(String packageName) { - Slog.i(TAG, "Health check passed for package: " + packageName); - boolean shouldUpdateFile = false; - synchronized (mLock) { - for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) { - ObserverInternal observer = mAllObservers.valueAt(observerIdx); - MonitoredPackage monitoredPackage = observer.mPackages.get(packageName); - if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) { - monitoredPackage.mHasPassedHealthCheck = true; - shouldUpdateFile = true; - } - } - } - if (shouldUpdateFile) { - saveToFileAsync(); - } - } - // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? // This currently adds about 7ms extra to shutdown thread /** Writes the package information to file during shutdown. */ public void writeNow() { if (!mAllObservers.isEmpty()) { - mWorkerHandler.removeCallbacks(this::saveToFile); + mLongTaskHandler.removeCallbacks(this::saveToFile); pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs); saveToFile(); Slog.i(TAG, "Last write to update package durations"); } } + // TODO(b/120598832): Set depending on DeviceConfig flag + /** + * Enables or disables explicit health checks. + * <p> If explicit health checks are enabled, the health check service is started. + * <p> If explicit health checks are disabled, pending explicit health check requests are + * passed and the health check service is stopped. + */ + public void setExplicitHealthCheckEnabled(boolean enabled) { + synchronized (mLock) { + mIsHealthCheckEnabled = enabled; + mHealthCheckController.setEnabled(enabled); + } + } + /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */ @Retention(SOURCE) @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE, @@ -371,6 +486,37 @@ public class PackageWatchdog { String getName(); } + /** + * Updates the observers monitoring {@code packageName} that explicit health check has passed. + * + * <p> This update is strictly for registered observers at the time of the call + * Observers that register after this signal will have no knowledge of prior signals and will + * effectively behave as if the explicit health check hasn't passed for {@code packageName}. + * + * <p> {@code packageName} can still be considered failed if reported by + * {@link #onPackageFailure} before the package expires. + * + * <p> Triggered by components outside the system server when they are fully functional after an + * update. + */ + private void onHealthCheckPassed(String packageName) { + Slog.i(TAG, "Health check passed for package: " + packageName); + boolean shouldUpdateFile = false; + synchronized (mLock) { + for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) { + ObserverInternal observer = mAllObservers.valueAt(observerIdx); + MonitoredPackage monitoredPackage = observer.mPackages.get(packageName); + if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) { + monitoredPackage.mHasPassedHealthCheck = true; + shouldUpdateFile = true; + } + } + } + if (shouldUpdateFile) { + saveToFileAsync(); + } + } + /** Reschedules handler to prune expired packages from observers. */ private void rescheduleCleanup() { synchronized (mLock) { @@ -393,8 +539,8 @@ public class PackageWatchdog { || nextDurationToScheduleMs < remainingDurationMs) { // First schedule or an earlier reschedule pruneObservers(elapsedDurationMs); - mTimerHandler.removeCallbacks(mPackageCleanup); - mTimerHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs); + mShortTaskHandler.removeCallbacks(mPackageCleanup); + mShortTaskHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs); mDurationAtLastReschedule = nextDurationToScheduleMs; mUptimeAtLastRescheduleMs = uptimeMs; } @@ -437,7 +583,7 @@ public class PackageWatchdog { List<MonitoredPackage> failedPackages = observer.updateMonitoringDurations(elapsedMs); if (!failedPackages.isEmpty()) { - onExplicitHealthCheckFailed(observer, failedPackages); + onHealthCheckFailed(observer, failedPackages); } if (observer.mPackages.isEmpty()) { Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired"); @@ -445,12 +591,13 @@ public class PackageWatchdog { } } } + updateHealthChecks(); saveToFileAsync(); } - private void onExplicitHealthCheckFailed(ObserverInternal observer, + private void onHealthCheckFailed(ObserverInternal observer, List<MonitoredPackage> failedPackages) { - mWorkerHandler.post(() -> { + mLongTaskHandler.post(() -> { synchronized (mLock) { PackageHealthObserver registeredObserver = observer.mRegisteredObserver; if (registeredObserver != null) { @@ -458,6 +605,7 @@ public class PackageWatchdog { for (int i = 0; i < failedPackages.size(); i++) { String packageName = failedPackages.get(i).mName; long versionCode = 0; + Slog.i(TAG, "Explicit health check failed for package " + packageName); try { versionCode = pm.getPackageInfo( packageName, 0 /* flags */).getLongVersionCode(); @@ -498,7 +646,7 @@ public class PackageWatchdog { } catch (FileNotFoundException e) { // Nothing to monitor } catch (IOException | NumberFormatException | XmlPullParserException e) { - Log.wtf(TAG, "Unable to read monitored packages, deleting file", e); + Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e); mPolicyFile.delete(); } finally { IoUtils.closeQuietly(infile); @@ -542,8 +690,8 @@ public class PackageWatchdog { } private void saveToFileAsync() { - mWorkerHandler.removeCallbacks(this::saveToFile); - mWorkerHandler.post(this::saveToFile); + mLongTaskHandler.removeCallbacks(this::saveToFile); + mLongTaskHandler.post(this::saveToFile); } /** diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 1751856066fb..b759dd4e8122 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -20,8 +20,10 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK import android.app.ActivityThread; import android.content.ContentResolver; +import android.content.Context; import android.database.ContentObserver; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.provider.DeviceConfig; import android.provider.DeviceConfig.OnPropertyChangedListener; @@ -38,6 +40,7 @@ import java.io.PrintWriter; * Settings constants that can modify the activity manager's behavior. */ final class ActivityManagerConstants extends ContentObserver { + private static final String TAG = "ActivityManagerConstants"; // Key names stored in the settings value. private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time"; @@ -272,6 +275,16 @@ final class ActivityManagerConstants extends ContentObserver { // memory trimming. public int CUR_TRIM_CACHED_PROCESSES; + private static final long MIN_AUTOMATIC_HEAP_DUMP_PSS_THRESHOLD_BYTES = 100 * 1024; // 100 KB + + private final boolean mSystemServerAutomaticHeapDumpEnabled; + + /** Package to report to when the memory usage exceeds the limit. */ + private final String mSystemServerAutomaticHeapDumpPackageName; + + /** Byte limit for dump heap monitoring. */ + private long mSystemServerAutomaticHeapDumpPssThresholdBytes; + private static final Uri ACTIVITY_MANAGER_CONSTANTS_URI = Settings.Global.getUriFor( Settings.Global.ACTIVITY_MANAGER_CONSTANTS); @@ -286,6 +299,9 @@ final class ActivityManagerConstants extends ContentObserver { Settings.Global.getUriFor( Settings.Global.BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST); + private static final Uri ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI = + Settings.Global.getUriFor(Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS); + private final OnPropertyChangedListener mOnDeviceConfigChangedListener = new OnPropertyChangedListener() { @Override @@ -296,9 +312,17 @@ final class ActivityManagerConstants extends ContentObserver { } }; - public ActivityManagerConstants(ActivityManagerService service, Handler handler) { + ActivityManagerConstants(Context context, ActivityManagerService service, Handler handler) { super(handler); mService = service; + mSystemServerAutomaticHeapDumpEnabled = Build.IS_DEBUGGABLE + && context.getResources().getBoolean( + com.android.internal.R.bool.config_debugEnableAutomaticSystemServerHeapDumps); + mSystemServerAutomaticHeapDumpPackageName = context.getPackageName(); + mSystemServerAutomaticHeapDumpPssThresholdBytes = Math.max( + MIN_AUTOMATIC_HEAP_DUMP_PSS_THRESHOLD_BYTES, + context.getResources().getInteger( + com.android.internal.R.integer.config_debugSystemServerPssThresholdBytes)); } public void start(ContentResolver resolver) { @@ -308,10 +332,17 @@ final class ActivityManagerConstants extends ContentObserver { mResolver.registerContentObserver(BACKGROUND_ACTIVITY_STARTS_ENABLED_URI, false, this); mResolver.registerContentObserver(BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI, false, this); + if (mSystemServerAutomaticHeapDumpEnabled) { + mResolver.registerContentObserver(ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI, + false, this); + } updateConstants(); updateActivityStartsLoggingEnabled(); updateBackgroundActivityStartsEnabled(); updateBackgroundActivityStartsPackageNamesWhitelist(); + if (mSystemServerAutomaticHeapDumpEnabled) { + updateEnableAutomaticSystemServerHeapDumps(); + } DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, ActivityThread.currentApplication().getMainExecutor(), mOnDeviceConfigChangedListener); @@ -343,6 +374,8 @@ final class ActivityManagerConstants extends ContentObserver { updateBackgroundActivityStartsEnabled(); } else if (BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI.equals(uri)) { updateBackgroundActivityStartsPackageNamesWhitelist(); + } else if (ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI.equals(uri)) { + updateEnableAutomaticSystemServerHeapDumps(); } } @@ -450,6 +483,24 @@ final class ActivityManagerConstants extends ContentObserver { mPackageNamesWhitelistedForBgActivityStarts = newSet; } + private void updateEnableAutomaticSystemServerHeapDumps() { + if (!mSystemServerAutomaticHeapDumpEnabled) { + Slog.wtf(TAG, + "updateEnableAutomaticSystemServerHeapDumps called when leak detection " + + "disabled"); + return; + } + // Monitoring is on by default, so if the setting hasn't been set by the user, + // monitoring should be on. + final boolean enabled = Settings.Global.getInt(mResolver, + Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS, 1) == 1; + + // Setting the threshold to 0 stops the checking. + final long threshold = enabled ? mSystemServerAutomaticHeapDumpPssThresholdBytes : 0; + mService.setDumpHeapDebugLimit(null, 0, threshold, + mSystemServerAutomaticHeapDumpPackageName); + } + private void updateMaxCachedProcesses() { String maxCachedProcessesFlag = DeviceConfig.getProperty( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MAX_CACHED_PROCESSES); @@ -460,7 +511,7 @@ final class ActivityManagerConstants extends ContentObserver { : mOverrideMaxCachedProcesses; } catch (NumberFormatException e) { // Bad flag value from Phenotype, revert to default. - Slog.e("ActivityManagerConstants", + Slog.e(TAG, "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e); CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index d4bff007462a..3eb7c0374a01 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2135,6 +2135,8 @@ public class ActivityManagerService extends IActivityManager.Stub mService.mServices.systemServicesReady(); } else if (phase == PHASE_ACTIVITY_MANAGER_READY) { mService.startBroadcastObservers(); + } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + mService.mPackageWatchdog.onPackagesReady(); } } @@ -2288,7 +2290,8 @@ public class ActivityManagerService extends IActivityManager.Stub mBatteryStatsService = null; mHandler = hasHandlerThread ? new MainHandler(handlerThread.getLooper()) : null; mHandlerThread = handlerThread; - mConstants = hasHandlerThread ? new ActivityManagerConstants(this, mHandler) : null; + mConstants = hasHandlerThread + ? new ActivityManagerConstants(mContext, this, mHandler) : null; final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */); mProcessList.init(this, activeUids); mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); @@ -2336,7 +2339,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcStartHandlerThread.start(); mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper()); - mConstants = new ActivityManagerConstants(this, mHandler); + mConstants = new ActivityManagerConstants(mContext, this, mHandler); final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */); mProcessList.init(this, activeUids); mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index cc901821030a..8a462dabd14c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -16,7 +16,6 @@ package com.android.server.am; -import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityTaskManager.RESIZE_MODE_USER; @@ -306,9 +305,7 @@ final class ActivityManagerShellCommand extends ShellCommand { mSamplingInterval = 0; mAutoStop = false; mStreaming = false; - mUserId = mInternal.mUserController.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), defUser, false, ALLOW_FULL_ONLY, - "ActivityManagerShellCommand", null); + mUserId = defUser; mDisplayId = INVALID_DISPLAY; mWindowingMode = WINDOWING_MODE_UNDEFINED; mActivityType = ACTIVITY_TYPE_UNDEFINED; diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index c385991e1c7b..fe762c06458a 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -39,12 +39,16 @@ import android.hardware.face.FaceManager; import android.hardware.face.IFaceService; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; +import android.os.Build; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; +import android.service.restricted_image.RestrictedImagesDumpProto; +import android.service.restricted_image.RestrictedImageProto; +import android.service.restricted_image.RestrictedImageSetProto; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -281,7 +285,9 @@ public class FaceService extends BiometricServiceBase { final long ident = Binder.clearCallingIdentity(); try { - if (args.length > 0 && "--proto".equals(args[0])) { + if (args.length == 1 && "--restricted_image".equals(args[0])) { + dumpRestrictedImage(fd); + } else if (args.length > 0 && "--proto".equals(args[0])) { dumpProto(fd); } else { dumpInternal(pw); @@ -1063,4 +1069,74 @@ public class FaceService extends BiometricServiceBase { mPerformanceMap.clear(); mCryptoPerformanceMap.clear(); } + + private void dumpRestrictedImage(FileDescriptor fd) { + // WARNING: CDD restricts image data from leaving TEE unencrypted on + // production devices: + // [C-1-10] MUST not allow unencrypted access to identifiable biometric + // data or any data derived from it (such as embeddings) to the + // Application Processor outside the context of the TEE. + // As such, this API should only be enabled for testing purposes on + // engineering and userdebug builds. All modules in the software stack + // MUST enforce final build products do NOT have this functionality. + // Additionally, the following check MUST NOT be removed. + if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { + return; + } + + final ProtoOutputStream proto = new ProtoOutputStream(fd); + + final long setToken = proto.start(RestrictedImagesDumpProto.SETS); + + // Name of the service + proto.write(RestrictedImageSetProto.CATEGORY, "face"); + + // Individual images + for (int i = 0; i < 5; i++) { + final long imageToken = proto.start(RestrictedImageSetProto.IMAGES); + proto.write(RestrictedImageProto.MIME_TYPE, "image/png"); + proto.write(RestrictedImageProto.IMAGE_DATA, new byte[] { + // png image data + -119, 80, 78, 71, 13, 10, 26, 10, + 0, 0, 0, 13, 73, 72, 68, 82, + 0, 0, 0, 100, 0, 0, 0, 100, + 1, 3, 0, 0, 0, 74, 44, 7, + 23, 0, 0, 0, 4, 103, 65, 77, + 65, 0, 0, -79, -113, 11, -4, 97, + 5, 0, 0, 0, 1, 115, 82, 71, + 66, 0, -82, -50, 28, -23, 0, 0, + 0, 6, 80, 76, 84, 69, -1, -1, + -1, 0, 0, 0, 85, -62, -45, 126, + 0, 0, 0, -115, 73, 68, 65, 84, + 56, -53, -19, -46, -79, 17, -128, 32, + 12, 5, -48, 120, 22, -106, -116, -32, + 40, -84, 101, -121, -93, 57, 10, 35, + 88, 82, 112, 126, 3, -60, 104, 6, + -112, 70, 127, -59, -69, -53, 29, 33, + -127, -24, 79, -49, -52, -15, 41, 36, + 34, -105, 85, 124, -14, 88, 27, 6, + 28, 68, 1, 82, 62, 22, -95, -108, + 55, -95, 40, -9, -110, -12, 98, -107, + 76, -41, -105, -62, -50, 111, -60, 46, + -14, -4, 24, -89, 42, -103, 16, 63, + -72, -11, -15, 48, -62, 102, -44, 102, + -73, -56, 56, -21, -128, 92, -70, -124, + 117, -46, -67, -77, 82, 80, 121, -44, + -56, 116, 93, -45, -90, -5, -29, -24, + -83, -75, 52, -34, 55, -22, 102, -21, + -105, -124, -23, 71, 87, -7, -25, -59, + -100, -73, -92, -122, -7, -109, -49, -80, + -89, 0, 0, 0, 0, 73, 69, 78, + 68, -82, 66, 96, -126 + }); + // proto.write(RestrictedImageProto.METADATA, flattened_protobuf); + proto.end(imageToken); + } + + // Face service metadata + // proto.write(RestrictedImageSetProto.METADATA, flattened_protobuf); + + proto.end(setToken); + proto.flush(); + } } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index e3fdbe84a1d3..cfa91314f490 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -30,6 +30,7 @@ import android.net.NetworkState; import android.os.Handler; import android.os.INetworkManagementService; import android.os.Messenger; +import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; @@ -121,7 +122,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // This Network object is always valid. public final Network network; public LinkProperties linkProperties; - // This should only be modified via ConnectivityService.updateCapabilities(). + // This should only be modified by ConnectivityService, via setNetworkCapabilities(). + // TODO: make this private with a getter. public NetworkCapabilities networkCapabilities; public final NetworkMisc networkMisc; // Indicates if netd has been told to create this Network. From this point on the appropriate @@ -279,6 +281,25 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { mNetworkMonitor = networkMonitor; } + /** + * Set the NetworkCapabilities on this NetworkAgentInfo. Also attempts to notify NetworkMonitor + * of the new capabilities, if NetworkMonitor has been created. + * + * <p>If {@link NetworkMonitor#notifyNetworkCapabilitiesChanged(NetworkCapabilities)} fails, + * the exception is logged but not reported to callers. + */ + public void setNetworkCapabilities(NetworkCapabilities nc) { + networkCapabilities = nc; + final INetworkMonitor nm = mNetworkMonitor; + if (nm != null) { + try { + nm.notifyNetworkCapabilitiesChanged(nc); + } catch (RemoteException e) { + Log.e(TAG, "Error notifying NetworkMonitor of updated NetworkCapabilities", e); + } + } + } + public ConnectivityService connService() { return mConnService; } diff --git a/services/core/java/com/android/server/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java index 5c69c1dd3930..9989c1a511ad 100644 --- a/services/core/java/com/android/server/incident/IncidentCompanionService.java +++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java @@ -23,7 +23,10 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.os.Binder; +import android.os.Build; +import android.os.IBinder; import android.os.IIncidentAuthListener; import android.os.IIncidentCompanion; import android.os.IIncidentManager; @@ -49,6 +52,12 @@ public class IncidentCompanionService extends SystemService { static final String TAG = "IncidentCompanionService"; /** + * Dump argument for proxying restricted image dumps to the services + * listed in the config. + */ + private static String[] RESTRICTED_IMAGE_DUMP_ARGS = new String[] { "--restricted_image" }; + + /** * The two permissions, for sendBroadcastAsUserMultiplePermissions. */ private static final String[] DUMP_AND_USAGE_STATS_PERMISSIONS = new String[] { @@ -260,7 +269,42 @@ public class IncidentCompanionService extends SystemService { if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) { return; } - mPendingReports.dump(fd, writer, args); + + if (args.length == 1 && "--restricted_image".equals(args[0])) { + // Does NOT clearCallingIdentity + dumpRestrictedImages(fd); + } else { + // Regular dump + mPendingReports.dump(fd, writer, args); + } + } + + /** + * Proxy for the restricted images section. + */ + private void dumpRestrictedImages(FileDescriptor fd) { + // Only supported on eng or userdebug. + if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { + return; + } + + final Resources res = getContext().getResources(); + final String[] services = res.getStringArray( + com.android.internal.R.array.config_restrictedImagesServices); + final int servicesCount = services.length; + for (int i = 0; i < servicesCount; i++) { + final String name = services[i]; + Log.d(TAG, "Looking up service " + name); + final IBinder service = ServiceManager.getService(name); + if (service != null) { + Log.d(TAG, "Calling dump on service: " + name); + try { + service.dump(fd, RESTRICTED_IMAGE_DUMP_ARGS); + } catch (RemoteException ex) { + Log.w(TAG, "dump --restricted_image of " + name + " threw", ex); + } + } + } } /** diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 622c49e67967..5abc73eb255a 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1809,6 +1809,10 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. + private void onPointerDownOutsideFocus(IBinder touchedToken) { + } + + // Native callback. private int getVirtualKeyQuietTimeMillis() { return mContext.getResources().getInteger( com.android.internal.R.integer.config_virtualKeyQuietTimeMillis); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 75b62cb349af..af58b195a491 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -1335,7 +1335,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { case TYPE_WARNING: { title = res.getText(R.string.data_usage_warning_title); body = res.getString(R.string.data_usage_warning_body, - Formatter.formatFileSize(mContext, totalBytes)); + Formatter.formatFileSize(mContext, totalBytes, Formatter.FLAG_IEC_UNITS)); builder.setSmallIcon(R.drawable.stat_notify_error); @@ -1383,7 +1383,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } final long overBytes = totalBytes - policy.limitBytes; body = res.getString(R.string.data_usage_limit_snoozed_body, - Formatter.formatFileSize(mContext, overBytes)); + Formatter.formatFileSize(mContext, overBytes, Formatter.FLAG_IEC_UNITS)); builder.setOngoing(true); builder.setSmallIcon(R.drawable.stat_notify_error); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 4cc08d88bd50..4ed24ec7971b 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1291,13 +1291,11 @@ public final class NotificationRecord { lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_INITIAL, stats.naturalImportance); } - // Log Assistant override if it was itself overridden by System. Since System can't be - // overridden, it never needs logging. - if (mImportanceExplanationCode == MetricsEvent.IMPORTANCE_EXPLANATION_SYSTEM - && mAssistantImportance != IMPORTANCE_UNSPECIFIED) { - lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST, + } + // Log Assistant override if present, whether or not importance calculation is complete. + if (mAssistantImportance != IMPORTANCE_UNSPECIFIED) { + lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST, mAssistantImportance); - } } return lm; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index ce3c452044f8..e4cb283fe864 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -360,7 +360,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final long age = System.currentTimeMillis() - session.createdMillis; final long timeSinceUpdate = - System.currentTimeMillis() - session.updatedMillis; + System.currentTimeMillis() - session.getUpdatedMillis(); final boolean valid; if (session.isStaged()) { if (timeSinceUpdate >= MAX_TIME_SINCE_UPDATE_MILLIS @@ -498,10 +498,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } - if (callingUid == Process.SYSTEM_UID) { + if (Build.IS_DEBUGGABLE || isDowngradeAllowedForCaller(callingUid)) { params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE; } else { params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE; + params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE; } boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0; @@ -621,6 +622,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements return sessionId; } + private boolean isDowngradeAllowedForCaller(int callingUid) { + return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID + || callingUid == Process.SHELL_UID; + } + @Override public void updateSessionAppIcon(int sessionId, Bitmap appIcon) { synchronized (mSessions) { @@ -813,7 +819,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, statusReceiver, versionedPackage.getPackageName(), - !canSilentlyInstallPackage, userId); + canSilentlyInstallPackage, userId); if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES) == PackageManager.PERMISSION_GRANTED) { // Sweet, call straight through! diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 5d539a4b1dda..e45a993b99f5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -202,7 +202,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** Timestamp of the last time this session changed state */ @GuardedBy("mLock") - long updatedMillis; + private long updatedMillis; /** Uid of the creator of this session. */ private final int mOriginalInstallerUid; @@ -1833,6 +1833,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * @return the timestamp of when this session last changed state + */ + public long getUpdatedMillis() { + synchronized (mLock) { + return updatedMillis; + } + } + String getInstallerPackageName() { synchronized (mLock) { return mInstallerPackageName; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 3cab9e553e8e..098225f9e820 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3502,7 +3502,7 @@ public class PackageManagerService extends IPackageManager.Stub private @NonNull String getRequiredInstallerLPr() { final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE); intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); + intent.setDataAndType(Uri.parse("content://com.example/foo.apk"), PACKAGE_MIME_TYPE); final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 9348806c0e59..d8f07feb1ddb 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -166,8 +166,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve * This may cause {@code packages} to be rolled back if they crash too freqeuntly. */ public void startObservingHealth(List<String> packages, long durationMs) { - PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs, - false /* withExplicitHealthCheck */); + PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs); } /** Verifies the rollback state after a reboot. */ diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index c2a4339eceda..d52ba169768f 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2671,7 +2671,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.connection.mReply = null; } try { - wallpaper.connection.mService.detach(); + // It can be null if user switching happens before service connection. + if (wallpaper.connection.mService != null) { + wallpaper.connection.mService.detach(); + } } catch (RemoteException e) { Slog.w(TAG, "Failed detaching wallpaper service ", e); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a93bdbaabdc7..91ec4a083ed9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2730,30 +2730,40 @@ final class ActivityRecord extends ConfigurationContainer { final int appHeight = resolvedAppBounds.height(); final int parentAppWidth = parentAppBounds.width(); final int parentAppHeight = parentAppBounds.height(); + if (parentAppWidth == appWidth && parentAppHeight == appHeight) { + // Matched the parent bounds. + return false; + } + if (parentAppWidth > appWidth && parentAppHeight > appHeight) { + // Both sides are smaller than the parent. + return true; + } if (parentAppWidth < appWidth || parentAppHeight < appHeight) { // One side is larger than the parent. return true; } - if (info.hasFixedAspectRatio()) { + // The rest of the condition is that only one side is smaller than the parent, but it still + // needs to exclude the cases where the size is limited by the fixed aspect ratio. + if (info.maxAspectRatio > 0) { final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) / Math.min(appWidth, appHeight); + if (aspectRatio >= info.maxAspectRatio) { + // The current size has reached the max aspect ratio. + return false; + } + } + if (info.minAspectRatio > 0) { + // The activity should have at least the min aspect ratio, so this checks if the parent + // still has available space to provide larger aspect ratio. final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight)) / Math.min(parentAppWidth, parentAppHeight); - // Check if the parent still has available space in long side. - if (aspectRatio < parentAspectRatio - && (aspectRatio < info.maxAspectRatio || info.minAspectRatio > 0)) { - return true; + if (parentAspectRatio <= info.minAspectRatio) { + // The long side has reached the parent. + return false; } } - - final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); - // If the width or height is the same as parent, it is already the best fit of the override - // bounds, therefore this condition is considered as not size compatibility mode. Here uses - // right and bottom as width and height of parent because the bounds may contain decor - // insets which has been accounted in override bounds. See {@link #computeBounds}. - return parentAppBounds.right != resolvedBounds.width() - && parentAppBounds.bottom != resolvedBounds.height(); + return true; } /** diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java index 3bbe28d429c5..90bb494232c7 100644 --- a/services/core/java/com/android/server/wm/BarController.java +++ b/services/core/java/com/android/server/wm/BarController.java @@ -51,6 +51,7 @@ public class BarController { private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1; protected final String mTag; + protected final int mDisplayId; private final int mTransientFlag; private final int mUnhideFlag; private final int mTranslucentFlag; @@ -74,9 +75,10 @@ public class BarController { private OnBarVisibilityChangedListener mVisibilityChangeListener; - BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag, + BarController(String tag, int displayId, int transientFlag, int unhideFlag, int translucentFlag, int statusBarManagerId, int translucentWmFlag, int transparentFlag) { mTag = "BarController." + tag; + mDisplayId = displayId; mTransientFlag = transientFlag; mUnhideFlag = unhideFlag; mTranslucentFlag = translucentFlag; @@ -230,7 +232,7 @@ public class BarController { public void run() { StatusBarManagerInternal statusbar = getStatusBarInternal(); if (statusbar != null) { - statusbar.setWindowState(mWin.getDisplayId(), mStatusBarManagerId, state); + statusbar.setWindowState(mDisplayId, mStatusBarManagerId, state); } } }); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 1888e9474267..bbb857f4c24c 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -26,6 +26,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.UI_MODE_TYPE_CAR; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.view.InsetsState.TYPE_TOP_BAR; +import static android.view.InsetsState.TYPE_TOP_GESTURES; +import static android.view.InsetsState.TYPE_TOP_TAPPABLE_ELEMENT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; @@ -40,6 +42,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; @@ -152,6 +155,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.util.ScreenshotHelper; +import com.android.internal.util.function.TriConsumer; import com.android.internal.widget.PointerLocationView; import com.android.server.LocalServices; import com.android.server.UiThread; @@ -215,6 +219,12 @@ public class DisplayPolicy { private final Object mServiceAcquireLock = new Object(); private StatusBarManagerInternal mStatusBarManagerInternal; + @Px + private int mBottomGestureAdditionalInset; + @Px + private int mSideGestureInset; + private boolean mNavigationBarLetsThroughTaps; + private StatusBarManagerInternal getStatusBarManagerInternal() { synchronized (mServiceAcquireLock) { if (mStatusBarManagerInternal == null) { @@ -261,15 +271,9 @@ public class DisplayPolicy { /** Cached value of {@link ScreenShapeHelper#getWindowOutsetBottomPx} */ @Px private int mWindowOutsetBottom; - private final StatusBarController mStatusBarController = new StatusBarController(); + private final StatusBarController mStatusBarController; - private final BarController mNavigationBarController = new BarController("NavigationBar", - View.NAVIGATION_BAR_TRANSIENT, - View.NAVIGATION_BAR_UNHIDE, - View.NAVIGATION_BAR_TRANSLUCENT, - StatusBarManager.WINDOW_NAVIGATION_BAR, - FLAG_TRANSLUCENT_NAVIGATION, - View.NAVIGATION_BAR_TRANSPARENT); + private final BarController mNavigationBarController; private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener = new BarController.OnBarVisibilityChangedListener() { @@ -416,6 +420,17 @@ public class DisplayPolicy { mDisplayContent = displayContent; mLock = service.getWindowManagerLock(); + final int displayId = displayContent.getDisplayId(); + mStatusBarController = new StatusBarController(displayId); + mNavigationBarController = new BarController("NavigationBar", + displayId, + View.NAVIGATION_BAR_TRANSIENT, + View.NAVIGATION_BAR_UNHIDE, + View.NAVIGATION_BAR_TRANSLUCENT, + StatusBarManager.WINDOW_NAVIGATION_BAR, + FLAG_TRANSLUCENT_NAVIGATION, + View.NAVIGATION_BAR_TRANSPARENT); + final Resources r = mContext.getResources(); mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer); mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer); @@ -527,7 +542,6 @@ public class DisplayPolicy { if (mWindowSleepToken != null) { return; } - final int displayId = displayContent.getDisplayId(); mWindowSleepToken = service.mAtmInternal.acquireSleepToken( "WindowSleepTokenOnDisplay" + displayId, displayId); }; @@ -857,11 +871,14 @@ public class DisplayPolicy { if (mDisplayContent.isDefaultDisplay) { mService.mPolicy.setKeyguardCandidateLw(win); } - mDisplayContent.setInsetProvider(TYPE_TOP_BAR, win, + final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider = (displayFrames, windowState, rect) -> { rect.top = 0; rect.bottom = getStatusBarHeight(displayFrames); - }); + }; + mDisplayContent.setInsetProvider(TYPE_TOP_BAR, win, frameProvider); + mDisplayContent.setInsetProvider(TYPE_TOP_GESTURES, win, frameProvider); + mDisplayContent.setInsetProvider(TYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider); break; case TYPE_NAVIGATION_BAR: mContext.enforceCallingOrSelfPermission( @@ -878,6 +895,31 @@ public class DisplayPolicy { mNavBarVisibilityListener, true); mDisplayContent.setInsetProvider(InsetsState.TYPE_NAVIGATION_BAR, win, null /* frameProvider */); + mDisplayContent.setInsetProvider(InsetsState.TYPE_BOTTOM_GESTURES, win, + (displayFrames, windowState, inOutFrame) -> { + inOutFrame.top -= mBottomGestureAdditionalInset; + }); + mDisplayContent.setInsetProvider(InsetsState.TYPE_LEFT_GESTURES, win, + (displayFrames, windowState, inOutFrame) -> { + inOutFrame.left = 0; + inOutFrame.top = 0; + inOutFrame.bottom = displayFrames.mDisplayHeight; + inOutFrame.right = displayFrames.mUnrestricted.left + mSideGestureInset; + }); + mDisplayContent.setInsetProvider(InsetsState.TYPE_RIGHT_GESTURES, win, + (displayFrames, windowState, inOutFrame) -> { + inOutFrame.left = displayFrames.mUnrestricted.right - mSideGestureInset; + inOutFrame.top = 0; + inOutFrame.bottom = displayFrames.mDisplayHeight; + inOutFrame.right = displayFrames.mDisplayWidth; + }); + mDisplayContent.setInsetProvider(InsetsState.TYPE_BOTTOM_TAPPABLE_ELEMENT, win, + (displayFrames, windowState, inOutFrame) -> { + if ((windowState.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0 + || mNavigationBarLetsThroughTaps) { + inOutFrame.setEmpty(); + } + }); if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar); break; case TYPE_NAVIGATION_BAR_PANEL: @@ -2544,6 +2586,7 @@ public class DisplayPolicy { */ public void onOverlayChangedLw() { onConfigurationChanged(); + mSystemGestures.onConfigurationChanged(); } /** @@ -2606,11 +2649,20 @@ public class DisplayPolicy { } mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode); + mSideGestureInset = res.getDimensionPixelSize(R.dimen.config_backGestureInset); + mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough); // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed mExperiments.onConfigurationChanged(uiContext); // EXPERIMENT END + // EXPERIMENT: TODO(b/113952590): Replace with real code after experiment. + // This should calculate how much above the frame we accept gestures. Currently, + // we extend the frame to capture the gestures, so this is 0. + mBottomGestureAdditionalInset = mExperiments.getNavigationBarFrameHeight() + - mExperiments.getNavigationBarFrameHeight(); + // EXPERIMENT END + updateConfigurationAndScreenSizeDependentBehaviors(); mWindowOutsetBottom = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources()); } diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java index 6db606d2a30b..f4260d32a77d 100644 --- a/services/core/java/com/android/server/wm/StatusBarController.java +++ b/services/core/java/com/android/server/wm/StatusBarController.java @@ -36,22 +36,22 @@ public class StatusBarController extends BarController { private Runnable mAppTransitionPending = () -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); - if (statusBar != null && mWin != null) { - statusBar.appTransitionPending(mWin.getDisplayId()); + if (statusBar != null) { + statusBar.appTransitionPending(mDisplayId); } }; private Runnable mAppTransitionCancelled = () -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); - if (statusBar != null && mWin != null) { - statusBar.appTransitionCancelled(mWin.getDisplayId()); + if (statusBar != null) { + statusBar.appTransitionCancelled(mDisplayId); } }; private Runnable mAppTransitionFinished = () -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); - if (statusBar != null && mWin != null) { - statusBar.appTransitionFinished(mWin.getDisplayId()); + if (statusBar != null) { + statusBar.appTransitionFinished(mDisplayId); } }; @@ -65,8 +65,8 @@ public class StatusBarController extends BarController { long statusBarAnimationStartTime, long statusBarAnimationDuration) { mHandler.post(() -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); - if (statusBar != null && mWin != null) { - statusBar.appTransitionStarting(mWin.getDisplayId(), + if (statusBar != null) { + statusBar.appTransitionStarting(mDisplayId, statusBarAnimationStartTime, statusBarAnimationDuration); } }); @@ -84,8 +84,9 @@ public class StatusBarController extends BarController { } }; - StatusBarController() { + StatusBarController(int displayId) { super("StatusBar", + displayId, View.STATUS_BAR_TRANSIENT, View.STATUS_BAR_UNHIDE, View.STATUS_BAR_TRANSLUCENT, diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java index bd4e542033c5..35afaedb0b43 100644 --- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java +++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java @@ -17,9 +17,13 @@ package com.android.server.wm; import android.content.Context; +import android.graphics.Rect; +import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.SystemClock; import android.util.Slog; +import android.view.Display; +import android.view.DisplayCutout; import android.view.GestureDetector; import android.view.InputDevice; import android.view.MotionEvent; @@ -46,8 +50,9 @@ class SystemGesturesPointerEventListener implements PointerEventListener { private final Context mContext; private final Handler mHandler; - private final int mSwipeStartThreshold; - private final int mSwipeDistanceThreshold; + private int mDisplayCutoutTouchableRegionSize; + private int mSwipeStartThreshold; + private int mSwipeDistanceThreshold; private final Callbacks mCallbacks; private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS]; private final float[] mDownX = new float[MAX_TRACKED_POINTERS]; @@ -65,14 +70,33 @@ class SystemGesturesPointerEventListener implements PointerEventListener { private long mLastFlingTime; SystemGesturesPointerEventListener(Context context, Handler handler, Callbacks callbacks) { - mContext = context; + mContext = checkNull("context", context); mHandler = handler; mCallbacks = checkNull("callbacks", callbacks); - mSwipeStartThreshold = checkNull("context", context).getResources() + + onConfigurationChanged(); + } + + void onConfigurationChanged() { + mSwipeStartThreshold = mContext.getResources() .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + + final Display display = DisplayManagerGlobal.getInstance() + .getRealDisplay(Display.DEFAULT_DISPLAY); + final DisplayCutout displayCutout = display.getCutout(); + if (displayCutout != null) { + final Rect bounds = displayCutout.getBoundingRectTop(); + if (!bounds.isEmpty()) { + // Expand swipe start threshold such that we can catch touches that just start below + // the notch area + mDisplayCutoutTouchableRegionSize = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.display_cutout_touchable_region_size); + mSwipeStartThreshold += mDisplayCutoutTouchableRegionSize; + } + } mSwipeDistanceThreshold = mSwipeStartThreshold; if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold - + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold); + + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold); } private static <T> T checkNull(String name, T arg) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 3d84bd40fb5a..3d6c8684e6d8 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -96,6 +96,7 @@ static struct { jmethodID interceptKeyBeforeDispatching; jmethodID dispatchUnhandledKey; jmethodID checkInjectEventsPermission; + jmethodID onPointerDownOutsideFocus; jmethodID getVirtualKeyQuietTimeMillis; jmethodID getExcludedDeviceNames; jmethodID getInputPortAssociations; @@ -259,6 +260,7 @@ public: virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType); virtual bool checkInjectEventsPermissionNonReentrant( int32_t injectorPid, int32_t injectorUid); + virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken); /* --- PointerControllerPolicyInterface implementation --- */ @@ -1205,6 +1207,15 @@ bool NativeInputManager::checkInjectEventsPermissionNonReentrant( return result; } +void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) { + ATRACE_CALL(); + JNIEnv* env = jniEnv(); + + jobject touchedTokenObj = javaObjectForIBinder(env, touchedToken); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDownOutsideFocus, touchedTokenObj); + checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus"); +} + void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1809,6 +1820,9 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz, "checkInjectEventsPermission", "(II)Z"); + GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz, + "onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V"); + GET_METHOD_ID(gServiceClassInfo.getVirtualKeyQuietTimeMillis, clazz, "getVirtualKeyQuietTimeMillis", "()I"); diff --git a/services/net/java/android/net/INetworkMonitor.aidl b/services/net/java/android/net/INetworkMonitor.aidl index 1b0e1d788ff3..3ed4640525c6 100644 --- a/services/net/java/android/net/INetworkMonitor.aidl +++ b/services/net/java/android/net/INetworkMonitor.aidl @@ -15,6 +15,8 @@ */ package android.net; +import android.net.LinkProperties; +import android.net.NetworkCapabilities; import android.net.PrivateDnsConfigParcel; /** @hide */ @@ -46,8 +48,8 @@ oneway interface INetworkMonitor { void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config); void notifyDnsResponse(int returnCode); void notifySystemReady(); - void notifyNetworkConnected(); + void notifyNetworkConnected(in LinkProperties lp, in NetworkCapabilities nc); void notifyNetworkDisconnected(); - void notifyLinkPropertiesChanged(); - void notifyNetworkCapabilitiesChanged(); + void notifyLinkPropertiesChanged(in LinkProperties lp); + void notifyNetworkCapabilitiesChanged(in NetworkCapabilities nc); }
\ No newline at end of file diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java index 6a9eae00e3ff..e76976991797 100644 --- a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java +++ b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java @@ -60,6 +60,13 @@ public class NetworkAttributes { public final Inet4Address assignedV4Address; private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f; + // The lease expiry timestamp of v4 address allocated from DHCP server, in milliseconds. + @Nullable + public final Long assignedV4AddressExpiry; + // lease expiry doesn't imply any correlation between "the same lease expiry value" and "the + // same L3 network". + private static final float WEIGHT_ASSIGNEDV4ADDREXPIRY = 0.0f; + // Optionally supplied by the client if it has an opinion on L3 network. For example, this // could be a hash of the SSID + security type on WiFi. @Nullable @@ -81,6 +88,7 @@ public class NetworkAttributes { /** @hide */ @VisibleForTesting public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR + + WEIGHT_ASSIGNEDV4ADDREXPIRY + WEIGHT_GROUPHINT + WEIGHT_DNSADDRESSES + WEIGHT_MTU; @@ -89,11 +97,16 @@ public class NetworkAttributes { @VisibleForTesting public NetworkAttributes( @Nullable final Inet4Address assignedV4Address, + @Nullable final Long assignedV4AddressExpiry, @Nullable final String groupHint, @Nullable final List<InetAddress> dnsAddresses, @Nullable final Integer mtu) { if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative"); + if (assignedV4AddressExpiry != null && assignedV4AddressExpiry <= 0) { + throw new IllegalArgumentException("lease expiry can't be negative or zero"); + } this.assignedV4Address = assignedV4Address; + this.assignedV4AddressExpiry = assignedV4AddressExpiry; this.groupHint = groupHint; this.dnsAddresses = null == dnsAddresses ? null : Collections.unmodifiableList(new ArrayList<>(dnsAddresses)); @@ -105,6 +118,8 @@ public class NetworkAttributes { // The call to the other constructor must be the first statement of this constructor, // so everything has to be inline this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address), + parcelable.assignedV4AddressExpiry > 0 + ? parcelable.assignedV4AddressExpiry : null, parcelable.groupHint, blobArrayToInetAddressList(parcelable.dnsAddresses), parcelable.mtu >= 0 ? parcelable.mtu : null); @@ -150,6 +165,8 @@ public class NetworkAttributes { final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable(); parcelable.assignedV4Address = (null == assignedV4Address) ? null : assignedV4Address.getAddress(); + parcelable.assignedV4AddressExpiry = + (null == assignedV4AddressExpiry) ? 0 : assignedV4AddressExpiry; parcelable.groupHint = groupHint; parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses); parcelable.mtu = (null == mtu) ? -1 : mtu; @@ -168,6 +185,8 @@ public class NetworkAttributes { public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) { final float samenessScore = samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address) + + samenessContribution(WEIGHT_ASSIGNEDV4ADDREXPIRY, assignedV4AddressExpiry, + o.assignedV4AddressExpiry) + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint) + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses) + samenessContribution(WEIGHT_MTU, mtu, o.mtu); @@ -189,6 +208,8 @@ public class NetworkAttributes { @Nullable private Inet4Address mAssignedAddress; @Nullable + private Long mAssignedAddressExpiry; + @Nullable private String mGroupHint; @Nullable private List<InetAddress> mDnsAddresses; @@ -206,6 +227,20 @@ public class NetworkAttributes { } /** + * Set the lease expiry timestamp of assigned v4 address. + * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address. + * @return This builder. + */ + public Builder setAssignedV4AddressExpiry( + @Nullable final Long assignedV4AddressExpiry) { + if (null != assignedV4AddressExpiry && assignedV4AddressExpiry <= 0) { + throw new IllegalArgumentException("lease expiry can't be negative or zero"); + } + mAssignedAddressExpiry = assignedV4AddressExpiry; + return this; + } + + /** * Set the group hint. * @param groupHint The group hint. * @return This builder. @@ -248,14 +283,15 @@ public class NetworkAttributes { * @return The built NetworkAttributes object. */ public NetworkAttributes build() { - return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu); + return new NetworkAttributes(mAssignedAddress, mAssignedAddressExpiry, + mGroupHint, mDnsAddresses, mMtu); } } /** @hide */ public boolean isEmpty() { - return (null == assignedV4Address) && (null == groupHint) - && (null == dnsAddresses) && (null == mtu); + return (null == assignedV4Address) && (null == assignedV4AddressExpiry) + && (null == groupHint) && (null == dnsAddresses) && (null == mtu); } @Override @@ -263,6 +299,7 @@ public class NetworkAttributes { if (!(o instanceof NetworkAttributes)) return false; final NetworkAttributes other = (NetworkAttributes) o; return Objects.equals(assignedV4Address, other.assignedV4Address) + && Objects.equals(assignedV4AddressExpiry, other.assignedV4AddressExpiry) && Objects.equals(groupHint, other.groupHint) && Objects.equals(dnsAddresses, other.dnsAddresses) && Objects.equals(mtu, other.mtu); @@ -270,7 +307,8 @@ public class NetworkAttributes { @Override public int hashCode() { - return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu); + return Objects.hash(assignedV4Address, assignedV4AddressExpiry, + groupHint, dnsAddresses, mtu); } /** Pretty print */ @@ -286,6 +324,13 @@ public class NetworkAttributes { nullFields.add("assignedV4Addr"); } + if (null != assignedV4AddressExpiry) { + resultJoiner.add("assignedV4AddressExpiry :"); + resultJoiner.add(assignedV4AddressExpiry.toString()); + } else { + nullFields.add("assignedV4AddressExpiry"); + } + if (null != groupHint) { resultJoiner.add("groupHint :"); resultJoiner.add(groupHint); diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl index 0894d7260915..997eb2b5128b 100644 --- a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl +++ b/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl @@ -30,6 +30,7 @@ import android.net.ipmemorystore.Blob; */ parcelable NetworkAttributesParcelable { byte[] assignedV4Address; + long assignedV4AddressExpiry; String groupHint; Blob[] dnsAddresses; int mtu; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index ee09c7e704c0..7c2235050caf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -477,8 +477,9 @@ public class NotificationRecordTest extends UiServiceTestCase { assertEquals(MetricsEvent.IMPORTANCE_EXPLANATION_APP, logMaker.getTaggedData( MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_INITIAL_EXPLANATION)); - // This field is only populated if the assistant was itself overridden by the system. - assertNull(logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST)); + // This field is populated whenever mImportanceExplanationCode is. + assertEquals(IMPORTANCE_LOW, + logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index d580557638cf..32e96a592207 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -438,8 +438,11 @@ public class ActivityRecordTests extends ActivityTestsBase { doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) .when(mActivity.mAppWindowToken).getOrientationIgnoreVisibility(); mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; - mActivity.info.maxAspectRatio = 1; + mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1; ensureActivityConfiguration(); + // The parent configuration doesn't change since the first resolved configuration, so the + // activity shouldn't be in the size compatibility mode. + assertFalse(mActivity.inSizeCompatMode()); final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds(); // Ensure the app bounds keep the declared aspect ratio. diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index d0c261279c9d..28af7cee8f38 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -19,11 +19,16 @@ package com.android.server; import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.content.pm.VersionedPackage; +import android.os.Handler; +import android.os.RemoteException; import android.os.test.TestLooper; +import android.util.AtomicFile; import androidx.test.InstrumentationRegistry; @@ -36,11 +41,14 @@ import org.junit.Test; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; -// TODO(zezeozue): Write test without using PackageWatchdog#getPackages. Just rely on +// TODO: Write test without using PackageWatchdog#getPackages. Just rely on // behavior of observers receiving crash notifications or not to determine if it's registered +// TODO: Use Truth in tests. /** * Test PackageWatchdog. */ @@ -77,12 +85,11 @@ public class PackageWatchdogTest { TestObserver observer3 = new TestObserver(OBSERVER_NAME_3); // Start observing for observer1 which will be unregistered - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Start observing for observer2 which will expire - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION, - false); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); // Start observing for observer3 which will have expiry duration reduced - watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION, false); + watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION); // Verify packages observed at start // 1 @@ -145,9 +152,8 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false); - watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION, - false); + watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); // Verify 2 observers are registered and saved internally // 1 @@ -193,8 +199,8 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A below the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) { @@ -220,8 +226,8 @@ public class PackageWatchdogTest { TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION, false); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION); // Then fail APP_C (not observed) above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -255,7 +261,7 @@ public class PackageWatchdogTest { } }; - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION, false); + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A (different version) above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -288,13 +294,13 @@ public class PackageWatchdogTest { // Start observing for all impact observers watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), - SHORT_DURATION, false); + SHORT_DURATION); watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), - SHORT_DURATION, false); + SHORT_DURATION); watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B), - SHORT_DURATION, false); + SHORT_DURATION); watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A), - SHORT_DURATION, false); + SHORT_DURATION); // Then fail all apps above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -346,8 +352,8 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_MEDIUM); // Start observing for observerFirst and observerSecond with failure handling - watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION, false); - watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION, false); + watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); + watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION); // Then fail APP_A above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -424,8 +430,8 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_HIGH); // Start observing for observer1 and observer2 with failure handling - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -442,11 +448,12 @@ public class PackageWatchdogTest { } /** - * Test explicit health check status determines package failure or success on expiry + * Test package passing explicit health checks does not fail and vice versa. */ @Test - public void testPackageFailureExplicitHealthCheck() throws Exception { - PackageWatchdog watchdog = createWatchdog(); + public void testExplicitHealthChecks() throws Exception { + TestController controller = new TestController(); + PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, PackageHealthObserverImpact.USER_IMPACT_HIGH); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, @@ -457,21 +464,36 @@ public class PackageWatchdogTest { // Start observing with explicit health checks for APP_A and APP_B respectively // with observer1 and observer2 - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, true); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION, true); - // Explicit health check passed for APP_A (observer1 is aware) - watchdog.onExplicitHealthCheckPassed(APP_A); - // Start observing APP_A with explicit health checks for observer3. + controller.setSupportedPackages(Arrays.asList(APP_A, APP_B)); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); + + // Run handler so requests are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify we requested health checks for APP_A and APP_B + List<String> requestedPackages = controller.getRequestedPackages(); + assertEquals(2, requestedPackages.size()); + assertEquals(APP_A, requestedPackages.get(0)); + assertEquals(APP_B, requestedPackages.get(1)); + + // Then health check passed for APP_A (observer1 is aware) + controller.setPackagePassed(APP_A); + + // Then start observing APP_A with explicit health checks for observer3. // Observer3 didn't exist when we got the explicit health check above, so // it starts out with a non-passing explicit health check and has to wait for a pass // otherwise it would be notified of APP_A failure on expiry - watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION, true); + watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION); // Then expire observers Thread.sleep(SHORT_DURATION); // Run handler so package failures are dispatched to observers mTestLooper.dispatchAll(); + // Verify we cancelled all requests on expiry + assertEquals(0, controller.getRequestedPackages().size()); + // Verify observer1 is not notified assertEquals(0, observer1.mFailedPackages.size()); @@ -484,9 +506,96 @@ public class PackageWatchdogTest { assertEquals(APP_A, observer3.mFailedPackages.get(0)); } + /** + * Test explicit health check state can be disabled and enabled correctly. + */ + @Test + public void testExplicitHealthCheckStateChanges() throws Exception { + TestController controller = new TestController(); + PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); + TestObserver observer = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + + // Start observing with explicit health checks for APP_A and APP_B + controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C)); + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION); + + // Run handler so requests are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify we requested health checks for APP_A and APP_B + List<String> requestedPackages = controller.getRequestedPackages(); + assertEquals(2, requestedPackages.size()); + assertEquals(APP_A, requestedPackages.get(0)); + assertEquals(APP_B, requestedPackages.get(1)); + + // Disable explicit health checks (marks APP_A and APP_B as passed) + watchdog.setExplicitHealthCheckEnabled(false); + + // Run handler so requests/cancellations are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify all checks are cancelled + assertEquals(0, controller.getRequestedPackages().size()); + + // Then expire APP_A + Thread.sleep(SHORT_DURATION); + mTestLooper.dispatchAll(); + + // Verify APP_A is not failed (APP_B) is not expired yet + assertEquals(0, observer.mFailedPackages.size()); + + // Re-enable explicit health checks + watchdog.setExplicitHealthCheckEnabled(true); + + // Run handler so requests/cancellations are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify no requests are made cos APP_A is expired and APP_B was marked as passed + assertEquals(0, controller.getRequestedPackages().size()); + + // Then set new supported packages + controller.setSupportedPackages(Arrays.asList(APP_C)); + // Start observing APP_A and APP_C; only APP_C has support for explicit health checks + watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION); + + // Run handler so requests/cancellations are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify requests are only made for APP_C + requestedPackages = controller.getRequestedPackages(); + assertEquals(1, requestedPackages.size()); + assertEquals(APP_C, requestedPackages.get(0)); + + // Then expire APP_A and APP_C + Thread.sleep(SHORT_DURATION); + mTestLooper.dispatchAll(); + + // Verify only APP_C is failed because explicit health checks was not supported for APP_A + assertEquals(1, observer.mFailedPackages.size()); + assertEquals(APP_C, observer.mFailedPackages.get(0)); + } + private PackageWatchdog createWatchdog() { - return new PackageWatchdog(InstrumentationRegistry.getContext(), - mTestLooper.getLooper()); + return createWatchdog(new TestController(), true /* withPackagesReady */); + } + + private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) { + Context context = InstrumentationRegistry.getContext(); + AtomicFile policyFile = + new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml")); + Handler handler = new Handler(mTestLooper.getLooper()); + PackageWatchdog watchdog = + new PackageWatchdog(context, policyFile, handler, handler, controller); + // Verify controller is not automatically started + assertFalse(controller.mIsEnabled); + if (withPackagesReady) { + watchdog.onPackagesReady(); + // Verify controller by default is started when packages are ready + assertTrue(controller.mIsEnabled); + } + return watchdog; } private static class TestObserver implements PackageHealthObserver { @@ -517,4 +626,69 @@ public class PackageWatchdogTest { return mName; } } + + private static class TestController extends ExplicitHealthCheckController { + TestController() { + super(null /* controller */); + } + + private boolean mIsEnabled; + private List<String> mSupportedPackages = new ArrayList<>(); + private List<String> mRequestedPackages = new ArrayList<>(); + private Runnable mStateChangedRunnable; + private Consumer<String> mPassedConsumer; + + @Override + public void request(String packageName) throws RemoteException { + if (!mRequestedPackages.contains(packageName)) { + mRequestedPackages.add(packageName); + } + } + + @Override + public void cancel(String packageName) throws RemoteException { + mRequestedPackages.remove(packageName); + } + + @Override + public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException { + consumer.accept(mIsEnabled ? mSupportedPackages : Collections.emptyList()); + } + + @Override + public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException { + // Pass copy to prevent ConcurrentModificationException during test + consumer.accept( + mIsEnabled ? new ArrayList<>(mRequestedPackages) : Collections.emptyList()); + } + + @Override + public void setEnabled(boolean enabled) { + mIsEnabled = enabled; + mStateChangedRunnable.run(); + } + + @Override + public void setCallbacks(Runnable stateChangedRunnable, Consumer<String> passedConsumer) { + mStateChangedRunnable = stateChangedRunnable; + mPassedConsumer = passedConsumer; + } + + public void setSupportedPackages(List<String> packages) { + mSupportedPackages.clear(); + mSupportedPackages.addAll(packages); + } + + public void setPackagePassed(String packageName) { + mPassedConsumer.accept(packageName); + } + + public List<String> getRequestedPackages() { + if (mIsEnabled) { + return mRequestedPackages; + } else { + return Collections.emptyList(); + } + } + } } diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java index 76cccc95742d..1a3ea6096c82 100644 --- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java +++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java @@ -44,6 +44,8 @@ public class ParcelableTests { assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + // lease will expire in two hours + builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000); // groupHint stays null this time around builder.setDnsAddresses(Collections.emptyList()); builder.setMtu(18); @@ -51,6 +53,7 @@ public class ParcelableTests { assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9")); + builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 3_600_000); builder.setGroupHint("groupHint"); builder.setDnsAddresses(Arrays.asList( InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"), @@ -66,7 +69,7 @@ public class ParcelableTests { // Verify that this test does not miss any new field added later. // If any field is added to NetworkAttributes it must be tested here for parceling // roundtrip. - assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) + assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 0ead2288a33e..d82b4e4850bc 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -499,7 +499,7 @@ public class ConnectivityServiceTest { }; try { - doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(); + doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any()); doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt()); } catch (RemoteException e) { fail(e.getMessage()); diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java index dc2018543050..fb84611cb662 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java @@ -57,6 +57,7 @@ public class NetworkAttributesTest { final NetworkAttributes na = new NetworkAttributes( (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}), + System.currentTimeMillis() + 7_200_000, "some hint", Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}), Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})), diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java index fff9635992d4..e2668bc4281f 100644 --- a/tests/testables/src/android/testing/TestableContext.java +++ b/tests/testables/src/android/testing/TestableContext.java @@ -296,13 +296,13 @@ public class TestableContext extends ContextWrapper implements TestRule { @Override public void registerComponentCallbacks(ComponentCallbacks callback) { if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable()); - super.registerComponentCallbacks(callback); + getBaseContext().registerComponentCallbacks(callback); } @Override public void unregisterComponentCallbacks(ComponentCallbacks callback) { if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations(); - super.unregisterComponentCallbacks(callback); + getBaseContext().unregisterComponentCallbacks(callback); } public TestablePermissions getTestablePermissions() { diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 53361414e9b8..857792192902 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -217,6 +217,29 @@ TEST_F(ResourceParserTest, ParseStyledStringWithWhitespace) { EXPECT_THAT(str->value->spans[1].last_char, Eq(13u)); } +TEST_F(ResourceParserTest, ParseStringTranslatableAttribute) { + // If there is no translate attribute the default is 'true' + EXPECT_TRUE(TestParse(R"(<string name="foo1">Translate</string>)")); + String* str = test::GetValue<String>(&table_, "string/foo1"); + ASSERT_THAT(str, NotNull()); + ASSERT_TRUE(str->IsTranslatable()); + + // Explicit 'true' translate attribute + EXPECT_TRUE(TestParse(R"(<string name="foo2" translatable="true">Translate</string>)")); + str = test::GetValue<String>(&table_, "string/foo2"); + ASSERT_THAT(str, NotNull()); + ASSERT_TRUE(str->IsTranslatable()); + + // Explicit 'false' translate attribute + EXPECT_TRUE(TestParse(R"(<string name="foo3" translatable="false">Do not translate</string>)")); + str = test::GetValue<String>(&table_, "string/foo3"); + ASSERT_THAT(str, NotNull()); + ASSERT_FALSE(str->IsTranslatable()); + + // Invalid value for the translate attribute, should be boolean ('true' or 'false') + EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)")); +} + TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) { std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 2ec1ab31a58c..9b81369fa9f0 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -143,6 +143,8 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer, const std::string& output_path) { TRACE_CALL(); + // Filenames starting with "donottranslate" are not localizable + bool translatable_file = path_data.name.find("donottranslate") != 0; ResourceTable table; { auto fin = file->OpenInputStream(); @@ -157,9 +159,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, ResourceParserOptions parser_options; parser_options.error_on_positional_arguments = !options.legacy_mode; - - // If the filename includes donottranslate, then the default translatable is false. - parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos; + parser_options.translatable = translatable_file; // If visibility was forced, we need to use it when creating a new resource and also error if // we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags. @@ -172,7 +172,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, } } - if (options.pseudolocalize) { + if (options.pseudolocalize && translatable_file) { // Generate pseudo-localized strings (en-XA and ar-XB). // These are created as weak symbols, and are only generated from default // configuration diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index c0c05cda35e7..5f637bd8d582 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -27,6 +27,8 @@ namespace aapt { +using CompilerTest = CommandTestFixture; + std::string BuildPath(std::vector<std::string> args) { std::string out; if (args.empty()) { @@ -51,7 +53,7 @@ int TestCompile(const std::string& path, const std::string& outDir, bool legacy, return CompileCommand(&diag).Execute(args, &std::cerr); } -TEST(CompilerTest, MultiplePeriods) { +TEST_F(CompilerTest, MultiplePeriods) { StdErrDiagnostics diag; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()), @@ -108,7 +110,7 @@ TEST(CompilerTest, MultiplePeriods) { ASSERT_EQ(::android::base::utf8::unlink(path5_out.c_str()), 0); } -TEST(CompilerTest, DirInput) { +TEST_F(CompilerTest, DirInput) { StdErrDiagnostics diag; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()), @@ -138,7 +140,7 @@ TEST(CompilerTest, DirInput) { ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0); } -TEST(CompilerTest, ZipInput) { +TEST_F(CompilerTest, ZipInput) { StdErrDiagnostics diag; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const std::string kResZip = @@ -169,4 +171,86 @@ TEST(CompilerTest, ZipInput) { ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0); } -} // namespace aapt
\ No newline at end of file +/* + * This tests the "protection" from pseudo-translation of + * non-translatable files (starting with 'donotranslate') + * and strings (with the translatable="false" attribute) + * + * We check 4 string files, 2 translatable, and 2 not (based on file name) + * Each file contains 2 strings, one translatable, one not (attribute based) + * Each of these files are compiled and linked into one .apk, then we load the + * strings from the apk and check if there are pseudo-translated strings. + */ + +// Using 000 and 111 because they are not changed by pseudo-translation, +// making our life easier. +constexpr static const char sTranslatableXmlContent[] = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<resources>" + " <string name=\"normal\">000</string>" + " <string name=\"non_translatable\" translatable=\"false\">111</string>" + "</resources>"; + +static void AssertTranslations(CommandTestFixture *ctf, std::string file_name, + std::vector<std::string> expected) { + + StdErrDiagnostics diag; + + const std::string source_file = ctf->GetTestPath("/res/values/" + file_name + ".xml"); + const std::string compiled_files_dir = ctf->GetTestPath("/compiled_" + file_name); + const std::string out_apk = ctf->GetTestPath("/" + file_name + ".apk"); + + CHECK(ctf->WriteFile(source_file, sTranslatableXmlContent)); + CHECK(file::mkdirs(compiled_files_dir.data())); + + ASSERT_EQ(CompileCommand(&diag).Execute({ + source_file, + "-o", compiled_files_dir, + "-v", + "--pseudo-localize" + }, &std::cerr), 0); + + ASSERT_TRUE(ctf->Link({ + "--manifest", ctf->GetDefaultManifest(), + "-o", out_apk + }, compiled_files_dir, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + + ResourceTable* table = apk->GetResourceTable(); + ASSERT_NE(table, nullptr); + table->string_pool.Sort(); + + const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings = + table->string_pool.strings(); + + // The actual / expected vectors have the same size + const size_t pool_size = pool_strings.size(); + ASSERT_EQ(pool_size, expected.size()); + + for (size_t i = 0; i < pool_size; i++) { + std::string actual = pool_strings[i]->value; + ASSERT_EQ(actual, expected[i]); + } +} + +TEST_F(CompilerTest, DoNotTranslateTest) { + // The first string (000) is translatable, the second is not + // ar-XB uses "\u200F\u202E...\u202C\u200F" + std::vector<std::string> expected_translatable = { + "000", "111", // default locale + "[000 one]", // en-XA + "\xE2\x80\x8F\xE2\x80\xAE" "000" "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB + }; + AssertTranslations(this, "foo", expected_translatable); + AssertTranslations(this, "foo_donottranslate", expected_translatable); + + // No translatable strings because these are non-translatable files + std::vector<std::string> expected_not_translatable = { + "000", "111", // default locale + }; + AssertTranslations(this, "donottranslate", expected_not_translatable); + AssertTranslations(this, "donottranslate_foo", expected_not_translatable); +} + +} // namespace aapt |