diff options
65 files changed, 2004 insertions, 479 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bee5011da657..975ec5430478 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -127,6 +127,7 @@ import android.os.GraphicsEnvironment; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; +import android.os.IBinderCallback; import android.os.ICancellationSignal; import android.os.LocaleList; import android.os.Looper; @@ -358,6 +359,15 @@ public final class ActivityThread extends ClientTransactionHandler /** Maps from activity token to the pending override configuration. */ @GuardedBy("mPendingOverrideConfigs") private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>(); + + /** + * A queue of pending ApplicationInfo updates. In case when we get a concurrent update + * this queue allows us to only apply the latest object, and it can be applied on demand + * instead of waiting for the handler thread to reach the scheduled callback. + */ + @GuardedBy("mResourcesManager") + private final ArrayMap<String, ApplicationInfo> mPendingAppInfoUpdates = new ArrayMap<>(); + /** The activities to be truly destroyed (not include relaunch). */ final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed = Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>()); @@ -1259,9 +1269,19 @@ public final class ActivityThread extends ClientTransactionHandler } public void scheduleApplicationInfoChanged(ApplicationInfo ai) { + synchronized (mResourcesManager) { + var oldAi = mPendingAppInfoUpdates.put(ai.packageName, ai); + if (oldAi != null && oldAi.createTimestamp > ai.createTimestamp) { + Slog.w(TAG, "Skipping application info changed for obsolete AI with TS " + + ai.createTimestamp + " < already pending TS " + + oldAi.createTimestamp); + mPendingAppInfoUpdates.put(ai.packageName, oldAi); + return; + } + } mResourcesManager.appendPendingAppInfoUpdate(new String[]{ai.sourceDir}, ai); - mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai); - sendMessage(H.APPLICATION_INFO_CHANGED, ai); + mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai.packageName); + sendMessage(H.APPLICATION_INFO_CHANGED, ai.packageName); } public void updateTimeZone() { @@ -2436,7 +2456,7 @@ public final class ActivityThread extends ClientTransactionHandler break; } case APPLICATION_INFO_CHANGED: - handleApplicationInfoChanged((ApplicationInfo) msg.obj); + applyPendingApplicationInfoChanges((String) msg.obj); break; case RUN_ISOLATED_ENTRY_POINT: handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1, @@ -3918,7 +3938,8 @@ public final class ActivityThread extends ClientTransactionHandler mProfiler.startProfiling(); } - // Make sure we are running with the most recent config. + // Make sure we are running with the most recent config and resource paths. + applyPendingApplicationInfoChanges(r.activityInfo.packageName); mConfigurationController.handleConfigurationChanged(null, null); updateDeviceIdForNonUIContexts(deviceId); @@ -6244,6 +6265,17 @@ public final class ActivityThread extends ClientTransactionHandler r.mLastReportedWindowingMode = newWindowingMode; } + private void applyPendingApplicationInfoChanges(String packageName) { + final ApplicationInfo ai; + synchronized (mResourcesManager) { + ai = mPendingAppInfoUpdates.remove(packageName); + } + if (ai == null) { + return; + } + handleApplicationInfoChanged(ai); + } + /** * Updates the application info. * @@ -6269,6 +6301,16 @@ public final class ActivityThread extends ClientTransactionHandler apk = ref != null ? ref.get() : null; ref = mResourcePackages.get(ai.packageName); resApk = ref != null ? ref.get() : null; + for (ActivityClientRecord ar : mActivities.values()) { + if (ar.activityInfo.applicationInfo.packageName.equals(ai.packageName)) { + ar.activityInfo.applicationInfo = ai; + if (apk != null || resApk != null) { + ar.packageInfo = apk != null ? apk : resApk; + } else { + apk = ar.packageInfo; + } + } + } } if (apk != null) { @@ -7051,6 +7093,18 @@ public final class ActivityThread extends ClientTransactionHandler } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } + + // Set binder transaction callback after finishing bindApplication + Binder.setTransactionCallback(new IBinderCallback() { + @Override + public void onTransactionError(int pid, int code, int flags, int err) { + try { + mgr.frozenBinderTransactionDetected(pid, code, flags, err); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + }); } @UnsupportedAppUsage diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index d859f3f9e175..24cb9ea87a12 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -460,6 +460,33 @@ public final class ApplicationExitInfo implements Parcelable { */ public static final int SUBREASON_SDK_SANDBOX_NOT_NEEDED = 28; + /** + * The process was killed because the binder proxy limit for system server was exceeded. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_EXCESSIVE_BINDER_OBJECTS = 29; + + /** + * The process was killed by the [kernel] Out-of-memory (OOM) killer; this + * would be set only when the reason is {@link #REASON_LOW_MEMORY}. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_OOM_KILL = 30; + + /** + * The process was killed because its async kernel binder buffer is running out + * while being frozen. + * this would be set only when the reason is {@link #REASON_FREEZER}. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_FREEZER_BINDER_ASYNC_FULL = 31; + // If there is any OEM code which involves additional app kill reasons, it should // be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000. @@ -635,6 +662,9 @@ public final class ApplicationExitInfo implements Parcelable { SUBREASON_KILL_BACKGROUND, SUBREASON_PACKAGE_UPDATE, SUBREASON_UNDELIVERED_BROADCAST, + SUBREASON_EXCESSIVE_BINDER_OBJECTS, + SUBREASON_OOM_KILL, + SUBREASON_FREEZER_BINDER_ASYNC_FULL, }) @Retention(RetentionPolicy.SOURCE) public @interface SubReason {} @@ -1360,6 +1390,12 @@ public final class ApplicationExitInfo implements Parcelable { return "PACKAGE UPDATE"; case SUBREASON_UNDELIVERED_BROADCAST: return "UNDELIVERED BROADCAST"; + case SUBREASON_EXCESSIVE_BINDER_OBJECTS: + return "EXCESSIVE BINDER OBJECTS"; + case SUBREASON_OOM_KILL: + return "OOM KILL"; + case SUBREASON_FREEZER_BINDER_ASYNC_FULL: + return "FREEZER BINDER ASYNC FULL"; default: return "UNKNOWN"; } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 46260ea5e658..37616e7d76af 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -924,4 +924,14 @@ interface IActivityManager { void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") int[] getUidFrozenState(in int[] uids); + + /** + * Notify AMS about binder transactions to frozen apps. + * + * @param debugPid The binder transaction sender + * @param code The binder transaction code + * @param flags The binder transaction flags + * @param err The binder transaction error + */ + oneway void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 0414f792f159..ccbccbfe4bf9 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -342,7 +342,9 @@ public final class LoadedApk { */ public void updateApplicationInfo(@NonNull ApplicationInfo aInfo, @Nullable List<String> oldPaths) { - setApplicationInfo(aInfo); + if (!setApplicationInfo(aInfo)) { + return; + } final List<String> newPaths = new ArrayList<>(); makePaths(mActivityThread, aInfo, newPaths); @@ -387,7 +389,13 @@ public final class LoadedApk { mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader); } - private void setApplicationInfo(ApplicationInfo aInfo) { + private boolean setApplicationInfo(ApplicationInfo aInfo) { + if (mApplicationInfo != null && mApplicationInfo.createTimestamp > aInfo.createTimestamp) { + Slog.w(TAG, "New application info for package " + aInfo.packageName + + " is out of date with TS " + aInfo.createTimestamp + " < the current TS " + + mApplicationInfo.createTimestamp); + return false; + } final int myUid = Process.myUid(); aInfo = adjustNativeLibraryPaths(aInfo); mApplicationInfo = aInfo; @@ -410,6 +418,7 @@ public final class LoadedApk { if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) { mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies); } + return true; } void setSdkSandboxStorage(@Nullable String sdkSandboxClientAppVolumeUuid, diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 52949d6d1fbd..2349ef8fd76d 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -180,6 +180,11 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { @Override public void injectInputEventToInputFilter(InputEvent event) throws RemoteException { + synchronized (mLock) { + throwIfCalledByNotTrustedUidLocked(); + throwIfShutdownLocked(); + throwIfNotConnectedLocked(); + } mAccessibilityManager.injectInputEventToInputFilter(event); } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index d352be16ae0c..581115b398cd 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -3203,7 +3203,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * stream configurations are the same as for applications targeting SDK version older than * 31.</p> * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} and - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations } + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">the table</a> * for additional mandatory stream configurations on a per-capability basis.</p> * <p>*1: For JPEG format, the sizes may be restricted by below conditions:</p> * <ul> @@ -3322,11 +3322,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL } * and {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES }. * This is an app-readable conversion of the mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations guideline} based on specific device level and capabilities. + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">guideline</a>. + * based on specific device level and capabilities. * Clients can use the array as a quick reference to find an appropriate camera stream * combination. * As per documentation, the stream combinations with given PREVIEW, RECORD and @@ -3355,11 +3356,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>An array of mandatory concurrent stream combinations. * This is an app-readable conversion of the concurrent mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline} for each device which has its Id present in the set returned by + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">guideline</a> + * for each device which has its Id present in the set returned by * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds }. * Clients can use the array as a quick reference to find an appropriate camera stream * combination. @@ -3464,7 +3466,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>If a camera device supports multi-resolution output streams for a particular format, for * each of its mandatory stream combinations, the camera device will support using a * MultiResolutionImageReader for the MAXIMUM stream of supported formats. Refer to - * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs } + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a> * for additional details.</p> * <p>To use multi-resolution input streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputFormats }. * A reprocessable CameraCaptureSession can then be created using an {@link android.hardware.camera2.params.InputConfiguration InputConfiguration} constructed with @@ -3473,7 +3475,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@code YUV} output, or multi-resolution {@code PRIVATE} input and multi-resolution * {@code PRIVATE} output, {@code JPEG} and {@code YUV} are guaranteed to be supported * multi-resolution output stream formats. Refer to - * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }} + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a> * for details about the additional mandatory stream combinations in this case.</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> */ @@ -3586,11 +3588,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link android.hardware.camera2.CaptureRequest } has {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set * to {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }. * This is an app-readable conversion of the maximum resolution mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors guideline} for each device which has the + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">guideline</a> + * for each device which has the * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } * capability. * Clients can use the array as a quick reference to find an appropriate camera stream @@ -3613,11 +3616,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * 10-bit output capability * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } * This is an app-readable conversion of the 10 bit output mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations guideline} for each device which has the + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">guideline</a> + * for each device which has the * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } * capability. * Clients can use the array as a quick reference to find an appropriate camera stream @@ -3638,11 +3642,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}. * This is an app-readable conversion of the preview stabilization mandatory stream * combination - * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations guideline} for each device which supports {@code PREVIEW_STABILIZATION} + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">guideline</a> + * for each device which supports {@code PREVIEW_STABILIZATION} * Clients can use the array as a quick reference to find an appropriate camera stream * combination. * The mandatory stream combination array will be {@code null} in case the device does not @@ -3715,8 +3720,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>The guaranteed stream combinations related to stream use case for a camera device with * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } * capability is documented in the camera device - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline}. The application is strongly recommended to use one of the guaranteed stream - * combinations. + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>. + * The application is strongly recommended to use one of the guaranteed stream combinations. * If the application creates a session with a stream combination not in the guaranteed * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session, * the camera device may ignore some stream use cases due to hardware constraints @@ -3752,11 +3757,13 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>An array of mandatory stream combinations with stream use cases. * This is an app-readable conversion of the mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations tables} with each stream's use case being set.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">tables</a> + * with each stream's use case being set.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline} for a camera device with + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guildeline</a> + * for a camera device with * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } * capability. * The mandatory stream combination array will be {@code null} in case the device doesn't diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index dfc27caa362c..a0cf97222263 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1213,8 +1213,7 @@ public abstract class CameraMetadata<TKey> { * <ul> * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li> * <li>All mandatory stream combinations for this specific capability as per - * documentation - * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations }</li> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li> * <li>In case the device is not able to capture some combination of supported * standard 8-bit and/or 10-bit dynamic range profiles within the same capture request, * then those constraints must be listed in @@ -1253,8 +1252,8 @@ public abstract class CameraMetadata<TKey> { * </ul> * <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES } * lists all of the supported stream use cases.</p> - * <p>Refer to - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations } + * <p>Refer to the + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a> * for the mandatory stream combinations involving stream use cases, which can also be * queried via {@link android.hardware.camera2.params.MandatoryStreamCombination }.</p> * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES @@ -1753,9 +1752,9 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device does not have enough capabilities to qualify as a <code>FULL</code> device or * better.</p> - * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> tables in the - * {@link android.hardware.camera2.CameraDevice#limited-level-additional-guaranteed-configurations } - * documentation are guaranteed to be supported.</p> + * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#limited-level-additional-guaranteed-configurations">tables</a> + * in the documentation are guaranteed to be supported.</p> * <p>All <code>LIMITED</code> devices support the <code>BACKWARDS_COMPATIBLE</code> capability, indicating basic * support for color image capture. The only exception is that the device may * alternatively support only the <code>DEPTH_OUTPUT</code> capability, if it can only output depth @@ -1781,9 +1780,9 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device is capable of supporting advanced imaging applications.</p> - * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> tables in the - * {@link android.hardware.camera2.CameraDevice#full-level-additional-guaranteed-configurations } - * documentation are guaranteed to be supported.</p> + * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#full-level-additional-guaranteed-configurations">tables</a> + * in the documentation are guaranteed to be supported.</p> * <p>A <code>FULL</code> device will support below capabilities:</p> * <ul> * <li><code>BURST_CAPTURE</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains @@ -1811,9 +1810,9 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device is running in backward compatibility mode.</p> - * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations } - * documentation are supported.</p> + * <p>Only the stream configurations listed in the <code>LEGACY</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">table</a> + * in the documentation are supported.</p> * <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual * post-processing, arbitrary cropping regions, and has relaxed performance constraints. * No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a @@ -1836,9 +1835,9 @@ public abstract class CameraMetadata<TKey> { * <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to * FULL-level capabilities.</p> * <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and - * <code>LIMITED</code> tables in the - * {@link android.hardware.camera2.CameraDevice#level-3-additional-guaranteed-configurations } - * documentation are guaranteed to be supported.</p> + * <code>LIMITED</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#level-3-additional-guaranteed-configurations">tables</a> + * in the documentation are guaranteed to be supported.</p> * <p>The following additional capabilities are guaranteed to be supported:</p> * <ul> * <li><code>YUV_REPROCESSING</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 25c84e5e9c58..78ae8d520b58 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1477,7 +1477,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>To start a CaptureSession with a target FPS range different from the * capture request template's default value, the application * is strongly recommended to call - * {@link SessionConfiguration#setSessionParameters } + * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters } * with the target fps range before creating the capture session. The aeTargetFpsRange is * typically a session parameter. Specifying it at session creation time helps avoid * session reconfiguration delays in cases like 60fps or high speed recording.</p> @@ -2154,7 +2154,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * OFF if the recording output is not stabilized, or if there are no output * Surface types that can be stabilized.</p> * <p>The application is strongly recommended to call - * {@link SessionConfiguration#setSessionParameters } + * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters } * with the desired video stabilization mode before creating the capture session. * Video stabilization mode is a session parameter on many devices. Specifying * it at session creation time helps avoid reconfiguration delay caused by difference diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 986dd889520d..18632e51bdd1 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -896,7 +896,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>To start a CaptureSession with a target FPS range different from the * capture request template's default value, the application * is strongly recommended to call - * {@link SessionConfiguration#setSessionParameters } + * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters } * with the target fps range before creating the capture session. The aeTargetFpsRange is * typically a session parameter. Specifying it at session creation time helps avoid * session reconfiguration delays in cases like 60fps or high speed recording.</p> @@ -2379,7 +2379,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * OFF if the recording output is not stabilized, or if there are no output * Surface types that can be stabilized.</p> * <p>The application is strongly recommended to call - * {@link SessionConfiguration#setSessionParameters } + * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters } * with the desired video stabilization mode before creating the capture session. * Video stabilization mode is a session parameter on many devices. Specifying * it at session creation time helps avoid reconfiguration delay caused by difference diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 0c753a51c508..227d55432e7a 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -669,6 +669,32 @@ public class Binder implements IBinder { */ public static final native void blockUntilThreadAvailable(); + + /** + * TODO (b/308179628): Move this to libbinder for non-Java usages. + */ + private static IBinderCallback sBinderCallback = null; + + /** + * Set callback function for unexpected binder transaction errors. + * + * @hide + */ + public static final void setTransactionCallback(IBinderCallback callback) { + sBinderCallback = callback; + } + + /** + * Execute the callback function if it's already set. + * + * @hide + */ + public static final void transactionCallback(int pid, int code, int flags, int err) { + if (sBinderCallback != null) { + sBinderCallback.onTransactionError(pid, code, flags, err); + } + } + /** * Default constructor just initializes the object. * diff --git a/core/java/android/os/IBinderCallback.java b/core/java/android/os/IBinderCallback.java new file mode 100644 index 000000000000..e4be5b02e7e0 --- /dev/null +++ b/core/java/android/os/IBinderCallback.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * Callback interface for binder transaction errors + * + * @hide + */ +public interface IBinderCallback { + /** + * Callback function for unexpected binder transaction errors. + * + * @param debugPid The binder transaction sender + * @param code The binder transaction code + * @param flags The binder transaction flags + * @param err The binder transaction error + */ + void onTransactionError(int debugPid, int code, int flags, int err); +} diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index 58376a77c705..0801dd8c0bd8 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -62,16 +62,14 @@ import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; -import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Queue; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local @@ -89,6 +87,8 @@ public abstract class FileSystemProvider extends DocumentsProvider { DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, DocumentsContract.QUERY_ARG_MIME_TYPES); + private static final int MAX_RESULTS_NUMBER = 23; + private static String joinNewline(String... args) { return TextUtils.join("\n", args); } @@ -375,62 +375,53 @@ public abstract class FileSystemProvider extends DocumentsProvider { } /** - * This method is similar to - * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns - * all children documents including hidden directories/files. - * - * <p> - * In a scoped storage world, access to "Android/data" style directories are hidden for privacy - * reasons. This method may show privacy sensitive data, so its usage should only be in - * restricted modes. - * - * @param parentDocumentId the directory to return children for. - * @param projection list of {@link Document} columns to put into the - * cursor. If {@code null} all supported columns should be - * included. - * @param sortOrder how to order the rows, formatted as an SQL - * {@code ORDER BY} clause (excluding the ORDER BY itself). - * Passing {@code null} will use the default sort order, which - * may be unordered. This ordering is a hint that can be used to - * prioritize how data is fetched from the network, but UI may - * always enforce a specific ordering - * @throws FileNotFoundException when parent document doesn't exist or query fails + * WARNING: this method should really be {@code final}, but for the backward compatibility it's + * not; new classes that extend {@link FileSystemProvider} should override + * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method. */ - protected Cursor queryChildDocumentsShowAll( - String parentDocumentId, String[] projection, String sortOrder) + @Override + public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder) throws FileNotFoundException { - return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true); + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false); } + /** + * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it + * could return <b>all</b> content of the directory, <b>including restricted (hidden) + * directories and files</b>. + * <p> + * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and + * {@code Android/obb/} on the external storage) are hidden for privacy reasons. + * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care. + */ @Override - public Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder) - throws FileNotFoundException { - // Access to some directories is hidden for privacy reasons. - return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow); + public final Cursor queryChildDocumentsForManage(String documentId, String[] projection, + String sortOrder) throws FileNotFoundException { + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true); } - private Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder, - @NonNull Predicate<File> filter) throws FileNotFoundException { - final File parent = getFileForDocId(parentDocumentId); + protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder, + boolean includeHidden) throws FileNotFoundException { + final File parent = getFileForDocId(documentId); final MatrixCursor result = new DirectoryCursor( - resolveProjection(projection), parentDocumentId, parent); + resolveProjection(projection), documentId, parent); + + if (!parent.isDirectory()) { + Log.w(TAG, '"' + documentId + "\" is not a directory"); + return result; + } - if (!filter.test(parent)) { - Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId); + if (!includeHidden && shouldHideDocument(documentId)) { + Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden"); return result; } - if (parent.isDirectory()) { - for (File file : FileUtils.listFilesOrEmpty(parent)) { - if (filter.test(file)) { - includeFile(result, null, file); - } - } - } else { - Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); + for (File file : FileUtils.listFilesOrEmpty(parent)) { + if (!includeHidden && shouldHideDocument(file)) continue; + + includeFile(result, null, file); } + return result; } @@ -452,23 +443,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { * * @see ContentResolver#EXTRA_HONORED_ARGS */ - protected final Cursor querySearchDocuments( - File folder, String[] projection, Set<String> exclusion, Bundle queryArgs) - throws FileNotFoundException { + protected final Cursor querySearchDocuments(File folder, String[] projection, + Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); - final List<File> pending = new ArrayList<>(); - pending.add(folder); - while (!pending.isEmpty() && result.getCount() < 24) { - final File file = pending.remove(0); - if (shouldHide(file)) continue; + + // We'll be a running a BFS here. + final Queue<File> pending = new ArrayDeque<>(); + pending.offer(folder); + + while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) { + final File file = pending.poll(); + + // Skip hidden documents (both files and directories) + if (shouldHideDocument(file)) continue; if (file.isDirectory()) { for (File child : FileUtils.listFilesOrEmpty(file)) { - pending.add(child); + pending.offer(child); } } - if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file, - queryArgs)) { + + if (exclusion.contains(file.getAbsolutePath())) continue; + + if (matchSearchQueryArguments(file, queryArgs)) { includeFile(result, null, file); } } @@ -612,26 +609,23 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS); if (flagIndex != -1) { + final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR); int flags = 0; if (file.canWrite()) { - if (mimeType.equals(Document.MIME_TYPE_DIR)) { + flags |= Document.FLAG_SUPPORTS_DELETE; + flags |= Document.FLAG_SUPPORTS_RENAME; + flags |= Document.FLAG_SUPPORTS_MOVE; + if (isDir) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; - flags |= Document.FLAG_SUPPORTS_DELETE; - flags |= Document.FLAG_SUPPORTS_RENAME; - flags |= Document.FLAG_SUPPORTS_MOVE; - - if (shouldBlockFromTree(docId)) { - flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; - } - } else { flags |= Document.FLAG_SUPPORTS_WRITE; - flags |= Document.FLAG_SUPPORTS_DELETE; - flags |= Document.FLAG_SUPPORTS_RENAME; - flags |= Document.FLAG_SUPPORTS_MOVE; } } + if (isDir && shouldBlockDirectoryFromTree(docId)) { + flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; + } + if (mimeType.startsWith("image/")) { flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } @@ -664,22 +658,36 @@ public abstract class FileSystemProvider extends DocumentsProvider { return row; } - private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile( - "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$"); - /** - * In a scoped storage world, access to "Android/data" style directories are - * hidden for privacy reasons. + * Some providers may want to restrict access to certain directories and files, + * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for + * privacy reasons. + * Such providers should override this method. */ - protected boolean shouldHide(@NonNull File file) { - return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches()); + protected boolean shouldHideDocument(@NonNull String documentId) + throws FileNotFoundException { + return false; } - private boolean shouldShow(@NonNull File file) { - return !shouldHide(file); + /** + * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of + * a {@link String} {@code documentId}. + * + * @see #shouldHideDocument(String) + */ + protected final boolean shouldHideDocument(@NonNull File document) + throws FileNotFoundException { + return shouldHideDocument(getDocIdForFile(document)); } - protected boolean shouldBlockFromTree(@NonNull String docId) { + /** + * @return if the directory that should be blocked from being selected when the user launches + * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent. + * + * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE + */ + protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) + throws FileNotFoundException { return false; } diff --git a/core/java/com/android/internal/os/BinderfsStatsReader.java b/core/java/com/android/internal/os/BinderfsStatsReader.java new file mode 100644 index 000000000000..9cc4a35b5c65 --- /dev/null +++ b/core/java/com/android/internal/os/BinderfsStatsReader.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import com.android.internal.util.ProcFileReader; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * Reads and parses {@code binder_logs/stats} file in the {@code binderfs} filesystem. + * Reuse procFileReader as the contents are generated by Linux kernel in the same way. + * + * A typical example of binderfs stats log + * + * binder stats: + * BC_TRANSACTION: 378004 + * BC_REPLY: 268352 + * BC_FREE_BUFFER: 665854 + * ... + * proc 12645 + * context binder + * threads: 12 + * requested threads: 0+5/15 + * ready threads 0 + * free async space 520192 + * ... + */ +public class BinderfsStatsReader { + private final String mPath; + + public BinderfsStatsReader() { + mPath = "/dev/binderfs/binder_logs/stats"; + } + + public BinderfsStatsReader(String path) { + mPath = path; + } + + /** + * Read binderfs stats and call the consumer(pid, free) function for each valid process + * + * @param predicate Test if the pid is valid. + * @param biConsumer Callback function for each valid pid and its free async space + * @param consumer The error function to deal with exceptions + */ + public void handleFreeAsyncSpace(Predicate<Integer> predicate, + BiConsumer<Integer, Integer> biConsumer, Consumer<Exception> consumer) { + try (ProcFileReader mReader = new ProcFileReader(new FileInputStream(mPath))) { + while (mReader.hasMoreData()) { + // find the next process + if (!mReader.nextString().equals("proc")) { + mReader.finishLine(); + continue; + } + + // read pid + int pid = mReader.nextInt(); + mReader.finishLine(); + + // check if we have interest in this process + if (!predicate.test(pid)) { + continue; + } + + // read free async space + mReader.finishLine(); // context binder + mReader.finishLine(); // threads: + mReader.finishLine(); // requested threads: + mReader.finishLine(); // ready threads + if (!mReader.nextString().equals("free")) { + mReader.finishLine(); + continue; + } + if (!mReader.nextString().equals("async")) { + mReader.finishLine(); + continue; + } + if (!mReader.nextString().equals("space")) { + mReader.finishLine(); + continue; + } + int free = mReader.nextInt(); + mReader.finishLine(); + biConsumer.accept(pid, free); + } + } catch (IOException | NumberFormatException e) { + consumer.accept(e); + } + } +} diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index bfd80a9e4f74..d2d5186eb8a1 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -73,6 +73,7 @@ static struct bindernative_offsets_t jclass mClass; jmethodID mExecTransact; jmethodID mGetInterfaceDescriptor; + jmethodID mTransactionCallback; // Object state. jfieldID mObject; @@ -1173,6 +1174,8 @@ static int int_register_android_os_Binder(JNIEnv* env) gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z"); gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor", "()Ljava/lang/String;"); + gBinderOffsets.mTransactionCallback = + GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V"); gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J"); return RegisterMethodsOrDie( @@ -1387,7 +1390,12 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, if (err == NO_ERROR) { return JNI_TRUE; - } else if (err == UNKNOWN_TRANSACTION) { + } + + env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback, getpid(), + code, flags, err); + + if (err == UNKNOWN_TRANSACTION) { return JNI_FALSE; } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 56066b2d813c..b12e14782361 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1121,10 +1121,8 @@ static std::string getAppDataDirName(std::string_view parent_path, std::string_v } } // Fallback done - - fail_fn(CREATE_ERROR("Unable to find %s:%lld in %s", package_name.data(), - ce_data_inode, parent_path.data())); - return nullptr; + ALOGW("Unable to find %s:%lld in %s", package_name.data(), ce_data_inode, parent_path.data()); + return ""; } } @@ -1145,11 +1143,19 @@ static void isolateAppDataPerPackage(int userId, std::string_view package_name, true /*call_fail_fn*/); std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn); + if (ce_data_path.empty()) { + ALOGE("Ignoring missing CE app data dir for %s\n", package_name.data()); + return; + } if (!createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn, false /*call_fail_fn*/)) { // CE might unlocks and the name is decrypted // get the name and mount again ce_data_path=getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn); + if (ce_data_path.empty()) { + ALOGE("Ignoring missing CE app data dir for %s\n", package_name.data()); + return; + } mountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn); } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index bc74f397fa35..b8553cd4170e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8284,6 +8284,10 @@ android:exported="true"> </provider> + <meta-data + android:name="com.android.server.patch.25239169" + android:value="true" /> + </application> </manifest> diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index de8ff3fb67fd..328a172127e3 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -68,6 +68,7 @@ public class DeviceConfigTest { deleteViaContentProvider(NAMESPACE, KEY); deleteViaContentProvider(NAMESPACE, KEY2); deleteViaContentProvider(NAMESPACE, KEY3); + DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE); } @Test diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java index 86f26e59e370..df212ebe1744 100644 --- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java @@ -17,7 +17,9 @@ package android.widget; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import android.content.Context; import android.platform.test.annotations.Presubmit; import android.util.PollingCheck; @@ -32,6 +34,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @RunWith(AndroidJUnit4.class) @MediumTest @Presubmit @@ -49,23 +54,43 @@ public class HorizontalScrollViewFunctionalTest { } @Test - public void testScrollAfterFlingTop() { - mHorizontalScrollView.scrollTo(100, 0); - mHorizontalScrollView.fling(-10000); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f); + public void testScrollAfterFlingLeft() throws Throwable { + WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); + mHorizontalScrollView.mEdgeGlowLeft = edgeEffect; + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0)); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000)); + assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); + mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame + PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f); assertEquals(0, mHorizontalScrollView.getScrollX()); } @Test - public void testScrollAfterFlingBottom() { + public void testScrollAfterFlingRight() throws Throwable { + WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); + mHorizontalScrollView.mEdgeGlowRight = edgeEffect; int childWidth = mHorizontalScrollView.getChildAt(0).getWidth(); int maxScroll = childWidth - mHorizontalScrollView.getWidth(); - mHorizontalScrollView.scrollTo(maxScroll - 100, 0); - mHorizontalScrollView.fling(10000); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0)); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000)); + assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); + mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f); assertEquals(maxScroll, mHorizontalScrollView.getScrollX()); } + + static class WatchedEdgeEffect extends EdgeEffect { + public CountDownLatch onAbsorbLatch = new CountDownLatch(1); + + WatchedEdgeEffect(Context context) { + super(context); + } + + @Override + public void onAbsorb(int velocity) { + super.onAbsorb(velocity); + onAbsorbLatch.countDown(); + } + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java new file mode 100644 index 000000000000..5498083b2182 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import android.content.Context; +import android.os.FileUtils; +import android.util.IntArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.nio.file.Files; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BinderfsStatsReaderTest { + private static final String BINDER_LOGS_STATS_HEADER = """ + binder stats: + BC_TRANSACTION: 695756 + BC_REPLY: 547779 + BC_FREE_BUFFER: 1283223 + BR_FAILED_REPLY: 4 + BR_FROZEN_REPLY: 3 + BR_ONEWAY_SPAM_SUSPECT: 1 + proc: active 313 total 377 + thread: active 3077 total 5227 + """; + private static final String BINDER_LOGS_STATS_PROC1 = """ + proc 14505 + context binder + threads: 4 + requested threads: 0+2/15 + ready threads 0 + free async space 520192 + nodes: 9 + refs: 29 s 29 w 29 + buffers: 0 + """; + private static final String BINDER_LOGS_STATS_PROC2 = """ + proc 14461 + context binder + threads: 8 + requested threads: 0+2/15 + ready threads 0 + free async space 62 + nodes: 30 + refs: 51 s 51 w 51 + buffers: 0 + """; + private static final String BINDER_LOGS_STATS_PROC3 = """ + proc 542 + context binder + threads: 2 + requested threads: 0+0/15 + ready threads 0 + free async space 519896 + nodes: 1 + refs: 2 s 3 w 2 + buffers: 1 + """; + private static final String BINDER_LOGS_STATS_PROC4 = """ + proc 540 + context binder + threads: 1 + requested threads: 0+0/0 + ready threads 1 + free async space 44 + nodes: 4 + refs: 1 s 1 w 1 + buffers: 0 + """; + private File mStatsDirectory; + private int mFreezerBinderAsyncThreshold; + private IntArray mValidPids; // The pool of valid pids + private IntArray mStatsPids; // The pids read from binderfs stats that are also valid + private IntArray mStatsFree; // The free async space of the above pids + private boolean mHasError; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getContext(); + mStatsDirectory = context.getDir("binder_logs", Context.MODE_PRIVATE); + mFreezerBinderAsyncThreshold = 1024; + mValidPids = IntArray.fromArray(new int[]{14505, 14461, 542, 540}, 4); + mStatsPids = new IntArray(); + mStatsFree = new IntArray(); + mHasError = false; + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteContents(mStatsDirectory); + } + + @Test + public void testNoneProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER); + assertFalse(mHasError); + assertEquals(0, mStatsPids.size()); + assertEquals(0, mStatsFree.size()); + } + + @Test + public void testOneProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1); + assertFalse(mHasError); + assertEquals(0, mStatsPids.size()); + assertEquals(0, mStatsFree.size()); + } + + @Test + public void testTwoProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1 + + BINDER_LOGS_STATS_PROC2); + assertFalse(mHasError); + assertArrayEquals(mStatsPids.toArray(), new int[]{14461}); + assertArrayEquals(mStatsFree.toArray(), new int[]{62}); + } + + @Test + public void testThreeProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1 + + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3); + assertFalse(mHasError); + assertArrayEquals(mStatsPids.toArray(), new int[]{14461}); + assertArrayEquals(mStatsFree.toArray(), new int[]{62}); + } + + @Test + public void testFourProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1 + + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3 + BINDER_LOGS_STATS_PROC4); + assertFalse(mHasError); + assertArrayEquals(mStatsPids.toArray(), new int[]{14461, 540}); + assertArrayEquals(mStatsFree.toArray(), new int[]{62, 44}); + } + + @Test + public void testInvalidProc() throws Exception { + mValidPids = new IntArray(); + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1 + + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3 + BINDER_LOGS_STATS_PROC4); + assertFalse(mHasError); + assertEquals(0, mStatsPids.size()); + assertEquals(0, mStatsFree.size()); + } + + private void runHandleBlockingFileLocks(String fileContents) throws Exception { + File tempFile = File.createTempFile("stats", null, mStatsDirectory); + Files.write(tempFile.toPath(), fileContents.getBytes()); + new BinderfsStatsReader(tempFile.toString()).handleFreeAsyncSpace( + // Check if the current process is a valid one + pid -> mValidPids.indexOf(pid) != -1, + + // Check if the current process is running out of async binder space + (pid, free) -> { + if (free < mFreezerBinderAsyncThreshold) { + mStatsPids.add(pid); + mStatsFree.add(free); + } + }, + + // Log the error if binderfs stats can't be accesses or correctly parsed + exception -> mHasError = true); + Files.delete(tempFile.toPath()); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index c95c9f099f20..1c792395d22d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2244,6 +2244,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } + /** + * Returns the {@link StageType} where {@param token} is being used + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise + */ + @StageType + public int getSplitItemStage(@Nullable WindowContainerToken token) { + if (token == null) { + return STAGE_TYPE_UNDEFINED; + } + + if (mMainStage.containsToken(token)) { + return STAGE_TYPE_MAIN; + } else if (mSideStage.containsToken(token)) { + return STAGE_TYPE_SIDE; + } + + return STAGE_TYPE_UNDEFINED; + } + @Override public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { final StageTaskListener topLeftStage = @@ -2491,7 +2510,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent( recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT); + @StageType int topStage = STAGE_TYPE_UNDEFINED; + if (isSplitScreenVisible()) { + // Get the stage where a child exists to keep that stage onTop + if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) { + topStage = STAGE_TYPE_MAIN; + } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) { + topStage = STAGE_TYPE_SIDE; + } + } + prepareExitSplitScreen(topStage, outWCT); } } @@ -2908,7 +2936,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - /** Synchronize split-screen state with transition and make appropriate preparations. */ + /** + * Synchronize split-screen state with transition and make appropriate preparations. + * @param toStage The stage that will not be dismissed. If set to + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed + */ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 4897c4ee7dd6..f0bb665f8082 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -50,6 +50,7 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -511,8 +512,26 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // make a new startTransaction because pip's startEnterAnimation "consumes" it so // we need a separate one to send over to launcher. SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; + if (mSplitHandler.isSplitScreenVisible()) { + // The non-going home case, we could be pip-ing one of the split stages and keep + // showing the other + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange) { + // Ignore the change/task that's going into Pip + continue; + } + @SplitScreen.StageType int splitItemStage = + mSplitHandler.getSplitItemStage(change.getLastParent()); + if (splitItemStage != STAGE_TYPE_UNDEFINED) { + topStageToKeep = splitItemStage; + break; + } + } + } // Let split update internal state for dismiss. - mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED, + mSplitHandler.prepareDismissAnimation(topStageToKeep, EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, finishTransaction); diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index af3c295b8d6c..5a274353f68e 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -68,7 +68,7 @@ public final class AudioDeviceAttributes implements Parcelable { /** * The unique address of the device. Some devices don't have addresses, only an empty string. */ - private final @NonNull String mAddress; + private @NonNull String mAddress; /** * The non-unique name of the device. Some devices don't have names, only an empty string. * Should not be used as a unique identifier for a device. @@ -188,6 +188,21 @@ public final class AudioDeviceAttributes implements Parcelable { /** * @hide + * Copy Constructor. + * @param ada the copied AudioDeviceAttributes + */ + public AudioDeviceAttributes(AudioDeviceAttributes ada) { + mRole = ada.getRole(); + mType = ada.getType(); + mAddress = ada.getAddress(); + mName = ada.getName(); + mNativeType = ada.getInternalType(); + mAudioProfiles = ada.getAudioProfiles(); + mAudioDescriptors = ada.getAudioDescriptors(); + } + + /** + * @hide * Returns the role of a device * @return the role */ @@ -218,6 +233,15 @@ public final class AudioDeviceAttributes implements Parcelable { /** * @hide + * Sets the device address. Only used by audio service. + */ + public void setAddress(@NonNull String address) { + Objects.requireNonNull(address); + mAddress = address; + } + + /** + * @hide * Returns the name of the audio device, or an empty string for devices without one * @return the device name */ diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 4c313b22f71e..3409c29d3c2c 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,6 +16,8 @@ package com.android.externalstorage; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; @@ -64,7 +66,19 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.UUID; - +import java.util.regex.Pattern; + +/** + * Presents content of the shared (a.k.a. "external") storage. + * <p> + * Starting with Android 11 (R), restricts access to the certain sections of the shared storage: + * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in + * the DocumentsUI by default. + * See <a href="https://developer.android.com/about/versions/11/privacy/storage"> + * Storage updates in Android 11</a>. + * <p> + * Documents ID format: {@code root:path/to/file}. + */ public class ExternalStorageProvider extends FileSystemProvider { private static final String TAG = "ExternalStorage"; @@ -75,7 +89,12 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); - // docId format: root:path/to/file + /** + * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and + * {@code /Android/sandbox/} along with all their subdirectories and content. + */ + private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = + Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE); private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, @@ -278,76 +297,91 @@ public class ExternalStorageProvider extends FileSystemProvider { return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } + /** + * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the + * integrated shared ("external") storage along with all their content and subdirectories as + * hidden. + */ @Override - public Cursor queryChildDocumentsForManage( - String parentDocId, String[] projection, String sortOrder) - throws FileNotFoundException { - return queryChildDocumentsShowAll(parentDocId, projection, sortOrder); + protected boolean shouldHideDocument(@NonNull String documentId) { + // Don't need to hide anything on USB drives. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } + + final String path = getPathFromDocId(documentId); + return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches(); } /** * Check that the directory is the root of storage or blocked file from tree. + * <p> + * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear + * the UI, but the user <b>WILL NOT</b> be able to select them. * - * @param docId the docId of the directory to be checked + * @param documentId the docId of the directory to be checked * @return true, should be blocked from tree. Otherwise, false. + * + * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE */ @Override - protected boolean shouldBlockFromTree(@NonNull String docId) { - try { - final File dir = getFileForDocId(docId, false /* visible */); - - // the file is null or it is not a directory - if (dir == null || !dir.isDirectory()) { - return false; - } + protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) + throws FileNotFoundException { + final File dir = getFileForDocId(documentId, false); + // The file is null or it is not a directory + if (dir == null || !dir.isDirectory()) { + return false; + } - // Allow all directories on USB, including the root. - try { - RootInfo rootInfo = getRootFromDocId(docId); - if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { - return false; - } - } catch (FileNotFoundException e) { - Log.e(TAG, "Failed to determine rootInfo for docId"); - } + // Allow all directories on USB, including the root. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } - final String path = getPathFromDocId(docId); + // Get canonical(!) path. Note that this path will have neither leading nor training "/". + // This the root's path will be just an empty string. + final String path = getPathFromDocId(documentId); - // Block the root of the storage - if (path.isEmpty()) { - return true; - } + // Block the root of the storage + if (path.isEmpty()) { + return true; + } - // Block Download folder from tree - if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT), - path.toLowerCase(Locale.ROOT))) { - return true; - } + // Block /Download/ and /Android/ folders from the tree. + if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) || + equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) { + return true; + } - // Block /Android - if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT), - path.toLowerCase(Locale.ROOT))) { - return true; - } + // This shouldn't really make a difference, but just in case - let's block hidden + // directories as well. + if (shouldHideDocument(documentId)) { + return true; + } - // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs - if (shouldHide(dir)) { - return true; - } + return false; + } + private boolean isOnRemovableUsbStorage(@NonNull String documentId) { + final RootInfo rootInfo; + try { + rootInfo = getRootFromDocId(documentId); + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"'); return false; - } catch (IOException e) { - throw new IllegalArgumentException( - "Failed to determine if " + docId + " should block from tree " + ": " + e); } + + return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0; } + @NonNull @Override - protected String getDocIdForFile(File file) throws FileNotFoundException { + protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException { return getDocIdForFileMaybeCreate(file, false); } - private String getDocIdForFileMaybeCreate(File file, boolean createNewDir) + @NonNull + private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir) throws FileNotFoundException { String path = file.getAbsolutePath(); @@ -417,31 +451,33 @@ public class ExternalStorageProvider extends FileSystemProvider { private File getFileForDocId(String docId, boolean visible, boolean mustExist) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return buildFile(root, docId, visible, mustExist); + return buildFile(root, docId, mustExist); } - private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) - throws FileNotFoundException { + private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return Pair.create(root, buildFile(root, docId, visible, true)); + return Pair.create(root, buildFile(root, docId, /* mustExist */ true)); } @VisibleForTesting - static String getPathFromDocId(String docId) throws IOException { + static String getPathFromDocId(String docId) { final int splitIndex = docId.indexOf(':', 1); final String docIdPath = docId.substring(splitIndex + 1); - // Get CanonicalPath and remove the first "/" - final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1); - if (canonicalPath.isEmpty()) { - return canonicalPath; + // Canonicalize path and strip the leading "/" + final String path; + try { + path = new File(docIdPath).getCanonicalPath().substring(1); + } catch (IOException e) { + Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"'); + return ""; } - // remove trailing "/" - if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') { - return canonicalPath.substring(0, canonicalPath.length() - 1); + // Remove the trailing "/" as well. + if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') { + return path.substring(0, path.length() - 1); } else { - return canonicalPath; + return path; } } @@ -460,7 +496,7 @@ public class ExternalStorageProvider extends FileSystemProvider { return root; } - private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist) + private File buildFile(RootInfo root, String docId, boolean mustExist) throws FileNotFoundException { final int splitIndex = docId.indexOf(':', 1); final String path = docId.substring(splitIndex + 1); @@ -544,7 +580,7 @@ public class ExternalStorageProvider extends FileSystemProvider { @Override public Path findDocumentPath(@Nullable String parentDocId, String childDocId) throws FileNotFoundException { - final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false); + final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId); final RootInfo root = resolvedDocId.first; File child = resolvedDocId.second; @@ -648,6 +684,13 @@ public class ExternalStorageProvider extends FileSystemProvider { } } + /** + * Print the state into the given stream. + * Gets invoked when you run: + * <pre> + * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider + * </pre> + */ @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); @@ -731,4 +774,8 @@ public class ExternalStorageProvider extends FileSystemProvider { } return bundle; } + + private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) { + return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT)); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java deleted file mode 100644 index bc5824a4758d..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.qrcode; - -import android.graphics.Bitmap; -import android.graphics.Color; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.MultiFormatWriter; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; - -import java.nio.charset.CharsetEncoder; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -public final class QrCodeGenerator { - /** - * Generates a barcode image with {@code contents}. - * - * @param contents The contents to encode in the barcode - * @param size The preferred image size in pixels - * @return Barcode bitmap - */ - public static Bitmap encodeQrCode(String contents, int size) - throws WriterException, IllegalArgumentException { - final Map<EncodeHintType, Object> hints = new HashMap<>(); - if (!isIso88591(contents)) { - hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name()); - } - - final BitMatrix qrBits = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, - size, size, hints); - final Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565); - for (int x = 0; x < size; x++) { - for (int y = 0; y < size; y++) { - bitmap.setPixel(x, y, qrBits.get(x, y) ? Color.BLACK : Color.WHITE); - } - } - return bitmap; - } - - private static boolean isIso88591(String contents) { - CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); - return encoder.canEncode(contents); - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.kt b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.kt new file mode 100644 index 000000000000..7b67ec6d9bec --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.qrcode + +import android.annotation.ColorInt +import android.graphics.Bitmap +import android.graphics.Color +import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType +import com.google.zxing.MultiFormatWriter +import com.google.zxing.WriterException +import java.nio.charset.StandardCharsets +import java.util.EnumMap + +object QrCodeGenerator { + /** + * Generates a barcode image with [contents]. + * + * @param contents The contents to encode in the barcode + * @param size The preferred image size in pixels + * @param invert Whether to invert the black/white pixels (e.g. for dark mode) + * @return Barcode bitmap + */ + @JvmStatic + @Throws(WriterException::class, java.lang.IllegalArgumentException::class) + fun encodeQrCode(contents: String, size: Int, invert: Boolean): Bitmap = + encodeQrCode(contents, size, DEFAULT_MARGIN, invert) + + private const val DEFAULT_MARGIN = -1 + + /** + * Generates a barcode image with [contents]. + * + * @param contents The contents to encode in the barcode + * @param size The preferred image size in pixels + * @param margin The margin around the actual barcode + * @param invert Whether to invert the black/white pixels (e.g. for dark mode) + * @return Barcode bitmap + */ + @JvmOverloads + @JvmStatic + @Throws(WriterException::class, IllegalArgumentException::class) + fun encodeQrCode( + contents: String, + size: Int, + margin: Int = DEFAULT_MARGIN, + invert: Boolean = false, + ): Bitmap { + val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java) + if (!isIso88591(contents)) { + hints[EncodeHintType.CHARACTER_SET] = StandardCharsets.UTF_8.name() + } + if (margin != DEFAULT_MARGIN) { + hints[EncodeHintType.MARGIN] = margin + } + val qrBits = MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, size, size, hints) + @ColorInt val setColor = if (invert) Color.WHITE else Color.BLACK + @ColorInt val unsetColor = if (invert) Color.BLACK else Color.WHITE + @ColorInt val pixels = IntArray(size * size) + for (x in 0 until size) { + for (y in 0 until size) { + pixels[x * size + y] = if (qrBits[x, y]) setColor else unsetColor + } + } + return Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565).apply { + setPixels(pixels, 0, size, 0, 0, size, size) + } + } + + private fun isIso88591(contents: String): Boolean = + StandardCharsets.ISO_8859_1.newEncoder().canEncode(contents) +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 3472a859ac82..54a25a4c1719 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -553,7 +553,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { Log.w(TAG, "ignoring onTouch with null overlay or animation view controller"); return false; } - if (mOverlay.getAnimationViewController().shouldPauseAuth()) { + // Wait to receive up or cancel before pausing auth + if (mActivePointerId == MotionEvent.INVALID_POINTER_ID + && mOverlay.getAnimationViewController().shouldPauseAuth()) { Log.w(TAG, "ignoring onTouch with shouldPauseAuth = true"); return false; } @@ -562,12 +564,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { + mOverlay.getRequestId()); return false; } - - if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f - && !mAlternateBouncerInteractor.isVisibleState()) - || mPrimaryBouncerInteractor.isInTransit()) { - return false; - } if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { // Reset on ACTION_DOWN, start of new gesture diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 4cade77c8312..323ed9871dc3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -261,6 +261,13 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } //TODO: brightnessfloat change usages to float. private int clampToUserSetting(int brightness) { + int screenBrightnessModeSetting = mSystemSettings.getIntForUser( + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT); + if (screenBrightnessModeSetting == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { + return brightness; + } + int userSetting = mSystemSettings.getIntForUser( Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE, UserHandle.USER_CURRENT); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index f2d9144c2461..57be2af70b19 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -388,6 +388,10 @@ object Flags { // TODO(b/280426085): Tracking Bug @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository") + // TODO(b/311222557): Tracking bug + val ROAMING_INDICATOR_VIA_DISPLAY_INFO = + releasedFlag("roaming_indicator_via_display_info") + // TODO(b/292533677): Tracking Bug val WIFI_TRACKER_LIB_FOR_WIFI_ICON = unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true) diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index c4749e093854..c77f3f49a77c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -231,7 +231,6 @@ internal constructor( animation.removeEndListener(this) if (!canceled) { - // The delay between finishing this animation and starting the runnable val delay = max(0, runnableDelay - elapsedTimeSinceEntry) @@ -461,7 +460,6 @@ internal constructor( } private fun handleMoveEvent(event: MotionEvent) { - val x = event.x val y = event.y @@ -927,17 +925,7 @@ internal constructor( GestureState.ACTIVE -> { previousXTranslationOnActiveOffset = previousXTranslation updateRestingArrowDimens() - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE - ) - } else { - vibratorHelper.cancel() - mainHandler.postDelayed(10L) { - vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) - } - } + performActivatedHapticFeedback() val popVelocity = if (previousState == GestureState.INACTIVE) { POP_ON_INACTIVE_TO_ACTIVE_VELOCITY @@ -958,25 +946,24 @@ internal constructor( mView.popOffEdge(POP_ON_INACTIVE_VELOCITY) - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE - ) - } else { - vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) - } + performDeactivatedHapticFeedback() updateRestingArrowDimens() } GestureState.FLUNG -> { + // Typically a vibration is only played while transitioning to ACTIVE. However there + // are instances where a fling to trigger back occurs while not in that state. + // (e.g. A fling is detected before crossing the trigger threshold.) + if (previousState != GestureState.ACTIVE) { + performActivatedHapticFeedback() + } mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(POP_ON_FLING_VELOCITY) } - updateRestingArrowDimens() mainHandler.postDelayed( onEndSetCommittedStateListener.runnable, MIN_DURATION_FLING_ANIMATION ) + updateRestingArrowDimens() } GestureState.COMMITTED -> { // In most cases, animating between states is handled via `updateRestingArrowDimens` @@ -1011,6 +998,31 @@ internal constructor( } } + private fun performDeactivatedHapticFeedback() { + if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + vibratorHelper.performHapticFeedback( + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE + ) + } else { + vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) + } + } + + private fun performActivatedHapticFeedback() { + if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + vibratorHelper.performHapticFeedback( + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE + ) + } else { + vibratorHelper.cancel() + mainHandler.postDelayed(10L) { + vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) + } + } + } + private fun convertVelocityToAnimationFactor( valueOnFastVelocity: Float, valueOnSlowVelocity: Float, diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 2469a98140e3..3750c44a4923 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -281,6 +281,11 @@ public class TileLifecycleManager extends BroadcastReceiver implements } @Override + public void onNullBinding(ComponentName name) { + executeSetBindService(false); + } + + @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); handleDeath(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index ae70384aa71a..c20732471cf4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -25,6 +25,7 @@ import android.util.MathUtils; import android.util.TimeUtils; import com.android.app.animation.Interpolators; +import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.systemui.Dumpable; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; @@ -95,6 +96,7 @@ public class LightBarTransitionsController implements Dumpable { private final KeyguardStateController mKeyguardStateController; private final StatusBarStateController mStatusBarStateController; private final CommandQueue mCommandQueue; + private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; private boolean mTransitionDeferring; private long mTransitionDeferringStartTime; @@ -134,6 +136,8 @@ public class LightBarTransitionsController implements Dumpable { mDozeAmount = mStatusBarStateController.getDozeAmount(); mContext = context; mDisplayId = mContext.getDisplayId(); + mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( + mHandler, mContext, null); } /** Call to cleanup the LightBarTransitionsController when done with it. */ @@ -279,7 +283,8 @@ public class LightBarTransitionsController implements Dumpable { */ public boolean supportsIconTintForNavMode(int navigationMode) { // In gesture mode, we already do region sampling to update tint based on content beneath. - return !QuickStepContract.isGesturalMode(navigationMode); + return !QuickStepContract.isGesturalMode(navigationMode) + || mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 02473f276b4b..3684d90c7ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -109,8 +109,9 @@ constructor( { int1 = subId str1 = displayInfo.toString() + bool1 = displayInfo.isRoaming }, - { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1" }, + { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1 isRoaming=$bool1" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index cd6862113ee9..f54f7a4039ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -41,6 +41,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected @@ -97,6 +99,7 @@ class MobileConnectionRepositoryImpl( bgDispatcher: CoroutineDispatcher, logger: MobileInputLogger, override val tableLogBuffer: TableLogBuffer, + flags: FeatureFlags, scope: CoroutineScope, ) : MobileConnectionRepository { init { @@ -192,9 +195,15 @@ class MobileConnectionRepositoryImpl( .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val isRoaming = - callbackEvents - .mapNotNull { it.onServiceStateChanged } - .map { it.serviceState.roaming } + if (flags.isEnabled(ROAMING_INDICATOR_VIA_DISPLAY_INFO)) { + callbackEvents + .mapNotNull { it.onDisplayInfoChanged } + .map { it.telephonyDisplayInfo.isRoaming } + } else { + callbackEvents + .mapNotNull { it.onServiceStateChanged } + .map { it.serviceState.roaming } + } .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val operatorAlphaShort = @@ -380,6 +389,7 @@ class MobileConnectionRepositoryImpl( private val logger: MobileInputLogger, private val carrierConfigRepository: CarrierConfigRepository, private val mobileMappingsProxy: MobileMappingsProxy, + private val flags: FeatureFlags, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, ) { @@ -403,6 +413,7 @@ class MobileConnectionRepositoryImpl( bgDispatcher, logger, mobileLogger, + flags, scope, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index e7fc870fab33..93a92fdf1f52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -1396,7 +1396,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - // Configure UdfpsView to not accept the ACTION_DOWN event + // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); @@ -1427,6 +1427,66 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void onTouch_withNewTouchDetection_ignoreAuthPauseIfFingerDown() throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, + 0 /* pointerId */, touchData); + final TouchProcessorResult processorResultUp = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.UP, + -1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(true); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received and touch is within sensor + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); + MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); + mBiometricExecutor.runAllReady(); + + // THEN the down touch is received + verify(mInputManager).pilferPointers(any()); + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), + anyBoolean()); + + // GIVEN that auth is paused + when(mUdfpsAnimationViewController.shouldPauseAuth()).thenReturn(true); + + // WHEN ACTION_UP is received and touch is within sensor + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultUp); + event.setAction(ACTION_UP); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); + mBiometricExecutor.runAllReady(); + event.recycle(); + + // THEN the UP is still received + verify(mInputManager).pilferPointers(any()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), + anyBoolean()); + } + + @Test public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); @@ -1552,53 +1612,6 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onTouch_withNewTouchDetection_doNotProcessTouchWhenPullingUpBouncer() - throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultMove = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_MOVE event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS - when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true); - when(mLockscreenShadeTransitionController.getFractionToShade()).thenReturn(1f); - - // WHEN ACTION_MOVE is received and touch is within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultMove); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // THEN the touch is NOT processed - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - - @Test public void onTouch_withNewTouchDetection_qsDrag_processesTouchWhenAlternateBouncerVisible() throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index 3af444a789c5..27fd3b13d55a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -165,12 +165,28 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { int maxBrightness = 3; when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(), eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightness); + when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), + eq(UserHandle.USER_CURRENT))) + .thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); assertEquals(maxBrightness, mServiceFake.screenBrightness); } @Test + public void testAod_usesLightSensorNotClampingToAutoBrightnessValue() { + int maxBrightness = 3; + when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(), + eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightness); + when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), + eq(UserHandle.USER_CURRENT))) + .thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness); + } + + @Test public void doze_doesNotUseLightSensor() { // GIVEN the device is DOZE and the display state changes to ON mScreen.transitionTo(UNINITIALIZED, INITIALIZED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 67587e3a8914..37df93e4c809 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -373,6 +373,30 @@ public class TileLifecycleManagerTest extends SysuiTestCase { verify(falseContext).bindServiceAsUser(any(), any(), eq(flags), any()); } + @Test + public void testNullBindingCallsUnbind() { + Context mockContext = mock(Context.class); + // Binding has to succeed + when(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true); + TileLifecycleManager manager = new TileLifecycleManager(mHandler, mockContext, + mock(IQSService.class), + mMockPackageManagerAdapter, + mMockBroadcastDispatcher, + mTileServiceIntent, + mUser, + mExecutor); + + manager.executeSetBindService(true); + mExecutor.runAllReady(); + + ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class); + verify(mockContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any()); + + captor.getValue().onNullBinding(mTileServiceComponentName); + mExecutor.runAllReady(); + verify(mockContext).unbindService(captor.getValue()); + } + private void mockChangeEnabled(long changeId, boolean enabled) { doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(), any(UserHandle.class))); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index ede02d1fbc9c..ee48734463bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -23,6 +23,8 @@ import android.telephony.TelephonyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -66,6 +68,8 @@ import org.mockito.Mockito.verify class FullMobileConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: FullMobileConnectionRepository + private val flags = FakeFeatureFlags().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) } + private val systemClock = FakeSystemClock() private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -685,6 +689,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { testDispatcher, logger = mock(), tableLogBuffer, + flags, testScope.backgroundScope, ) whenever( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 3af960b74a5f..a4698f7d7574 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -27,7 +27,9 @@ import android.telephony.ServiceState.STATE_OUT_OF_SERVICE import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.DataActivityListener +import android.telephony.TelephonyCallback.DisplayInfoListener import android.telephony.TelephonyCallback.ServiceStateListener +import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE import android.telephony.TelephonyManager @@ -58,6 +60,8 @@ import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState @@ -103,6 +107,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionRepositoryImpl private lateinit var connectionsRepo: FakeMobileConnectionsRepository + private val flags = FakeFeatureFlags().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) } + @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: MobileInputLogger @Mock private lateinit var tableLogger: TableLogBuffer @@ -147,6 +153,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { testDispatcher, logger, tableLogger, + flags, testScope.backgroundScope, ) } @@ -599,8 +606,78 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test - fun roaming_gsm_queriesServiceState() = + fun roaming_gsm_queriesDisplayInfo_viaDisplayInfo() = testScope.runTest { + // GIVEN flag is true + flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) + + // Re-create the repository, because the flag is read at init + underTest = + MobileConnectionRepositoryImpl( + SUB_1_ID, + context, + subscriptionModel, + DEFAULT_NAME_MODEL, + SEP, + telephonyManager, + systemUiCarrierConfig, + fakeBroadcastDispatcher, + mobileMappings, + testDispatcher, + logger, + tableLogger, + flags, + testScope.backgroundScope, + ) + + var latest: Boolean? = null + val job = underTest.isRoaming.onEach { latest = it }.launchIn(this) + + val cb = getTelephonyCallbackForType<DisplayInfoListener>() + + // CDMA roaming is off, GSM roaming is off + whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF) + cb.onDisplayInfoChanged( + TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false) + ) + + assertThat(latest).isFalse() + + // CDMA roaming is off, GSM roaming is on + cb.onDisplayInfoChanged( + TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true) + ) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun roaming_gsm_queriesDisplayInfo_viaServiceState() = + testScope.runTest { + // GIVEN flag is false + flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, false) + + // Re-create the repository, because the flag is read at init + underTest = + MobileConnectionRepositoryImpl( + SUB_1_ID, + context, + subscriptionModel, + DEFAULT_NAME_MODEL, + SEP, + telephonyManager, + systemUiCarrierConfig, + fakeBroadcastDispatcher, + mobileMappings, + testDispatcher, + logger, + tableLogger, + flags, + testScope.backgroundScope, + ) + var latest: Boolean? = null val job = underTest.isRoaming.onEach { latest = it }.launchIn(this) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt index 852ed2054fcd..0939f28fcb49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt @@ -31,6 +31,8 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState @@ -96,6 +98,9 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { private lateinit var underTest: MobileConnectionRepositoryImpl private lateinit var connectionsRepo: FakeMobileConnectionsRepository + private val flags = + FakeFeatureFlags().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) } + @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: MobileInputLogger @Mock private lateinit var tableLogger: TableLogBuffer @@ -136,6 +141,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { testDispatcher, logger, tableLogger, + flags, testScope.backgroundScope, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 6f9764a907fc..77577f802950 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -41,6 +41,8 @@ import com.android.settingslib.R import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository @@ -91,6 +93,9 @@ import org.mockito.MockitoAnnotations class MobileConnectionsRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionsRepositoryImpl + private val flags = + FakeFeatureFlags().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) } + private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory @@ -182,6 +187,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { logger = logger, mobileMappingsProxy = mobileMappings, scope = testScope.backgroundScope, + flags = flags, carrierConfigRepository = carrierConfigRepository, ) carrierMergedFactory = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt index cf815c27a0bf..ec04da7030b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt @@ -50,10 +50,7 @@ object MobileTelephonyHelpers { } fun telephonyDisplayInfo(networkType: Int, overrideNetworkType: Int) = - mock<TelephonyDisplayInfo>().also { - whenever(it.networkType).thenReturn(networkType) - whenever(it.overrideNetworkType).thenReturn(overrideNetworkType) - } + TelephonyDisplayInfo(networkType, overrideNetworkType) inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T { val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>() diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index fdb28ba9103e..531227947ba0 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -5232,6 +5232,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void injectInputEventToInputFilter(InputEvent event) { + mSecurityPolicy.enforceCallingPermission(Manifest.permission.INJECT_EVENTS, + "injectInputEventToInputFilter"); synchronized (mLock) { final long endMillis = SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS; diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 34c2548b3d69..bfa042844fb1 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -705,8 +705,7 @@ public class CompanionDeviceManagerService extends SystemService { public PendingIntent requestNotificationAccess(ComponentName component, int userId) throws RemoteException { String callingPackage = component.getPackageName(); - checkCanCallNotificationApi(callingPackage); - // TODO: check userId. + checkCanCallNotificationApi(callingPackage, userId); if (component.flattenToString().length() > MAX_CN_LENGTH) { throw new IllegalArgumentException("Component name is too long."); } @@ -732,7 +731,7 @@ public class CompanionDeviceManagerService extends SystemService { @Deprecated @Override public boolean hasNotificationAccess(ComponentName component) throws RemoteException { - checkCanCallNotificationApi(component.getPackageName()); + checkCanCallNotificationApi(component.getPackageName(), getCallingUserId()); NotificationManager nm = getContext().getSystemService(NotificationManager.class); return nm.isNotificationListenerAccessGranted(component); } @@ -946,8 +945,7 @@ public class CompanionDeviceManagerService extends SystemService { createNewAssociation(userId, packageName, macAddressObj, null, null, false); } - private void checkCanCallNotificationApi(String callingPackage) { - final int userId = getCallingUserId(); + private void checkCanCallNotificationApi(String callingPackage, int userId) { enforceCallerIsSystemOr(userId, callingPackage); if (getCallingUid() == SYSTEM_UID) return; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9555ddc69932..3bbb62cecd70 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5410,7 +5410,20 @@ public class ActivityManagerService extends IActivityManager.Stub intent = new Intent(Intent.ACTION_MAIN); } try { - target.send(code, intent, resolvedType, allowlistToken, null, + if (allowlistToken != null) { + final int callingUid = Binder.getCallingUid(); + final String packageName; + final long token = Binder.clearCallingIdentity(); + try { + packageName = AppGlobals.getPackageManager().getNameForUid(callingUid); + } finally { + Binder.restoreCallingIdentity(token); + } + Slog.wtf(TAG, "Send a non-null allowlistToken to a non-PI target." + + " Calling package: " + packageName + "; intent: " + intent + + "; options: " + options); + } + target.send(code, intent, resolvedType, null, null, requiredPermission, options); } catch (RemoteException e) { } @@ -20121,7 +20134,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long token = Binder.clearCallingIdentity(); try { - return mOomAdjuster.mCachedAppOptimizer.isFreezerSupported(); + return CachedAppOptimizer.isFreezerSupported(); } finally { Binder.restoreCallingIdentity(token); } @@ -20249,4 +20262,21 @@ public class ActivityManagerService extends IActivityManager.Stub return index >= 0 && !mMediaProjectionTokenMap.valueAt(index).isEmpty(); } } + + /** + * Deal with binder transactions to frozen apps. + * + * @param debugPid The binder transaction sender + * @param code The binder transaction code + * @param flags The binder transaction flags + * @param err The binder transaction error + */ + @Override + public void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err) { + final ProcessRecord app; + synchronized (mPidsSelfLocked) { + app = mPidsSelfLocked.get(debugPid); + } + mOomAdjuster.mCachedAppOptimizer.binderError(debugPid, app, code, flags, err); + } } diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java index 9b5f18caf71a..710278d6b3c6 100644 --- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java +++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + import android.content.Context; import android.content.DialogInterface; import android.os.Handler; @@ -54,6 +56,7 @@ final class AppWaitingForDebuggerDialog extends BaseErrorDialog { setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app)); setTitle("Waiting For Debugger"); WindowManager.LayoutParams attrs = getWindow().getAttributes(); + attrs.privateFlags |= SYSTEM_FLAG_SHOW_FOR_ALL_USERS; attrs.setTitle("Waiting For Debugger: " + app.info.processName); getWindow().setAttributes(attrs); } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index db933239874f..70ed9165a18a 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -52,6 +52,8 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityThread; import android.app.ApplicationExitInfo; +import android.app.ApplicationExitInfo.Reason; +import android.app.ApplicationExitInfo.SubReason; import android.app.IApplicationThread; import android.database.ContentObserver; import android.net.Uri; @@ -67,6 +69,7 @@ import android.provider.DeviceConfig.OnPropertiesChangedListener; import android.provider.DeviceConfig.Properties; import android.provider.Settings; import android.text.TextUtils; +import android.util.ArraySet; import android.util.EventLog; import android.util.IntArray; import android.util.Pair; @@ -75,6 +78,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BinderfsStatsReader; import com.android.internal.os.ProcLocksReader; import com.android.internal.util.FrameworkStatsLog; import com.android.server.ServiceThread; @@ -131,6 +135,12 @@ public final class CachedAppOptimizer { "freeze_binder_offset"; @VisibleForTesting static final String KEY_FREEZER_BINDER_THRESHOLD = "freeze_binder_threshold"; + @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_ENABLED = + "freeze_binder_callback_enabled"; + @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_THROTTLE = + "freeze_binder_callback_throttle"; + @VisibleForTesting static final String KEY_FREEZER_BINDER_ASYNC_THRESHOLD = + "freeze_binder_async_threshold"; static final int UNFREEZE_REASON_NONE = FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_NONE; @@ -269,6 +279,9 @@ public final class CachedAppOptimizer { @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4; @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500; @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_THRESHOLD = 1_000; + @VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED = true; + @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE = 10_000L; + @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD = 1_024; @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor( Settings.Global.CACHED_APPS_FREEZER_ENABLED); @@ -311,6 +324,7 @@ public final class CachedAppOptimizer { static final int COMPACT_NATIVE_MSG = 5; static final int UID_FROZEN_STATE_CHANGED_MSG = 6; static final int DEADLOCK_WATCHDOG_MSG = 7; + static final int BINDER_ERROR_MSG = 8; // When free swap falls below this percentage threshold any full (file + anon) // compactions will be downgraded to file only compactions to reduce pressure @@ -407,7 +421,10 @@ public final class CachedAppOptimizer { } else if (KEY_FREEZER_BINDER_ENABLED.equals(name) || KEY_FREEZER_BINDER_DIVISOR.equals(name) || KEY_FREEZER_BINDER_THRESHOLD.equals(name) - || KEY_FREEZER_BINDER_OFFSET.equals(name)) { + || KEY_FREEZER_BINDER_OFFSET.equals(name) + || KEY_FREEZER_BINDER_CALLBACK_ENABLED.equals(name) + || KEY_FREEZER_BINDER_CALLBACK_THROTTLE.equals(name) + || KEY_FREEZER_BINDER_ASYNC_THRESHOLD.equals(name)) { updateFreezerBinderState(); } } @@ -479,7 +496,15 @@ public final class CachedAppOptimizer { @VisibleForTesting volatile int mFreezerBinderOffset = DEFAULT_FREEZER_BINDER_OFFSET; @GuardedBy("mPhenotypeFlagLock") @VisibleForTesting volatile long mFreezerBinderThreshold = DEFAULT_FREEZER_BINDER_THRESHOLD; - + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile boolean mFreezerBinderCallbackEnabled = + DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile long mFreezerBinderCallbackThrottle = + DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile int mFreezerBinderAsyncThreshold = + DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD; // Handler on which compaction runs. @VisibleForTesting @@ -487,6 +512,7 @@ public final class CachedAppOptimizer { private Handler mFreezeHandler; @GuardedBy("mProcLock") private boolean mFreezerOverride = false; + private long mFreezerBinderCallbackLast = -1; @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT; @VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG; @@ -789,6 +815,12 @@ public final class CachedAppOptimizer { pw.println(" " + KEY_FREEZER_BINDER_THRESHOLD + "=" + mFreezerBinderThreshold); pw.println(" " + KEY_FREEZER_BINDER_DIVISOR + "=" + mFreezerBinderDivisor); pw.println(" " + KEY_FREEZER_BINDER_OFFSET + "=" + mFreezerBinderOffset); + pw.println(" " + KEY_FREEZER_BINDER_CALLBACK_ENABLED + "=" + + mFreezerBinderCallbackEnabled); + pw.println(" " + KEY_FREEZER_BINDER_CALLBACK_THROTTLE + "=" + + mFreezerBinderCallbackThrottle); + pw.println(" " + KEY_FREEZER_BINDER_ASYNC_THRESHOLD + "=" + + mFreezerBinderAsyncThreshold); synchronized (mProcLock) { int size = mFrozenProcesses.size(); pw.println(" Apps frozen: " + size); @@ -1308,10 +1340,22 @@ public final class CachedAppOptimizer { mFreezerBinderThreshold = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, KEY_FREEZER_BINDER_THRESHOLD, DEFAULT_FREEZER_BINDER_THRESHOLD); + mFreezerBinderCallbackEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_FREEZER_BINDER_CALLBACK_ENABLED, DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED); + mFreezerBinderCallbackThrottle = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_FREEZER_BINDER_CALLBACK_THROTTLE, DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE); + mFreezerBinderAsyncThreshold = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_FREEZER_BINDER_ASYNC_THRESHOLD, DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD); Slog.d(TAG_AM, "Freezer binder state set to enabled=" + mFreezerBinderEnabled + ", divisor=" + mFreezerBinderDivisor + ", offset=" + mFreezerBinderOffset - + ", threshold=" + mFreezerBinderThreshold); + + ", threshold=" + mFreezerBinderThreshold + + ", callback enabled=" + mFreezerBinderCallbackEnabled + + ", callback throttle=" + mFreezerBinderCallbackThrottle + + ", async threshold=" + mFreezerBinderAsyncThreshold); } private boolean parseProcStateThrottle(String procStateThrottleString) { @@ -2174,6 +2218,21 @@ public final class CachedAppOptimizer { Slog.w(TAG_AM, "Unable to check file locks"); } } break; + case BINDER_ERROR_MSG: { + IntArray pids = new IntArray(); + // Copy the frozen pids to a local array to release mProcLock ASAP + synchronized (mProcLock) { + int size = mFrozenProcesses.size(); + for (int i = 0; i < size; i++) { + pids.add(mFrozenProcesses.keyAt(i)); + } + } + + // Check binder errors to frozen processes with a local freezer lock + synchronized (mFreezerLock) { + binderErrorLocked(pids); + } + } break; default: return; } @@ -2479,4 +2538,115 @@ public final class CachedAppOptimizer { return UNFREEZE_REASON_NONE; } } + + /** + * Kill a frozen process with a specified reason + */ + public void killProcess(int pid, String reason, @Reason int reasonCode, + @SubReason int subReason) { + mAm.mHandler.post(() -> { + synchronized (mAm) { + synchronized (mProcLock) { + ProcessRecord proc = mFrozenProcesses.get(pid); + // The process might have been killed or unfrozen by others + if (proc != null && proc.getThread() != null && !proc.isKilledByAm()) { + proc.killLocked(reason, reasonCode, subReason, true); + } + } + } + }); + } + + /** + * Sending binder transactions to frozen apps most likely indicates there's a bug. Log it and + * kill the frozen apps if they 1) receive sync binder transactions while frozen, or 2) miss + * async binder transactions due to kernel binder buffer running out. + * + * @param debugPid The binder transaction sender + * @param app The ProcessRecord of the sender + * @param code The binder transaction code + * @param flags The binder transaction flags + * @param err The binder transaction error + */ + public void binderError(int debugPid, ProcessRecord app, int code, int flags, int err) { + Slog.w(TAG_AM, "pid " + debugPid + " " + (app == null ? "null" : app.processName) + + " sent binder code " + code + " with flags " + flags + + " to frozen apps and got error " + err); + + // Do nothing if the binder error callback is not enabled. + // That means the frozen apps in a wrong state will be killed when they are unfrozen later. + if (!mUseFreezer || !mFreezerBinderCallbackEnabled) { + return; + } + + final long now = SystemClock.uptimeMillis(); + if (now < mFreezerBinderCallbackLast + mFreezerBinderCallbackThrottle) { + Slog.d(TAG_AM, "Too many transaction errors, throttling freezer binder callback."); + return; + } + mFreezerBinderCallbackLast = now; + + // Check all frozen processes in Freezer handler + mFreezeHandler.sendEmptyMessage(BINDER_ERROR_MSG); + } + + private void binderErrorLocked(IntArray pids) { + // PIDs that run out of async binder buffer when being frozen + ArraySet<Integer> pidsAsync = (mFreezerBinderAsyncThreshold < 0) ? null : new ArraySet<>(); + + for (int i = 0; i < pids.size(); i++) { + int current = pids.get(i); + try { + int freezeInfo = getBinderFreezeInfo(current); + + if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) { + killProcess(current, "Sync transaction while frozen", + ApplicationExitInfo.REASON_FREEZER, + ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION); + + // No need to check async transactions in this case + continue; + } + + if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) { + if (pidsAsync != null) { + pidsAsync.add(current); + } + if (DEBUG_FREEZER) { + Slog.w(TAG_AM, "pid " + current + + " received async transactions while frozen"); + } + } + } catch (Exception e) { + // The process has died. No need to kill it again. + Slog.w(TAG_AM, "Unable to query binder frozen stats for pid " + current); + } + } + + // TODO: when kernel binder driver supports, poll the binder status directly. + // Binderfs stats, like other debugfs files, is not a reliable interface. But it's the + // only true source for now. The following code checks all frozen PIDs. If any of them + // is running out of async binder buffer, kill it. Otherwise it will be killed at a + // later time when AMS unfreezes it, which causes race issues. + if (pidsAsync == null || pidsAsync.size() == 0) { + return; + } + new BinderfsStatsReader().handleFreeAsyncSpace( + // Check if the frozen process has pending async calls + pidsAsync::contains, + + // Kill the current process if it's running out of async binder space + (current, free) -> { + if (free < mFreezerBinderAsyncThreshold) { + Slog.w(TAG_AM, "pid " + current + + " has " + free + " free async space, killing"); + killProcess(current, "Async binder space running out while frozen", + ApplicationExitInfo.REASON_FREEZER, + ApplicationExitInfo.SUBREASON_FREEZER_BINDER_ASYNC_FULL); + } + }, + + // Log the error if binderfs stats can't be accesses or correctly parsed + exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats")); + } } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 96cca0d54e99..2ca7b6edd39b 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1928,24 +1928,26 @@ class UserController implements Handler.Callback { EventLog.writeEvent(EventLogTags.UC_SWITCH_USER, targetUserId); int currentUserId = getCurrentUserId(); UserInfo targetUserInfo = getUserInfo(targetUserId); - if (targetUserId == currentUserId) { - Slogf.i(TAG, "user #" + targetUserId + " is already the current user"); - return true; - } - if (targetUserInfo == null) { - Slogf.w(TAG, "No user info for user #" + targetUserId); - return false; - } - if (!targetUserInfo.supportsSwitchTo()) { - Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported"); - return false; - } - if (FactoryResetter.isFactoryResetting()) { - Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": factory reset in progress"); - return false; - } boolean userSwitchUiEnabled; synchronized (mLock) { + if (targetUserId == currentUserId && mTargetUserId == UserHandle.USER_NULL) { + Slogf.i(TAG, "user #" + targetUserId + " is already the current user"); + return true; + } + if (targetUserInfo == null) { + Slogf.w(TAG, "No user info for user #" + targetUserId); + return false; + } + if (!targetUserInfo.supportsSwitchTo()) { + Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported"); + return false; + } + if (FactoryResetter.isFactoryResetting()) { + Slogf.w(TAG, "Cannot switch to User #" + targetUserId + + ": factory reset in progress"); + return false; + } + if (!mInitialized) { Slogf.e(TAG, "Cannot switch to User #" + targetUserId + ": UserController not ready yet"); diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index 292fc14ac6eb..51cb9505ed4f 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -165,7 +165,8 @@ import java.util.Objects; @Override public String toString() { - return "type: " + mDeviceType + "internal type: " + mInternalDeviceType + return "type: " + mDeviceType + + " internal type: 0x" + Integer.toHexString(mInternalDeviceType) + " addr: " + mDeviceAddress + " bt audio type: " + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index b9535d6afc6c..96d20789c053 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1250,8 +1250,8 @@ public class AudioDeviceBroker { } /*package*/ void registerStrategyPreferredDevicesDispatcher( - @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { - mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher); + @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) { + mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyPreferredDevicesDispatcher( @@ -1260,8 +1260,8 @@ public class AudioDeviceBroker { } /*package*/ void registerStrategyNonDefaultDevicesDispatcher( - @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { - mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher); + @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) { + mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher( @@ -1279,8 +1279,8 @@ public class AudioDeviceBroker { } /*package*/ void registerCapturePresetDevicesRoleDispatcher( - @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { - mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher); + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) { + mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher, isPrivileged); } /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( @@ -1288,6 +1288,11 @@ public class AudioDeviceBroker { mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher); } + /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked( + List<AudioDeviceAttributes> devices) { + return mAudioService.anonymizeAudioDeviceAttributesListUnchecked(devices); + } + /*package*/ void registerCommunicationDeviceDispatcher( @NonNull ICommunicationDeviceDispatcher dispatcher) { mCommDevDispatchers.register(dispatcher); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e08fdd65ad94..a1d2e1412777 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -121,6 +121,26 @@ public class AudioDeviceInventory { } /** + * Adds a new entry in mDeviceInventory if the AudioDeviceAttributes passed is an sink + * Bluetooth device and no corresponding entry already exists. + * @param ada the device to add if needed + */ + void addAudioDeviceInInventoryIfNeeded(AudioDeviceAttributes ada) { + if (!AudioSystem.isBluetoothOutDevice(ada.getInternalType())) { + return; + } + synchronized (mDeviceInventoryLock) { + if (findDeviceStateForAudioDeviceAttributes(ada, ada.getType()) != null) { + return; + } + AdiDeviceState ads = new AdiDeviceState( + ada.getType(), ada.getInternalType(), ada.getAddress()); + mDeviceInventory.put(ads.getDeviceId(), ads); + } + mDeviceBroker.persistAudioDeviceSettings(); + } + + /** * Adds a new AdiDeviceState or updates the audio device cateogory of the matching * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. * @param deviceState the device to update @@ -992,8 +1012,8 @@ public class AudioDeviceInventory { /*package*/ void registerStrategyPreferredDevicesDispatcher( - @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { - mPrefDevDispatchers.register(dispatcher); + @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) { + mPrefDevDispatchers.register(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyPreferredDevicesDispatcher( @@ -1002,8 +1022,8 @@ public class AudioDeviceInventory { } /*package*/ void registerStrategyNonDefaultDevicesDispatcher( - @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { - mNonDefDevDispatchers.register(dispatcher); + @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) { + mNonDefDevDispatchers.register(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher( @@ -1084,8 +1104,8 @@ public class AudioDeviceInventory { } /*package*/ void registerCapturePresetDevicesRoleDispatcher( - @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { - mDevRoleCapturePresetDispatchers.register(dispatcher); + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) { + mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged); } /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( @@ -1414,6 +1434,8 @@ public class AudioDeviceInventory { updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/); if (!connect) { purgeDevicesRoles_l(); + } else { + addAudioDeviceInInventoryIfNeeded(attributes); } } mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); @@ -1702,6 +1724,7 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); + addAudioDeviceInInventoryIfNeeded(ada); } static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER, @@ -2006,9 +2029,9 @@ public class AudioDeviceInventory { final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, AudioSystem.DEVICE_OUT_HEARING_AID); mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); - - mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_HEARING_AID, address, name), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_HEARING_AID, address, name); + mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( @@ -2018,6 +2041,7 @@ public class AudioDeviceInventory { mDeviceBroker.postApplyVolumeOnDevice(streamType, AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/); + addAudioDeviceInInventoryIfNeeded(ada); new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable") .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") .set(MediaMetrics.Property.DEVICE, @@ -2128,6 +2152,7 @@ public class AudioDeviceInventory { sensorUuid)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); + addAudioDeviceInInventoryIfNeeded(ada); } if (streamType == AudioSystem.STREAM_DEFAULT) { @@ -2462,6 +2487,9 @@ public class AudioDeviceInventory { final int nbDispatchers = mPrefDevDispatchers.beginBroadcast(); for (int i = 0; i < nbDispatchers; i++) { try { + if (!((Boolean) mPrefDevDispatchers.getBroadcastCookie(i))) { + devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices); + } mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged( strategy, devices); } catch (RemoteException e) { @@ -2475,6 +2503,9 @@ public class AudioDeviceInventory { final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast(); for (int i = 0; i < nbDispatchers; i++) { try { + if (!((Boolean) mNonDefDevDispatchers.getBroadcastCookie(i))) { + devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices); + } mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged( strategy, devices); } catch (RemoteException e) { @@ -2488,6 +2519,9 @@ public class AudioDeviceInventory { final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast(); for (int i = 0; i < nbDispatchers; ++i) { try { + if (!((Boolean) mDevRoleCapturePresetDispatchers.getBroadcastCookie(i))) { + devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices); + } mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged( capturePreset, role, devices); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index b555a52fe720..c31747389845 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2851,8 +2851,11 @@ public class AudioService extends IAudioService.Stub if (devices == null) { return AudioSystem.ERROR; } + + devices = retrieveBluetoothAddresses(devices); + final String logString = String.format( - "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s", + "setPreferredDevicesForStrategy u/pid:%d/%d strat:%d dev:%s", Binder.getCallingUid(), Binder.getCallingPid(), strategy, devices.stream().map(e -> e.toString()).collect(Collectors.joining(","))); sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); @@ -2908,7 +2911,7 @@ public class AudioService extends IAudioService.Stub status, strategy)); return new ArrayList<AudioDeviceAttributes>(); } else { - return devices; + return anonymizeAudioDeviceAttributesList(devices); } } @@ -2923,6 +2926,9 @@ public class AudioService extends IAudioService.Stub @NonNull AudioDeviceAttributes device) { super.setDeviceAsNonDefaultForStrategy_enforcePermission(); Objects.requireNonNull(device); + + device = retrieveBluetoothAddress(device); + final String logString = String.format( "setDeviceAsNonDefaultForStrategy u/pid:%d/%d strat:%d dev:%s", Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString()); @@ -2949,6 +2955,9 @@ public class AudioService extends IAudioService.Stub AudioDeviceAttributes device) { super.removeDeviceAsNonDefaultForStrategy_enforcePermission(); Objects.requireNonNull(device); + + device = retrieveBluetoothAddress(device); + final String logString = String.format( "removeDeviceAsNonDefaultForStrategy strat:%d dev:%s", strategy, device.toString()); sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); @@ -2983,7 +2992,7 @@ public class AudioService extends IAudioService.Stub status, strategy)); return new ArrayList<AudioDeviceAttributes>(); } else { - return devices; + return anonymizeAudioDeviceAttributesList(devices); } } @@ -2996,7 +3005,8 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher); + mDeviceBroker.registerStrategyPreferredDevicesDispatcher( + dispatcher, isBluetoothPrividged()); } /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener( @@ -3020,7 +3030,8 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher); + mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher( + dispatcher, isBluetoothPrividged()); } /** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener( @@ -3036,7 +3047,7 @@ public class AudioService extends IAudioService.Stub } /** - * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes) + * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes) */ public int setPreferredDevicesForCapturePreset( int capturePreset, List<AudioDeviceAttributes> devices) { @@ -3055,6 +3066,8 @@ public class AudioService extends IAudioService.Stub return AudioSystem.ERROR; } + devices = retrieveBluetoothAddresses(devices); + final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync( capturePreset, devices); if (status != AudioSystem.SUCCESS) { @@ -3101,7 +3114,7 @@ public class AudioService extends IAudioService.Stub status, capturePreset)); return new ArrayList<AudioDeviceAttributes>(); } else { - return devices; + return anonymizeAudioDeviceAttributesList(devices); } } @@ -3115,7 +3128,8 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher); + mDeviceBroker.registerCapturePresetDevicesRoleDispatcher( + dispatcher, isBluetoothPrividged()); } /** @@ -3135,7 +3149,9 @@ public class AudioService extends IAudioService.Stub public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes) { enforceQueryStateOrModifyRoutingPermission(); - return getDevicesForAttributesInt(attributes, false /* forVolume */); + + return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList( + getDevicesForAttributesInt(attributes, false /* forVolume */))); } /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes) @@ -3145,7 +3161,8 @@ public class AudioService extends IAudioService.Stub */ public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected( @NonNull AudioAttributes attributes) { - return getDevicesForAttributesInt(attributes, false /* forVolume */); + return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList( + getDevicesForAttributesInt(attributes, false /* forVolume */))); } /** @@ -7346,6 +7363,8 @@ public class AudioService extends IAudioService.Stub Objects.requireNonNull(device); AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); + device = retrieveBluetoothAddress(device); + sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:" + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:" + device.getAddress() + " behavior:" @@ -7429,6 +7448,8 @@ public class AudioService extends IAudioService.Stub // verify parameters Objects.requireNonNull(device); + device = retrieveBluetoothAddress(device); + return getDeviceVolumeBehaviorInt(device); } @@ -7503,9 +7524,12 @@ public class AudioService extends IAudioService.Stub /** * see AudioManager.setWiredDeviceConnectionState() */ - public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, + public void setWiredDeviceConnectionState(@NonNull AudioDeviceAttributes attributes, @ConnectionState int state, String caller) { super.setWiredDeviceConnectionState_enforcePermission(); + Objects.requireNonNull(attributes); + + attributes = retrieveBluetoothAddress(attributes); if (state != CONNECTION_STATE_CONNECTED && state != CONNECTION_STATE_DISCONNECTED) { @@ -7546,6 +7570,9 @@ public class AudioService extends IAudioService.Stub boolean connected) { Objects.requireNonNull(device); enforceModifyAudioRoutingPermission(); + + device = retrieveBluetoothAddress(device); + mDeviceBroker.setTestDeviceConnectionState(device, connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED); // simulate a routing update from native @@ -10304,6 +10331,100 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect); } + private boolean isBluetoothPrividged() { + return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( + android.Manifest.permission.BLUETOOTH_CONNECT) + || Binder.getCallingUid() == Process.SYSTEM_UID; + } + + List<AudioDeviceAttributes> retrieveBluetoothAddresses(List<AudioDeviceAttributes> devices) { + if (isBluetoothPrividged()) { + return devices; + } + + List<AudioDeviceAttributes> checkedDevices = new ArrayList<AudioDeviceAttributes>(); + for (AudioDeviceAttributes ada : devices) { + if (ada == null) { + continue; + } + checkedDevices.add(retrieveBluetoothAddressUncheked(ada)); + } + return checkedDevices; + } + + AudioDeviceAttributes retrieveBluetoothAddress(@NonNull AudioDeviceAttributes ada) { + if (isBluetoothPrividged()) { + return ada; + } + return retrieveBluetoothAddressUncheked(ada); + } + + AudioDeviceAttributes retrieveBluetoothAddressUncheked(@NonNull AudioDeviceAttributes ada) { + Objects.requireNonNull(ada); + if (AudioSystem.isBluetoothDevice(ada.getInternalType())) { + String anonymizedAddress = anonymizeBluetoothAddress(ada.getAddress()); + for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) { + if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType()) + && (ada.getInternalType() == ads.getInternalDeviceType()) + && anonymizedAddress.equals(anonymizeBluetoothAddress( + ads.getDeviceAddress())))) { + continue; + } + ada.setAddress(ads.getDeviceAddress()); + break; + } + } + return ada; + } + + /** + * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app + * Must match the implementation of BluetoothUtils.toAnonymizedAddress() + * @param address Mac address to be anonymized + * @return anonymized mac address + */ + static String anonymizeBluetoothAddress(String address) { + if (address == null || address.length() != "AA:BB:CC:DD:EE:FF".length()) { + return null; + } + return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length()); + } + + private List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesList( + List<AudioDeviceAttributes> devices) { + if (isBluetoothPrividged()) { + return devices; + } + return anonymizeAudioDeviceAttributesListUnchecked(devices); + } + + /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked( + List<AudioDeviceAttributes> devices) { + List<AudioDeviceAttributes> anonymizedDevices = new ArrayList<AudioDeviceAttributes>(); + for (AudioDeviceAttributes ada : devices) { + anonymizedDevices.add(anonymizeAudioDeviceAttributesUnchecked(ada)); + } + return anonymizedDevices; + } + + private AudioDeviceAttributes anonymizeAudioDeviceAttributesUnchecked( + AudioDeviceAttributes ada) { + if (!AudioSystem.isBluetoothDevice(ada.getInternalType())) { + return ada; + } + AudioDeviceAttributes res = new AudioDeviceAttributes(ada); + res.setAddress(anonymizeBluetoothAddress(ada.getAddress())); + return res; + } + + private AudioDeviceAttributes anonymizeAudioDeviceAttributes(AudioDeviceAttributes ada) { + if (isBluetoothPrividged()) { + return ada; + } + + return anonymizeAudioDeviceAttributesUnchecked(ada); + } + //========================================================================================== // camera sound is forced if any of the resources corresponding to one active SIM @@ -10351,13 +10472,16 @@ public class AudioService extends IAudioService.Stub Objects.requireNonNull(usages); Objects.requireNonNull(device); enforceModifyAudioRoutingPermission(); + + final AudioDeviceAttributes ada = retrieveBluetoothAddress(device); + if (timeOutMs <= 0 || usages.length == 0) { throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute"); } Log.i(TAG, "muteAwaitConnection dev:" + device + " timeOutMs:" + timeOutMs + " usages:" + Arrays.toString(usages)); - if (mDeviceBroker.isDeviceConnected(device)) { + if (mDeviceBroker.isDeviceConnected(ada)) { // not throwing an exception as there could be a race between a connection (server-side, // notification of connection in flight) and a mute operation (client-side) Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected"); @@ -10369,12 +10493,19 @@ public class AudioService extends IAudioService.Stub + mMutingExpectedDevice); throw new IllegalStateException("muteAwaitConnection already in progress"); } - mMutingExpectedDevice = device; + mMutingExpectedDevice = ada; mMutedUsagesAwaitingConnection = usages; - mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs); + mPlaybackMonitor.muteAwaitConnection(usages, ada, timeOutMs); } - dispatchMuteAwaitConnection(cb -> { try { - cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } }); + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { + AudioDeviceAttributes dev = ada; + if (!isPrivileged) { + dev = anonymizeAudioDeviceAttributesUnchecked(ada); + } + cb.dispatchOnMutedUntilConnection(dev, usages); + } catch (RemoteException e) { } + }); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) @@ -10383,7 +10514,7 @@ public class AudioService extends IAudioService.Stub super.getMutingExpectedDevice_enforcePermission(); synchronized (mMuteAwaitConnectionLock) { - return mMutingExpectedDevice; + return anonymizeAudioDeviceAttributes(mMutingExpectedDevice); } } @@ -10392,6 +10523,9 @@ public class AudioService extends IAudioService.Stub public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) { Objects.requireNonNull(device); enforceModifyAudioRoutingPermission(); + + final AudioDeviceAttributes ada = retrieveBluetoothAddress(device); + Log.i(TAG, "cancelMuteAwaitConnection for device:" + device); final int[] mutedUsages; synchronized (mMuteAwaitConnectionLock) { @@ -10401,7 +10535,7 @@ public class AudioService extends IAudioService.Stub Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device"); return; } - if (!device.equalTypeAddress(mMutingExpectedDevice)) { + if (!ada.equalTypeAddress(mMutingExpectedDevice)) { Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device + "] but expected device is" + mMutingExpectedDevice); throw new IllegalStateException("cancelMuteAwaitConnection for wrong device"); @@ -10411,8 +10545,14 @@ public class AudioService extends IAudioService.Stub mMutedUsagesAwaitingConnection = null; mPlaybackMonitor.cancelMuteAwaitConnection("cancelMuteAwaitConnection dev:" + device); } - dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent( - AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages); + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { + AudioDeviceAttributes dev = ada; + if (!isPrivileged) { + dev = anonymizeAudioDeviceAttributesUnchecked(ada); + } + cb.dispatchOnUnmutedEvent( + AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, dev, mutedUsages); } catch (RemoteException e) { } }); } @@ -10426,7 +10566,7 @@ public class AudioService extends IAudioService.Stub super.registerMuteAwaitConnectionDispatcher_enforcePermission(); if (register) { - mMuteAwaitConnectionDispatchers.register(cb); + mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged()); } else { mMuteAwaitConnectionDispatchers.unregister(cb); } @@ -10450,8 +10590,14 @@ public class AudioService extends IAudioService.Stub mPlaybackMonitor.cancelMuteAwaitConnection( "checkMuteAwaitConnection device " + device + " connected, unmuting"); } - dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent( - AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages); + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { + AudioDeviceAttributes ada = device; + if (!isPrivileged) { + ada = anonymizeAudioDeviceAttributesUnchecked(device); + } + cb.dispatchOnUnmutedEvent(AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, + ada, mutedUsages); } catch (RemoteException e) { } }); } @@ -10471,7 +10617,8 @@ public class AudioService extends IAudioService.Stub mMutingExpectedDevice = null; mMutedUsagesAwaitingConnection = null; } - dispatchMuteAwaitConnection(cb -> { try { + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { cb.dispatchOnUnmutedEvent( AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT, timedOutDevice, mutedUsages); @@ -10479,13 +10626,14 @@ public class AudioService extends IAudioService.Stub } private void dispatchMuteAwaitConnection( - java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) { + java.util.function.BiConsumer<IMuteAwaitConnectionCallback, Boolean> callback) { final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast(); // lazy initialization as errors unlikely ArrayList<IMuteAwaitConnectionCallback> errorList = null; for (int i = 0; i < nbDispatchers; i++) { try { - callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i)); + callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i), + (Boolean) mMuteAwaitConnectionDispatchers.getBroadcastCookie(i)); } catch (Exception e) { if (errorList == null) { errorList = new ArrayList<>(1); @@ -13048,6 +13196,9 @@ public class AudioService extends IAudioService.Stub @NonNull AudioDeviceAttributes device, @IntRange(from = 0) long delayMillis) { Objects.requireNonNull(device, "device must not be null"); enforceModifyAudioRoutingPermission(); + + device = retrieveBluetoothAddress(device); + final String getterKey = "additional_output_device_delay=" + device.getInternalType() + "," + device.getAddress(); // "getter" key as an id. final String setterKey = getterKey + "," + delayMillis; // append the delay for setter @@ -13068,6 +13219,9 @@ public class AudioService extends IAudioService.Stub @IntRange(from = 0) public long getAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) { Objects.requireNonNull(device, "device must not be null"); + + device = retrieveBluetoothAddress(device); + final String key = "additional_output_device_delay"; final String reply = AudioSystem.getParameters( key + "=" + device.getInternalType() + "," + device.getAddress()); @@ -13095,6 +13249,9 @@ public class AudioService extends IAudioService.Stub @IntRange(from = 0) public long getMaxAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) { Objects.requireNonNull(device, "device must not be null"); + + device = retrieveBluetoothAddress(device); + final String key = "max_additional_output_device_delay"; final String reply = AudioSystem.getParameters( key + "=" + device.getInternalType() + "," + device.getAddress()); diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 115421db4d31..fe4b042652d6 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -467,13 +467,14 @@ public class LocationManagerService extends ILocationManager.Stub implements // If we have a GNSS provider override, add the hardware provider as a standalone // option for use by apps with the correct permission. Note the GNSS HAL can only // support a single client, so mGnssManagerService.getGnssLocationProvider() can - // only be installed with a single provider. + // only be installed with a single provider. Locations from this provider won't + // be reported through the passive provider. LocationProviderManager gnssHardwareManager = new LocationProviderManager( mContext, mInjector, GPS_HARDWARE_PROVIDER, - mPassiveManager, + /*passiveManager=*/ null, Collections.singletonList(Manifest.permission.LOCATION_HARDWARE)); addLocationProviderManager( gnssHardwareManager, mGnssManagerService.getGnssLocationProvider()); diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 017698943fc9..e8f78f31729c 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -118,7 +118,10 @@ public final class SnoozeHelper { protected boolean canSnooze(int numberToSnooze) { synchronized (mLock) { - if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) { + if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT + || (mPersistedSnoozedNotifications.size() + + mPersistedSnoozedNotificationsWithContext.size() + numberToSnooze) + > CONCURRENT_SNOOZE_LIMIT) { return false; } } @@ -343,6 +346,9 @@ public final class SnoozeHelper { if (groupSummaryKey != null) { NotificationRecord record = mSnoozedNotifications.remove(groupSummaryKey); + String trimmedKey = getTrimmedString(groupSummaryKey); + mPersistedSnoozedNotificationsWithContext.remove(trimmedKey); + mPersistedSnoozedNotifications.remove(trimmedKey); if (record != null && !record.isCanceled) { Runnable runnable = () -> { diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java index e53c4367a497..ca149c5d2f31 100644 --- a/services/core/java/com/android/server/os/SchedulingPolicyService.java +++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java @@ -219,6 +219,7 @@ public class SchedulingPolicyService extends ISchedulingPolicyService.Stub { case Process.AUDIOSERVER_UID: // fastcapture, fastmixer case Process.CAMERASERVER_UID: // camera high frame rate recording case Process.BLUETOOTH_UID: // Bluetooth audio playback + case Process.PHONE_UID: // phone call return true; default: return false; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 062d797937c0..6a6880ab57c0 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3706,7 +3706,8 @@ public class UserManagerService extends IUserManager.Stub { if (type == XmlPullParser.START_TAG) { final String name = parser.getName(); if (name.equals(TAG_USER)) { - UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID)); + UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID), + mUserVersion); if (userData != null) { synchronized (mUsersLock) { @@ -4387,7 +4388,7 @@ public class UserManagerService extends IUserManager.Stub { } @GuardedBy({"mPackagesLock"}) - private UserData readUserLP(int id) { + private UserData readUserLP(int id, int userVersion) { try (ResilientAtomicFile file = getUserFile(id)) { FileInputStream fis = null; try { @@ -4396,19 +4397,19 @@ public class UserManagerService extends IUserManager.Stub { Slog.e(LOG_TAG, "User info not found, returning null, user id: " + id); return null; } - return readUserLP(id, fis); + return readUserLP(id, fis, userVersion); } catch (Exception e) { // Remove corrupted file and retry. Slog.e(LOG_TAG, "Error reading user info, user id: " + id); file.failRead(fis, e); - return readUserLP(id); + return readUserLP(id, userVersion); } } } @GuardedBy({"mPackagesLock"}) @VisibleForTesting - UserData readUserLP(int id, InputStream is) throws IOException, + UserData readUserLP(int id, InputStream is, int userVersion) throws IOException, XmlPullParserException { int flags = 0; String userType = null; @@ -4501,7 +4502,17 @@ public class UserManagerService extends IUserManager.Stub { } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) { legacyLocalRestrictions = UserRestrictionsUtils.readRestrictions(parser); } else if (TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS.equals(tag)) { - localRestrictions = UserRestrictionsUtils.readRestrictions(parser); + if (userVersion < 10) { + // Prior to version 10, the local user restrictions were stored as sub tags + // grouped by the user id of the source user. The source is no longer stored + // on versions 10+ as this is now stored in the DevicePolicyEngine. + RestrictionsSet oldLocalRestrictions = + RestrictionsSet.readRestrictions( + parser, TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS); + localRestrictions = oldLocalRestrictions.mergeAll(); + } else { + localRestrictions = UserRestrictionsUtils.readRestrictions(parser); + } } else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) { globalRestrictions = UserRestrictionsUtils.readRestrictions(parser); } else if (TAG_ACCOUNT.equals(tag)) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 04f1fac9134b..3caba7318988 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -700,7 +700,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private boolean mCurrentLaunchCanTurnScreenOn = true; /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */ - private boolean mLastSurfaceShowing; + boolean mLastSurfaceShowing; /** * The activity is opaque and fills the entire space of this task. @@ -5348,19 +5348,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Finish should only ever commit visibility=false, so we can check full containment // rather than just direct membership. inFinishingTransition = mTransitionController.inFinishingTransition(this); - if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) { - Slog.e(TAG, "setVisibility=" + visible - + " while transition is not collecting or finishing " - + this + " caller=" + Debug.getCallers(8)); - // Force showing the parents because they may be hidden by previous transition. + if (!inFinishingTransition) { if (visible) { - final Transaction t = getSyncTransaction(); - for (WindowContainer<?> p = getParent(); p != null && p != mDisplayContent; - p = p.getParent()) { - if (p.mSurfaceControl != null) { - t.show(p.mSurfaceControl); - } + if (!mDisplayContent.isSleeping() || canShowWhenLocked()) { + mTransitionController.onVisibleWithoutCollectingTransition(this, + Debug.getCallers(1, 1)); } + } else if (!mDisplayContent.isSleeping()) { + Slog.w(TAG, "Set invisible without transition " + this); } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index be7d9b63f779..15a3f9349007 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -87,9 +87,11 @@ class ActivityRecordInputSink { activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid() || activityBelowInTask.isUid(mActivityRecord.getUid())); if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()) { + // Set to non-touchable, so the touch events can pass through. mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE, InputConfig.NOT_TOUCHABLE); } else { + // Set to touchable, so it can block by intercepting the touch events. mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE); } mInputWindowHandleWrapper.setDisplayId(mActivityRecord.getDisplayId()); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index e7c6ff4d887f..7ef90fe12824 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1374,7 +1374,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { dc.handleCompleteDeferredRemoval(); } validateKeyguardOcclusion(); - validateVisibility(); mState = STATE_FINISHED; // Rotation change may be deferred while there is a display change transition, so check @@ -2734,29 +2733,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } - private void validateVisibility() { - for (int i = mTargets.size() - 1; i >= 0; --i) { - if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) { - return; - } - } - // All modes are CLOSE. The surfaces may be hidden by the animation unexpectedly. - // If the window container should be visible, then recover it. - mController.mStateValidators.add(() -> { - for (int i = mTargets.size() - 1; i >= 0; --i) { - final ChangeInfo change = mTargets.get(i); - if (!change.mContainer.isVisibleRequested() - || change.mContainer.mSurfaceControl == null) { - continue; - } - Slog.e(TAG, "Force show for visible " + change.mContainer - + " which may be hidden by transition unexpectedly"); - change.mContainer.getSyncTransaction().show(change.mContainer.mSurfaceControl); - change.mContainer.scheduleAnimation(); - } - }); - } - /** * Returns {@code true} if the transition and the corresponding transaction should be applied * on display thread. Currently, this only checks for display rotation change because the order diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index a43a6f6292ae..3ad42328a2df 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -951,6 +951,44 @@ class TransitionController { mValidateDisplayVis.clear(); } + void onVisibleWithoutCollectingTransition(WindowContainer<?> wc, String caller) { + final boolean isPlaying = !mPlayingTransitions.isEmpty(); + Slog.e(TAG, "Set visible without transition " + wc + " playing=" + isPlaying + + " caller=" + caller); + if (!isPlaying) { + enforceSurfaceVisible(wc); + return; + } + // Update surface visibility after the playing transitions are finished, so the last + // visibility won't be replaced by the finish transaction of transition. + mStateValidators.add(() -> { + if (wc.isVisibleRequested()) { + enforceSurfaceVisible(wc); + } + }); + } + + private void enforceSurfaceVisible(WindowContainer<?> wc) { + if (wc.mSurfaceControl == null) return; + wc.getSyncTransaction().show(wc.mSurfaceControl); + final ActivityRecord ar = wc.asActivityRecord(); + if (ar != null) { + ar.mLastSurfaceShowing = true; + } + // Force showing the parents because they may be hidden by previous transition. + for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent; + p = p.getParent()) { + if (p.mSurfaceControl != null) { + p.getSyncTransaction().show(p.mSurfaceControl); + final Task task = p.asTask(); + if (task != null) { + task.mLastSurfaceShowing = true; + } + } + } + wc.scheduleAnimation(); + } + /** * Called when the transition has a complete set of participants for its operation. In other * words, it is when the transition is "ready" but is still waiting for participants to draw. diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e6ab78d341a2..758a971089fe 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -65,6 +65,7 @@ import android.os.Environment; import android.os.FactoryTest; import android.os.FileUtils; import android.os.IBinder; +import android.os.IBinderCallback; import android.os.IIncidentManager; import android.os.Looper; import android.os.Message; @@ -970,6 +971,14 @@ public final class SystemServer implements Dumpable { } } + // Set binder transaction callback after starting system services + Binder.setTransactionCallback(new IBinderCallback() { + @Override + public void onTransactionError(int pid, int code, int flags, int err) { + mActivityManagerService.frozenBinderTransactionDetected(pid, code, flags, err); + } + }); + // Loop forever. Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); diff --git a/services/tests/servicestests/res/xml/user_100_v9.xml b/services/tests/servicestests/res/xml/user_100_v9.xml new file mode 100644 index 000000000000..03c08ed40828 --- /dev/null +++ b/services/tests/servicestests/res/xml/user_100_v9.xml @@ -0,0 +1,20 @@ +<user id="100" + serialNumber="0" + flags="3091" + type="android.os.usertype.full.SYSTEM" + created="0" + lastLoggedIn="0" + lastLoggedInFingerprint="0" + profileBadge="0"> + <restrictions no_oem_unlock="true" /> + <device_policy_local_restrictions> + <restrictions_user user_id="0"> + <restrictions no_camera="true" /> + </restrictions_user> + <restrictions_user user_id="100"> + <restrictions no_camera="true" /> + <restrictions no_install_unknown_sources="true" /> + </restrictions_user> + </device_policy_local_restrictions> + <ignorePrepareStorageErrors>false</ignorePrepareStorageErrors> +</user>
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index 9f75cf8d552e..429c58e768bf 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -43,27 +43,33 @@ import android.annotation.UserIdInt; import android.app.PropertyInvalidatedCache; import android.content.pm.UserInfo; import android.content.pm.UserInfo.UserInfoFlag; +import android.content.res.Resources; import android.os.Looper; import android.os.Parcel; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.text.TextUtils; +import android.util.Xml; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; +import com.android.frameworks.servicestests.R; import com.android.server.LocalServices; import com.android.server.pm.UserManagerService.UserData; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlSerializer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; +import java.nio.charset.StandardCharsets; import java.util.List; /** @@ -76,6 +82,7 @@ import java.util.List; @MediumTest public class UserManagerServiceUserInfoTest { private UserManagerService mUserManagerService; + private Resources mResources; @Before public void setup() { @@ -95,6 +102,8 @@ public class UserManagerServiceUserInfoTest { assertEquals("Multiple users so this test can't run.", 1, users.size()); assertEquals("Only user present isn't the system user.", UserHandle.USER_SYSTEM, users.get(0).id); + + mResources = InstrumentationRegistry.getTargetContext().getResources(); } @Test @@ -108,7 +117,7 @@ public class UserManagerServiceUserInfoTest { byte[] bytes = baos.toByteArray(); UserData read = mUserManagerService.readUserLP( - data.info.id, new ByteArrayInputStream(bytes)); + data.info.id, new ByteArrayInputStream(bytes), 0); assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false); } @@ -135,7 +144,11 @@ public class UserManagerServiceUserInfoTest { // Clear the restrictions to see if they are properly read in from the user file. setUserRestrictions(data.info.id, globalRestriction, localRestriction, false); - mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes)); + final int userVersion = 10; + //read the secondary and SYSTEM user file to fetch local/global device policy restrictions. + mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes), + userVersion); + assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction)); assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(localRestriction)); } @@ -286,6 +299,45 @@ public class UserManagerServiceUserInfoTest { assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO)); } + /** Tests readUserLP upgrading from version 9 to 10+. */ + @Test + public void testUserRestrictionsUpgradeFromV9() throws Exception { + final String[] localRestrictions = new String[] { + UserManager.DISALLOW_CAMERA, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + }; + + final int userId = 100; + UserData data = new UserData(); + data.info = createUser(userId, FLAG_FULL, "A type"); + + mUserManagerService.putUserInfo(data.info); + + for (String restriction : localRestrictions) { + assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId)); + assertFalse(mUserManagerService.hasUserRestriction(restriction, userId)); + } + + // Convert the xml resource to the system storage xml format. + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream os = new DataOutputStream(baos); + XmlPullParser in = mResources.getXml(R.xml.user_100_v9); + XmlSerializer out = Xml.newBinarySerializer(); + out.setOutput(os, StandardCharsets.UTF_8.name()); + Xml.copy(in, out); + byte[] userBytes = baos.toByteArray(); + baos.reset(); + + final int userVersion = 9; + mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(userBytes), + userVersion); + + for (String restriction : localRestrictions) { + assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId)); + assertTrue(mUserManagerService.hasUserRestriction(restriction, userId)); + } + } + /** Creates a UserInfo with the given flags and userType. */ private UserInfo createUser(@UserIdInt int userId, @UserInfoFlag int flags, String userType) { return new UserInfo(userId, "A Name", "A path", flags, userType); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index 51b9c176a245..22c7f9c88867 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -18,6 +18,8 @@ package com.android.server.notification; import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT; import static com.android.server.notification.SnoozeHelper.EXTRA_KEY; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -72,6 +74,14 @@ import java.io.IOException; public class SnoozeHelperTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "test_channel_id"; + private static final String XML_TAG_NAME = "snoozed-notifications"; + private static final String XML_SNOOZED_NOTIFICATION = "notification"; + private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context"; + private static final String XML_SNOOZED_NOTIFICATION_KEY = "key"; + private static final String XML_SNOOZED_NOTIFICATION_TIME = "time"; + private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id"; + private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version"; + @Mock SnoozeHelper.Callback mCallback; @Mock AlarmManager mAm; @Mock ManagedServices.UserProfiles mUserProfiles; @@ -315,6 +325,53 @@ public class SnoozeHelperTest extends UiServiceTestCase { } @Test + public void testSnoozeLimit_maximumPersisted() throws XmlPullParserException, IOException { + final long snoozeTimeout = 1234; + final String snoozeContext = "ctx"; + // Serialize & deserialize notifications so that only persisted lists are used + TypedXmlSerializer serializer = Xml.newFastSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); + serializer.startDocument(null, true); + serializer.startTag(null, XML_TAG_NAME); + // Serialize maximum number of timed + context snoozed notifications, half of each + for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++) { + final boolean timedNotification = i % 2 == 0; + if (timedNotification) { + serializer.startTag(null, XML_SNOOZED_NOTIFICATION); + } else { + serializer.startTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT); + } + serializer.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, 1); + serializer.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, "key" + i); + if (timedNotification) { + serializer.attributeLong(null, XML_SNOOZED_NOTIFICATION_TIME, snoozeTimeout); + serializer.endTag(null, XML_SNOOZED_NOTIFICATION); + } else { + serializer.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, snoozeContext); + serializer.endTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT); + } + } + serializer.endTag(null, XML_TAG_NAME); + serializer.endDocument(); + serializer.flush(); + + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), "utf-8"); + mSnoozeHelper.readXml(parser, 1); + // Verify that we can't snooze any more notifications + // and that the limit is caused by persisted notifications + assertThat(mSnoozeHelper.canSnooze(1)).isFalse(); + assertThat(mSnoozeHelper.isSnoozed(UserHandle.USER_SYSTEM, "pkg", "key0")).isFalse(); + assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, + "pkg", "key0")).isEqualTo(snoozeTimeout); + assertThat( + mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg", + "key1")).isEqualTo(snoozeContext); + } + + @Test public void testCancelByApp() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); @@ -587,6 +644,7 @@ public class SnoozeHelperTest extends UiServiceTestCase { @Test public void repostGroupSummary_repostsSummary() throws Exception { + final int snoozeDuration = 1000; IntArray profileIds = new IntArray(); profileIds.add(UserHandle.USER_SYSTEM); when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); @@ -594,10 +652,44 @@ public class SnoozeHelperTest extends UiServiceTestCase { "pkg", 1, "one", UserHandle.SYSTEM, "group1", true); NotificationRecord r2 = getNotificationRecord( "pkg", 2, "two", UserHandle.SYSTEM, "group1", false); - mSnoozeHelper.snooze(r, 1000); - mSnoozeHelper.snooze(r2, 1000); + final long snoozeTime = System.currentTimeMillis() + snoozeDuration; + mSnoozeHelper.snooze(r, snoozeDuration); + mSnoozeHelper.snooze(r2, snoozeDuration); + assertEquals(2, mSnoozeHelper.getSnoozed().size()); + assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size()); + // Verify that summary notification was added to the persisted list + assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg", + r.getKey())).isAtLeast(snoozeTime); + + mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey()); + + verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false); + verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r2, false); + + assertEquals(1, mSnoozeHelper.getSnoozed().size()); + assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size()); + // Verify that summary notification was removed from the persisted list + assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg", + r.getKey())).isEqualTo(0); + } + + @Test + public void snoozeWithContext_repostGroupSummary_removesPersisted() throws Exception { + final String snoozeContext = "zzzzz"; + IntArray profileIds = new IntArray(); + profileIds.add(UserHandle.USER_SYSTEM); + when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); + NotificationRecord r = getNotificationRecord( + "pkg", 1, "one", UserHandle.SYSTEM, "group1", true); + NotificationRecord r2 = getNotificationRecord( + "pkg", 2, "two", UserHandle.SYSTEM, "group1", false); + mSnoozeHelper.snooze(r, snoozeContext); + mSnoozeHelper.snooze(r2, snoozeContext); assertEquals(2, mSnoozeHelper.getSnoozed().size()); assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size()); + // Verify that summary notification was added to the persisted list + assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, + "pkg", r.getKey())).isEqualTo(snoozeContext); mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey()); @@ -606,6 +698,9 @@ public class SnoozeHelperTest extends UiServiceTestCase { assertEquals(1, mSnoozeHelper.getSnoozed().size()); assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size()); + // Verify that summary notification was removed from the persisted list + assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, + "pkg", r.getKey())).isNull(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 3eed0b72e0bb..185f5ba9316e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1179,10 +1179,12 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testFinishActivityIfPossible_nonVisibleNoAppTransition() { registerTestTransitionPlayer(); + spyOn(mRootWindowContainer.mTransitionController); + final ActivityRecord bottomActivity = createActivityWithTask(); + bottomActivity.setVisibility(false); + bottomActivity.setState(STOPPED, "test"); + bottomActivity.mLastSurfaceShowing = false; final ActivityRecord activity = createActivityWithTask(); - // Put an activity on top of test activity to make it invisible and prevent us from - // accidentally resuming the topmost one again. - new ActivityBuilder(mAtm).build(); activity.setVisibleRequested(false); activity.setState(STOPPED, "test"); @@ -1190,6 +1192,14 @@ public class ActivityRecordTests extends WindowTestsBase { verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE)); assertFalse(activity.inTransition()); + + // finishIfPossible -> completeFinishing -> addToFinishingAndWaitForIdle + // -> resumeFocusedTasksTopActivities + assertTrue(bottomActivity.isState(RESUMED)); + assertTrue(bottomActivity.isVisible()); + verify(mRootWindowContainer.mTransitionController).onVisibleWithoutCollectingTransition( + eq(bottomActivity), any()); + assertTrue(bottomActivity.mLastSurfaceShowing); } /** diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 7d9160a1c506..2328f7977ed8 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3141,6 +3141,15 @@ public class CarrierConfigManager { public static final String KEY_ROAMING_OPERATOR_STRING_ARRAY = "roaming_operator_string_array"; /** + * Config to show the roaming indicator (i.e. the "R" icon) from the status bar when roaming. + * The roaming indicator will be shown if this is {@code true} and will not be shown if this is + * {@code false}. + * + * @hide + */ + public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; + + /** * URL from which the proto containing the public key of the Carrier used for * IMSI encryption will be downloaded. * @hide @@ -3306,11 +3315,11 @@ public class CarrierConfigManager { * If {@code false} the SPN display checks if the current MCC/MNC is different from the * SIM card's MCC/MNC. * - * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY - * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY - * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY - * @see KEY_ROAMING_OPERATOR_STRING_ARRAY - * @see KEY_FORCE_HOME_NETWORK_BOOL + * @see #KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY + * @see #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY + * @see #KEY_NON_ROAMING_OPERATOR_STRING_ARRAY + * @see #KEY_ROAMING_OPERATOR_STRING_ARRAY + * @see #KEY_FORCE_HOME_NETWORK_BOOL * * @hide */ @@ -3334,12 +3343,42 @@ public class CarrierConfigManager { /** * Determines whether we should show a notification when the phone established a data * connection in roaming network, to warn users about possible roaming charges. + * + * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY + * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY * @hide */ public static final String KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL = "show_data_connected_roaming_notification"; /** + * Determines what MCCs are exceptions for the value of + * {@link #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL}. + * An empty list indicates that there are no exceptions. + * + * @see #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL + * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY + * @hide + */ + public static final String + KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY = + "data_connected_roaming_notification_excluded_mccs_string_array"; + + /** + * Determines what MCC+MNCs are exceptions for the MCCs specified in + * {@link #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY}, meaning the + * value for the MCC+MNC is {@link #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL}. + * An empty list indicates that there are no MNC-specific exceptions. + * + * @see #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL + * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY + * @hide + */ + public static final String + KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY = + "data_connected_roaming_notification_included_mcc_mncs_string_array"; + + /** * A list of 4 LTE RSRP thresholds above which a signal level is considered POOR, * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting. * @@ -10171,6 +10210,7 @@ public class CarrierConfigManager { false); sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null); sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_SHOW_ROAMING_INDICATOR_BOOL, true); sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); sDefaults.putBoolean(KEY_RTT_SUPPORTED_BOOL, false); sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true); @@ -10207,6 +10247,11 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false); sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false); sDefaults.putBoolean(KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL, false); + sDefaults.putStringArray(KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY, + new String[0]); + sDefaults.putStringArray( + KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY, + new String[0]); sDefaults.putIntArray(KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY, // Boundaries: [-140 dBm, -44 dBm] new int[] { |