Merge "Return `RESULT_CONTINUE` in `DesktopModeLaunchParamsModifier`" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index e649485..e82df12 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -41,10 +41,10 @@
]
},
{
- "name": "CtsHostsideNetworkTests",
+ "name": "CtsHostsideNetworkPolicyTests",
"options": [
- {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
- {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
+ {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
+ {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
]
},
{
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index f1e44cc..1df8f63 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1104,6 +1104,10 @@
return true;
}
+ if (mDataDir == null) {
+ return false;
+ }
+
// Temporarily disable logging of disk reads on the Looper thread as this is necessary -
// and the loader will access the directory anyway if we don't check it.
StrictMode.ThreadPolicy oldThreadPolicy = allowThreadDiskReads();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4d7e29b..05a2aec 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -9770,6 +9770,12 @@
* You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
* <p>
*
+ * <p>
+ * Starting at {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V} the
+ * {@link Notification#FLAG_NO_CLEAR NO_CLEAR flag} will be set for valid MediaStyle
+ * notifications.
+ * <p>
+ *
* To use this style with your Notification, feed it to
* {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
* <pre class="prettyprint">
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index f092945..9b3fb5c 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1197,6 +1197,24 @@
/**
* Callback called when a particular foreground service type has timed out.
*
+ * <p>This callback is meant to give the app a small grace period of a few seconds to finish
+ * the foreground service of the offending type - if it fails to do so, the app will be
+ * declared an ANR.
+ *
+ * <p>The foreground service of the offending type can be stopped within the time limit by
+ * {@link android.app.Service#stopSelf()},
+ * {@link android.content.Context#stopService(android.content.Intent)} or their overloads.
+ * {@link android.app.Service#stopForeground(int)} can be used as well, which demotes the
+ * service to a "background" service, which will soon be stopped by the system.
+ *
+ * <p>The specific time limit for each type (if one exists) is mentioned in the documentation
+ * for that foreground service type.
+ *
+ * <p>Note: time limits are restricted to a rolling 24-hour window - for example, if a
+ * foreground service type has a time limit of 6 hours, that time counter begins as soon as the
+ * foreground service starts. This time limit will only be reset once every 24 hours or if the
+ * app comes into the foreground state.
+ *
* @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
* the service started.
* @param fgsType the {@link ServiceInfo.ForegroundServiceType foreground service type} which
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index c6a8762..342479b 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5062,21 +5062,29 @@
/**
* <p>The version of the session configuration query
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * API</p>
+ * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+ * APIs.</p>
* <p>The possible values in this key correspond to the values defined in
* android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the
* camera device must reliably report whether they are supported via
- * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
- * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }.
- * Calling the method for this camera ID throws an UnsupportedOperationException.</p>
- * <p>If set to VANILLA_ICE_CREAM, the application can call
+ * It also defines the set of session specific keys in CameraCharacteristics when returned from
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }.
+ * The version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
+ * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support the
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup } API.
+ * Trying to create a CameraDeviceSetup instance throws an UnsupportedOperationException.</p>
+ * <p>From VANILLA_ICE_CREAM onwards, the camera compliance tests verify a set of
+ * commonly used SessionConfigurations to ensure that the outputs of
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * to check if the combinations of below features are supported.</p>
+ * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+ * are accurate. The application is encouraged to use these SessionConfigurations when turning on
+ * multiple features at the same time.</p>
+ * <p>When set to VANILLA_ICE_CREAM, the combinations of the following configurations are verified
+ * by the compliance tests:</p>
* <ul>
- * <li>A subset of LIMITED-level device stream combinations.</li>
- * </ul>
+ * <li>
+ * <p>A set of commonly used stream combinations:</p>
* <table>
* <thead>
* <tr>
@@ -5084,257 +5092,108 @@
* <th style="text-align: center;">Size</th>
* <th style="text-align: center;">Target 2</th>
* <th style="text-align: center;">Size</th>
- * <th style="text-align: center;">Sample use case(s)</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;">Simple preview, GPU video processing, or no-preview video recording.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
* <td style="text-align: center;"></td>
* <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
* <td style="text-align: center;"></td>
* <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;">In-application video/image processing.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;">Standard still imaging.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_16_9</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">UHD</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">S1440P</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">S1080P</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">UHD</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_16_9</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;">In-app processing plus still capture.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">Standard recording.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">UHD</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">Preview plus in-app processing.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">XVGA</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_4_3</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P_4_3</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_4_3</td>
* </tr>
* </tbody>
* </table>
- * <pre><code>- {@code MAXIMUM} size refers to the camera device's maximum output resolution for
- * that format from {@code StreamConfigurationMap#getOutputSizes}. {@code PREVIEW} size
- * refers to the best size match to the device's screen resolution, or to 1080p
- * (@code 1920x1080}, whichever is smaller. Both sizes are guaranteed to be supported.
- *
- * - {@code S1440P} refers to {@code 1920x1440 (4:3)} and {@code 2560x1440 (16:9)}.
- * {@code S1080P} refers to {@code 1440x1080 (4:3)} and {@code 1920x1080 (16:9)}.
- * And {@code S720P} refers to {@code 960x720 (4:3)} and {@code 1280x720 (16:9)}.
- *
- * - If a combination contains a S1440P, S1080P, or S720P stream,
- * both 4:3 and 16:9 aspect ratio sizes can be queried. For example, for the
- * stream combination of {PRIV, S1440P, JPEG, MAXIMUM}, and if MAXIMUM ==
- * 4032 x 3024, the application will be able to query both
- * {PRIV, 1920 x 1440, JPEG, 4032 x 3024} and {PRIV, 2560 x 1440, JPEG, 4032 x 2268}
- * without an exception being thrown.
- * </code></pre>
* <ul>
- * <li>VIDEO_STABILIZATION_MODES: {OFF, PREVIEW}</li>
- * <li>AE_TARGET_FPS_RANGE: { {<em>, 30}, {</em>, 60} }</li>
- * <li>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</li>
+ * <li>{@code MAXIMUM_4_3} refers to the camera device's maximum output resolution with
+ * 4:3 aspect ratio for that format from {@code StreamConfigurationMap#getOutputSizes}.</li>
+ * <li>{@code MAXIMUM_16_9} is the maximum output resolution with 16:9 aspect ratio.</li>
+ * <li>{@code S1440P} refers to {@code 2560x1440 (16:9)}.</li>
+ * <li>{@code S1080P} refers to {@code 1920x1080 (16:9)}.</li>
+ * <li>{@code S720P} refers to {@code 1280x720 (16:9)}.</li>
+ * <li>{@code UHD} refers to {@code 3840x2160 (16:9)}.</li>
+ * <li>{@code XVGA} refers to {@code 1024x768 (4:3)}.</li>
+ * <li>{@code S1080P_43} refers to {@code 1440x1080 (4:3)}.</li>
* </ul>
+ * </li>
+ * <li>
+ * <p>VIDEO_STABILIZATION_MODE: {OFF, PREVIEW}</p>
+ * </li>
+ * <li>
+ * <p>AE_TARGET_FPS_RANGE: { {*, 30}, {*, 60} }</p>
+ * </li>
+ * <li>
+ * <p>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</p>
+ * </li>
+ * </ul>
+ * <p>All of the above configurations can be set up with a SessionConfiguration. The list of
+ * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and
+ * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p>
* <p>This key is available on all devices.</p>
*/
@PublicKey
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index eb644e8..dfbf06b 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -917,8 +917,11 @@
* image. For example, it can be used as a temporary placeholder for the requested capture
* while the final image is being processed. The supported sizes for a still capture's postview
* can be retrieved using
- * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.
- * The formats of the still capture and postview should be equivalent upon capture request.</p>
+ * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.</p>
+ *
+ * <p>Starting with Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+ * the formats of the still capture and postview are not required to be equivalent upon capture
+ * request.</p>
*
* @param extension the extension type
* @return {@code true} in case postview is supported, {@code false} otherwise
@@ -976,8 +979,7 @@
*
* @param extension the extension type
* @param captureSize size of the still capture for which the postview is requested
- * @param format device-specific extension output format of the still capture and
- * postview
+ * @param format device-specific extension output format of the postview
* @return non-modifiable list of available sizes or an empty list if the format and
* size is not supported.
* @throws IllegalArgumentException in case of unsupported extension or if postview
@@ -1018,8 +1020,8 @@
}
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
extender.init(mCameraId, mCharacteristicsMapNative);
- return generateSupportedSizes(extender.getSupportedPostviewResolutions(
- sz), format, streamMap);
+ return getSupportedSizes(extender.getSupportedPostviewResolutions(sz),
+ format);
} else {
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
initializeExtension(extension);
@@ -1034,15 +1036,13 @@
}
if (format == ImageFormat.YUV_420_888) {
- return generateSupportedSizes(
- extenders.second.getSupportedPostviewResolutions(sz),
- format, streamMap);
+ return getSupportedSizes(
+ extenders.second.getSupportedPostviewResolutions(sz), format);
} else if (format == ImageFormat.JPEG) {
// The framework will perform the additional encoding pass on the
// processed YUV_420 buffers.
- return generateJpegSupportedSizes(
- extenders.second.getSupportedPostviewResolutions(sz),
- streamMap);
+ return getSupportedSizes(
+ extenders.second.getSupportedPostviewResolutions(sz), format);
} else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
// Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
// extension case
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index 875550a..a10e250 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -36,6 +36,8 @@
import android.util.Log;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Iterator;
@@ -57,6 +59,8 @@
private android.hardware.camera2.extension.Size mResolution = null;
private android.hardware.camera2.extension.Size mPostviewResolution = null;
private int mFormat = -1;
+ private int mPostviewFormat = -1;
+ private int mCaptureFormat = -1;
private Surface mOutputSurface = null;
private ImageWriter mOutputWriter = null;
private Surface mPostviewOutputSurface = null;
@@ -204,10 +208,12 @@
}
public void onOutputSurface(Surface surface, int format) throws RemoteException {
- if (format != ImageFormat.JPEG) {
+ if (!Flags.extension10Bit() && format != ImageFormat.JPEG) {
Log.e(TAG, "Unsupported output format: " + format);
return;
}
+ CameraExtensionUtils.SurfaceInfo surfaceInfo = CameraExtensionUtils.querySurface(surface);
+ mCaptureFormat = surfaceInfo.mFormat;
mOutputSurface = surface;
initializePipeline();
}
@@ -215,10 +221,11 @@
public void onPostviewOutputSurface(Surface surface) throws RemoteException {
CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
CameraExtensionUtils.querySurface(surface);
- if (postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
+ if (!Flags.extension10Bit() && postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
Log.e(TAG, "Unsupported output format: " + postviewSurfaceInfo.mFormat);
return;
}
+ mPostviewFormat = postviewSurfaceInfo.mFormat;
mPostviewOutputSurface = surface;
initializePostviewPipeline();
}
@@ -233,7 +240,7 @@
}
public void onImageFormatUpdate(int format) throws RemoteException {
- if (format != ImageFormat.YUV_420_888) {
+ if (!Flags.extension10Bit() && format != ImageFormat.YUV_420_888) {
Log.e(TAG, "Unsupported input format: " + format);
return;
}
@@ -244,33 +251,45 @@
private void initializePipeline() throws RemoteException {
if ((mFormat != -1) && (mOutputSurface != null) && (mResolution != null) &&
(mYuvReader == null)) {
- // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
- mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
- ImageFormat.JPEG,
- (mResolution.width * mResolution.height * 3)/2 + JPEG_APP_SEGMENT_SIZE, 1);
- mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height, mFormat,
- JPEG_QUEUE_SIZE);
- mYuvReader.setOnImageAvailableListener(
- new YuvCallback(mYuvReader, mOutputWriter), mHandler);
- mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+ if (Flags.extension10Bit() && mCaptureFormat == ImageFormat.YUV_420_888) {
+ // For the case when postview is JPEG and capture is YUV
+ mProcessor.onOutputSurface(mOutputSurface, mCaptureFormat);
+ } else {
+ // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
+ mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
+ ImageFormat.JPEG,
+ (mResolution.width * mResolution.height * 3) / 2
+ + JPEG_APP_SEGMENT_SIZE, 1);
+ mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height,
+ mFormat, JPEG_QUEUE_SIZE);
+ mYuvReader.setOnImageAvailableListener(
+ new YuvCallback(mYuvReader, mOutputWriter), mHandler);
+ mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+ }
mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
- mProcessor.onImageFormatUpdate(mFormat);
+ mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
}
}
private void initializePostviewPipeline() throws RemoteException {
if ((mFormat != -1) && (mPostviewOutputSurface != null) && (mPostviewResolution != null)
&& (mPostviewYuvReader == null)) {
- // Jpeg/blobs are expected to be configured with (w*h)x1
- mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface, 1/*maxImages*/,
- ImageFormat.JPEG, mPostviewResolution.width * mPostviewResolution.height, 1);
- mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
- mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
- mPostviewYuvReader.setOnImageAvailableListener(
- new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
- mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+ if (Flags.extension10Bit() && mPostviewFormat == ImageFormat.YUV_420_888) {
+ // For the case when postview is YUV and capture is JPEG
+ mProcessor.onPostviewOutputSurface(mPostviewOutputSurface);
+ } else {
+ // Jpeg/blobs are expected to be configured with (w*h)x1
+ mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface,
+ 1/*maxImages*/, ImageFormat.JPEG,
+ mPostviewResolution.width * mPostviewResolution.height, 1);
+ mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
+ mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
+ mPostviewYuvReader.setOnImageAvailableListener(
+ new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
+ mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+ }
mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
- mProcessor.onImageFormatUpdate(mFormat);
+ mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index c00e610..3ae3199 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -390,7 +390,16 @@
if (surfaceInfo.mFormat == ImageFormat.JPEG) {
mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
mImageProcessor = mImageJpegProcessor;
+ } else if (Flags.extension10Bit() && mClientPostviewSurface != null) {
+ // Handles case when postview is JPEG and capture is YUV
+ CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
+ CameraExtensionUtils.querySurface(mClientPostviewSurface);
+ if (postviewSurfaceInfo.mFormat == ImageFormat.JPEG) {
+ mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
+ mImageProcessor = mImageJpegProcessor;
+ }
}
+
mBurstCaptureImageReader = ImageReader.newInstance(surfaceInfo.mWidth,
surfaceInfo.mHeight, CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
mImageExtender.getMaxCaptureStage());
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index f0c6e2e..40f0477 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -112,19 +112,30 @@
if (outputConfig == null) return null;
SurfaceInfo surfaceInfo = querySurface(outputConfig.getSurface());
- if (surfaceInfo.mFormat == captureFormat) {
- if (supportedPostviewSizes.containsKey(captureFormat)) {
- Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
- if (supportedPostviewSizes.get(surfaceInfo.mFormat)
- .contains(postviewSize)) {
- return outputConfig.getSurface();
- } else {
- throw new IllegalArgumentException("Postview size not supported!");
- }
+
+ if (Flags.extension10Bit()) {
+ Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+ if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+ .contains(postviewSize)) {
+ return outputConfig.getSurface();
+ } else {
+ throw new IllegalArgumentException("Postview size not supported!");
}
} else {
- throw new IllegalArgumentException("Postview format should be equivalent to " +
- " the capture format!");
+ if (surfaceInfo.mFormat == captureFormat) {
+ if (supportedPostviewSizes.containsKey(captureFormat)) {
+ Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+ if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+ .contains(postviewSize)) {
+ return outputConfig.getSurface();
+ } else {
+ throw new IllegalArgumentException("Postview size not supported!");
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Postview format should be equivalent to "
+ + " the capture format!");
+ }
}
return null;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 72ab970..e6ddf35 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1736,6 +1736,24 @@
"android.settings.NETWORK_OPERATOR_SETTINGS";
/**
+ * Activity Action: Show settings for selecting the network provider.
+ * <p>
+ * In some cases, a matching Activity may not be provided, so ensure you
+ * safeguard against this.
+ * <p>
+ * Access to this preference can be customized via Settings' app.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NETWORK_PROVIDER_SETTINGS =
+ "android.settings.NETWORK_PROVIDER_SETTINGS";
+
+ /**
* Activity Action: Show settings for selection of 2G/3G.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 997c9581..5f6bdbf 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1383,16 +1383,22 @@
DreamService.DREAM_META_DATA, DREAM_META_DATA_ROOT_TAG,
com.android.internal.R.styleable.Dream)) {
if (rawMetadata == null) return null;
- return new DreamMetadata(
- convertToComponentName(
- rawMetadata.getString(
- com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo),
- rawMetadata.getDrawable(
- com.android.internal.R.styleable.Dream_previewImage),
- rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
- DEFAULT_SHOW_COMPLICATIONS),
- rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
- );
+ try {
+ return new DreamMetadata(
+ convertToComponentName(
+ rawMetadata.getString(
+ com.android.internal.R.styleable.Dream_settingsActivity),
+ serviceInfo),
+ rawMetadata.getDrawable(
+ com.android.internal.R.styleable.Dream_previewImage),
+ rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
+ DEFAULT_SHOW_COMPLICATIONS),
+ rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
+ );
+ } catch (Exception exception) {
+ Log.e(TAG, "Failed to create read metadata", exception);
+ return null;
+ }
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c7ca16b..f0d27da 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4276,6 +4276,10 @@
mPreferredFrameRate = -1;
mIsFrameRateConflicted = false;
mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+ } else if (mPreferredFrameRate == 0) {
+ // From MSG_FRAME_RATE_SETTING, where mPreferredFrameRate is set to 0
+ setPreferredFrameRate(0);
+ mPreferredFrameRate = -1;
}
}
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 7c7c7b8..9f9aae5 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -473,7 +473,14 @@
} finally {
// Unconditionally skip to the end of the written data, even if the actual parcel
// format is incompatible
- parcel.setDataPosition(endPos);
+ if (endPos > parcel.dataPosition()) {
+ if (endPos >= parcel.dataSize()) {
+ throw new IndexOutOfBoundsException(
+ "PowerStats end position: " + endPos + " is outside the parcel bounds: "
+ + parcel.dataSize());
+ }
+ parcel.setDataPosition(endPos);
+ }
}
}
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 69d3d6a..c21a43e 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -53,8 +53,6 @@
public ScreenshotHelper(Context context) {
mContext = context;
- IntentFilter filter = new IntentFilter(ACTION_USER_SWITCHED);
- mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
}
/**
@@ -108,6 +106,8 @@
public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
synchronized (mScreenshotLock) {
+ mContext.registerReceiver(mBroadcastReceiver,
+ new IntentFilter(ACTION_USER_SWITCHED), Context.RECEIVER_EXPORTED);
final Runnable mScreenshotTimeout = () -> {
synchronized (mScreenshotLock) {
@@ -223,6 +223,11 @@
mScreenshotConnection = null;
mScreenshotService = null;
}
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Attempted to remove broadcast receiver twice");
+ }
}
/**
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index f885e31..32aec1a 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -36,6 +36,7 @@
import android.annotation.NonNull;
import android.app.Activity;
+import android.app.Instrumentation;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
@@ -47,6 +48,7 @@
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -537,6 +539,28 @@
});
waitForAfterDraw();
}
+
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+ })
+ public void frameRateReset() throws Throwable {
+ mMovingView.setRequestedFrameRate(120f);
+ waitForFrameRateCategoryToSettle();
+ mActivityRule.runOnUiThread(() -> mMovingView.setVisibility(View.INVISIBLE));
+
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ for (int i = 0; i < 120; i++) {
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.getParent().onDescendantInvalidated(mMovingView, mMovingView);
+ });
+ instrumentation.waitForIdleSync();
+ }
+
+ assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
+ }
+
private void runAfterDraw(@NonNull Runnable runnable) {
Handler handler = new Handler(Looper.getMainLooper());
mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index 6402206..baab3b2 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -168,6 +168,20 @@
assertThat(end).isEqualTo("END");
}
+ @Test
+ public void parceling_corruptParcel() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel);
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+ newParcel.writeInt(-42); // Negative section length
+ newParcel.setDataPosition(0);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats).isNull();
+ }
+
private static Parcel marshallAndUnmarshall(Parcel parcel) {
byte[] bytes = parcel.marshall();
parcel.recycle();
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index e1bf40c..6110133 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -105,18 +105,21 @@
}
@Test
- fun onDragUpdate_stayOnSameSide() {
+ fun drag_stayOnSameSide() {
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ controller.onDragEnd()
}
waitForAnimateIn()
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
- fun onDragUpdate_toLeft() {
+ fun drag_toLeft() {
+ // Drag to left, but don't finish
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -132,10 +135,16 @@
.isEqualTo(expectedDropTargetBounds.height())
assertThat(testListener.locationChanges).containsExactly(BubbleBarLocation.LEFT)
+ assertThat(testListener.locationReleases).isEmpty()
+
+ // Finish the drag
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.LEFT)
}
@Test
- fun onDragUpdate_toLeftAndBackToRight() {
+ fun drag_toLeftAndBackToRight() {
+ // Drag to left
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -143,6 +152,7 @@
waitForAnimateIn()
assertThat(dropTargetView).isNotNull()
+ // Drag to right
runOnMainSync { controller.onDragUpdate(pointOnRight.x, pointOnRight.y) }
// We have to wait for existing drop target to animate out and new to animate in
waitForAnimateOut()
@@ -158,10 +168,15 @@
assertThat(testListener.locationChanges)
.containsExactly(BubbleBarLocation.LEFT, BubbleBarLocation.RIGHT)
+ assertThat(testListener.locationReleases).isEmpty()
+
+ // Release the view
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
- fun onDragUpdate_toLeftInExclusionRect() {
+ fun drag_toLeftInExclusionRect() {
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
// Exclusion rect is around the bottom center area of the screen
@@ -170,6 +185,10 @@
waitForAnimateIn()
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).isEmpty()
+
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
@@ -256,8 +275,13 @@
internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
val locationChanges = mutableListOf<BubbleBarLocation>()
+ val locationReleases = mutableListOf<BubbleBarLocation>()
override fun onChange(location: BubbleBarLocation) {
locationChanges.add(location)
}
+
+ override fun onRelease(location: BubbleBarLocation) {
+ locationReleases.add(location)
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d295877..98b2431 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -725,6 +725,17 @@
}
}
+ /**
+ * Animate bubble bar to the given location. The location change is transient. It does not
+ * update the state of the bubble bar.
+ * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+ */
+ public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ if (canShowAsBubbleBar()) {
+ mBubbleStateListener.animateBubbleBarLocation(bubbleBarLocation);
+ }
+ }
+
/** Whether this userId belongs to the current user. */
private boolean isCurrentProfile(int userId) {
return userId == UserHandle.USER_ALL
@@ -2250,15 +2261,19 @@
private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
private final Bubbles.BubbleStateListener mBubbleListener =
new Bubbles.BubbleStateListener() {
+ @Override
+ public void onBubbleStateChange(BubbleBarUpdate update) {
+ Bundle b = new Bundle();
+ b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+ b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
+ mListener.call(l -> l.onBubbleStateChange(b));
+ }
- @Override
- public void onBubbleStateChange(BubbleBarUpdate update) {
- Bundle b = new Bundle();
- b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
- b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
- mListener.call(l -> l.onBubbleStateChange(b));
- }
- };
+ @Override
+ public void animateBubbleBarLocation(BubbleBarLocation location) {
+ mListener.call(l -> l.animateBubbleBarLocation(location));
+ }
+ };
IBubblesImpl(BubbleController controller) {
mController = controller;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 127a49f..322088b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -37,6 +37,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -304,6 +305,12 @@
* Called when the bubbles state changes.
*/
void onBubbleStateChange(BubbleBarUpdate update);
+
+ /**
+ * Called when bubble bar should temporarily be animated to a new location.
+ * Does not result in a state change.
+ */
+ void animateBubbleBarLocation(BubbleBarLocation location);
}
/** Listener to find out about stack expansion / collapse events. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
index e48f8d5..14d29cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -15,8 +15,9 @@
*/
package com.android.wm.shell.bubbles;
-import android.os.Bundle;
+import android.os.Bundle;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
/**
* Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
*/
@@ -26,4 +27,10 @@
* Called when the bubbles state changes.
*/
void onBubbleStateChange(in Bundle update);
+
+ /**
+ * Called when bubble bar should temporarily be animated to a new location.
+ * Does not result in a state change.
+ */
+ void animateBubbleBarLocation(in BubbleBarLocation location);
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index fe9c4d4..a51ac63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -135,9 +135,9 @@
private fun finishDrag() {
if (!isStuckToDismiss) {
- animationHelper.animateToRestPosition()
pinController.onDragEnd()
dragListener.onReleased(inDismiss = false)
+ animationHelper.animateToRestPosition()
dismissView.hide()
}
isMoving = false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 62cc4da..a351cef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -33,6 +33,8 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -42,6 +44,8 @@
import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
+import com.android.wm.shell.common.bubbles.BaseBubblePinController;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.DismissView;
import kotlin.Unit;
@@ -115,7 +119,18 @@
mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
context, this, mPositioner);
- mBubbleExpandedViewPinController.setListener(mBubbleController::setBubbleBarLocation);
+ mBubbleExpandedViewPinController.setListener(
+ new BaseBubblePinController.LocationChangeListener() {
+ @Override
+ public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
+ mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
+ }
+
+ @Override
+ public void onRelease(@NonNull BubbleBarLocation location) {
+ mBubbleController.setBubbleBarLocation(location);
+ }
+ });
setOnClickListener(view -> hideMenuOrCollapse());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
index a008045..e514f9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
@@ -82,6 +82,7 @@
fun onDragEnd() {
getDropTargetView()?.let { view -> view.animateOut { removeDropTargetView(view) } }
dismissZone = null
+ listener?.onRelease(if (onLeft) LEFT else RIGHT)
}
/**
@@ -170,14 +171,22 @@
/** Receive updates on location changes */
interface LocationChangeListener {
/**
- * Bubble bar [BubbleBarLocation] has changed as a result of dragging
+ * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in
+ * progress.
*
* Triggered when drag gesture passes the middle of the screen and before touch up. Can be
* triggered multiple times per gesture.
*
* @param location new location as a result of the ongoing drag operation
*/
- fun onChange(location: BubbleBarLocation)
+ fun onChange(location: BubbleBarLocation) {}
+
+ /**
+ * Bubble bar has been released in the [BubbleBarLocation].
+ *
+ * @param location final location of the bubble bar once drag is released
+ */
+ fun onRelease(location: BubbleBarLocation)
}
companion object {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
index 64dcf6e..395354e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -34,6 +34,7 @@
"compatibility-device-util-axt",
"platform-test-annotations",
"truth",
+ "uiautomator-helpers",
],
srcs: [
"src/**/*.java",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 0ab99fa..66943d4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -30,6 +30,8 @@
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.KeyguardManager;
@@ -43,6 +45,8 @@
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.platform.uiautomator_helpers.WaitUtils;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
@@ -51,6 +55,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.Configurator;
+import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.TestUtils;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
@@ -60,7 +66,6 @@
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,11 +81,13 @@
private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
private static final int TIMEOUT_UI_CHANGE_S = 5;
private static final int NO_GLOBAL_ACTION = -1;
- private static final Intent INTENT_OPEN_MENU = new Intent(INTENT_TOGGLE_MENU)
- .setPackage(PACKAGE_NAME);
+ private static final Intent INTENT_OPEN_MENU =
+ new Intent(INTENT_TOGGLE_MENU).setPackage(PACKAGE_NAME);
+ private static final String SERVICE_NAME = PACKAGE_NAME + "/.AccessibilityMenuService";
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
+ private static UiDevice sUiDevice;
private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
@@ -91,12 +98,14 @@
@BeforeClass
public static void classSetup() throws Throwable {
- final String serviceName = PACKAGE_NAME + "/.AccessibilityMenuService";
+ Configurator.getInstance()
+ .setUiAutomationFlags(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sUiAutomation.adoptShellPermissionIdentity(
UiAutomation.ALL_PERMISSIONS.toArray(new String[0]));
+ sUiDevice = UiDevice.getInstance(sInstrumentation);
final Context context = sInstrumentation.getTargetContext();
sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -117,13 +126,13 @@
// Enable a11yMenu service.
Settings.Secure.putString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, serviceName);
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, SERVICE_NAME);
TestUtils.waitUntil("Failed to enable service",
TIMEOUT_SERVICE_STATUS_CHANGE_S,
() -> sAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
- info -> info.getId().contains(serviceName)).count() == 1);
+ info -> info.getId().contains(SERVICE_NAME)).count() == 1);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -159,8 +168,11 @@
public void tearDown() throws Throwable {
closeMenu();
sLastGlobalAction.set(NO_GLOBAL_ACTION);
+ // Leave the device in clean state when the test finished
+ unlockSignal();
// dismisses screenshot popup if present.
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+ sUiDevice.pressBack();
+ sUiDevice.pressHome();
}
private static boolean isMenuVisible() {
@@ -168,38 +180,25 @@
return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
}
- private static void wakeUpScreen() throws Throwable {
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- TestUtils.waitUntil("Screen did not wake up.",
- TIMEOUT_UI_CHANGE_S,
- () -> sPowerManager.isInteractive());
+ private static void wakeUpScreen() throws RemoteException {
+ sUiDevice.wakeUp();
+ WaitUtils.waitForValueToSettle("Screen On", AccessibilityMenuServiceTest::isScreenOn);
+ assertWithMessage("Screen is on").that(isScreenOn()).isTrue();
}
private static void closeScreen() throws Throwable {
- Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
- TestUtils.waitUntil("Screen did not close.",
- TIMEOUT_UI_CHANGE_S,
- () -> !sPowerManager.isInteractive()
- && display.getState() == Display.STATE_OFF
- );
+ WaitUtils.waitForValueToSettle("Screen Off", AccessibilityMenuServiceTest::isScreenOff);
+ assertWithMessage("Screen is off").that(isScreenOff()).isTrue();
}
private static void openMenu() throws Throwable {
unlockSignal();
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
-
- TestUtils.waitUntil("Timed out before menu could appear.",
- TIMEOUT_UI_CHANGE_S,
- () -> {
- if (isMenuVisible()) {
- return true;
- } else {
- unlockSignal();
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
- return false;
- }
- });
+ if (!isMenuVisible()) {
+ sInstrumentation.getTargetContext().sendBroadcast(INTENT_OPEN_MENU);
+ sUiDevice.waitForIdle();
+ WaitUtils.ensureThat("Accessibility Menu is visible", () -> isMenuVisible());
+ }
}
private static void closeMenu() throws Throwable {
@@ -342,7 +341,9 @@
sUiAutomation.executeAndWaitForEvent(
() -> assistantButton.performAction(CLICK_ID),
- (event) -> expectedPackage.contains(event.getPackageName()),
+ (event) ->
+ event.getPackageName() != null
+ && expectedPackage.contains(event.getPackageName()),
TIMEOUT_UI_CHANGE_S * 1000
);
}
@@ -358,7 +359,9 @@
sUiAutomation.executeAndWaitForEvent(
() -> settingsButton.performAction(CLICK_ID),
- (event) -> expectedPackage.contains(event.getPackageName()),
+ (event) ->
+ event.getPackageName() != null
+ && expectedPackage.contains(event.getPackageName()),
TIMEOUT_UI_CHANGE_S * 1000
);
}
@@ -454,24 +457,40 @@
}
@Test
- @Ignore("Test failure in pre/postsubmit cannot be replicated on local devices. "
- + "Coverage is low-impact.")
public void testOnScreenLock_cannotOpenMenu() throws Throwable {
closeScreen();
wakeUpScreen();
+ sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
+ sUiDevice.waitForIdle();
TestUtils.waitUntil("Did not receive signal that menu cannot open",
TIMEOUT_UI_CHANGE_S,
- () -> {
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
- return sOpenBlocked.get();
- });
+ sOpenBlocked::get);
}
- private static void unlockSignal() {
- // MENU unlocks screen,
- // BACK closes any menu that may appear if the screen wasn't locked.
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU");
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+ private static void unlockSignal() throws RemoteException {
+ if (!sKeyguardManager.isKeyguardLocked()) {
+ return;
+ }
+ // go/adb-cheats#unlock-screen
+ wakeUpScreen();
+ if (sKeyguardManager.isKeyguardLocked()) {
+ sUiDevice.pressMenu();
+ }
+ WaitUtils.ensureThat(
+ "Device unlocked & isInteractive",
+ () -> isScreenOn() && !sKeyguardManager.isKeyguardLocked());
+ }
+
+ private static boolean isScreenOn() {
+ int display = Display.DEFAULT_DISPLAY;
+ return sPowerManager.isInteractive(display)
+ && sDisplayManager.getDisplay(display).getState() == Display.STATE_ON;
+ }
+
+ private static boolean isScreenOff() {
+ int display = Display.DEFAULT_DISPLAY;
+ return !sPowerManager.isInteractive(display)
+ && sDisplayManager.getDisplay(display).getState() == Display.STATE_OFF;
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 338987a..7d56a67 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,6 +20,8 @@
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
import android.widget.FrameLayout
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
@@ -115,8 +117,6 @@
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import androidx.window.layout.WindowMetricsCalculator
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -300,7 +300,7 @@
viewModel.onHidePopup()
viewModel.onOpenWidgetEditor(selectedKey.value)
},
- onHide = { viewModel.onHidePopup()}
+ onHide = { viewModel.onHidePopup() }
)
}
null -> {}
@@ -374,7 +374,7 @@
liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
// Scroll if current position is behind the first updated content
- if (indexOfFirstUpdatedContent in 0..<gridState.firstVisibleItemIndex) {
+ if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
// Launching with a scope to prevent the job from being canceled in the case of a
// recomposition during scrolling
coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
@@ -841,6 +841,8 @@
widgetConfigurator: WidgetConfigurator?,
modifier: Modifier = Modifier,
) {
+ val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+
Box(
modifier =
modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
@@ -865,6 +867,16 @@
setPadding(0)
}
},
+ update = {
+ it.apply {
+ importantForAccessibility =
+ if (isFocusable) {
+ IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ } else {
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ }
+ }
+ },
// For reusing composition in lazy lists.
onReset = {},
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
index f354b80..74af3ca 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -98,10 +98,10 @@
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
- return onClick != null || super.onInterceptTouchEvent(ev)
+ return (isSliceViewClickable && onClick != null) || super.onInterceptTouchEvent(ev)
}
override fun onClick(v: View?) {
- onClick?.let { it() } ?: super.onClick(v)
+ onClick?.takeIf { isSliceViewClickable }?.let { it() } ?: super.onClick(v)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 4f3a6c8..874c0a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -38,6 +38,8 @@
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
@@ -79,6 +81,12 @@
modifier =
Modifier.fillMaxSize().padding(8.dp).semantics {
role = Role.Switch
+ toggleableState =
+ if (viewModel.isActive) {
+ ToggleableState.On
+ } else {
+ ToggleableState.Off
+ }
contentDescription = label
},
onClick = { onCheckedChange(!viewModel.isActive) },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index c96a8ce..9e9a002 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -24,17 +24,21 @@
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -45,8 +49,14 @@
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -63,6 +73,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -94,6 +105,8 @@
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var userRepository: FakeUserRepository
private lateinit var shadeTestUtil: ShadeTestUtil
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var communalRepository: FakeCommunalRepository
private lateinit var underTest: CommunalViewModel
@@ -106,12 +119,14 @@
MockitoAnnotations.initMocks(this)
keyguardRepository = kosmos.fakeKeyguardRepository
+ keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
tutorialRepository = kosmos.fakeCommunalTutorialRepository
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
userRepository = kosmos.fakeUserRepository
shadeTestUtil = kosmos.shadeTestUtil
+ communalRepository = kosmos.fakeCommunalRepository
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
@@ -125,6 +140,7 @@
underTest =
CommunalViewModel(
testScope,
+ kosmos.keyguardTransitionInteractor,
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
@@ -326,6 +342,105 @@
assertThat(underTest.canChangeScene()).isFalse()
}
+ @Test
+ fun isFocusable_isFalse_whenTransitioningAwayFromGlanceableHub() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // Shade not expanded.
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Open bouncer.
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+ assertThat(isFocusable).isEqualTo(false)
+
+ // Transitioned to bouncer.
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenNotOnCommunalScene() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ // Transitioned away from communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Blank))
+ )
+
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
+ @Test
+ fun isFocusable_isTrue_whenIdleOnCommunal_andShadeNotExpanded() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ // Shade not expanded.
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+
+ assertThat(isFocusable).isEqualTo(true)
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenQsIsExpanded() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ // Qs is expanded.
+ shadeTestUtil.setQsExpansion(1f)
+
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
whenever(user.isMain).thenReturn(isMainUser)
userRepository.setUserInfos(listOf(user))
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 511bdc4..a081ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -37,8 +37,8 @@
) {
val currentScene: Flow<SceneKey> = communalInteractor.desiredScene
- /** Whether communal hub can be focused to enable accessibility actions. */
- val isFocusable: Flow<Boolean> = communalInteractor.isIdleOnCommunal
+ /** Whether communal hub can be focused by accessibility tools. */
+ open val isFocusable: Flow<Boolean> = MutableStateFlow(false)
/** Whether widgets are currently being re-ordered. */
open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 9dacf8c..24ea7b6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -22,6 +22,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -54,6 +56,7 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@@ -93,6 +96,18 @@
private val _currentPopup: MutableStateFlow<PopupType?> = MutableStateFlow(null)
override val currentPopup: Flow<PopupType?> = _currentPopup.asStateFlow()
+ // The widget is focusable for accessibility when the hub is fully visible and shade is not
+ // opened.
+ override val isFocusable: Flow<Boolean> =
+ combine(
+ keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+ communalInteractor.isIdleOnCommunal,
+ shadeInteractor.isAnyFullyExpanded,
+ ) { transitionedToGlanceableHub, isIdleOnCommunal, isAnyFullyExpanded ->
+ transitionedToGlanceableHub && isIdleOnCommunal && !isAnyFullyExpanded
+ }
+ .distinctUntilChanged()
+
private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
index 840c3a8..0fe9bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -22,8 +22,10 @@
import android.graphics.Rect
import android.view.View
import android.view.ViewOutlineProvider
+import android.view.accessibility.AccessibilityNodeInfo
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
+import com.android.systemui.res.R
/** AppWidgetHostView that displays in communal hub with support for rounded corners. */
class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView {
@@ -42,6 +44,25 @@
init {
enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context)
enforcedRectangle = Rect()
+
+ accessibilityDelegate =
+ object : AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ // Hint user to long press in order to enter edit mode
+ info.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+ context.getString(
+ R.string.accessibility_action_label_edit_widgets
+ ).lowercase()
+ )
+ )
+ }
+ }
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index d5b05ef..60469c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -116,8 +116,6 @@
public class InternetDialogController implements AccessPointController.AccessPointCallback {
private static final String TAG = "InternetDialogController";
- private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
- "android.settings.NETWORK_PROVIDER_SETTINGS";
private static final String ACTION_WIFI_SCANNING_SETTINGS =
"android.settings.WIFI_SCANNING_SETTINGS";
/**
@@ -361,7 +359,8 @@
@VisibleForTesting
protected Intent getSettingsIntent() {
- return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return new Intent(Settings.ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK);
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index 0dcbe9b2..229bdce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -26,8 +26,9 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.annotations.WeaklyReferencedCallback;
@@ -118,7 +119,7 @@
private final Intent mServiceIntent;
private final UserTracker mUserTracker;
private final int mFlags;
- private final Executor mExecutor;
+ private final Executor mBgExecutor;
private final ServiceTransformer<T> mTransformer;
private final ArrayList<WeakReference<Callback<T>>> mCallbacks;
private Optional<Integer> mLastDisconnectReason;
@@ -130,30 +131,34 @@
* Default constructor for {@link ObservableServiceConnection}.
* @param context The context from which the service will be bound with.
* @param serviceIntent The intent to bind service with.
- * @param executor The executor for connection callbacks to be delivered on
+ * @param bgExecutor The executor for connection callbacks to be delivered on
* @param transformer A {@link ServiceTransformer} for transforming the resulting service
* into a desired type.
*/
@Inject
public ObservableServiceConnection(Context context, Intent serviceIntent,
UserTracker userTracker,
- @Main Executor executor,
+ @Background Executor bgExecutor,
ServiceTransformer<T> transformer) {
mContext = context;
mServiceIntent = serviceIntent;
mUserTracker = userTracker;
mFlags = Context.BIND_AUTO_CREATE;
- mExecutor = executor;
+ mBgExecutor = bgExecutor;
mTransformer = transformer;
mCallbacks = new ArrayList<>();
mLastDisconnectReason = Optional.empty();
}
/**
- * Initiate binding to the service.
- * @return {@code true} if initiating binding succeed, {@code false} otherwise.
+ * Initiate binding to the service in the background.
*/
- public boolean bind() {
+ public void bind() {
+ mBgExecutor.execute(this::bindInternal);
+ }
+
+ @WorkerThread
+ private void bindInternal() {
boolean bindResult = false;
try {
bindResult = mContext.bindServiceAsUser(mServiceIntent, this, mFlags,
@@ -166,18 +171,17 @@
if (DEBUG) {
Log.d(TAG, "bind. bound:" + bindResult);
}
- return bindResult;
}
/**
* Disconnect from the service if bound.
*/
public void unbind() {
- onDisconnected(DISCONNECT_REASON_UNBIND);
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_UNBIND));
}
/**
- * Adds a callback for receiving connection updates.
+ * Adds a callback for receiving connection updates. The callback is executed in the background.
* @param callback The {@link Callback} to receive future updates.
*/
public void addCallback(Callback<T> callback) {
@@ -185,7 +189,7 @@
Log.d(TAG, "addCallback:" + callback);
}
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
@@ -210,14 +214,15 @@
* Removes previously added callback from receiving future connection updates.
* @param callback The {@link Callback} to be removed.
*/
- public void removeCallback(Callback callback) {
+ public void removeCallback(Callback<T> callback) {
if (DEBUG) {
Log.d(TAG, "removeCallback:" + callback);
}
- mExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
+ mBgExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
}
+ @WorkerThread
private void onDisconnected(@DisconnectReason int reason) {
if (DEBUG) {
Log.d(TAG, "onDisconnected:" + reason);
@@ -240,7 +245,7 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
if (DEBUG) {
Log.d(TAG, "onServiceConnected");
}
@@ -268,7 +273,7 @@
final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
- final Callback cb = iterator.next().get();
+ final Callback<T> cb = iterator.next().get();
if (cb != null) {
applicator.accept(cb);
} else {
@@ -279,16 +284,16 @@
@Override
public void onServiceDisconnected(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
}
@Override
public void onBindingDied(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
}
@Override
public void onNullBinding(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 5979f3e..64f8246 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -48,7 +48,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final SystemClock mSystemClock;
- private final DelayableExecutor mMainExecutor;
+ private final DelayableExecutor mBgExecutor;
private final int mBaseReconnectDelayMs;
private final int mMaxReconnectAttempts;
private final int mMinConnectionDuration;
@@ -71,8 +71,8 @@
private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
- private final ObservableServiceConnection.Callback mConnectionCallback =
- new ObservableServiceConnection.Callback() {
+ private final ObservableServiceConnection.Callback<T> mConnectionCallback =
+ new ObservableServiceConnection.Callback<>() {
private long mStartTime;
@Override
@@ -95,12 +95,10 @@
}
};
- // TODO: b/326449074 - Ensure the DelayableExecutor is on the correct thread, and update the
- // qualifier (to @Main) or name (to bgExecutor) to be consistent with that.
@Inject
public PersistentConnectionManager(
SystemClock clock,
- @Background DelayableExecutor mainExecutor,
+ @Background DelayableExecutor bgExecutor,
DumpManager dumpManager,
@Named(DUMPSYS_NAME) String dumpsysName,
@Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
@@ -109,7 +107,7 @@
@Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
@Named(OBSERVER) Observer observer) {
mSystemClock = clock;
- mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
mConnection = serviceConnection;
mObserver = observer;
mDumpManager = dumpManager;
@@ -195,7 +193,7 @@
"scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
}
- mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+ mCurrentReconnectCancelable = mBgExecutor.executeDelayed(mConnectRunnable,
reconnectDelayMs);
mReconnectAttempts++;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index ee642a6..0386338 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.media.AudioManager
+import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
@@ -144,6 +145,7 @@
if (isMutedOrNoVolume) {
when (audioStream.value) {
AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+ AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off
AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
AudioManager.STREAM_RING ->
if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
@@ -158,12 +160,18 @@
R.drawable.ic_volume_off
}
AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
- else -> null
+ else -> {
+ Log.wtf(TAG, "No icon for the stream: $audioStream")
+ R.drawable.ic_volume_off
+ }
}
} else {
iconsByStream[audioStream]
+ ?: run {
+ Log.wtf(TAG, "No icon for the stream: $audioStream")
+ R.drawable.ic_music_note
+ }
}
- ?: error("No icon for the stream: $audioStream")
return Icon.Resource(iconRes, null)
}
@@ -196,4 +204,8 @@
* when using [AudioStream] directly because it expects another type.
*/
class FactoryAudioStreamWrapper(val audioStream: AudioStream)
+
+ private companion object {
+ const val TAG = "AudioStreamSliderViewModel"
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 5d34120..8d26c87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.util.service;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -118,6 +116,7 @@
connection.addCallback(mCallback);
mExecutor.runAllReady();
connection.bind();
+ mExecutor.runAllReady();
when(mTransformer.convert(eq(mBinder))).thenReturn(mResult);
@@ -143,8 +142,8 @@
when(mContext.bindServiceAsUser(eq(mIntent), eq(connection), anyInt(),
eq(UserHandle.of(MAIN_USER_ID)))).thenReturn(true);
connection.bind();
+ mExecutor.runAllReady();
connection.onServiceDisconnected(mComponentName);
-
mExecutor.runAllReady();
// Ensure proper disconnect reason reported back
@@ -157,6 +156,7 @@
clearInvocations(mContext);
// Ensure unbind after disconnect has no effect on the connection
connection.unbind();
+ mExecutor.runAllReady();
verify(mContext, never()).unbindService(eq(connection));
}
@@ -197,7 +197,8 @@
// Verify that the exception was caught and that bind returns false, and we properly
// unbind.
- assertThat(connection.bind()).isFalse();
+ connection.bind();
+ mExecutor.runAllReady();
verify(mContext).unbindService(connection);
}
@@ -212,13 +213,15 @@
.thenThrow(new SecurityException());
// Verify that bind returns false and we properly unbind.
- assertThat(connection.bind()).isFalse();
+ connection.bind();
+ mExecutor.runAllReady();
verify(mContext).unbindService(connection);
clearInvocations(mContext);
// Ensure unbind after the failed bind has no effect.
connection.unbind();
+ mExecutor.runAllReady();
verify(mContext, never()).unbindService(eq(connection));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 2017954..56e5e29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,10 +77,10 @@
import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
import android.util.SparseArray;
@@ -99,46 +99,28 @@
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.data.repository.SceneContainerRepository;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.LargeScreenHeaderHelper;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeWindowLogger;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -154,7 +136,6 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -163,13 +144,10 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
-import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.settings.SystemSettings;
@@ -207,8 +185,6 @@
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
-import kotlinx.coroutines.test.TestScope;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -227,8 +203,11 @@
import java.util.Optional;
import java.util.concurrent.Executor;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubblesTest extends SysuiTestCase {
@Mock
@@ -355,11 +334,8 @@
private Icon mAppBubbleIcon;
@Mock
private Display mDefaultDisplay;
- @Mock
- private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -376,8 +352,16 @@
private UserHandle mUser0;
private FakeBubbleProperties mBubbleProperties;
- private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
- private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+ }
+
+ public BubblesTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
@@ -402,77 +386,14 @@
FakeDeviceProvisioningRepository deviceProvisioningRepository =
- new FakeDeviceProvisioningRepository();
+ mKosmos.getFakeDeviceProvisioningRepository();
deviceProvisioningRepository.setDeviceProvisioned(true);
- FakeKeyguardRepository keyguardRepository = new FakeKeyguardRepository();
- FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
- FakeShadeRepository shadeRepository = new FakeShadeRepository();
- FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
-
- PowerInteractor powerInteractor = new PowerInteractor(
- mKosmos.getPowerRepository(),
- mKosmos.getFalsingCollector(),
- mock(ScreenOffAnimationController.class),
- mStatusBarStateController);
-
- SceneInteractor sceneInteractor = new SceneInteractor(
- mTestScope.getBackgroundScope(),
- new SceneContainerRepository(
- mTestScope.getBackgroundScope(),
- mKosmos.getFakeSceneContainerConfig(),
- mKosmos.getSceneDataSource()),
- mock(SceneLogger.class),
- mKosmos.getDeviceUnlockedInteractor());
-
- KeyguardTransitionInteractor keyguardTransitionInteractor =
- mKosmos.getKeyguardTransitionInteractor();
- KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
- keyguardRepository,
- new FakeCommandQueue(),
- powerInteractor,
- new FakeKeyguardBouncerRepository(),
- new ConfigurationInteractor(configurationRepository),
- shadeRepository,
- keyguardTransitionInteractor,
- () -> sceneInteractor,
- () -> mKosmos.getFromGoneTransitionInteractor(),
- () -> mKosmos.getSharedNotificationContainerInteractor(),
- mTestScope);
-
- mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
- mFromPrimaryBouncerTransitionInteractor =
- mKosmos.getFromPrimaryBouncerTransitionInteractor();
-
- ResourcesSplitShadeStateController splitShadeStateController =
- new ResourcesSplitShadeStateController();
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
- mShadeInteractor =
- new ShadeInteractorImpl(
- mTestScope.getBackgroundScope(),
- mKosmos.getDeviceProvisioningInteractor(),
- new FakeDisableFlagsRepository(),
- mDozeParameters,
- keyguardRepository,
- keyguardTransitionInteractor,
- powerInteractor,
- new FakeUserSetupRepository(),
- mock(UserSwitcherInteractor.class),
- new ShadeInteractorLegacyImpl(
- mTestScope.getBackgroundScope(), keyguardRepository,
- new SharedNotificationContainerInteractor(
- configurationRepository,
- mContext,
- splitShadeStateController,
- keyguardInteractor,
- deviceEntryUdfpsInteractor,
- () -> mLargeScreenHeaderHelper),
- shadeRepository
- )
- );
+ mShadeInteractor = mKosmos.getShadeInteractor();
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
@@ -2467,6 +2388,10 @@
mStateChangeCalls++;
mLastUpdate = update;
}
+
+ @Override
+ public void animateBubbleBarLocation(BubbleBarLocation location) {
+ }
}
private static class FakeBubbleProperties implements BubbleProperties {
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index e77f846f..f6885e1 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -1,11 +1,18 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
"presubmit": [
+ { "name": "tiny-framework-dump-test" },
+ { "name": "hoststubgentest" },
+ { "name": "hoststubgen-invoke-test" },
{
"name": "RavenwoodMockitoTest_device"
},
{
"name": "RavenwoodBivalentTest_device"
},
+ // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
{
"name": "SystemUIGoogleTests",
"options": [
@@ -18,6 +25,19 @@
]
}
],
+ "presubmit-large": [
+ {
+ "name": "SystemUITests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ],
"ravenwood-presubmit": [
{
"name": "RavenwoodMinimumTest",
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index b5843d0..beacde2 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -39,14 +39,18 @@
local jar=$1
local file=$2
- sed -e '1d' -e "s/^/$jar,/" $file
+ # Use sed to remove the header + prepend the jar filename.
+ sed -e '1d' -e "s/^/$jar,/" $file
}
collect_stats() {
local out="$1"
{
- echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
- dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
+ # Copy the header, with the first column appended.
+ echo -n "Jar,"
+ head -n 1 hoststubgen_framework-minus-apex_stats.csv
+
+ dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
dump "service.core" hoststubgen_services.core_stats.csv
} > "$out"
@@ -56,7 +60,10 @@
collect_apis() {
local out="$1"
{
- echo 'Jar,PackageName,ClassName,MethodName,Descriptor'
+ # Copy the header, with the first column appended.
+ echo -n "Jar,"
+ head -n 1 hoststubgen_framework-minus-apex_apis.csv
+
dump "framework-minus-apex" hoststubgen_framework-minus-apex_apis.csv
dump "service.core" hoststubgen_services.core_apis.csv
} > "$out"
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 23891d2..ec0d897 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3864,10 +3864,12 @@
final long lastTopTime = sr.app.mState.getLastTopTime();
final long constantTimeLimit = getTimeLimitForFgsType(fgsType);
final long nowUptime = SystemClock.uptimeMillis();
- if (constantTimeLimit > (nowUptime - lastTopTime)) {
+ if (lastTopTime != Long.MIN_VALUE && constantTimeLimit > (nowUptime - lastTopTime)) {
+ // Discard any other messages for this service
+ mFGSAnrTimer.discard(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
// The app was in the TOP state after the FGS was started so its time allowance
// should be counted from that time since this is considered a user interaction
- mFGSAnrTimer.discard(sr);
final Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
mAm.mHandler.sendMessageAtTime(msg, lastTopTime + constantTimeLimit);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 77654d4..da528a2 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1772,6 +1772,7 @@
@Override
public void handleMessage(Message msg) {
+ int muteCheckDelayMs = BTA2DP_MUTE_CHECK_DELAY_MS;
switch (msg.what) {
case MSG_RESTORE_DEVICES:
synchronized (mSetModeLock) {
@@ -1870,7 +1871,7 @@
btInfo.mDevice, btInfo.mProfile, btInfo.mIsLeOutput,
"MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
synchronized (mDeviceStateLock) {
- mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
+ muteCheckDelayMs += mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
codecAndChanged.first, codecAndChanged.second,
BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
@@ -2060,7 +2061,7 @@
// Give some time to Bluetooth service to post a connection message
// in case of active device switch
if (MESSAGES_MUTE_MUSIC.contains(msg.what)) {
- sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, BTA2DP_MUTE_CHECK_DELAY_MS);
+ sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, muteCheckDelayMs);
}
if (isMessageHandledUnderWakelock(msg.what)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 9bdc51e..c9612ca 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -864,9 +864,25 @@
}
}
+ // Additional delay added to the music mute duration when a codec config change is executed.
+ static final int BT_CONFIG_CHANGE_MUTE_DELAY_MS = 500;
+ /**
+ * Handles a Bluetooth link codec configuration change communicated by the Bluetooth stack.
+ * Called when either A2DP or LE Audio codec encoding or sampling rate changes:
+ * the change is communicated to native audio policy to eventually reconfigure the audio
+ * path.
+ * Also used to notify a change in preferred mode (duplex or output) for Bluetooth profiles.
+ *
+ * @param btInfo contains all information on the Bluetooth device and profile
+ * @param codec the requested audio encoding (e.g SBC)
+ * @param codecChanged true if a codec parameter changed, false for preferred mode change
+ * @param event currently only EVENT_DEVICE_CONFIG_CHANGE
+ * @return an optional additional delay in milliseconds to add to the music mute period in
+ * case of an actual codec reconfiguration.
+ */
@GuardedBy("mDeviceBroker.mDeviceStateLock")
- /*package*/ void onBluetoothDeviceConfigChange(
+ /*package*/ int onBluetoothDeviceConfigChange(
@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
@AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
boolean codecChanged, int event) {
@@ -874,10 +890,11 @@
+ "onBluetoothDeviceConfigChange")
.set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
+ int delayMs = 0;
final BluetoothDevice btDevice = btInfo.mDevice;
if (btDevice == null) {
mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
- return;
+ return delayMs;
}
if (AudioService.DEBUG_DEVICES) {
Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
@@ -899,7 +916,7 @@
.printSlog(EventLogger.Event.ALOGI, TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
.record();
- return;
+ return delayMs;
}
final String key = DeviceInfo.makeDeviceListKey(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -907,7 +924,7 @@
if (di == null) {
Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
- return;
+ return delayMs;
}
mmi.set(MediaMetrics.Property.ADDRESS, address)
@@ -915,7 +932,6 @@
.set(MediaMetrics.Property.INDEX, volume)
.set(MediaMetrics.Property.NAME, di.mDeviceName);
-
if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
if (btInfo.mProfile == BluetoothProfile.A2DP
|| btInfo.mProfile == BluetoothProfile.LE_AUDIO
@@ -943,6 +959,7 @@
+ address
+ " codec=" + AudioSystem.audioFormatToString(codec))
.printSlog(EventLogger.Event.ALOGI, TAG));
+ delayMs = BT_CONFIG_CHANGE_MUTE_DELAY_MS;
}
}
}
@@ -952,6 +969,7 @@
}
}
mmi.record();
+ return delayMs;
}
/*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index d669c8d..030ce12 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -61,6 +61,8 @@
return getSoundDoseValue();
case "reset-sound-dose-timeout":
return resetSoundDoseTimeout();
+ case "set-ringer-mode":
+ return setRingerMode();
case "set-volume":
return setVolume();
case "set-device-volume":
@@ -100,6 +102,8 @@
pw.println(" Returns the current sound dose value");
pw.println(" reset-sound-dose-timeout");
pw.println(" Resets the sound dose timeout used for momentary exposure");
+ pw.println(" set-ringer-mode NORMAL|SILENT|VIBRATE");
+ pw.println(" Sets the Ringer mode to one of NORMAL|SILENT|VIBRATE");
pw.println(" set-volume STREAM_TYPE VOLUME_INDEX");
pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX");
pw.println(" set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE");
@@ -150,6 +154,34 @@
return 0;
}
+ private int setRingerMode() {
+ String ringerModeText = getNextArg();
+ if (ringerModeText == null) {
+ getErrPrintWriter().println("Error: no ringer mode specified");
+ return 1;
+ }
+
+ final int ringerMode = getRingerMode(ringerModeText);
+ if (!AudioManager.isValidRingerMode(ringerMode)) {
+ getErrPrintWriter().println(
+ "Error: invalid value of ringerMode, should be one of NORMAL|SILENT|VIBRATE");
+ return 1;
+ }
+
+ final AudioManager am = mService.mContext.getSystemService(AudioManager.class);
+ am.setRingerModeInternal(ringerMode);
+ return 0;
+ }
+
+ private int getRingerMode(String ringerModeText) {
+ return switch (ringerModeText) {
+ case "NORMAL" -> AudioManager.RINGER_MODE_NORMAL;
+ case "VIBRATE" -> AudioManager.RINGER_MODE_VIBRATE;
+ case "SILENT" -> AudioManager.RINGER_MODE_SILENT;
+ default -> -1;
+ };
+ }
+
private int getIsSurroundFormatEnabled() {
String surroundFormatText = getNextArg();
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 4fc1a17..ad6b0ca 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"presubmit-large": [
{
- "name": "CtsHostsideNetworkTests",
+ "name": "CtsHostsideNetworkPolicyTests",
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 4747689..143bc5c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1725,7 +1725,28 @@
synchronized (mConfigLock) {
if (policy == null || mConfig == null) return;
final ZenModeConfig newConfig = mConfig.copy();
- newConfig.applyNotificationPolicy(policy);
+ if (Flags.modesApi() && !Flags.modesUi()) {
+ // Fix for b/337193321 -- propagate changes to notificationPolicy to rules where
+ // the user cannot edit zen policy to emulate the previous "inheritance".
+ ZenPolicy previousPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+ newConfig.toNotificationPolicy());
+ ZenPolicy newPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+
+ newConfig.applyNotificationPolicy(policy);
+
+ if (!previousPolicy.equals(newPolicy)) {
+ for (ZenRule rule : newConfig.automaticRules.values()) {
+ if (!SystemZenRules.isSystemOwnedRule(rule)
+ && rule.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ && (rule.zenPolicy == null || rule.zenPolicy.equals(previousPolicy)
+ || rule.zenPolicy.equals(getDefaultZenPolicy()))) {
+ rule.zenPolicy = newPolicy;
+ }
+ }
+ }
+ } else {
+ newConfig.applyNotificationPolicy(policy);
+ }
setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index c8fd7e4..8a85328 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -19,6 +19,7 @@
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
@@ -68,6 +69,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
@@ -335,14 +337,22 @@
}
static class Injector {
+ class RoleManagerWrapper {
+ List<String> getRoleHolders(@NonNull String roleName) {
+ return mContext.getSystemService(RoleManager.class).getRoleHolders(roleName);
+ }
+ }
+
Context mContext;
ArraySet<String> mAllowlistedPackages;
AtomicFile mMappingFile;
+ RoleManagerWrapper mRoleManagerWrapper;
Injector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile) {
mContext = context;
mAllowlistedPackages = allowlistedPackages;
mMappingFile = mappingFile;
+ mRoleManagerWrapper = new RoleManagerWrapper();
}
Context getContext() {
@@ -368,6 +378,10 @@
void setSystemProperty(String key, String value) {
SystemProperties.set(key, value);
}
+
+ RoleManagerWrapper getRoleManagerWrapper() {
+ return mRoleManagerWrapper;
+ }
}
BugreportManagerServiceImpl(Context context) {
@@ -546,7 +560,7 @@
if (!allowlisted) {
final long token = Binder.clearCallingIdentity();
try {
- allowlisted = mContext.getSystemService(RoleManager.class).getRoleHolders(
+ allowlisted = mInjector.getRoleManagerWrapper().getRoleHolders(
ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage);
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0f4e482..ae485ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3984,6 +3984,8 @@
// packageName -> list of components to send broadcasts now
final ArrayMap<String, ArrayList<String>> sendNowBroadcasts = new ArrayMap<>(targetSize);
+ final List<PackageMetrics.ComponentStateMetrics> componentStateMetricsList =
+ new ArrayList<PackageMetrics.ComponentStateMetrics>();
synchronized (mLock) {
Computer computer = snapshotComputer();
boolean scheduleBroadcastMessage = false;
@@ -3997,11 +3999,17 @@
// update enabled settings
final ComponentEnabledSetting setting = settings.get(i);
final String packageName = setting.getPackageName();
- if (!setEnabledSettingInternalLocked(computer, pkgSettings.get(packageName),
- setting, userId, callingPackage)) {
+ final PackageSetting packageSetting = pkgSettings.get(packageName);
+ final PackageMetrics.ComponentStateMetrics componentStateMetrics =
+ new PackageMetrics.ComponentStateMetrics(setting,
+ UserHandle.getUid(userId, packageSetting.getAppId()),
+ packageSetting.getEnabled(userId));
+ if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
+ callingPackage)) {
continue;
}
anyChanged = true;
+ componentStateMetricsList.add(componentStateMetrics);
if ((setting.getEnabledFlags() & PackageManager.SYNCHRONOUS) != 0) {
isSynchronous = true;
@@ -4029,6 +4037,9 @@
return;
}
+ // Log the metrics when the component state is changed.
+ PackageMetrics.reportComponentStateChanged(computer, componentStateMetricsList, userId);
+
if (isSynchronous) {
flushPackageRestrictionsAsUserInternalLocked(userId);
} else {
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index a0b6897..20598f9 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,12 +19,21 @@
import static android.os.Process.INVALID_UID;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.admin.SecurityLog;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
@@ -41,12 +50,14 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* Metrics class for reporting stats to logging infrastructures like statsd
*/
final class PackageMetrics {
+ private static final String TAG = "PackageMetrics";
public static final int STEP_PREPARE = 1;
public static final int STEP_SCAN = 2;
public static final int STEP_RECONCILE = 3;
@@ -344,4 +355,76 @@
SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UNINSTALLED, packageName, versionCode,
userId);
}
+
+ public static class ComponentStateMetrics {
+ public int mUid;
+ public int mComponentOldState;
+ public int mComponentNewState;
+ public boolean mIsForWholeApp;
+ @NonNull private String mPackageName;
+ @Nullable private String mClassName;
+
+ ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid,
+ int componentOldState) {
+ mUid = uid;
+ mComponentOldState = componentOldState;
+ mComponentNewState = setting.getEnabledState();
+ mIsForWholeApp = !setting.isComponent();
+ mPackageName = setting.getPackageName();
+ mClassName = setting.getClassName();
+ }
+
+ public boolean isSameComponent(ActivityInfo activityInfo) {
+ if (activityInfo == null) {
+ return false;
+ }
+ return mIsForWholeApp ? TextUtils.equals(activityInfo.packageName, mPackageName)
+ : activityInfo.getComponentName().equals(
+ new ComponentName(mPackageName, mClassName));
+ }
+ }
+
+ public static void reportComponentStateChanged(@NonNull Computer computer,
+ List<ComponentStateMetrics> componentStateMetricsList, @UserIdInt int userId) {
+ if (!Flags.componentStateChangedMetrics()) {
+ return;
+ }
+ if (componentStateMetricsList == null || componentStateMetricsList.isEmpty()) {
+ Slog.d(TAG, "Fail to report component state due to metrics is empty");
+ return;
+ }
+ boolean isLauncher = false;
+ final List<ResolveInfo> resolveInfosForLauncher = getHomeActivitiesResolveInfoAsUser(
+ computer, userId);
+ final int resolveInfosForLauncherSize =
+ resolveInfosForLauncher != null ? resolveInfosForLauncher.size() : 0;
+ final int metricsSize = componentStateMetricsList.size();
+ for (int i = 0; i < metricsSize; i++) {
+ final ComponentStateMetrics componentStateMetrics = componentStateMetricsList.get(i);
+ for (int j = 0; j < resolveInfosForLauncherSize; j++) {
+ ResolveInfo resolveInfo = resolveInfosForLauncher.get(j);
+ if (componentStateMetrics.isSameComponent(resolveInfo.activityInfo)) {
+ isLauncher = true;
+ break;
+ }
+ }
+ reportComponentStateChanged(componentStateMetrics.mUid,
+ componentStateMetrics.mComponentOldState,
+ componentStateMetrics.mComponentNewState,
+ isLauncher,
+ componentStateMetrics.mIsForWholeApp);
+ }
+ }
+
+ private static void reportComponentStateChanged(int uid, int componentOldState,
+ int componentNewState, boolean isLauncher, boolean isForWholeApp) {
+ FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
+ uid, componentOldState, componentNewState, isLauncher, isForWholeApp);
+ }
+
+ private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
+ @UserIdInt int userId) {
+ return computer.queryIntentActivitiesInternal(computer.getHomeIntent(), /* resolvedType */
+ null, /* flags */ 0, userId);
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ff75736..d555f1a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5673,18 +5673,15 @@
private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token,
int flags, CallerIdentity caller) {
- final boolean isPin = PasswordMetrics.isNumericOnly(password);
- try (LockscreenCredential newCredential =
- isPin ? LockscreenCredential.createPin(password) :
- LockscreenCredential.createPasswordOrNone(password)) {
- return resetPasswordInternal(newCredential, tokenHandle, token, flags, caller);
- }
- }
-
- private boolean resetPasswordInternal(LockscreenCredential newCredential,
- long tokenHandle, byte[] token, int flags, CallerIdentity caller) {
final int callingUid = caller.getUid();
final int userHandle = UserHandle.getUserId(callingUid);
+ final boolean isPin = PasswordMetrics.isNumericOnly(password);
+ final LockscreenCredential newCredential;
+ if (isPin) {
+ newCredential = LockscreenCredential.createPin(password);
+ } else {
+ newCredential = LockscreenCredential.createPasswordOrNone(password);
+ }
synchronized (getLockObject()) {
final PasswordMetrics minMetrics = getPasswordMinimumMetricsUnchecked(userHandle);
final int complexity = getAggregatedPasswordComplexityLocked(userHandle);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index 23314cd..1322545 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -20,12 +20,17 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
import android.os.Looper;
import android.platform.test.annotations.EnableFlags;
import android.service.dreams.DreamService;
@@ -41,6 +46,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -83,6 +89,18 @@
assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
}
+ @Test
+ public void testMetadataParsing_exceptionReading() {
+ final PackageManager packageManager = Mockito.mock(PackageManager.class);
+ final ServiceInfo serviceInfo = Mockito.mock(ServiceInfo.class);
+ final TypedArray rawMetadata = Mockito.mock(TypedArray.class);
+ when(packageManager.extractPackageItemInfoAttributes(eq(serviceInfo), any(), any(), any()))
+ .thenReturn(rawMetadata);
+ when(rawMetadata.getString(anyInt())).thenThrow(new RuntimeException("failure"));
+
+ assertThat(DreamService.getDreamMetadata(packageManager, serviceInfo)).isNull();
+ }
+
private DreamService.DreamMetadata getDreamMetadata(String dreamClassName)
throws PackageManager.NameNotFoundException {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index a6f2196..9862663 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,8 +16,6 @@
package com.android.server.os;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -25,9 +23,9 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.admin.flags.Flags;
-import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -61,6 +59,8 @@
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -104,7 +104,7 @@
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
mAllowlistedPackages.add(mContext.getPackageName());
mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
- mMockUserManager, mMockDevicePolicyManager);
+ mMockUserManager, mMockDevicePolicyManager, null);
mService = new BugreportManagerServiceImpl(mInjector);
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
@@ -114,24 +114,8 @@
@After
public void tearDown() throws Exception {
- // Changes to RoleManager persist between tests, so we need to clear out any funny
- // business we did in previous tests.
+ // Clean up the mapping file between tests since it would otherwise persist.
mMappingFile.delete();
- RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- CallbackFuture future = new CallbackFuture();
- runWithShellPermissionIdentity(
- () -> {
- roleManager.setBypassingRoleQualification(false);
- roleManager.removeRoleHolderAsUser(
- "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
- mContext.getPackageName(),
- /* flags= */ 0,
- Process.myUserHandle(),
- mContext.getMainExecutor(),
- future);
- });
-
- assertThat(future.get()).isEqualTo(true);
}
@Test
@@ -267,7 +251,10 @@
@Test
public void testCancelBugreportWithoutRole() {
- clearAllowlist();
+ // Create a new service to clear the allowlist
+ mService = new BugreportManagerServiceImpl(
+ new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager, null));
assertThrows(SecurityException.class, () -> mService.cancelBugreport(
Binder.getCallingUid(), mContext.getPackageName()));
@@ -275,29 +262,13 @@
@Test
public void testCancelBugreportWithRole() throws Exception {
- clearAllowlist();
- RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- CallbackFuture future = new CallbackFuture();
- runWithShellPermissionIdentity(
- () -> {
- roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(
- "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
- mContext.getPackageName(),
- /* flags= */ 0,
- Process.myUserHandle(),
- mContext.getMainExecutor(),
- future);
- });
-
- assertThat(future.get()).isEqualTo(true);
- mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
- }
-
- private void clearAllowlist() {
+ // Create a new service to clear the allowlist, but override the role manager
mService = new BugreportManagerServiceImpl(
new TestInjector(mContext, new ArraySet<>(), mMappingFile,
- mMockUserManager, mMockDevicePolicyManager));
+ mMockUserManager, mMockDevicePolicyManager,
+ "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"));
+
+ mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
}
private static class Listener implements IDumpstateListener {
@@ -359,10 +330,22 @@
private boolean mBugreportStarted = false;
TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
- UserManager um, DevicePolicyManager dpm) {
+ UserManager um, DevicePolicyManager dpm, String grantedRole) {
super(context, allowlistedPackages, mappingFile);
mUserManager = um;
mDevicePolicyManager = dpm;
+
+ if (grantedRole != null) {
+ mRoleManagerWrapper =
+ new BugreportManagerServiceImpl.Injector.RoleManagerWrapper() {
+ @Override
+ List<String> getRoleHolders(@NonNull String roleName) {
+ return roleName.equals(grantedRole)
+ ? Collections.singletonList(mContext.getPackageName())
+ : Collections.emptyList();
+ }
+ };
+ }
}
@Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 9559a25..5fdb396 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -6095,6 +6095,67 @@
assertThat(readPolicy.allowConversations()).isFalse();
}
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ public void setNotificationPolicy_updatesRulePolicies_ifRulePolicyIsDefaultOrGlobalPolicy() {
+ ZenPolicy defaultZenPolicy = mZenModeHelper.getDefaultZenPolicy();
+ Policy previousManualPolicy = mZenModeHelper.mConfig.toNotificationPolicy();
+ ZenPolicy previousManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+ previousManualPolicy);
+ ZenPolicy customZenPolicy = new ZenPolicy.Builder(defaultZenPolicy).allowConversations(
+ CONVERSATION_SENDERS_ANYONE).build();
+
+ mZenModeHelper.mConfig.automaticRules.clear();
+ addZenRule(mZenModeHelper.mConfig, "appWithDefault", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithSameAsManual", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithCustom", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, customZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithOtherFilter", "app.pkg",
+ ZEN_MODE_ALARMS, null);
+ addZenRule(mZenModeHelper.mConfig, "systemWithDefault", "android",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "systemWithSameAsManual", "android",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+
+ Policy newManualPolicy = new Policy(PRIORITY_CATEGORY_EVENTS, 0, 0);
+ mZenModeHelper.setNotificationPolicy(newManualPolicy, UPDATE_ORIGIN_USER, 0);
+ ZenPolicy newManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(newManualPolicy);
+
+ // Only app rules with default or same-as-manual policies were updated.
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithDefault").zenPolicy)
+ .isEqualTo(newManualZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithSameAsManual").zenPolicy)
+ .isEqualTo(newManualZenPolicy);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithCustom").zenPolicy)
+ .isEqualTo(customZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithOtherFilter").zenPolicy)
+ .isNull();
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithDefault").zenPolicy)
+ .isEqualTo(defaultZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithSameAsManual").zenPolicy)
+ .isEqualTo(previousManualZenPolicy);
+ }
+
+ private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
+ @Nullable ZenPolicy zenPolicy) {
+ ZenRule rule = new ZenRule();
+ rule.id = id;
+ rule.pkg = ownerPkg;
+ rule.enabled = true;
+ rule.zenMode = zenMode;
+ rule.zenPolicy = zenPolicy;
+ // Plus stuff so that isValidAutomaticRule() passes
+ rule.name = String.format("Rule %s from %s with mode=%s and policy=%s", id, ownerPkg,
+ zenMode, zenPolicy);
+ rule.conditionId = Uri.parse(rule.name);
+
+ config.automaticRules.put(id, rule);
+ }
+
private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
Correspondence.transforming(zr -> {
Parcel p = Parcel.obtain();
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index eca258c..f6885e1 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,13 +1,63 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
"presubmit": [
{ "name": "tiny-framework-dump-test" },
{ "name": "hoststubgentest" },
- { "name": "hoststubgen-invoke-test" }
+ { "name": "hoststubgen-invoke-test" },
+ {
+ "name": "RavenwoodMockitoTest_device"
+ },
+ {
+ "name": "RavenwoodBivalentTest_device"
+ },
+ // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
+ {
+ "name": "SystemUIGoogleTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "SystemUITests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
],
"ravenwood-presubmit": [
{
+ "name": "RavenwoodMinimumTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodMockitoTest",
+ "host": true
+ },
+ {
"name": "CtsUtilTestCasesRavenwood",
"host": true
+ },
+ {
+ "name": "RavenwoodCoreTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodBivalentTest",
+ "host": true
}
]
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 2f432cc..7212beb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -16,6 +16,7 @@
package com.android.hoststubgen
import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.dumper.ApiDumper
import com.android.hoststubgen.filters.AnnotationBasedFilter
import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
import com.android.hoststubgen.filters.ConstantFilter
@@ -89,7 +90,11 @@
log.i("Dump file created at $it")
}
options.apiListFile.ifSet {
- PrintWriter(it).use { pw -> stats.dumpApis(pw) }
+ PrintWriter(it).use { pw ->
+ // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
+ // framework-minus-apex.jar so that we can dump inherited methods from it.
+ ApiDumper(pw, allClasses, null, filter).dump()
+ }
log.i("API list file created at $it")
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
index da61469..9045db2 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
@@ -15,7 +15,8 @@
*/
package com.android.hoststubgen
-import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.asm.getOuterClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
import com.android.hoststubgen.filters.FilterPolicyWithReason
import org.objectweb.asm.Opcodes
import java.io.PrintWriter
@@ -55,8 +56,8 @@
// Ignore methods where policy isn't relevant
if (policy.isIgnoredForStats) return
- val packageName = resolvePackageName(fullClassName)
- val className = resolveOuterClassName(fullClassName)
+ val packageName = getPackageNameFromFullClassName(fullClassName)
+ val className = getOuterClassNameFromFullClassName(fullClassName)
// Ignore methods for certain generated code
if (className.endsWith("Proto")
@@ -88,42 +89,4 @@
}
}
}
-
- fun dumpApis(pw: PrintWriter) {
- pw.printf("PackageName,ClassName,MethodName,MethodDesc\n")
- apis.sortedWith(compareBy({ it.fullClassName }, { it.methodName }, { it.methodDesc }))
- .forEach { api ->
- pw.printf(
- "%s,%s,%s,%s\n",
- csvEscape(resolvePackageName(api.fullClassName)),
- csvEscape(resolveClassName(api.fullClassName)),
- csvEscape(api.methodName),
- csvEscape(api.methodDesc),
- )
- }
- }
-
- private fun resolvePackageName(fullClassName: String): String {
- val start = fullClassName.lastIndexOf('/')
- return fullClassName.substring(0, start).toHumanReadableClassName()
- }
-
- private fun resolveOuterClassName(fullClassName: String): String {
- val start = fullClassName.lastIndexOf('/')
- val end = fullClassName.indexOf('$')
- if (end == -1) {
- return fullClassName.substring(start + 1)
- } else {
- return fullClassName.substring(start + 1, end)
- }
- }
-
- private fun resolveClassName(fullClassName: String): String {
- val pos = fullClassName.lastIndexOf('/')
- if (pos == -1) {
- return fullClassName
- } else {
- return fullClassName.substring(pos + 1)
- }
- }
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 83e122f..b8d1800 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -76,17 +76,45 @@
return null
}
-private val removeLastElement = """[./][^./]*$""".toRegex()
+val periodOrSlash = charArrayOf('.', '/')
-fun getPackageNameFromClassName(className: String): String {
- return className.replace(removeLastElement, "")
+fun getPackageNameFromFullClassName(fullClassName: String): String {
+ val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+ if (pos == -1) {
+ return ""
+ } else {
+ return fullClassName.substring(0, pos)
+ }
}
-fun resolveClassName(className: String, packageName: String): String {
+fun getClassNameFromFullClassName(fullClassName: String): String {
+ val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+ if (pos == -1) {
+ return fullClassName
+ } else {
+ return fullClassName.substring(pos + 1)
+ }
+}
+
+fun getOuterClassNameFromFullClassName(fullClassName: String): String {
+ val start = fullClassName.lastIndexOfAny(periodOrSlash)
+ val end = fullClassName.indexOf('$')
+ if (end == -1) {
+ return fullClassName.substring(start + 1)
+ } else {
+ return fullClassName.substring(start + 1, end)
+ }
+}
+
+/**
+ * If [className] is a fully qualified name, just return it.
+ * Otherwise, prepend [defaultPackageName].
+ */
+fun resolveClassNameWithDefaultPackage(className: String, defaultPackageName: String): String {
if (className.contains('.') || className.contains('/')) {
return className
}
- return "$packageName.$className"
+ return "$defaultPackageName.$className"
}
fun String.toJvmClassName(): String {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
new file mode 100644
index 0000000..aaefee4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 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.hoststubgen.dumper
+
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.CTOR_NAME
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.getClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.csvEscape
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.log
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.ClassNode
+import java.io.PrintWriter
+
+/**
+ * Dump all the API methods in [classes], with inherited methods, with their policies.
+ */
+class ApiDumper(
+ val pw: PrintWriter,
+ val classes: ClassNodes,
+ val frameworkClasses: ClassNodes?,
+ val filter: OutputFilter,
+) {
+ private data class MethodKey(
+ val name: String,
+ val descriptor: String,
+ )
+
+ val javaStandardApiPolicy = FilterPolicy.Stub.withReason("Java standard API")
+
+ private val shownMethods = mutableSetOf<MethodKey>()
+
+ /**
+ * Do the dump.
+ */
+ fun dump() {
+ pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" +
+ ",Supported,Policy,Reason\n")
+
+ classes.forEach { classNode ->
+ shownMethods.clear()
+ dump(classNode, classNode)
+ }
+ }
+
+ private fun dumpMethod(
+ classPackage: String,
+ className: String,
+ isSuperClass: Boolean,
+ methodClassName: String,
+ methodName: String,
+ methodDesc: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ pw.printf(
+ "%s,%s,%d,%s,%s,%s,%d,%s,%s\n",
+ csvEscape(classPackage),
+ csvEscape(className),
+ if (isSuperClass) { 1 } else { 0 },
+ csvEscape(methodClassName),
+ csvEscape(methodName),
+ csvEscape(methodDesc),
+ if (policy.policy.isSupported) { 1 } else { 0 },
+ policy.policy,
+ csvEscape(policy.reason),
+ )
+ }
+
+ private fun isDuplicate(methodName: String, methodDesc: String): Boolean {
+ val methodKey = MethodKey(methodName, methodDesc)
+
+ if (shownMethods.contains(methodKey)) {
+ return true
+ }
+ shownMethods.add(methodKey)
+ return false
+ }
+
+ private fun dump(
+ dumpClass: ClassNode,
+ methodClass: ClassNode,
+ ) {
+ val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+ val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+ val isSuperClass = dumpClass != methodClass
+
+ methodClass.methods?.sortedWith(compareBy({ it.name }, { it.desc }))?.forEach { method ->
+
+ // Don't print ctor's from super classes.
+ if (isSuperClass) {
+ if (CTOR_NAME == method.name || CLASS_INITIALIZER_NAME == method.name) {
+ return@forEach
+ }
+ }
+ // If we already printed the method from a subclass, don't print it.
+ if (isDuplicate(method.name, method.desc)) {
+ return@forEach
+ }
+
+ val policy = filter.getPolicyForMethod(methodClass.name, method.name, method.desc)
+
+ // Let's skip "Remove" APIs. Ideally we want to print it, just to make the CSV
+ // complete, we still need to hide methods substituted (== @RavenwoodReplace) methods
+ // and for now we don't have an easy way to detect it.
+ if (policy.policy == FilterPolicy.Remove) {
+ return@forEach
+ }
+
+ val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc)
+
+ dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(),
+ renameTo ?: method.name, method.desc, policy)
+ }
+
+ // Dump super class methods.
+ dumpSuper(dumpClass, methodClass.superName)
+
+ // Dump interface methods (which may have default methods).
+ methodClass.interfaces?.sorted()?.forEach { interfaceName ->
+ dumpSuper(dumpClass, interfaceName)
+ }
+ }
+
+ /**
+ * Dump a given super class / interface.
+ */
+ private fun dumpSuper(
+ dumpClass: ClassNode,
+ methodClassName: String,
+ ) {
+ classes.findClass(methodClassName)?.let { methodClass ->
+ dump(dumpClass, methodClass)
+ return
+ }
+ frameworkClasses?.findClass(methodClassName)?.let { methodClass ->
+ dump(dumpClass, methodClass)
+ return
+ }
+ if (methodClassName.startsWith("java/") ||
+ methodClassName.startsWith("javax/")
+ ) {
+ dumpStandardClass(dumpClass, methodClassName)
+ return
+ }
+ log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.")
+ }
+
+ /**
+ * Dump methods from Java standard classes.
+ */
+ private fun dumpStandardClass(
+ dumpClass: ClassNode,
+ methodClassName: String,
+ ) {
+ val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+ val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+ val methodClassName = methodClassName.toHumanReadableClassName()
+
+ try {
+ val clazz = Class.forName(methodClassName)
+
+ // Method.getMethods() returns only public methods, but with inherited ones.
+ // Method.getDeclaredMethods() returns private methods too, but no inherited methods.
+ //
+ // Since we're only interested in public ones, just use getMethods().
+ clazz.methods.forEach { method ->
+ val methodName = method.name
+ val methodDesc = Type.getMethodDescriptor(method)
+
+ // If we already printed the method from a subclass, don't print it.
+ if (isDuplicate(methodName, methodDesc)) {
+ return@forEach
+ }
+
+ dumpMethod(pkg, cls, true, methodClassName,
+ methodName, methodDesc, javaStandardApiPolicy)
+ }
+ } catch (e: ClassNotFoundException) {
+ log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.")
+ }
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 45e140c..6643492 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -20,8 +20,8 @@
import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.UnifiedVisitor
-import com.android.hoststubgen.asm.getPackageNameFromClassName
-import com.android.hoststubgen.asm.resolveClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage
import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.FilterPolicyWithReason
@@ -89,7 +89,7 @@
) {
super.visit(version, access, name, signature, superName, interfaces)
currentClassName = name
- currentPackageName = getPackageNameFromClassName(name)
+ currentPackageName = getPackageNameFromFullClassName(name)
classPolicy = filter.getPolicyForClass(currentClassName)
log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName)
@@ -98,7 +98,8 @@
log.indent()
filter.getNativeSubstitutionClass(currentClassName)?.let { className ->
- val fullClassName = resolveClassName(className, currentPackageName).toJvmClassName()
+ val fullClassName = resolveClassNameWithDefaultPackage(className, currentPackageName)
+ .toJvmClassName()
log.d(" NativeSubstitutionClass: $fullClassName")
if (classes.findClass(fullClassName) == null) {
log.w("Native substitution class $fullClassName not found. Class must be " +