diff options
71 files changed, 2063 insertions, 503 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 9c40a28dfd81..14943d36ba6a 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/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java index a49bb6af13d2..109c8080de94 100644 --- a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java +++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.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 ScrollViewFunctionalTest {      }      @Test -    public void testScrollAfterFlingTop() { -        mScrollView.scrollTo(0, 100); -        mScrollView.fling(-10000); -        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() > 0); -        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() == 0f); +    public void testScrollAfterFlingTop() throws Throwable { +        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); +        mScrollView.mEdgeGlowTop = edgeEffect; +        mActivityRule.runOnUiThread(() -> mScrollView.scrollTo(0, 100)); +        mActivityRule.runOnUiThread(() -> mScrollView.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, mScrollView.getScrollY());      }      @Test -    public void testScrollAfterFlingBottom() { +    public void testScrollAfterFlingBottom() throws Throwable { +        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); +        mScrollView.mEdgeGlowBottom = edgeEffect;          int childHeight = mScrollView.getChildAt(0).getHeight();          int maxScroll = childHeight - mScrollView.getHeight(); -        mScrollView.scrollTo(0, maxScroll - 100); -        mScrollView.fling(10000); -        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() > 0); -        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() == 0f); +        mActivityRule.runOnUiThread(() -> mScrollView.scrollTo(0, maxScroll - 100)); +        mActivityRule.runOnUiThread(() -> mScrollView.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(maxScroll, mScrollView.getScrollY());      } + +    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/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 4416b1979524..61af1f45ae5e 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -5,6 +5,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A  import android.annotation.NonNull;  import android.annotation.Nullable; +import android.app.ActivityManager;  import android.app.ActivityOptions;  import android.app.SearchManager;  import android.content.ActivityNotFoundException; @@ -144,6 +145,7 @@ public class AssistManager {      private final UserTracker mUserTracker;      private final DisplayTracker mDisplayTracker;      private final SecureSettings mSecureSettings; +    private final ActivityManager mActivityManager;      private final DeviceProvisionedController mDeviceProvisionedController; @@ -183,7 +185,8 @@ public class AssistManager {              @Main Handler uiHandler,              UserTracker userTracker,              DisplayTracker displayTracker, -            SecureSettings secureSettings) { +            SecureSettings secureSettings, +            ActivityManager activityManager) {          mContext = context;          mDeviceProvisionedController = controller;          mCommandQueue = commandQueue; @@ -195,6 +198,7 @@ public class AssistManager {          mUserTracker = userTracker;          mDisplayTracker = displayTracker;          mSecureSettings = secureSettings; +        mActivityManager = activityManager;          registerVoiceInteractionSessionListener();          registerVisualQueryRecognitionStatusListener(); @@ -266,6 +270,9 @@ public class AssistManager {      }      public void startAssist(Bundle args) { +        if (mActivityManager.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED) { +            return; +        }          if (shouldOverrideAssist(args)) {              try {                  if (mOverviewProxyService.getProxy() == null) { 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/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt index 5766f1be8894..089b64b69c6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase  import com.android.systemui.shade.ShadeExpansionStateManager  import org.junit.Assert  import org.junit.Before +import org.junit.Ignore  import org.junit.Rule  import org.junit.Test  import org.junit.runner.RunWith @@ -50,6 +51,7 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {              AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor)      } +    @Ignore("b/316929376")      @Test      fun testEnableDetector_expandWithTrack_shouldPostRunnable() {          detector.enable(action) 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 0caf26a5175b..78797bee3f45 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 39d9b45bb22d..255fc909ed63 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); @@ -13052,6 +13200,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 @@ -13072,6 +13223,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()); @@ -13099,6 +13253,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/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java index 96f4a01f7f3a..2c90649c68c5 100644 --- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java +++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java @@ -206,22 +206,16 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn          synchronized (mLock) {              ClientState clientState = mClients.get(listener.asBinder()); -            if (clientState == null) { -                if (DEBUG) { -                    Slog.w(TAG, "#cancel called with no preceding #startListening - ignoring."); -                } -                return; +            if (clientState != null) { +                clientState.mRecordingInProgress = false; +                // Temporary reference to allow for resetting mDelegatingListener to null. +                final IRecognitionListener delegatingListener = clientState.mDelegatingListener; +                run(service -> service.cancel(delegatingListener, isShutdown));              } -            clientState.mRecordingInProgress = false; - -            // Temporary reference to allow for resetting the hard link mDelegatingListener to null. -            final IRecognitionListener delegatingListener = clientState.mDelegatingListener; -            run(service -> service.cancel(delegatingListener, isShutdown));              // If shutdown, remove the client info from the map. Unbind if that was the last client.              if (isShutdown) {                  removeClient(listener); -                  if (mClients.isEmpty()) {                      if (DEBUG) {                          Slog.d(TAG, "Unbinding from the recognition service."); 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/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index bb49e0b4a94f..562ed13fad8e 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5688,6 +5688,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP              // Skip sync for invisible app windows which are not managed by activity lifecycle.              return false;          } +        if (mActivityRecord != null && mViewVisibility != View.VISIBLE +                && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION +                && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) { +            // Skip sync for invisible app windows which are not managed by activity lifecycle. +            return false; +        }          // In the WindowContainer implementation we immediately mark ready          // since a generic WindowContainer only needs to wait for its          // children to finish and is immediately ready from its own 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/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java index 5f84e9ee54d0..3d3688e5c678 100644 --- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java @@ -116,7 +116,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {          testServiceDefaultValue_On(ServiceType.NULL);      } -    @Suppress +    @Suppress // TODO: b/317823111 - Remove once test fixed.      @SmallTest      public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {          testServiceDefaultValue_Off(ServiceType.VIBRATION); @@ -202,7 +202,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {          testServiceDefaultValue_On(ServiceType.QUICK_DOZE);      } -    @Suppress +    @Suppress // TODO: b/317823111 - Remove once test fixed.      @SmallTest      public void testUpdateConstants_getCorrectData() {          mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_CONSTANTS, ""); @@ -302,6 +302,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {          }      } +    @Suppress // TODO: b/317823111 - Remove once test fixed.      public void testSetPolicyLevel_Adaptive() {          mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_ADAPTIVE); 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[] {  |