Merge "Import translations. DO NOT MERGE ANYWHERE" into sc-dev
diff --git a/core/java/android/appwidget/AppWidgetManagerInternal.java b/core/java/android/appwidget/AppWidgetManagerInternal.java
index 5694ca8..266e33a 100644
--- a/core/java/android/appwidget/AppWidgetManagerInternal.java
+++ b/core/java/android/appwidget/AppWidgetManagerInternal.java
@@ -19,6 +19,8 @@
 import android.annotation.Nullable;
 import android.util.ArraySet;
 
+import java.util.Set;
+
 /**
  * App widget manager local system service interface.
  *
@@ -42,4 +44,16 @@
      * @param userId The user that is being unlocked.
      */
     public abstract void unlockUser(int userId);
+
+    /**
+     * Updates all widgets, applying changes to Runtime Resource Overlay affecting the specified
+     * target packages.
+     *
+     * @param packageNames The names of all target packages for which an overlay was modified
+     * @param userId The user for which overlay modifications occurred.
+     * @param updateFrameworkRes Whether or not an overlay affected the values of framework
+     *                           resources.
+     */
+    public abstract void applyResourceOverlaysToWidgets(Set<String> packageNames, int userId,
+            boolean updateFrameworkRes);
 }
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index cb3bf29..8ff0181 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -168,6 +168,15 @@
                 dest.writeByteArray(mManufacturerDataMask);
             }
         }
+
+        // IRK
+        if (mDeviceAddress != null) {
+            dest.writeInt(mAddressType);
+            dest.writeInt(mIrk == null ? 0 : 1);
+            if (mIrk != null) {
+                dest.writeByteArray(mIrk);
+            }
+        }
     }
 
     /**
@@ -187,8 +196,10 @@
             if (in.readInt() == 1) {
                 builder.setDeviceName(in.readString());
             }
+            String address = null;
+            // If we have a non-null address
             if (in.readInt() == 1) {
-                builder.setDeviceAddress(in.readString());
+                address = in.readString();
             }
             if (in.readInt() == 1) {
                 ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
@@ -245,6 +256,17 @@
                 }
             }
 
+            // IRK
+            if (address != null) {
+                final int addressType = in.readInt();
+                if (in.readInt() == 1) {
+                    final byte[] irk = new byte[16];
+                    in.readByteArray(irk);
+                    builder.setDeviceAddress(address, addressType, irk);
+                } else {
+                    builder.setDeviceAddress(address, addressType);
+                }
+            }
             return builder.build();
         }
     };
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 4526ab77..fa7ce11 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -24,12 +24,12 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.service.SensorPrivacyIndividualEnabledSensorProto;
 import android.service.SensorPrivacyToggleSourceProto;
 import android.util.ArrayMap;
@@ -379,7 +379,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
     public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor) {
-        return isSensorPrivacyEnabled(sensor, getCurrentUserId());
+        return isSensorPrivacyEnabled(sensor, UserHandle.USER_CURRENT);
     }
 
     /**
@@ -410,7 +410,7 @@
     @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
     public void setSensorPrivacy(@Sources.Source int source, @Sensors.Sensor int sensor,
             boolean enable) {
-        setSensorPrivacy(source, sensor, enable, getCurrentUserId());
+        setSensorPrivacy(source, sensor, enable, UserHandle.USER_CURRENT);
     }
 
     /**
@@ -446,7 +446,7 @@
     @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
     public void setSensorPrivacyForProfileGroup(@Sources.Source int source,
             @Sensors.Sensor int sensor, boolean enable) {
-        setSensorPrivacyForProfileGroup(source , sensor, enable, getCurrentUserId());
+        setSensorPrivacyForProfileGroup(source , sensor, enable, UserHandle.USER_CURRENT);
     }
 
     /**
@@ -481,7 +481,7 @@
     @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
     public void suppressSensorPrivacyReminders(int sensor,
             boolean suppress) {
-        suppressSensorPrivacyReminders(sensor, suppress, getCurrentUserId());
+        suppressSensorPrivacyReminders(sensor, suppress, UserHandle.USER_CURRENT);
     }
 
     /**
@@ -609,12 +609,4 @@
         }
     }
 
-    private int getCurrentUserId() {
-        try {
-            return ActivityManager.getService().getCurrentUserId();
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-        return 0;
-    }
 }
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index f3e0ce9..1bc6495 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -1460,15 +1460,15 @@
     /** {@hide} */
     @VisibleForTesting
     public static ParcelFileDescriptor convertToModernFd(FileDescriptor fd) {
-        try {
-            Context context = AppGlobals.getInitialApplication();
-            if (UserHandle.getAppId(Process.myUid()) == getMediaProviderAppId(context)) {
-                // Never convert modern fd for MediaProvider, because this requires
-                // MediaStore#scanFile and can cause infinite loops when MediaProvider scans
-                return null;
-            }
-            return MediaStore.getOriginalMediaFormatFileDescriptor(context,
-                    ParcelFileDescriptor.dup(fd));
+        Context context = AppGlobals.getInitialApplication();
+        if (UserHandle.getAppId(Process.myUid()) == getMediaProviderAppId(context)) {
+            // Never convert modern fd for MediaProvider, because this requires
+            // MediaStore#scanFile and can cause infinite loops when MediaProvider scans
+            return null;
+        }
+
+        try (ParcelFileDescriptor dupFd = ParcelFileDescriptor.dup(fd)) {
+            return MediaStore.getOriginalMediaFormatFileDescriptor(context, dupFd);
         } catch (Exception e) {
             Log.d(TAG, "Failed to convert to modern format file descriptor", e);
             return null;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 6bca336..9f37c48 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -34,12 +34,9 @@
 
 import libcore.io.IoUtils;
 
-import java.io.BufferedReader;
 import java.io.FileDescriptor;
-import java.io.FileReader;
 import java.io.IOException;
 import java.util.Map;
-import java.util.StringTokenizer;
 import java.util.concurrent.TimeoutException;
 
 /**
@@ -1472,43 +1469,4 @@
     }
 
     private static native int nativePidFdOpen(int pid, int flags) throws ErrnoException;
-
-    /**
-     * Checks if a process corresponding to a specific pid owns any file locks.
-     * @param pid The process ID for which we want to know the existence of file locks.
-     * @return true If the process holds any file locks, false otherwise.
-     * @throws IOException if /proc/locks can't be accessed.
-     *
-     * @hide
-     */
-    public static boolean hasFileLocks(int pid) throws Exception {
-        BufferedReader br = null;
-
-        try {
-            br = new BufferedReader(new FileReader("/proc/locks"));
-            String line;
-
-            while ((line = br.readLine()) != null) {
-                StringTokenizer st = new StringTokenizer(line);
-
-                for (int i = 0; i < 5 && st.hasMoreTokens(); i++) {
-                    String str = st.nextToken();
-                    try {
-                        if (i == 4 && Integer.parseInt(str) == pid) {
-                            return true;
-                        }
-                    } catch (NumberFormatException nfe) {
-                        throw new Exception("Exception parsing /proc/locks at \" "
-                                + line +  " \", token #" + i);
-                    }
-                }
-            }
-
-            return false;
-        } finally {
-            if (br != null) {
-                br.close();
-            }
-        }
-    }
 }
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 38019c9..5f036a3 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1013,8 +1013,10 @@
      *
      * @param imeConsumesInput {@code true} when the IME is consuming input and the cursor should be
      * hidden, {@code false} when input to the editor resumes and the cursor should be shown again.
-     * @return {@code true} on success, {@code false} if the input connection is no longer valid, or
-     * the protocol is not supported.
+     * @return For editor authors, the return value will always be ignored. For IME authors, this
+     *         method returns {@code true} if the request was sent (whether or not the associated
+     *         editor does something based on this request), {@code false} if the input connection
+     *         is no longer valid.
      */
     default boolean setImeConsumesInput(boolean imeConsumesInput) {
         return false;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e827f0a..91fc5a5 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5824,6 +5824,25 @@
         return false;
     }
 
+    /** @hide */
+    public void updateAppInfo(@NonNull ApplicationInfo info) {
+        if (mApplication != null && mApplication.sourceDir.equals(info.sourceDir)) {
+            // Overlay paths are generated against a particular version of an application.
+            // The overlays paths of a newly upgraded application are incompatible with the
+            // old version of the application.
+            mApplication = info;
+        }
+        if (hasSizedRemoteViews()) {
+            for (RemoteViews layout : mSizedRemoteViews) {
+                layout.updateAppInfo(info);
+            }
+        }
+        if (hasLandscapeAndPortraitLayouts()) {
+            mLandscape.updateAppInfo(info);
+            mPortrait.updateAppInfo(info);
+        }
+    }
+
     private Context getContextForResources(Context context) {
         if (mApplication != null) {
             if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java
new file mode 100644
index 0000000..bd3115fc5
--- /dev/null
+++ b/core/java/com/android/internal/os/ProcLocksReader.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 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 libcore.io.IoUtils;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * Reads and parses {@code locks} files in the {@code proc} filesystem.
+ * A typical example of /proc/locks
+ *
+ * 1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335
+ * 2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF
+ * 2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF
+ * 2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF
+ * 3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128
+ * 4: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335
+ */
+public class ProcLocksReader {
+    private final String mPath;
+
+    public ProcLocksReader() {
+        mPath = "/proc/locks";
+    }
+
+    public ProcLocksReader(String path) {
+        mPath = path;
+    }
+
+    /**
+     * Checks if a process corresponding to a specific pid owns any file locks.
+     * @param pid The process ID for which we want to know the existence of file locks.
+     * @return true If the process holds any file locks, false otherwise.
+     * @throws IOException if /proc/locks can't be accessed.
+     */
+    public boolean hasFileLocks(int pid) throws Exception {
+        ProcFileReader reader = null;
+        long last = -1;
+        long id; // ordinal position of the lock in the list
+        int owner; // the PID of the process that owns the lock
+
+        try {
+            reader = new ProcFileReader(new FileInputStream(mPath));
+
+            while (reader.hasMoreData()) {
+                id = reader.nextLong(true); // lock id
+                if (id == last) {
+                    reader.finishLine(); // blocked lock
+                    continue;
+                }
+
+                reader.nextIgnored(); // lock type: POSIX?
+                reader.nextIgnored(); // lock type: MANDATORY?
+                reader.nextIgnored(); // lock type: RW?
+
+                owner = reader.nextInt(); // pid
+                if (owner == pid) {
+                    return true;
+                }
+                reader.finishLine();
+                last = id;
+            }
+        } catch (IOException e) {
+            // TODO: let ProcFileReader log the failed line
+            throw new Exception("Exception parsing /proc/locks");
+        } finally {
+            IoUtils.closeQuietly(reader);
+        }
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
index ead58c7d..0dd8ad8 100644
--- a/core/java/com/android/internal/util/ProcFileReader.java
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -28,8 +28,8 @@
  * requires each line boundary to be explicitly acknowledged using
  * {@link #finishLine()}. Assumes {@link StandardCharsets#US_ASCII} encoding.
  * <p>
- * Currently doesn't support formats based on {@code \0}, tabs, or repeated
- * delimiters.
+ * Currently doesn't support formats based on {@code \0}, tabs.
+ * Consecutive spaces are treated as a single delimiter.
  */
 public class ProcFileReader implements Closeable {
     private final InputStream mStream;
@@ -75,6 +75,11 @@
     private void consumeBuf(int count) throws IOException {
         // TODO: consider moving to read pointer, but for now traceview says
         // these copies aren't a bottleneck.
+
+        // skip all consecutive delimiters.
+        while (count < mTail && mBuffer[count] == ' ') {
+            count++;
+        }
         System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count);
         mTail -= count;
         if (mTail == 0) {
@@ -159,11 +164,18 @@
      * Parse and return next token as base-10 encoded {@code long}.
      */
     public long nextLong() throws IOException {
+        return nextLong(false);
+    }
+
+    /**
+     * Parse and return next token as base-10 encoded {@code long}.
+     */
+    public long nextLong(boolean stopAtInvalid) throws IOException {
         final int tokenIndex = nextTokenIndex();
         if (tokenIndex == -1) {
             throw new ProtocolException("Missing required long");
         } else {
-            return parseAndConsumeLong(tokenIndex);
+            return parseAndConsumeLong(tokenIndex, stopAtInvalid);
         }
     }
 
@@ -176,7 +188,7 @@
         if (tokenIndex == -1) {
             return def;
         } else {
-            return parseAndConsumeLong(tokenIndex);
+            return parseAndConsumeLong(tokenIndex, false);
         }
     }
 
@@ -186,7 +198,10 @@
         return s;
     }
 
-    private long parseAndConsumeLong(int tokenIndex) throws IOException {
+    /**
+     * If stopAtInvalid is true, don't throw IOException but return whatever parsed so far.
+     */
+    private long parseAndConsumeLong(int tokenIndex, boolean stopAtInvalid) throws IOException {
         final boolean negative = mBuffer[0] == '-';
 
         // TODO: refactor into something like IntegralToString
@@ -194,7 +209,11 @@
         for (int i = negative ? 1 : 0; i < tokenIndex; i++) {
             final int digit = mBuffer[i] - '0';
             if (digit < 0 || digit > 9) {
-                throw invalidLong(tokenIndex);
+                if (stopAtInvalid) {
+                    break;
+                } else {
+                    throw invalidLong(tokenIndex);
+                }
             }
 
             // always parse as negative number and apply sign later; this
@@ -226,6 +245,18 @@
         return (int) value;
     }
 
+    /**
+     * Bypass the next token.
+     */
+    public void nextIgnored() throws IOException {
+        final int tokenIndex = nextTokenIndex();
+        if (tokenIndex == -1) {
+            throw new ProtocolException("Missing required token");
+        } else {
+            consumeBuf(tokenIndex + 1);
+        }
+    }
+
     @Override
     public void close() throws IOException {
         mStream.close();
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
new file mode 100644
index 0000000..d800c2c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.FileUtils;
+
+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 ProcLocksReaderTest {
+    private File mProcDirectory;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getContext();
+        mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtils.deleteContents(mProcDirectory);
+    }
+
+    @Test
+    public void testRunSimpleLocks() throws Exception {
+        String simpleLocks =
+                "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
+                "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n";
+        assertFalse(runHasFileLocks(simpleLocks, 18402));
+        assertFalse(runHasFileLocks(simpleLocks, 18404));
+        assertTrue(runHasFileLocks(simpleLocks, 18403));
+        assertTrue(runHasFileLocks(simpleLocks, 18292));
+    }
+
+    @Test
+    public void testRunBlockedLocks() throws Exception {
+        String blockedLocks =
+                "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
+                "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n" +
+                "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n" +
+                "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n" +
+                "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n" +
+                "4: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335\n";
+        assertFalse(runHasFileLocks(blockedLocks, 18402));
+        assertFalse(runHasFileLocks(blockedLocks, 18404));
+        assertTrue(runHasFileLocks(blockedLocks, 18403));
+        assertTrue(runHasFileLocks(blockedLocks, 18292));
+
+        assertFalse(runHasFileLocks(blockedLocks, 18291));
+        assertFalse(runHasFileLocks(blockedLocks, 18293));
+        assertTrue(runHasFileLocks(blockedLocks, 3888));
+    }
+
+    private boolean runHasFileLocks(String fileContents, int pid) throws Exception {
+        File tempFile = File.createTempFile("locks", null, mProcDirectory);
+        Files.write(tempFile.toPath(), fileContents.getBytes());
+        boolean result = new ProcLocksReader(tempFile.toString()).hasFileLocks(pid);
+        Files.delete(tempFile.toPath());
+        return result;
+    }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
index b6da195..0532628 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
@@ -166,6 +166,46 @@
         assertEquals(-1L, reader.nextOptionalLong(-1L));
     }
 
+    public void testInvalidLongs() throws Exception {
+        final ProcFileReader reader = buildReader("12: 34\n56 78@#\n");
+
+        assertEquals(12L, reader.nextLong(true));
+        assertEquals(34L, reader.nextLong(true));
+        reader.finishLine();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals(56L, reader.nextLong(true));
+        assertEquals(78L, reader.nextLong(true));
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+    }
+
+    public void testConsecutiveDelimiters() throws Exception {
+        final ProcFileReader reader = buildReader("1 2  3   4     5\n");
+
+        assertEquals(1L, reader.nextLong());
+        assertEquals(2L, reader.nextLong());
+        assertEquals(3L, reader.nextLong());
+        assertEquals(4L, reader.nextLong());
+        assertEquals(5L, reader.nextLong());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+    }
+
+    public void testIgnore() throws Exception {
+        final ProcFileReader reader = buildReader("a b c\n");
+
+        assertEquals("a", reader.nextString());
+        assertTrue(reader.hasMoreData());
+
+        reader.nextIgnored();
+        assertTrue(reader.hasMoreData());
+
+        assertEquals("c", reader.nextString());
+        reader.finishLine();
+        assertFalse(reader.hasMoreData());
+    }
+
     private static ProcFileReader buildReader(String string) throws IOException {
         return buildReader(string, 2048);
     }
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index d746c85..37054b8 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1573,6 +1573,9 @@
             if (isFdDuped) {
                 closeFileDescriptor(fileDescriptor);
             }
+            if (modernFd != null) {
+                modernFd.close();
+            }
         }
     }
 
@@ -2554,12 +2557,13 @@
 
     private void initForFilename(String filename) throws IOException {
         FileInputStream in = null;
+        ParcelFileDescriptor modernFd = null;
         mAssetInputStream = null;
         mFilename = filename;
         mIsInputStream = false;
         try {
             in = new FileInputStream(filename);
-            ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(in.getFD());
+            modernFd = FileUtils.convertToModernFd(in.getFD());
             if (modernFd != null) {
                 closeQuietly(in);
                 in = new FileInputStream(modernFd.getFileDescriptor());
@@ -2570,6 +2574,9 @@
             loadAttributes(in);
         } finally {
             closeQuietly(in);
+            if (modernFd != null) {
+                modernFd.close();
+            }
         }
     }
 
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 2943eee..a15529e 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -36,6 +36,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.SystemProperties;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -52,6 +53,8 @@
  * frame and meta data from an input media file.
  */
 public class MediaMetadataRetriever implements AutoCloseable {
+    private static final String TAG = "MediaMetadataRetriever";
+
     // borrowed from ExoPlayer
     private static final String[] STANDARD_GENRES = new String[] {
             // These are the official ID3v1 genres.
@@ -301,11 +304,15 @@
      */
     public void setDataSource(FileDescriptor fd, long offset, long length)
             throws IllegalArgumentException  {
-        ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(fd);
-        if (modernFd == null) {
-            _setDataSource(fd, offset, length);
-        } else {
-            _setDataSource(modernFd.getFileDescriptor(), offset, length);
+
+        try (ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(fd)) {
+            if (modernFd == null) {
+                _setDataSource(fd, offset, length);
+            } else {
+                _setDataSource(modernFd.getFileDescriptor(), offset, length);
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Ignoring IO error while setting data source", e);
         }
     }
 
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 4f761ba..26eb2a9 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1271,11 +1271,14 @@
      */
     public void setDataSource(FileDescriptor fd, long offset, long length)
             throws IOException, IllegalArgumentException, IllegalStateException {
-        ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(fd);
-        if (modernFd == null) {
-            _setDataSource(fd, offset, length);
-        } else {
-            _setDataSource(modernFd.getFileDescriptor(), offset, length);
+        try (ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(fd)) {
+            if (modernFd == null) {
+                _setDataSource(fd, offset, length);
+            } else {
+                _setDataSource(modernFd.getFileDescriptor(), offset, length);
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Ignoring IO error while setting data source", e);
         }
     }
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 628f7ee..7f7fb60 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -292,8 +292,7 @@
         }
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : mRoutes.values()) {
-                if (sessionInfo.getSelectedRoutes().contains(route.getId())
-                        || sessionInfo.getTransferableRoutes().contains(route.getId())) {
+                if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
                     routes.add(route);
                     continue;
                 }
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index b3bdf89..65992a7 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -444,7 +444,7 @@
     <string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"Carica residua: meno di <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration" msgid="318215464914990578">"Carica residua: meno di <xliff:g id="THRESHOLD">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_more_than_subtext" msgid="446388082266121894">"Tempo residuo: più di <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_remaining_only_more_than_subtext" msgid="4873750633368888062">"Tempo residuo: più di <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
+    <string name="power_remaining_only_more_than_subtext" msgid="4873750633368888062">"Tempo rimanente: più di <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_shutdown_imminent" product="default" msgid="137330009791560774">"Il telefono potrebbe spegnersi a breve"</string>
     <string name="power_remaining_duration_only_shutdown_imminent" product="tablet" msgid="145489081521468132">"Il tablet potrebbe spegnersi a breve"</string>
     <string name="power_remaining_duration_only_shutdown_imminent" product="device" msgid="1070562682853942350">"Il dispositivo potrebbe spegnersi a breve"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index b1aee50..7387985 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -309,7 +309,7 @@
     <string name="dev_settings_warning_message" msgid="37741686486073668">"Бул орнотуулар өндүрүүчүлөр үчүн гана берилген. Булар түзмөгүңүздүн колдонмолорун бузулушуна же туура эмес иштешине алып келиши мүмкүн."</string>
     <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Орнотулуучу колдонмону текшерүү"</string>
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT аркылуу орнотулган колдонмолордун коопсуздугу текшерилет."</string>
-    <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Аталышсыз Bluetooth түзмөктөрү (MAC даректери менен гана) көрсөтүлөт"</string>
+    <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Аталышсыз Bluetooth түзмөктөрү (MAC даректери менен гана) көрүнөт"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Алыскы түзмөктөр өтө катуу добуш чыгарып же көзөмөлдөнбөй жатса Bluetooth \"Үндүн абсолюттук деңгээли\" функциясын өчүрөт."</string>
     <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche функциясынын топтомун иштетет."</string>
     <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Жакшыртылган туташуу функциясын иштетет."</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index fea9601..2bdd617 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -443,8 +443,8 @@
     <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Bateria może się wyczerpać do <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"Pozostało mniej niż <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration" msgid="318215464914990578">"Pozostało mniej niż <xliff:g id="THRESHOLD">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_remaining_more_than_subtext" msgid="446388082266121894">"Pozostało mniej niż <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_remaining_only_more_than_subtext" msgid="4873750633368888062">"Pozostało mniej niż <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
+    <string name="power_remaining_more_than_subtext" msgid="446388082266121894">"Pozostało ponad <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_remaining_only_more_than_subtext" msgid="4873750633368888062">"Pozostało ponad <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_shutdown_imminent" product="default" msgid="137330009791560774">"Wkrótce telefon może się wyłączyć"</string>
     <string name="power_remaining_duration_only_shutdown_imminent" product="tablet" msgid="145489081521468132">"Tablet może się wkrótce wyłączyć"</string>
     <string name="power_remaining_duration_only_shutdown_imminent" product="device" msgid="1070562682853942350">"Urządzenie może się wkrótce wyłączyć"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
index 205054d..98ad875 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
@@ -93,6 +93,8 @@
     @Nullable private ModalityListener mModalityListener;
     @Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps;
     @Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
+    @Nullable @VisibleForTesting UdfpsIconController mUdfpsIconController;
+
 
     public AuthBiometricFaceToFingerprintView(Context context) {
         super(context);
@@ -107,6 +109,12 @@
         super(context, attrs, injector);
     }
 
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mUdfpsIconController = new UdfpsIconController(mContext, mIconView, mIndicatorView);
+    }
+
     @Modality
     int getActiveSensorType() {
         return mActiveSensorType;
@@ -168,35 +176,26 @@
     }
 
     @Override
-    @NonNull
-    protected IconController getIconController() {
-        if (mActiveSensorType == TYPE_FINGERPRINT) {
-            if (!(mIconController instanceof UdfpsIconController)) {
-                mIconController = createUdfpsIconController();
-            }
-            return mIconController;
-        }
-        return super.getIconController();
-    }
-
-    @NonNull
-    protected IconController createUdfpsIconController() {
-        return new UdfpsIconController(getContext(), mIconView, mIndicatorView);
-    }
-
-    @Override
     public void updateState(@BiometricState int newState) {
-        if (mState == STATE_HELP || mState == STATE_ERROR) {
-            @Modality final int currentType = mActiveSensorType;
-            mActiveSensorType = TYPE_FINGERPRINT;
+        if (mActiveSensorType == TYPE_FACE) {
+            if (newState == STATE_HELP || newState == STATE_ERROR) {
+                mActiveSensorType = TYPE_FINGERPRINT;
 
-            setRequireConfirmation(false);
-            mConfirmButton.setEnabled(false);
-            mConfirmButton.setVisibility(View.GONE);
+                setRequireConfirmation(false);
+                mConfirmButton.setEnabled(false);
+                mConfirmButton.setVisibility(View.GONE);
 
-            if (mModalityListener != null && currentType != mActiveSensorType) {
-                mModalityListener.onModalitySwitched(currentType, mActiveSensorType);
+                if (mModalityListener != null) {
+                    mModalityListener.onModalitySwitched(TYPE_FACE, mActiveSensorType);
+                }
+
+                // Deactivate the face icon controller so it stops drawing to the view
+                mFaceIconController.deactivate();
+                // Then, activate this icon controller. We need to start in the "error" state
+                mUdfpsIconController.updateState(mState, newState);
             }
+        } else { // Fingerprint
+            mUdfpsIconController.updateState(mState, newState);
         }
 
         super.updateState(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
index e8da7c5..c32c1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
@@ -49,6 +49,7 @@
         protected Handler mHandler;
         protected boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
         protected @BiometricState int mState;
+        protected boolean mDeactivated;
 
         protected IconController(Context context, ImageView iconView, TextView textView) {
             mContext = context;
@@ -67,6 +68,11 @@
         }
 
         protected void animateIcon(int iconRes, boolean repeat) {
+            Log.d(TAG, "animateIcon, state: " + mState + ", deactivated: " + mDeactivated);
+            if (mDeactivated) {
+                return;
+            }
+
             final AnimatedVectorDrawable icon =
                     (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
             mIconView.setImageDrawable(icon);
@@ -92,12 +98,26 @@
         @Override
         public void onAnimationEnd(Drawable drawable) {
             super.onAnimationEnd(drawable);
+            Log.d(TAG, "onAnimationEnd, mState: " + mState + ", deactivated: " + mDeactivated);
+            if (mDeactivated) {
+                return;
+            }
+
             if (mState == STATE_AUTHENTICATING || mState == STATE_HELP) {
                 pulseInNextDirection();
             }
         }
 
+        protected void deactivate() {
+            mDeactivated = true;
+        }
+
         protected void updateState(int lastState, int newState) {
+            if (mDeactivated) {
+                Log.w(TAG, "Ignoring updateState when deactivated: " + newState);
+                return;
+            }
+
             final boolean lastStateIsErrorIcon =
                     lastState == STATE_ERROR || lastState == STATE_HELP;
 
@@ -142,7 +162,7 @@
         }
     }
 
-    protected IconController mIconController;
+    @Nullable @VisibleForTesting IconController mFaceIconController;
 
     public AuthBiometricFaceView(Context context) {
         this(context, null);
@@ -158,6 +178,12 @@
     }
 
     @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mFaceIconController = new IconController(mContext, mIconView, mIndicatorView);
+    }
+
+    @Override
     protected int getDelayAfterAuthenticatedDurationMs() {
         return HIDE_DELAY_MS;
     }
@@ -187,17 +213,9 @@
         return true;
     }
 
-    @NonNull
-    protected IconController getIconController() {
-        if (mIconController == null) {
-            mIconController = new IconController(mContext, mIconView, mIndicatorView);
-        }
-        return mIconController;
-    }
-
     @Override
     public void updateState(@BiometricState int newState) {
-        getIconController().updateState(mState, newState);
+        mFaceIconController.updateState(mState, newState);
 
         if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
                 (newState == STATE_AUTHENTICATING && getSize() == AuthDialog.SIZE_MEDIUM)) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index ce6e469..93e5021 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -45,9 +45,12 @@
 import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Background;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Inject;
 
 /**
@@ -63,6 +66,8 @@
 
     private static final String TAG = LogConfig.logTag(ScrollCaptureClient.class);
 
+    private final Executor mBgExecutor;
+
     /**
      * Represents the connection to a target window and provides a mechanism for requesting tiles.
      */
@@ -155,8 +160,10 @@
     private IBinder mHostWindowToken;
 
     @Inject
-    public ScrollCaptureClient(@UiContext Context context, IWindowManager windowManagerService) {
+    public ScrollCaptureClient(IWindowManager windowManagerService,
+            @Background Executor bgExecutor, @UiContext Context context) {
         requireNonNull(context.getDisplay(), "context must be associated with a Display!");
+        mBgExecutor = bgExecutor;
         mWindowManagerService = windowManagerService;
     }
 
@@ -220,21 +227,25 @@
                 return "";
             }
             SessionWrapper session = new SessionWrapper(connection, response.getWindowBounds(),
-                    response.getBoundsInWindow(), maxPages);
+                    response.getBoundsInWindow(), maxPages, mBgExecutor);
             session.start(completer);
             return "IScrollCaptureCallbacks#onCaptureStarted";
         });
     }
 
     private static class SessionWrapper extends IScrollCaptureCallbacks.Stub implements Session,
-            IBinder.DeathRecipient {
+            IBinder.DeathRecipient, ImageReader.OnImageAvailableListener {
 
         private IScrollCaptureConnection mConnection;
+        private final Executor mBgExecutor;
+        private final Object mLock = new Object();
 
         private ImageReader mReader;
         private final int mTileHeight;
         private final int mTileWidth;
         private Rect mRequestRect;
+        private Rect mCapturedArea;
+        private Image mCapturedImage;
         private boolean mStarted;
         private final int mTargetHeight;
 
@@ -247,7 +258,8 @@
         private Completer<Void> mEndCompleter;
 
         private SessionWrapper(IScrollCaptureConnection connection, Rect windowBounds,
-                Rect boundsInWindow, float maxPages) throws RemoteException {
+                Rect boundsInWindow, float maxPages, Executor bgExecutor)
+                throws RemoteException {
             mConnection = requireNonNull(connection);
             mConnection.asBinder().linkToDeath(SessionWrapper.this, 0);
             mWindowBounds = requireNonNull(windowBounds);
@@ -259,7 +271,7 @@
             mTileWidth = mBoundsInWindow.width();
             mTileHeight = pxPerTile / mBoundsInWindow.width();
             mTargetHeight = (int) (mBoundsInWindow.height() * maxPages);
-
+            mBgExecutor = bgExecutor;
             if (DEBUG_SCROLL) {
                 Log.d(TAG, "boundsInWindow: " + mBoundsInWindow);
                 Log.d(TAG, "tile size: " + mTileWidth + "x" + mTileHeight);
@@ -289,6 +301,7 @@
             mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888,
                     MAX_TILES, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
             mStartCompleter = completer;
+            mReader.setOnImageAvailableListenerWithExecutor(this, mBgExecutor);
             try {
                 mCancellationSignal = mConnection.startCapture(mReader.getSurface(), this);
                 completer.addCancellationListener(() -> {
@@ -339,9 +352,34 @@
 
         @BinderThread
         @Override
-        public void onImageRequestCompleted(int flags, Rect contentArea) {
-            Image image = mReader.acquireLatestImage();
-            mTileRequestCompleter.set(new CaptureResult(image, mRequestRect, contentArea));
+        public void onImageRequestCompleted(int flagsUnused, Rect contentArea) {
+            synchronized (mLock) {
+                mCapturedArea = contentArea;
+                if (mCapturedImage != null || (mCapturedArea == null || mCapturedArea.isEmpty())) {
+                    completeCaptureRequest();
+                }
+            }
+        }
+
+        /** @see ImageReader.OnImageAvailableListener */
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            synchronized (mLock) {
+                mCapturedImage = mReader.acquireLatestImage();
+                if (mCapturedArea != null) {
+                    completeCaptureRequest();
+                }
+            }
+        }
+
+        /** Produces a result for the caller as soon as both asynchronous results are received. */
+        private void completeCaptureRequest() {
+            CaptureResult result =
+                    new CaptureResult(mCapturedImage, mRequestRect, mCapturedArea);
+            mCapturedImage = null;
+            mRequestRect = null;
+            mCapturedArea = null;
+            mTileRequestCompleter.set(result);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 92922b6..3890c1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -821,6 +821,16 @@
         }
     }
 
+    private void showTryFingerprintMsg() {
+        if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
+            // if udfps available, there will always be a tappable affordance to unlock
+            // For example, the lock icon
+            showTransientIndication(R.string.keyguard_unlock_press);
+        } else {
+            showTransientIndication(R.string.keyguard_try_fingerprint);
+        }
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardIndicationController:");
         pw.println("  mInitialTextColorState: " + mInitialTextColorState);
@@ -891,7 +901,7 @@
                 return;
             }
 
-            boolean showSwipeToUnlock =
+            boolean showActionToUnlock =
                     msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 mStatusBarKeyguardViewManager.showBouncerMessage(helpString,
@@ -899,13 +909,11 @@
             } else if (mKeyguardUpdateMonitor.isScreenOn()) {
                 if (biometricSourceType == BiometricSourceType.FACE
                         && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
-                    // suggest trying fingerprint
-                    showTransientIndication(R.string.keyguard_try_fingerprint);
+                    showTryFingerprintMsg();
                     return;
                 }
-                showTransientIndication(helpString, false /* isError */, showSwipeToUnlock);
-            }
-            if (showSwipeToUnlock) {
+                showTransientIndication(helpString, false /* isError */, showActionToUnlock);
+            } else if (showActionToUnlock) {
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK),
                         TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
             }
@@ -921,8 +929,7 @@
                     && shouldSuppressFaceMsgAndShowTryFingerprintMsg()
                     && !mStatusBarKeyguardViewManager.isBouncerShowing()
                     && mKeyguardUpdateMonitor.isScreenOn()) {
-                // suggest trying fingerprint
-                showTransientIndication(R.string.keyguard_try_fingerprint);
+                showTryFingerprintMsg();
                 return;
             }
             if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -931,11 +938,10 @@
                 if (!mStatusBarKeyguardViewManager.isBouncerShowing()
                         && mKeyguardUpdateMonitor.isUdfpsEnrolled()
                         && mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
-                    // suggest trying fingerprint
-                    showTransientIndication(R.string.keyguard_try_fingerprint);
+                    showTryFingerprintMsg();
                 } else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
                     mStatusBarKeyguardViewManager.showBouncerMessage(
-                            mContext.getResources().getString(R.string.keyguard_try_fingerprint),
+                            mContext.getResources().getString(R.string.keyguard_unlock_press),
                             mInitialTextColorState
                     );
                 } else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
index 5cd7810..090cf9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
@@ -23,7 +23,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -50,7 +49,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -78,14 +76,16 @@
     @Mock private TextView mIndicatorView;
     @Mock private ImageView mIconView;
     @Mock private View mIconHolderView;
-    @Mock private AuthBiometricFaceView.IconController mIconController;
+    @Mock private AuthBiometricFaceView.IconController mFaceIconController;
+    @Mock private AuthBiometricFaceToFingerprintView.UdfpsIconController mUdfpsIconController;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
         mFaceToFpView = new TestableView(mContext);
-        mFaceToFpView.mIconController = mIconController;
+        mFaceToFpView.mFaceIconController = mFaceIconController;
+        mFaceToFpView.mUdfpsIconController = mUdfpsIconController;
         mFaceToFpView.setCallback(mCallback);
 
         mFaceToFpView.mNegativeButton = mNegativeButton;
@@ -99,20 +99,23 @@
     @Test
     public void testStateUpdated_whenDialogAnimatedIn() {
         mFaceToFpView.onDialogAnimatedIn();
-        verify(mFaceToFpView.mIconController)
+        verify(mFaceToFpView.mFaceIconController)
                 .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
+        verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt(), anyInt());
     }
 
     @Test
     public void testIconUpdatesState_whenDialogStateUpdated() {
         mFaceToFpView.onDialogAnimatedIn();
-        verify(mFaceToFpView.mIconController)
+        verify(mFaceToFpView.mFaceIconController)
                 .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
+        verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt(), anyInt());
 
         mFaceToFpView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
-        verify(mFaceToFpView.mIconController).updateState(
+        verify(mFaceToFpView.mFaceIconController).updateState(
                 eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING),
                 eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED));
+        verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt(), anyInt());
 
         assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED, mFaceToFpView.mState);
     }
@@ -120,21 +123,22 @@
     @Test
     public void testStateUpdated_whenSwitchToFingerprint() {
         mFaceToFpView.onDialogAnimatedIn();
-        verify(mFaceToFpView.mIconController)
+        verify(mFaceToFpView.mFaceIconController)
                 .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
 
         mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
-        mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
 
-        InOrder order = inOrder(mFaceToFpView.mIconController);
-        order.verify(mFaceToFpView.mIconController).updateState(
+        verify(mFaceToFpView.mFaceIconController).deactivate();
+        verify(mFaceToFpView.mUdfpsIconController).updateState(
                 eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING),
                 eq(AuthBiometricFaceToFingerprintView.STATE_ERROR));
-        order.verify(mFaceToFpView.mIconController).updateState(
+        verify(mConfirmButton).setVisibility(eq(View.GONE));
+
+        mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
+
+        verify(mFaceToFpView.mUdfpsIconController).updateState(
                 eq(AuthBiometricFaceToFingerprintView.STATE_ERROR),
                 eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-
-        verify(mConfirmButton).setVisibility(eq(View.GONE));
     }
 
     @Test
@@ -172,7 +176,10 @@
                 eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
         verify(mCallback).onAction(
                 eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
-        assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING, mFaceToFpView.mState);
+
+        // First we enter the error state, since we need to show the error animation/text. The
+        // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
+        assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
     }
 
     @Test
@@ -185,13 +192,16 @@
                 eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
         verify(mCallback).onAction(
                 eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
-        assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING, mFaceToFpView.mState);
+
+        // First we enter the error state, since we need to show the error animation/text. The
+        // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
+        assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
     }
 
     @Test
     public void testFingerprintOnlyStartsOnFirstError() {
         mFaceToFpView.onDialogAnimatedIn();
-        verify(mFaceToFpView.mIconController)
+        verify(mFaceToFpView.mFaceIconController)
                 .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
 
         mFaceToFpView.onDialogAnimatedIn();
@@ -260,11 +270,6 @@
         protected int getDelayAfterAuthenticatedDurationMs() {
             return 0;
         }
-
-        @Override
-        protected IconController createUdfpsIconController() {
-            return AuthBiometricFaceToFingerprintViewTest.this.mIconController;
-        }
     }
 
     private class MockInjector extends AuthBiometricView.Injector {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
index 043bd5c..b93381d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
@@ -62,7 +62,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mFaceView = new TestableFaceView(mContext);
-        mFaceView.mIconController = mock(TestableFaceView.TestableIconController.class);
+        mFaceView.mFaceIconController = mock(TestableFaceView.TestableIconController.class);
         mFaceView.setCallback(mCallback);
 
         mFaceView.mNegativeButton = mNegativeButton;
@@ -78,18 +78,18 @@
     @Test
     public void testStateUpdated_whenDialogAnimatedIn() {
         mFaceView.onDialogAnimatedIn();
-        verify(mFaceView.mIconController)
+        verify(mFaceView.mFaceIconController)
                 .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
     }
 
     @Test
     public void testIconUpdatesState_whenDialogStateUpdated() {
         mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATING);
-        verify(mFaceView.mIconController)
+        verify(mFaceView.mFaceIconController)
                 .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
 
         mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
-        verify(mFaceView.mIconController).updateState(
+        verify(mFaceView.mFaceIconController).updateState(
                 eq(AuthBiometricFaceView.STATE_AUTHENTICATING),
                 eq(AuthBiometricFaceView.STATE_AUTHENTICATED));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
index 3a4bc69..670a130 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
@@ -36,7 +35,6 @@
 import android.view.ScrollCaptureResponse;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
@@ -83,7 +81,7 @@
                 /* taskId */ anyInt(), any(IScrollCaptureResponseListener.class));
 
         // Create client
-        ScrollCaptureClient client = new ScrollCaptureClient(mContext, mWm);
+        ScrollCaptureClient client = new ScrollCaptureClient(mWm, Runnable::run, mContext);
 
         // Request scroll capture
         ListenableFuture<ScrollCaptureResponse> requestFuture =
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 5aec6aa..a56b1db 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -3285,6 +3285,57 @@
         }
     }
 
+    private void applyResourceOverlaysToWidgetsLocked(Set<String> packageNames, int userId,
+            boolean updateFrameworkRes) {
+        for (int i = 0, N = mProviders.size(); i < N; i++) {
+            Provider provider = mProviders.get(i);
+            if (provider.getUserId() != userId) {
+                continue;
+            }
+
+            final String packageName = provider.id.componentName.getPackageName();
+            if (!updateFrameworkRes && !packageNames.contains(packageName)) {
+                continue;
+            }
+
+            ApplicationInfo newAppInfo = null;
+            try {
+                newAppInfo = mPackageManager.getApplicationInfo(packageName,
+                        PackageManager.GET_SHARED_LIBRARY_FILES, userId);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to retrieve app info for " + packageName
+                        + " userId=" + userId, e);
+            }
+            if (newAppInfo == null) {
+                continue;
+            }
+            ApplicationInfo oldAppInfo = provider.info.providerInfo.applicationInfo;
+            if (!newAppInfo.sourceDir.equals(oldAppInfo.sourceDir)) {
+                // Overlay paths are generated against a particular version of an application.
+                // The overlays paths of a newly upgraded application are incompatible with the
+                // old version of the application.
+                continue;
+            }
+
+            // Isolate the changes relating to RROs. The app info must be copied to prevent
+            // affecting other parts of system server that may have cached this app info.
+            oldAppInfo = new ApplicationInfo(oldAppInfo);
+            oldAppInfo.overlayPaths = newAppInfo.overlayPaths.clone();
+            oldAppInfo.resourceDirs = newAppInfo.resourceDirs.clone();
+            provider.info.providerInfo.applicationInfo = oldAppInfo;
+
+            for (int j = 0, M = provider.widgets.size(); j < M; j++) {
+                Widget widget = provider.widgets.get(j);
+                if (widget.views != null) {
+                    widget.views.updateAppInfo(oldAppInfo);
+                }
+                if (widget.maskedViews != null) {
+                    widget.maskedViews.updateAppInfo(oldAppInfo);
+                }
+            }
+        }
+    }
+
     /**
      * Updates all providers with the specified package names, and records any providers that were
      * pruned.
@@ -4875,5 +4926,14 @@
         public void unlockUser(int userId) {
             handleUserUnlocked(userId);
         }
+
+        @Override
+        public void applyResourceOverlaysToWidgets(Set<String> packageNames, int userId,
+                boolean updateFrameworkRes) {
+            synchronized (mLock) {
+                applyResourceOverlaysToWidgetsLocked(new HashSet<>(packageNames), userId,
+                        updateFrameworkRes);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 85ff2be..b7c61a0d 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -2505,6 +2505,16 @@
         mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, getTempAllowlistBroadcastOptions());
     }
 
+    private boolean isBleState(int state) {
+        switch (state) {
+            case BluetoothAdapter.STATE_BLE_ON:
+            case BluetoothAdapter.STATE_BLE_TURNING_ON:
+            case BluetoothAdapter.STATE_BLE_TURNING_OFF:
+                return true;
+        }
+        return false;
+    }
+
     @RequiresPermission(allOf = {
             android.Manifest.permission.BLUETOOTH_CONNECT,
             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
@@ -2527,8 +2537,15 @@
                 sendBluetoothServiceDownCallback();
                 unbindAndFinish();
                 sendBleStateChanged(prevState, newState);
-                // Don't broadcast as it has already been broadcast before
-                isStandardBroadcast = false;
+
+                /* Currently, the OFF intent is broadcasted externally only when we transition
+                 * from TURNING_OFF to BLE_ON state. So if the previous state is a BLE state,
+                 * we are guaranteed that the OFF intent has been broadcasted earlier and we
+                 * can safely skip it.
+                 * Conversely, if the previous state is not a BLE state, it indicates that some
+                 * sort of crash has occurred, moving us directly to STATE_OFF without ever
+                 * passing through BLE_ON. We should broadcast the OFF intent in this case. */
+                isStandardBroadcast = !isBleState(prevState);
 
             } else if (!intermediate_off) {
                 // connect to GattService
@@ -2581,6 +2598,11 @@
                 // Show prevState of BLE_ON as OFF to standard users
                 prevState = BluetoothAdapter.STATE_OFF;
             }
+            if (DBG) {
+                Slog.d(TAG,
+                        "Sending State Change: " + BluetoothAdapter.nameForState(prevState) + " > "
+                                + BluetoothAdapter.nameForState(newState));
+            }
             Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
             intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
             intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 019e4ea..ecd620e 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -21,7 +21,6 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
 import static android.app.ActivityManager.RunningServiceInfo;
 import static android.app.ActivityManager.RunningTaskInfo;
-import static android.app.ActivityManager.getCurrentUser;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
@@ -718,6 +717,9 @@
         public void setIndividualSensorPrivacy(@UserIdInt int userId,
                 @SensorPrivacyManager.Sources.Source int source, int sensor, boolean enable) {
             enforceManageSensorPrivacyPermission();
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = mCurrentUser;
+            }
             if (!canChangeIndividualSensorPrivacy(userId, sensor)) {
                 return;
             }
@@ -843,6 +845,9 @@
         public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId,
                 @SensorPrivacyManager.Sources.Source int source, int sensor, boolean enable) {
             enforceManageSensorPrivacyPermission();
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = mCurrentUser;
+            }
             int parentId = mUserManagerInternal.getProfileParentId(userId);
             forAllUsers(userId2 -> {
                 if (parentId == mUserManagerInternal.getProfileParentId(userId2)) {
@@ -896,6 +901,9 @@
         @Override
         public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) {
             enforceObserveSensorPrivacyPermission();
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = mCurrentUser;
+            }
             synchronized (mLock) {
                 return isIndividualSensorPrivacyEnabledLocked(userId, sensor);
             }
@@ -1213,6 +1221,9 @@
         public void suppressIndividualSensorPrivacyReminders(int userId, int sensor,
                 IBinder token, boolean suppress) {
             enforceManageSensorPrivacyPermission();
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = mCurrentUser;
+            }
             Objects.requireNonNull(token);
 
             Pair<Integer, UserHandle> key = new Pair<>(sensor, UserHandle.of(userId));
@@ -1898,9 +1909,9 @@
                 if (!mIsInEmergencyCall) {
                     mIsInEmergencyCall = true;
                     if (mSensorPrivacyServiceImpl
-                            .isIndividualSensorPrivacyEnabled(getCurrentUser(), MICROPHONE)) {
+                            .isIndividualSensorPrivacyEnabled(mCurrentUser, MICROPHONE)) {
                         mSensorPrivacyServiceImpl.setIndividualSensorPrivacyUnchecked(
-                                getCurrentUser(), OTHER, MICROPHONE, false);
+                                mCurrentUser, OTHER, MICROPHONE, false);
                         mMicUnmutedForEmergencyCall = true;
                     } else {
                         mMicUnmutedForEmergencyCall = false;
@@ -1915,7 +1926,7 @@
                     mIsInEmergencyCall = false;
                     if (mMicUnmutedForEmergencyCall) {
                         mSensorPrivacyServiceImpl.setIndividualSensorPrivacyUnchecked(
-                                getCurrentUser(), OTHER, MICROPHONE, true);
+                                mCurrentUser, OTHER, MICROPHONE, true);
                         mMicUnmutedForEmergencyCall = false;
                     }
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 99ae52c..e0df4b7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -193,6 +193,7 @@
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetManagerInternal;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -16597,6 +16598,13 @@
         if (updateFrameworkRes) {
             ParsingPackageUtils.readConfigUseRoundIcon(null);
         }
+
+        AppWidgetManagerInternal widgets = LocalServices.getService(AppWidgetManagerInternal.class);
+        if (widgets != null) {
+            widgets.applyResourceOverlaysToWidgets(new HashSet<>(packagesToUpdate), userId,
+                    updateFrameworkRes);
+        }
+
         mProcessList.updateApplicationInfoLOSP(packagesToUpdate, userId, updateFrameworkRes);
 
         if (updateFrameworkRes) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 9dbb707..7c336d7 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ProcLocksReader;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.ServiceThread;
 
@@ -319,6 +320,7 @@
     private int mPersistentCompactionCount;
     private int mBfgsCompactionCount;
     private final ProcessDependencies mProcessDependencies;
+    private final ProcLocksReader mProcLocksReader;
 
     public CachedAppOptimizer(ActivityManagerService am) {
         this(am, null, new DefaultProcessDependencies());
@@ -335,6 +337,7 @@
         mProcessDependencies = processDependencies;
         mTestCallback = callback;
         mSettingsObserver = new SettingsContentObserver();
+        mProcLocksReader = new ProcLocksReader();
     }
 
     /**
@@ -1312,7 +1315,7 @@
 
             try {
                 // pre-check for locks to avoid unnecessary freeze/unfreeze operations
-                if (Process.hasFileLocks(pid)) {
+                if (mProcLocksReader.hasFileLocks(pid)) {
                     if (DEBUG_FREEZER) {
                         Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
                     }
@@ -1399,7 +1402,7 @@
 
             try {
                 // post-check to prevent races
-                if (Process.hasFileLocks(pid)) {
+                if (mProcLocksReader.hasFileLocks(pid)) {
                     if (DEBUG_FREEZER) {
                         Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
                     }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 4973d45..b1d300c 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1106,6 +1106,11 @@
             return Settings.Secure.getInt(context.getContentResolver(),
                     CoexCoordinator.SETTING_ENABLE_NAME, 1) != 0;
         }
+
+        public boolean isCoexFaceNonBypassHapticsDisabled(Context context) {
+            return Settings.Secure.getInt(context.getContentResolver(),
+                    CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0;
+        }
     }
 
     /**
@@ -1137,6 +1142,8 @@
         //  by default.
         CoexCoordinator coexCoordinator = CoexCoordinator.getInstance();
         coexCoordinator.setAdvancedLogicEnabled(injector.isAdvancedCoexLogicEnabled(context));
+        coexCoordinator.setFaceHapticDisabledWhenNonBypass(
+                injector.isCoexFaceNonBypassHapticsDisabled(context));
 
         try {
             injector.getActivityManagerService().registerUserSwitchObserver(
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 013c74d..f4327e8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -54,12 +54,18 @@
     public static final int STATE_NEW = 0;
     // Framework/HAL have started this operation
     public static final int STATE_STARTED = 1;
-    // Operation is started, but requires some user action (such as finger lift & re-touch)
+    // Operation is started, but requires some user action to start (such as finger lift & re-touch)
     public static final int STATE_STARTED_PAUSED = 2;
+    // Same as above, except auth was attempted (rejected, timed out, etc).
+    public static final int STATE_STARTED_PAUSED_ATTEMPTED = 3;
     // Done, errored, canceled, etc. HAL/framework are not running this sensor anymore.
-    public static final int STATE_STOPPED = 3;
+    public static final int STATE_STOPPED = 4;
 
-    @IntDef({STATE_NEW, STATE_STARTED, STATE_STARTED_PAUSED, STATE_STOPPED})
+    @IntDef({STATE_NEW,
+            STATE_STARTED,
+            STATE_STARTED_PAUSED,
+            STATE_STARTED_PAUSED_ATTEMPTED,
+            STATE_STOPPED})
     @interface State {}
 
     private final boolean mIsStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
index b576673..a15ecad 100644
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
@@ -45,6 +45,8 @@
     private static final String TAG = "BiometricCoexCoordinator";
     public static final String SETTING_ENABLE_NAME =
             "com.android.server.biometrics.sensors.CoexCoordinator.enable";
+    public static final String FACE_HAPTIC_DISABLE =
+            "com.android.server.biometrics.sensors.CoexCoordinator.disable_face_haptics";
     private static final boolean DEBUG = true;
 
     // Successful authentications should be used within this amount of time.
@@ -144,6 +146,10 @@
         mAdvancedLogicEnabled = enabled;
     }
 
+    public void setFaceHapticDisabledWhenNonBypass(boolean disabled) {
+        mFaceHapticDisabledWhenNonBypass = disabled;
+    }
+
     @VisibleForTesting
     void reset() {
         mClientMap.clear();
@@ -153,6 +159,7 @@
     private final Map<Integer, AuthenticationClient<?>> mClientMap;
     @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths;
     private boolean mAdvancedLogicEnabled;
+    private boolean mFaceHapticDisabledWhenNonBypass;
     private final Handler mHandler;
 
     private CoexCoordinator() {
@@ -225,7 +232,11 @@
                         mSuccessfulAuths.add(new SuccessfulAuth(mHandler, mSuccessfulAuths,
                                 currentTimeMillis, SENSOR_TYPE_FACE, client, callback));
                     } else {
-                        callback.sendHapticFeedback();
+                        if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
+                            Slog.w(TAG, "Skipping face success haptic");
+                        } else {
+                            callback.sendHapticFeedback();
+                        }
                         callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
                         callback.handleLifecycleAfterAuth();
                     }
@@ -278,12 +289,23 @@
                         // BiometricScheduler do not get stuck.
                         Slog.d(TAG, "Face rejected in multi-sensor auth, udfps: " + udfps);
                         callback.handleLifecycleAfterAuth();
-                    } else {
-                        // UDFPS is not actively authenticating (finger not touching, already
-                        // rejected, etc).
+                    } else if (isUdfpsAuthAttempted(udfps)) {
+                        // If UDFPS is STATE_STARTED_PAUSED (e.g. finger rejected but can still
+                        // auth after pointer goes down, it means UDFPS encountered a rejection. In
+                        // this case, we need to play the final reject haptic since face auth is
+                        // also done now.
                         callback.sendHapticFeedback();
                         callback.handleLifecycleAfterAuth();
                     }
+                    else {
+                        // UDFPS auth has never been attempted.
+                        if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
+                            Slog.w(TAG, "Skipping face reject haptic");
+                        } else {
+                            callback.sendHapticFeedback();
+                        }
+                        callback.handleLifecycleAfterAuth();
+                    }
                 } else if (isCurrentUdfps(client)) {
                     // Face should either be running, or have already finished
                     SuccessfulAuth auth = popSuccessfulFaceAuthIfExists(currentTimeMillis);
@@ -374,6 +396,13 @@
         return false;
     }
 
+    private static boolean isUdfpsAuthAttempted(@Nullable AuthenticationClient<?> client) {
+        if (client instanceof Udfps) {
+            return client.getState() == AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED;
+        }
+        return false;
+    }
+
     private boolean isUnknownClient(@NonNull AuthenticationClient<?> client) {
         for (AuthenticationClient<?> c : mClientMap.values()) {
             if (c == client) {
@@ -400,6 +429,7 @@
     public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append("Enabled: ").append(mAdvancedLogicEnabled);
+        sb.append(", Face Haptic Disabled: ").append(mFaceHapticDisabledWhenNonBypass);
         sb.append(", Queue size: " ).append(mSuccessfulAuths.size());
         for (SuccessfulAuth auth : mSuccessfulAuths) {
             sb.append(", Auth: ").append(auth.toString());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 99e6e62..8835c1e0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -114,7 +114,7 @@
             mState = STATE_STOPPED;
             UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
         } else {
-            mState = STATE_STARTED_PAUSED;
+            mState = STATE_STARTED_PAUSED_ATTEMPTED;
         }
     }
 
@@ -188,7 +188,7 @@
     public void onPointerUp() {
         try {
             mIsPointerDown = false;
-            mState = STATE_STARTED_PAUSED;
+            mState = STATE_STARTED_PAUSED_ATTEMPTED;
             mALSProbeCallback.getProbe().disable();
             getFreshDaemon().onPointerUp(0 /* pointerId */);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 7558d15..83f1480 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -112,7 +112,7 @@
             resetFailedAttempts(getTargetUserId());
             UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
         } else {
-            mState = STATE_STARTED_PAUSED;
+            mState = STATE_STARTED_PAUSED_ATTEMPTED;
             final @LockoutTracker.LockoutMode int lockoutMode =
                     mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId());
             if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
@@ -206,7 +206,7 @@
     @Override
     public void onPointerUp() {
         mIsPointerDown = false;
-        mState = STATE_STARTED_PAUSED;
+        mState = STATE_STARTED_PAUSED_ATTEMPTED;
         mALSProbeCallback.getProbe().disable();
         UdfpsHelper.onFingerUp(getFreshDaemon());
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 4ee867b..097b071 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -289,8 +289,7 @@
     private String mActiveIface;
 
     /** Set of any ifaces associated with mobile networks since boot. */
-    @GuardedBy("mStatsLock")
-    private String[] mMobileIfaces = new String[0];
+    private volatile String[] mMobileIfaces = new String[0];
 
     /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
     @GuardedBy("mStatsLock")
@@ -935,7 +934,12 @@
 
     @Override
     public String[] getMobileIfaces() {
-        return mMobileIfaces;
+        // TODO (b/192758557): Remove debug log.
+        if (ArrayUtils.contains(mMobileIfaces, null)) {
+            throw new NullPointerException(
+                    "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
+        }
+        return mMobileIfaces.clone();
     }
 
     @Override
@@ -1084,7 +1088,8 @@
     }
 
     @Override
-    public long getIfaceStats(String iface, int type) {
+    public long getIfaceStats(@NonNull String iface, int type) {
+        Objects.requireNonNull(iface);
         long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
         if (nativeIfaceStats == -1) {
             return nativeIfaceStats;
@@ -1382,7 +1387,12 @@
             }
         }
 
-        mMobileIfaces = mobileIfaces.toArray(new String[mobileIfaces.size()]);
+        mMobileIfaces = mobileIfaces.toArray(new String[0]);
+        // TODO (b/192758557): Remove debug log.
+        if (ArrayUtils.contains(mMobileIfaces, null)) {
+            throw new NullPointerException(
+                    "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
+        }
     }
 
     private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
index f1adcae..7f5f3c2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
@@ -70,6 +70,7 @@
 
         mCoexCoordinator = CoexCoordinator.getInstance();
         mCoexCoordinator.setAdvancedLogicEnabled(true);
+        mCoexCoordinator.setFaceHapticDisabledWhenNonBypass(true);
     }
 
     @Test
@@ -151,12 +152,76 @@
 
         mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
                 mCallback);
-        verify(mCallback).sendHapticFeedback();
+        // Haptics tested in #testKeyguard_bypass_haptics. Let's leave this commented out (instead
+        // of removed) to keep this context.
+        // verify(mCallback).sendHapticFeedback();
         verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
         verify(mCallback).handleLifecycleAfterAuth();
     }
 
     @Test
+    public void testKeyguard_faceAuthSuccess_nonBypass_udfpsRunning_noHaptics() {
+        testKeyguard_bypass_haptics(false /* bypassEnabled */,
+                true /* faceAccepted */,
+                false /* shouldReceiveHaptics */);
+    }
+
+    @Test
+    public void testKeyguard_faceAuthReject_nonBypass_udfpsRunning_noHaptics() {
+        testKeyguard_bypass_haptics(false /* bypassEnabled */,
+                false /* faceAccepted */,
+                false /* shouldReceiveHaptics */);
+    }
+
+    @Test
+    public void testKeyguard_faceAuthSuccess_bypass_udfpsRunning_haptics() {
+        testKeyguard_bypass_haptics(true /* bypassEnabled */,
+                true /* faceAccepted */,
+                true /* shouldReceiveHaptics */);
+    }
+
+    @Test
+    public void testKeyguard_faceAuthReject_bypass_udfpsRunning_haptics() {
+        testKeyguard_bypass_haptics(true /* bypassEnabled */,
+                false /* faceAccepted */,
+                true /* shouldReceiveHaptics */);
+    }
+
+    private void testKeyguard_bypass_haptics(boolean bypassEnabled, boolean faceAccepted,
+            boolean shouldReceiveHaptics) {
+        mCoexCoordinator.reset();
+
+        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
+        when(faceClient.isKeyguard()).thenReturn(true);
+        when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+
+        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
+                withSettings().extraInterfaces(Udfps.class));
+        when(udfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+
+        if (faceAccepted) {
+            mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
+                    mCallback);
+        } else {
+            mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+                    LockoutTracker.LOCKOUT_NONE, mCallback);
+        }
+
+        if (shouldReceiveHaptics) {
+            verify(mCallback).sendHapticFeedback();
+        } else {
+            verify(mCallback, never()).sendHapticFeedback();
+        }
+
+        verify(mCallback).sendAuthenticationResult(eq(faceAccepted) /* addAuthTokenIfStrong */);
+        verify(mCallback).handleLifecycleAfterAuth();
+    }
+
+    @Test
     public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsRejectedWithinBounds() {
         testKeyguard_faceAuth_udfpsTouching_faceSuccess(false /* thenUdfpsAccepted */,
                 0 /* udfpsRejectedAfterMs */);
@@ -294,12 +359,13 @@
     }
 
     @Test
-    public void testKeyguard_udfpsRejected_thenFaceRejected() {
+    public void testKeyguard_udfpsRejected_thenFaceRejected_noKeyguardBypass() {
         mCoexCoordinator.reset();
 
         AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
         when(faceClient.isKeyguard()).thenReturn(true);
         when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(faceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
 
         AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
                 withSettings().extraInterfaces(Udfps.class));
@@ -312,8 +378,9 @@
 
         mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient,
                 LockoutTracker.LOCKOUT_NONE, mCallback);
-        // Client becomes paused, but finger does not necessarily lift, since we suppress the haptic
-        when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED_PAUSED);
+        // Auth was attempted
+        when(udfpsClient.getState())
+                .thenReturn(AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED);
         verify(mCallback, never()).sendHapticFeedback();
         verify(mCallback).handleLifecycleAfterAuth();
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
index b9e1fcd..68b2e61 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
@@ -29,7 +29,6 @@
 import android.media.permission.PermissionUtil;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.ServiceSpecificException;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -117,8 +116,8 @@
 
     /**
      * Throws a {@link SecurityException} if originator permanently doesn't have the given
-     * permission, or a {@link ServiceSpecificException} with a {@link
-     * #TEMPORARY_PERMISSION_DENIED} if caller originator doesn't have the given permission.
+     * permission.
+     * Soft (temporary) denials are considered OK for preflight purposes.
      *
      * @param context    A {@link Context}, used for permission checks.
      * @param identity   The identity to check.
@@ -130,15 +129,12 @@
                 permission);
         switch (status) {
             case PermissionChecker.PERMISSION_GRANTED:
+            case PermissionChecker.PERMISSION_SOFT_DENIED:
                 return;
             case PermissionChecker.PERMISSION_HARD_DENIED:
                 throw new SecurityException(
                         TextUtils.formatSimple("Failed to obtain permission %s for identity %s",
                                 permission, toString(identity)));
-            case PermissionChecker.PERMISSION_SOFT_DENIED:
-                throw new ServiceSpecificException(TEMPORARY_PERMISSION_DENIED,
-                        TextUtils.formatSimple("Failed to obtain permission %s for identity %s",
-                                permission, toString(identity)));
             default:
                 throw new RuntimeException("Unexpected permission check result.");
         }