Merge "Update the scrimColor and the shape of the modal bottom sheet"
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index e3bd5ac..dcc6aa6 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -29,6 +29,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
+import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -314,7 +315,9 @@
     private Sensor mMotionSensor;
     private LocationRequest mLocationRequest;
     private Intent mIdleIntent;
+    private Bundle mIdleIntentOptions;
     private Intent mLightIdleIntent;
+    private Bundle mLightIdleIntentOptions;
     private AnyMotionDetector mAnyMotionDetector;
     private final AppStateTrackerImpl mAppStateTracker;
     @GuardedBy("this")
@@ -1798,10 +1801,12 @@
                     } catch (RemoteException e) {
                     }
                     if (deepChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mIdleIntentOptions);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mLightIdleIntentOptions);
                     }
                     EventLogTags.writeDeviceIdleOnComplete();
                     mGoingIdleWakeLock.release();
@@ -1821,13 +1826,13 @@
                         incActiveIdleOps();
                         mLocalActivityManager.broadcastIntentWithCallback(mIdleIntent,
                                 mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
-                                null, null, null);
+                                null, null, mIdleIntentOptions);
                     }
                     if (lightChanged) {
                         incActiveIdleOps();
                         mLocalActivityManager.broadcastIntentWithCallback(mLightIdleIntent,
                                 mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
-                                null, null, null);
+                                null, null, mLightIdleIntentOptions);
                     }
                     // Always start with one active op for the message being sent here.
                     // Now we are done!
@@ -1849,10 +1854,12 @@
                     } catch (RemoteException e) {
                     }
                     if (deepChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mIdleIntentOptions);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mLightIdleIntentOptions);
                     }
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
@@ -2531,6 +2538,9 @@
                 mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
                 mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+                mIdleIntentOptions = mLightIdleIntentOptions = options.toBundle();
 
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index fa73178..725cef6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1908,6 +1908,7 @@
   }
 
   public abstract class VibrationEffect implements android.os.Parcelable {
+    method @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
     method public static android.os.VibrationEffect get(int);
     method public static android.os.VibrationEffect get(int, boolean);
     method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
@@ -1922,6 +1923,7 @@
   }
 
   public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
+    method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull();
     method public long getDuration();
     method public int getRepeatIndex();
     method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 71bc4b3..3448a9e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -227,6 +227,31 @@
     }
 
     /**
+     * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on"
+     * vibration components) that is equivalent to this VibrationEffect.
+     *
+     * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible
+     * into an equivalent vibration pattern with this method. It is not guaranteed that an effect
+     * created with other means becomes converted into an equivalent legacy vibration pattern, even
+     * if it has an equivalent vibration pattern. If this method is unable to create an equivalent
+     * vibration pattern for such effects, it will return {@code null}.
+     *
+     * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any
+     * form of repeating behavior, regardless of how the effect was created. For repeating effects,
+     * the method will always return {@code null}.
+     *
+     * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if
+     *               the method successfully derived a vibration pattern equivalent to the effect
+     *               (this will always be the case if the effect was created via
+     *               {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns
+     *               {@code null}.
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
+
+    /**
      * Create a waveform vibration.
      *
      * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
@@ -641,6 +666,51 @@
             return mRepeatIndex;
         }
 
+         /** @hide */
+        @Override
+        @Nullable
+        public long[] computeCreateWaveformOffOnTimingsOrNull() {
+            if (getRepeatIndex() >= 0) {
+                // Repeating effects cannot be fully represented as a long[] legacy pattern.
+                return null;
+            }
+
+            List<VibrationEffectSegment> segments = getSegments();
+
+            // The maximum possible size of the final pattern is 1 plus the number of segments in
+            // the original effect. This is because we will add an empty "off" segment at the
+            // start of the pattern if the first segment of the original effect is an "on" segment.
+            // (because the legacy patterns start with an "off" pattern). Other than this one case,
+            // we will add the durations of back-to-back segments of similar amplitudes (amplitudes
+            // that are all "on" or "off") and create a pattern entry for the total duration, which
+            // will not take more number pattern entries than the number of segments processed.
+            long[] patternBuffer = new long[segments.size() + 1];
+            int patternIndex = 0;
+
+            for (int i = 0; i < segments.size(); i++) {
+                StepSegment stepSegment =
+                        castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i));
+                if (stepSegment == null) {
+                    // This means that there is 1 or more segments of this effect that is/are not a
+                    // possible component of a legacy vibration pattern. Thus, the VibrationEffect
+                    // does not have any equivalent legacy vibration pattern.
+                    return null;
+                }
+
+                boolean isSegmentOff = stepSegment.getAmplitude() == 0;
+                // Even pattern indices are "off", and odd pattern indices are "on"
+                boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0;
+                if (isSegmentOff != isCurrentPatternIndexOff) {
+                    // Move the pattern index one step ahead, so that the current segment's
+                    // "off"/"on" property matches that of the index's
+                    ++patternIndex;
+                }
+                patternBuffer[patternIndex] += stepSegment.getDuration();
+            }
+
+            return Arrays.copyOf(patternBuffer, patternIndex + 1);
+        }
+
         /** @hide */
         @Override
         public void validate() {
@@ -806,6 +876,31 @@
                         return new Composed[size];
                     }
                 };
+
+        /**
+         * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it,
+         * only if it can possibly be a segment for an effect created via
+         * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}.
+         */
+        @Nullable
+        private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull(
+                VibrationEffectSegment segment) {
+            if (!(segment instanceof StepSegment)) {
+                return null;
+            }
+
+            StepSegment stepSegment = (StepSegment) segment;
+            if (stepSegment.getFrequencyHz() != 0) {
+                return null;
+            }
+
+            float amplitude = stepSegment.getAmplitude();
+            if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) {
+                return null;
+            }
+
+            return stepSegment;
+        }
     }
 
     /**
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index f7ca822..0c7ff4a 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import static android.os.VibrationEffect.DEFAULT_AMPLITUDE;
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
@@ -48,6 +49,7 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.time.Duration;
+import java.util.Arrays;
 
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
@@ -62,16 +64,363 @@
     private static final int TEST_AMPLITUDE = 100;
     private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
     private static final int[] TEST_AMPLITUDES =
-            new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE };
+            new int[] { 255, 0, DEFAULT_AMPLITUDE };
 
     private static final VibrationEffect TEST_ONE_SHOT =
             VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE);
     private static final VibrationEffect DEFAULT_ONE_SHOT =
-            VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
+            VibrationEffect.createOneShot(TEST_TIMING, DEFAULT_AMPLITUDE);
     private static final VibrationEffect TEST_WAVEFORM =
             VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
 
     @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnEvenIndices() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnOddIndices() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {
+                        DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 2, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheStart() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {0, 0, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {3, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheEnd() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_allDefaultAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {
+                        DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 6};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_allZeroAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {0, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {6};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_sparsedZeroAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5, 6, 7},
+                /* amplitudes= */ new int[] {
+                        0, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {3, 3, 4, 11, 7};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithDefaultAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithZeroAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_repeating() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ 0);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ 3);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ 1);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_badAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {200},
+                /* repeatIndex= */ -1);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_nonZeroTimings() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_oneValue() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {5},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheEnd() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3, 0, 0};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheStart() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 0, 1, 2, 3},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 0, 1, 2, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheMiddle() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 0, 0, 3, 4, 5},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 0, 0, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_sparsedZeroes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_repeating() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+                /* repeatIndex= */ 0);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4},
+                /* repeatIndex= */ 2);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_notPatternPased() {
+        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_oneShot_defaultAmplitude() {
+        VibrationEffect effect = VibrationEffect.createOneShot(
+                /* milliseconds= */ 5, /* ampliutde= */ DEFAULT_AMPLITUDE);
+        long[] expectedPattern = new long[] {0, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_oneShot_badAmplitude() {
+        VibrationEffect effect = VibrationEffect.createOneShot(
+                /* milliseconds= */ 5, /* ampliutde= */ 50);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_noOffDuration() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {2, 3},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {10, 20},
+                                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {4, 5},
+                                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .compose();
+        long[] expectedPattern = new long[] {7, 33, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_withOffDuration() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addOffDuration(Duration.ofMillis(20))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {10, 20},
+                                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {30, 40},
+                                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addOffDuration(Duration.ofMillis(10))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {4, 5},
+                                /* repeatIndex= */ -1))
+                .addOffDuration(Duration.ofMillis(5))
+                .compose();
+        long[] expectedPattern = new long[] {30, 90, 14, 5, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_withPrimitives() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .addOffDuration(Duration.ofMillis(20))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .compose();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_repeating() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .repeatEffectIndefinitely(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {2, 3},
+                                /* repeatIndex= */ -1))
+                .compose();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_effectsViaStartWaveform() {
+        // Effects created via startWaveform are not expected to be converted to long[] patterns, as
+        // they are not configured to always play with the default amplitude.
+        VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(80), targetAmplitude(1))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetFrequency(50))
+                .addSustain(Duration.ofMillis(50))
+                .addTransition(Duration.ofMillis(20), targetFrequency(75))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
     public void getRingtones_noPrebakedRingtones() {
         Resources r = mockRingtoneResources(new String[0]);
         Context context = mockContext(r);
@@ -100,7 +449,7 @@
     @Test
     public void testValidateOneShot() {
         VibrationEffect.createOneShot(1, 255).validate();
-        VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE).validate();
+        VibrationEffect.createOneShot(1, DEFAULT_AMPLITUDE).validate();
 
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.createOneShot(-1, 255).validate());
@@ -501,6 +850,13 @@
         assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate());
     }
 
+    private void assertArrayEq(long[] expected, long[] actual) {
+        assertTrue(
+                String.format("Expected pattern %s, but was %s",
+                        Arrays.toString(expected), Arrays.toString(actual)),
+                Arrays.equals(expected, actual));
+    }
+
     private Resources mockRingtoneResources() {
         return mockRingtoneResources(new String[]{
                 RINGTONE_URI_1,
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 82ced43..0e198d5 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -382,9 +382,9 @@
             }
 
             /**
-             * Creates a PixelCopy request for the given {@link Window}
+             * Creates a PixelCopy Builder for the given {@link Window}
              * @param source The Window to copy from
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofWindow(@NonNull Window source) {
@@ -394,7 +394,7 @@
             }
 
             /**
-             * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+             * Creates a PixelCopy Builder for the {@link Window} that the given {@link View} is
              * attached to.
              *
              * Note that this copy request is not cropped to the area the View occupies by default.
@@ -404,7 +404,7 @@
              *
              * @param source A View that {@link View#isAttachedToWindow() is attached} to a window
              *               that will be used to retrieve the window to copy from.
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofWindow(@NonNull View source) {
@@ -427,10 +427,10 @@
             }
 
             /**
-             * Creates a PixelCopy request for the given {@link Surface}
+             * Creates a PixelCopy Builder for the given {@link Surface}
              *
              * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofSurface(@NonNull Surface source) {
@@ -441,12 +441,12 @@
             }
 
             /**
-             * Creates a PixelCopy request for the {@link Surface} belonging to the
+             * Creates a PixelCopy Builder for the {@link Surface} belonging to the
              * given {@link SurfaceView}
              *
              * @param source The SurfaceView to copy from. The backing surface must be
              *               {@link Surface#isValid() valid}
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofSurface(@NonNull SurfaceView source) {
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 9aa3787..c0fa63a 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -83,15 +83,16 @@
     return {};
   }
 
+  std::string overlay_path(loaded_idmap->OverlayApkPath());
+  auto fd = unique_fd(::open(overlay_path.c_str(), O_RDONLY|O_CLOEXEC));
   std::unique_ptr<AssetsProvider> overlay_assets;
-  const std::string overlay_path(loaded_idmap->OverlayApkPath());
-  if (IsFabricatedOverlay(overlay_path)) {
+  if (IsFabricatedOverlay(fd)) {
     // Fabricated overlays do not contain resource definitions. All of the overlay resource values
     // are defined inline in the idmap.
-    overlay_assets = EmptyAssetsProvider::Create(overlay_path);
+    overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
   } else {
     // The overlay should be an APK.
-    overlay_assets = ZipAssetsProvider::Create(overlay_path, flags);
+    overlay_assets = ZipAssetsProvider::Create(std::move(fd), std::move(overlay_path), flags);
   }
   if (overlay_assets == nullptr) {
     return {};
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 61e842a..c3d153d 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -112,7 +112,7 @@
   // A mapping from path of apk assets that could be target packages of overlays to the runtime
   // package id of its first loaded package. Overlays currently can only override resources in the
   // first package in the target resource table.
-  std::unordered_map<std::string, uint8_t> target_assets_package_ids;
+  std::unordered_map<std::string_view, uint8_t> target_assets_package_ids;
 
   // Overlay resources are not directly referenced by an application so their resource ids
   // can change throughout the application's lifetime. Assign overlay package ids last.
@@ -135,7 +135,7 @@
     if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
       // The target package must precede the overlay package in the apk assets paths in order
       // to take effect.
-      auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+      auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath());
       if (iter == target_assets_package_ids.end()) {
          LOG(INFO) << "failed to find target package for overlay "
                    << loaded_idmap->OverlayApkPath();
@@ -180,7 +180,7 @@
         if (overlay_ref_table != nullptr) {
           // If this package is from an overlay, use a dynamic reference table that can rewrite
           // overlay resource ids to their corresponding target resource ids.
-          new_group.dynamic_ref_table = overlay_ref_table;
+          new_group.dynamic_ref_table = std::move(overlay_ref_table);
         }
 
         DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
@@ -188,9 +188,9 @@
         ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
       }
 
-      // Add the package and to the set of packages with the same ID.
+      // Add the package to the set of packages with the same ID.
       PackageGroup* package_group = &package_groups_[idx];
-      package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
+      package_group->packages_.emplace_back().loaded_package_ = package.get();
       package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
 
       // Add the package name -> build time ID mappings.
@@ -202,7 +202,7 @@
 
       if (auto apk_assets_path = apk_assets->GetPath()) {
         // Overlay target ApkAssets must have been created using path based load apis.
-        target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id));
+        target_assets_package_ids.emplace(*apk_assets_path, package_id);
       }
     }
   }
@@ -219,11 +219,13 @@
 
     for (const auto& package : group.packages_) {
       const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap();
-      aliases.insert(package_aliases.begin(), package_aliases.end());
+      aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end());
     }
   }
 
   if (!aliases.empty()) {
+    std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; });
+
     // Add the alias resources to the dynamic reference table of every package group. Since
     // staging aliases can only be defined by the framework package (which is not a shared
     // library), the compile-time package id of the framework is the same across all packages
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 289d7e6..80e5607 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -393,8 +393,8 @@
   return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({}));
 }
 
-std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) {
-  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path));
+std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(std::string path) {
+  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(std::move(path)));
 }
 
 std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 035ed4f..31516dc 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -33,7 +33,9 @@
 #include <type_traits>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/macros.h>
+#include <android-base/utf8.h>
 #include <androidfw/ByteBucketArray.h>
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/TypeWrappers.h>
@@ -236,12 +238,23 @@
 }
 
 bool IsFabricatedOverlay(const std::string& path) {
-  std::ifstream fin(path);
-  uint32_t magic;
-  if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
-    return magic == kFabricatedOverlayMagic;
+  return IsFabricatedOverlay(path.c_str());
+}
+
+bool IsFabricatedOverlay(const char* path) {
+  auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC));
+  if (fd < 0) {
+    return false;
   }
-  return false;
+  return IsFabricatedOverlay(fd);
+}
+
+bool IsFabricatedOverlay(base::borrowed_fd fd) {
+  uint32_t magic;
+  if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) {
+    return false;
+  }
+  return magic == kFabricatedOverlayMagic;
 }
 
 static bool assertIdmapHeader(const void* idmap, size_t size) {
@@ -6988,11 +7001,10 @@
 DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {}
 
 DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
-    : mAssignedPackageId(packageId)
+    : mLookupTable()
+    , mAssignedPackageId(packageId)
     , mAppAsLib(appAsLib)
 {
-    memset(mLookupTable, 0, sizeof(mLookupTable));
-
     // Reserved package ids
     mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
     mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
@@ -7073,10 +7085,6 @@
     mLookupTable[buildPackageId] = runtimePackageId;
 }
 
-void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
-    mAliasId[stagedId] = finalizedId;
-}
-
 status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
     uint32_t res = *resId;
     size_t packageId = Res_GETPACKAGE(res) + 1;
@@ -7086,11 +7094,12 @@
         return NO_ERROR;
     }
 
-    auto alias_id = mAliasId.find(res);
-    if (alias_id != mAliasId.end()) {
+    const auto alias_it = std::lower_bound(mAliasId.begin(), mAliasId.end(), res,
+        [](const AliasMap::value_type& pair, uint32_t val) { return pair.first < val; });
+    if (alias_it != mAliasId.end() && alias_it->first == res) {
       // Rewrite the resource id to its alias resource id. Since the alias resource id is a
       // compile-time id, it still needs to be resolved further.
-      res = alias_id->second;
+      res = alias_it->second;
     }
 
     if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 13cbe3b..af6e7f4 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -181,7 +181,7 @@
 // Does not provide any assets.
 struct EmptyAssetsProvider : public AssetsProvider {
   static std::unique_ptr<AssetsProvider> Create();
-  static std::unique_ptr<AssetsProvider> Create(const std::string& path);
+  static std::unique_ptr<AssetsProvider> Create(std::string path);
 
   bool ForEachFile(const std::string& path,
                    const std::function<void(StringPiece, FileType)>& f) const override;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index d98e97a..52321da 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -21,6 +21,7 @@
 #define _LIBS_UTILS_RESOURCE_TYPES_H
 
 #include <android-base/expected.h>
+#include <android-base/unique_fd.h>
 
 #include <androidfw/Asset.h>
 #include <androidfw/Errors.h>
@@ -58,6 +59,8 @@
 
 // Returns whether or not the path represents a fabricated overlay.
 bool IsFabricatedOverlay(const std::string& path);
+bool IsFabricatedOverlay(const char* path);
+bool IsFabricatedOverlay(android::base::borrowed_fd fd);
 
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -1882,11 +1885,10 @@
 
     void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
 
-    using AliasMap = std::map<uint32_t, uint32_t>;
+    using AliasMap = std::vector<std::pair<uint32_t, uint32_t>>;
     void setAliases(AliasMap aliases) {
         mAliasId = std::move(aliases);
     }
-    void addAlias(uint32_t stagedId, uint32_t finalizedId);
 
     // Returns whether or not the value must be looked up.
     bool requiresLookup(const Res_value* value) const;
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 252ecf4..8c9023c 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -49,6 +49,8 @@
   <string name="save_password_to_title">Save password to</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] -->
   <string name="save_sign_in_to_title">Save sign-in to</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
+  <string name="create_passkey_in_other_device_title">Create a passkey in another device?</string>
   <!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
   <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
   <!-- TODO: Check the wording here. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 530f1c4..0cc1194 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -44,6 +44,8 @@
 import com.android.credentialmanager.createflow.CreateCredentialUiState
 import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.RemoteInfo
+import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
@@ -142,25 +144,22 @@
     val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
       // Handle runtime cast error
       providerDisabledList as List<DisabledProviderData>, context)
-    var hasDefault = false
-    var defaultProvider: EnabledProviderInfo = providerEnabledList.first()
+    var defaultProvider: EnabledProviderInfo? = null
+    var remoteEntry: RemoteInfo? = null
     providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
       providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
-      if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
+      if (providerInfo.isDefault) {defaultProvider = providerInfo}
+      if (providerInfo.remoteEntry != null) {
+        remoteEntry = providerInfo.remoteEntry!!
+      }
+    }
     return CreateCredentialUiState(
       enabledProviders = providerEnabledList,
       disabledProviders = providerDisabledList,
-      // TODO: Add the screen when defaultProvider has no createOption but
-      //  there's remoteInfo under other providers
-      if (!hasDefault || defaultProvider.createOptions.isEmpty()) {
-        if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL)
-        {CreateScreenState.PASSKEY_INTRO} else {CreateScreenState.PROVIDER_SELECTION}
-      } else {CreateScreenState.CREATION_OPTION_SELECTION},
+      toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
       requestDisplayInfo,
       false,
-      if (hasDefault) {
-        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
-      } else null
+      toActiveEntry(defaultProvider, remoteEntry),
     )
   }
 
@@ -509,4 +508,38 @@
       "tribank.us"
     )
   }
+
+  private fun toCreateScreenState(
+    requestDisplayInfo: RequestDisplayInfo,
+    defaultProvider: EnabledProviderInfo?,
+    remoteEntry: RemoteInfo?,
+  ): CreateScreenState {
+    return if (
+      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
+    ){
+      CreateScreenState.EXTERNAL_ONLY_SELECTION
+    } else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
+      if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
+        CreateScreenState.PASSKEY_INTRO
+      } else {
+        CreateScreenState.PROVIDER_SELECTION
+      }
+    } else {
+      CreateScreenState.CREATION_OPTION_SELECTION
+    }
+  }
+
+  private fun toActiveEntry(
+    defaultProvider: EnabledProviderInfo?,
+    remoteEntry: RemoteInfo?,
+  ): ActiveEntry? {
+    return if (
+      defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
+    ) {
+      ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+    } else if (
+      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
+      ActiveEntry(defaultProvider, remoteEntry)
+    } else null
+  }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 0e36b3a..5552d05 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -60,7 +60,7 @@
     viewModel.onEntrySelected(it, providerActivityLauncher)
   }
   val confirmEntryCallback: () -> Unit = {
-    viewModel.onConfirmCreationSelected(providerActivityLauncher)
+    viewModel.onConfirmEntrySelected(providerActivityLauncher)
   }
   val state = rememberModalBottomSheetState(
     initialValue = ModalBottomSheetValue.Expanded,
@@ -108,6 +108,13 @@
           providerInfo = uiState.activeEntry?.activeProvider!!,
           onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
         )
+        CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
+          requestDisplayInfo = uiState.requestDisplayInfo,
+          activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
+          onOptionSelected = selectEntryCallback,
+          onConfirm = confirmEntryCallback,
+          onCancel = viewModel::onCancel,
+        )
       }
     },
     scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
@@ -489,7 +496,7 @@
       ) {
         PrimaryCreateOptionRow(
           requestDisplayInfo = requestDisplayInfo,
-          createOptionInfo = createOptionInfo,
+          entryInfo = createOptionInfo,
           onOptionSelected = onOptionSelected
         )
       }
@@ -562,16 +569,85 @@
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
+fun ExternalOnlySelectionCard(
+  requestDisplayInfo: RequestDisplayInfo,
+  activeRemoteEntry: EntryInfo,
+  onOptionSelected: (EntryInfo) -> Unit,
+  onConfirm: () -> Unit,
+  onCancel: () -> Unit,
+) {
+  Card() {
+    Column() {
+      Icon(
+        painter = painterResource(R.drawable.ic_other_devices),
+        contentDescription = null,
+        tint = Color.Unspecified,
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+          .padding(all = 24.dp).size(32.dp)
+      )
+      Text(
+        text = stringResource(R.string.create_passkey_in_other_device_title),
+        style = MaterialTheme.typography.titleMedium,
+        modifier = Modifier.padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally),
+        textAlign = TextAlign.Center,
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Card(
+        shape = MaterialTheme.shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally),
+      ) {
+        PrimaryCreateOptionRow(
+          requestDisplayInfo = requestDisplayInfo,
+          entryInfo = activeRemoteEntry,
+          onOptionSelected = onOptionSelected
+        )
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.SpaceBetween,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(
+          stringResource(R.string.string_cancel),
+          onClick = onCancel
+        )
+        ConfirmButton(
+          stringResource(R.string.string_continue),
+          onClick = onConfirm
+        )
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
 fun PrimaryCreateOptionRow(
   requestDisplayInfo: RequestDisplayInfo,
-  createOptionInfo: CreateOptionInfo,
+  entryInfo: EntryInfo,
   onOptionSelected: (EntryInfo) -> Unit
 ) {
   Entry(
-    onClick = {onOptionSelected(createOptionInfo)},
+    onClick = {onOptionSelected(entryInfo)},
     icon = {
       Icon(
-        bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
+        bitmap = if (entryInfo is CreateOptionInfo) {
+          entryInfo.profileIcon.toBitmap().asImageBitmap()
+        } else {requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()},
         contentDescription = null,
         tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
         modifier = Modifier.padding(start = 18.dp).size(32.dp)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 0f685a1..393cf7d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -155,7 +155,7 @@
     }
   }
 
-  fun onConfirmCreationSelected(
+  fun onConfirmEntrySelected(
     launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
   ) {
     val selectedEntry = uiState.activeEntry?.activeEntryInfo
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 31d0365..9ac524a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -95,4 +95,5 @@
   CREATION_OPTION_SELECTION,
   MORE_OPTIONS_SELECTION,
   MORE_OPTIONS_ROW_INTRO,
+  EXTERNAL_ONLY_SELECTION,
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 0c9a043..238204a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -36,6 +36,7 @@
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.createIntent
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
@@ -91,6 +92,7 @@
                     spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
                     SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
                 }
+                .setStatusDataFn { EntryStatusData(isDisabled = false) }
                 .build()
         )
         entryList.add(
@@ -103,6 +105,7 @@
                         searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
                     )
                 }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
                 .build()
         )
         entryList.add(singleLineSummaryEntry())
@@ -269,7 +272,7 @@
                 )
             }
             .setSliceDataFn { sliceUri, _ ->
-                val intent = owner.createBrowseIntent()?.createBrowsePendingIntent()
+                val intent = owner.createIntent()?.createBrowsePendingIntent()
                     ?: return@setSliceDataFn null
                 return@setSliceDataFn object : EntrySliceData() {
                     init {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index 238268a..f7cbdae 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -39,7 +39,10 @@
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.slice.appendSliceParams
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
+import com.android.settingslib.spa.slice.fromEntry
 import com.android.settingslib.spa.slice.presenter.SliceDemo
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -158,14 +161,13 @@
             remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
         RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
             for (entry in allSliceEntry) {
-                SliceDemo(sliceUri = entry.createSliceUri(authority))
+                SliceDemo(sliceUri = Uri.Builder().fromEntry(entry, authority).build())
             }
         }
     }
 
     @Composable
     fun OnePage(arguments: Bundle?) {
-        val context = LocalContext.current
         val entryRepository by spaEnvironment.entryRepository
         val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
         val pageWithEntry = entryRepository.getPageWithEntry(id)!!
@@ -176,8 +178,8 @@
             Text(text = "Entry size: ${pageWithEntry.entries.size}")
             Preference(model = object : PreferenceModel {
                 override val title = "open page"
-                override val enabled =
-                    page.isBrowsable(context, spaEnvironment.browseActivityClass).toState()
+                override val enabled = (spaEnvironment.browseActivityClass != null &&
+                    page.isBrowsable()).toState()
                 override val onClick = openPage(page)
             })
             EntryList(pageWithEntry.entries)
@@ -186,7 +188,6 @@
 
     @Composable
     fun OneEntry(arguments: Bundle?) {
-        val context = LocalContext.current
         val entryRepository by spaEnvironment.entryRepository
         val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")
         val entry = entryRepository.getEntry(id)!!
@@ -194,9 +195,9 @@
         RegularScaffold(title = "Entry - ${entry.debugBrief()}") {
             Preference(model = object : PreferenceModel {
                 override val title = "open entry"
-                override val enabled =
-                    entry.containerPage().isBrowsable(context, spaEnvironment.browseActivityClass)
-                        .toState()
+                override val enabled = (spaEnvironment.browseActivityClass != null &&
+                    entry.containerPage().isBrowsable())
+                    .toState()
                 override val onClick = openEntry(entry)
             })
             Text(text = entryContent)
@@ -219,7 +220,7 @@
     private fun openPage(page: SettingsPage): (() -> Unit)? {
         val context = LocalContext.current
         val intent =
-            page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: return null
+            page.createIntent(SESSION_BROWSE) ?: return null
         val route = page.buildRoute()
         return {
             spaEnvironment.logger.message(
@@ -232,8 +233,7 @@
     @Composable
     private fun openEntry(entry: SettingsEntry): (() -> Unit)? {
         val context = LocalContext.current
-        val intent = entry.containerPage()
-            .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
+        val intent = entry.createIntent(SESSION_SEARCH)
             ?: return null
         val route = entry.containerPage().buildRoute()
         return {
@@ -245,18 +245,6 @@
     }
 }
 
-private fun SettingsEntry.createSliceUri(
-    authority: String?,
-    runtimeArguments: Bundle? = null
-): Uri {
-    if (authority == null) return Uri.EMPTY
-    return Uri.Builder().scheme("content").authority(authority).appendSliceParams(
-        route = this.containerPage().buildRoute(),
-        entryId = this.id,
-        runtimeArguments = runtimeArguments,
-    ).build()
-}
-
 /**
  * A blank activity without any page.
  */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 3df7727..59ec985 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -32,6 +32,12 @@
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.addUri
 import com.android.settingslib.spa.framework.common.getColumns
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
 
 private const val TAG = "DebugProvider"
 
@@ -116,9 +122,11 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
-            val command = pageWithEntry.page.createBrowseAdbCommand(
-                context,
-                spaEnvironment.browseActivityClass
+            val page = pageWithEntry.page
+            if (!page.isBrowsable()) continue
+            val command = createBrowseAdbCommand(
+                destination = page.buildRoute(),
+                sessionName = SESSION_BROWSE
             )
             if (command != null) {
                 cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command)
@@ -131,8 +139,13 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
-            val command = entry.containerPage()
-                .createBrowseAdbCommand(context, spaEnvironment.browseActivityClass, entry.id)
+            val page = entry.containerPage()
+            if (!page.isBrowsable()) continue
+            val command = createBrowseAdbCommand(
+                destination = page.buildRoute(),
+                entryId = entry.id,
+                sessionName = SESSION_SEARCH
+            )
             if (command != null) {
                 cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command)
             }
@@ -145,8 +158,7 @@
         val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
             val page = pageWithEntry.page
-            val intent =
-                page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: Intent()
+            val intent = page.createIntent(SESSION_BROWSE) ?: Intent()
             cursor.newRow()
                 .add(ColumnEnum.PAGE_ID.id, page.id)
                 .add(ColumnEnum.PAGE_NAME.id, page.displayName)
@@ -162,17 +174,36 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
-            val intent = entry.containerPage()
-                .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
-                ?: Intent()
+            val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
             cursor.newRow()
                 .add(ColumnEnum.ENTRY_ID.id, entry.id)
                 .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
                 .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
                 .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
-                .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id,
-                    entryRepository.getEntryPathWithDisplayName(entry.id))
+                .add(
+                    ColumnEnum.ENTRY_HIERARCHY_PATH.id,
+                    entryRepository.getEntryPathWithDisplayName(entry.id)
+                )
         }
         return cursor
     }
 }
+
+private fun createBrowseAdbCommand(
+    destination: String? = null,
+    entryId: String? = null,
+    sessionName: String? = null,
+): String? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    val packageName = context.packageName
+    val activityName = browseActivityClass.name.replace(packageName, "")
+    val destinationParam =
+        if (destination != null) " -e $KEY_DESTINATION $destination" else ""
+    val highlightParam =
+        if (entryId != null) " -e $KEY_HIGHLIGHT_ENTRY $entryId" else ""
+    val sessionParam =
+        if (sessionName != null) " -e $KEY_SESSION_SOURCE_NAME $sessionName" else ""
+    return "adb shell am start -n $packageName/$activityName" +
+        "$destinationParam$highlightParam$sessionParam"
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index cd3ec96..aa10cc8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -42,6 +42,9 @@
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.util.PageEvent
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
+import com.android.settingslib.spa.framework.util.getSessionName
 import com.android.settingslib.spa.framework.util.navRoute
 
 private const val TAG = "BrowseActivity"
@@ -78,12 +81,6 @@
             }
         }
     }
-
-    companion object {
-        const val KEY_DESTINATION = "spaActivityDestination"
-        const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
-        const val KEY_SESSION_SOURCE_NAME = "sessionSource"
-    }
 }
 
 @VisibleForTesting
@@ -126,11 +123,10 @@
     if (destinationNavigated.value) return
     destinationNavigated.value = true
 
-    val initialDestination = initialIntent?.getStringExtra(BrowseActivity.KEY_DESTINATION)
-        ?: defaultDestination
+    val initialDestination = initialIntent?.getDestination() ?: defaultDestination
     if (initialDestination.isEmpty()) return
-    val initialEntryId = initialIntent?.getStringExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY)
-    val sessionSourceName = initialIntent?.getStringExtra(BrowseActivity.KEY_SESSION_SOURCE_NAME)
+    val initialEntryId = initialIntent?.getEntryId()
+    val sessionSourceName = initialIntent?.getSessionName()
 
     LaunchedEffect(Unit) {
         highlightId = initialEntryId
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index bc5dca8..7a39b73 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -16,13 +16,8 @@
 
 package com.android.settingslib.spa.framework.common
 
-import android.app.Activity
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.BrowseActivity
 import com.android.settingslib.spa.framework.util.isRuntimeParam
 import com.android.settingslib.spa.framework.util.navLink
 import com.android.settingslib.spa.framework.util.normalize
@@ -95,45 +90,8 @@
         return false
     }
 
-    fun createBrowseIntent(entryId: String? = null): Intent? {
-        val context = SpaEnvironmentFactory.instance.appContext
-        val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass
-        return createBrowseIntent(context, browseActivityClass, entryId)
-    }
-
-    fun createBrowseIntent(
-        context: Context?,
-        browseActivityClass: Class<out Activity>?,
-        entryId: String? = null
-    ): Intent? {
-        if (!isBrowsable(context, browseActivityClass)) return null
-        return Intent().setComponent(ComponentName(context!!, browseActivityClass!!))
-            .apply {
-                putExtra(BrowseActivity.KEY_DESTINATION, buildRoute())
-                if (entryId != null) {
-                    putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId)
-                }
-            }
-    }
-
-    fun createBrowseAdbCommand(
-        context: Context?,
-        browseActivityClass: Class<out Activity>?,
-        entryId: String? = null
-    ): String? {
-        if (!isBrowsable(context, browseActivityClass)) return null
-        val packageName = context!!.packageName
-        val activityName = browseActivityClass!!.name.replace(packageName, "")
-        val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${buildRoute()}"
-        val highlightParam =
-            if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else ""
-        return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam"
-    }
-
-    fun isBrowsable(context: Context?, browseActivityClass: Class<out Activity>?): Boolean {
-        return context != null &&
-            browseActivityClass != null &&
-            !isCreateBy(NULL_PAGE_NAME) &&
+    fun isBrowsable(): Boolean {
+        return !isCreateBy(NULL_PAGE_NAME) &&
             !hasRuntimeParam()
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
new file mode 100644
index 0000000..2c3c2e0
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.content.ComponentName
+import android.content.Intent
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+const val SESSION_BROWSE = "browse"
+const val SESSION_SEARCH = "search"
+const val SESSION_SLICE = "slice"
+
+const val KEY_DESTINATION = "spaActivityDestination"
+const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
+const val KEY_SESSION_SOURCE_NAME = "sessionSource"
+
+val SPA_INTENT_RESERVED_KEYS = listOf(
+    KEY_DESTINATION,
+    KEY_HIGHLIGHT_ENTRY,
+    KEY_SESSION_SOURCE_NAME
+)
+
+private fun createBaseIntent(): Intent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    return Intent().setComponent(ComponentName(context, browseActivityClass))
+}
+
+fun SettingsPage.createIntent(sessionName: String? = null): Intent? {
+    if (!isBrowsable()) return null
+    return createBaseIntent()?.appendSpaParams(
+        destination = buildRoute(),
+        sessionName = sessionName
+    )
+}
+
+fun SettingsEntry.createIntent(sessionName: String? = null): Intent? {
+    val sp = containerPage()
+    if (!sp.isBrowsable()) return null
+    return createBaseIntent()?.appendSpaParams(
+        destination = sp.buildRoute(),
+        entryId = id,
+        sessionName = sessionName
+    )
+}
+
+fun Intent.appendSpaParams(
+    destination: String? = null,
+    entryId: String? = null,
+    sessionName: String? = null
+): Intent {
+    return apply {
+        if (destination != null) putExtra(KEY_DESTINATION, destination)
+        if (entryId != null) putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
+        if (sessionName != null) putExtra(KEY_SESSION_SOURCE_NAME, sessionName)
+    }
+}
+
+fun Intent.getDestination(): String? {
+    return getStringExtra(KEY_DESTINATION)
+}
+
+fun Intent.getEntryId(): String? {
+    return getStringExtra(KEY_HIGHLIGHT_ENTRY)
+}
+
+fun Intent.getSessionName(): String? {
+    return getStringExtra(KEY_SESSION_SOURCE_NAME)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index 7f2f4fd..02aed1c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -33,6 +33,8 @@
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.addUri
 import com.android.settingslib.spa.framework.common.getColumns
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
 
 private const val TAG = "SpaSearchProvider"
 
@@ -162,20 +164,19 @@
 
     private fun fetchSearchData(entry: SettingsEntry, cursor: MatrixCursor) {
         val entryRepository by spaEnvironment.entryRepository
-        val browseActivityClass = spaEnvironment.browseActivityClass
 
         // Fetch search data. We can add runtime arguments later if necessary
         val searchData = entry.getSearchData() ?: return
-        val intent = entry.containerPage()
-            .createBrowseIntent(context, browseActivityClass, entry.id)
-            ?: Intent()
+        val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
         cursor.newRow()
             .add(ColumnEnum.ENTRY_ID.id, entry.id)
             .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
             .add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
             .add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
-            .add(ColumnEnum.SEARCH_PATH.id,
-                entryRepository.getEntryPathWithTitle(entry.id, searchData.title))
+            .add(
+                ColumnEnum.SEARCH_PATH.id,
+                entryRepository.getEntryPathWithTitle(entry.id, searchData.title)
+            )
     }
 
     private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
index 14855a8..7a4750d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import com.android.settingslib.spa.framework.common.EntrySliceData
 import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+import com.android.settingslib.spa.framework.util.getEntryId
 
 private const val TAG = "SliceDataRepository"
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
index ff143ed..f362890 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -24,9 +24,15 @@
 import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.SESSION_SLICE
+import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS
+import com.android.settingslib.spa.framework.util.appendSpaParams
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
 
 // Defines SliceUri, which contains special query parameters:
 //  -- KEY_DESTINATION: The route that this slice is navigated to.
@@ -35,11 +41,6 @@
 // Use {entryId, runtimeParams} as the unique Id of this Slice.
 typealias SliceUri = Uri
 
-val RESERVED_KEYS = listOf(
-    KEY_DESTINATION,
-    KEY_HIGHLIGHT_ENTRY
-)
-
 fun SliceUri.getEntryId(): String? {
     return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
 }
@@ -51,7 +52,7 @@
 fun SliceUri.getRuntimeArguments(): Bundle {
     val params = Bundle()
     for (queryName in queryParameterNames) {
-        if (RESERVED_KEYS.contains(queryName)) continue
+        if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue
         params.putString(queryName, getQueryParameter(queryName))
     }
     return params
@@ -63,12 +64,12 @@
     return "${entryId}_$params"
 }
 
-fun Uri.Builder.appendSliceParams(
-    route: String? = null,
+fun Uri.Builder.appendSpaParams(
+    destination: String? = null,
     entryId: String? = null,
     runtimeArguments: Bundle? = null
 ): Uri.Builder {
-    if (route != null) appendQueryParameter(KEY_DESTINATION, route)
+    if (destination != null) appendQueryParameter(KEY_DESTINATION, destination)
     if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId)
     if (runtimeArguments != null) {
         for (key in runtimeArguments.keySet()) {
@@ -78,6 +79,20 @@
     return this
 }
 
+fun Uri.Builder.fromEntry(
+    entry: SettingsEntry,
+    authority: String?,
+    runtimeArguments: Bundle? = null
+): Uri.Builder {
+    if (authority == null) return this
+    val sp = entry.containerPage()
+    return scheme("content").authority(authority).appendSpaParams(
+        destination = sp.buildRoute(),
+        entryId = entry.id,
+        runtimeArguments = runtimeArguments
+    )
+}
+
 fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
     val context = SpaEnvironmentFactory.instance.appContext
     val sliceBroadcastClass =
@@ -97,8 +112,8 @@
 fun Intent.createBrowsePendingIntent(): PendingIntent? {
     val context = SpaEnvironmentFactory.instance.appContext
     val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
-    val destination = getStringExtra(KEY_DESTINATION) ?: return null
-    val entryId = getStringExtra(KEY_HIGHLIGHT_ENTRY)
+    val destination = getDestination() ?: return null
+    val entryId = getEntryId()
     return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
 }
 
@@ -109,15 +124,12 @@
     entryId: String?
 ): PendingIntent {
     val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
+        .appendSpaParams(destination, entryId, SESSION_SLICE)
         .apply {
             // Set both extra and data (which is a Uri) in Slice Intent:
             // 1) extra is used in SPA navigation framework
             // 2) data is used in Slice framework
-            putExtra(KEY_DESTINATION, destination)
-            if (entryId != null) {
-                putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
-            }
-            data = Uri.Builder().appendSliceParams(destination, entryId).build()
+            data = Uri.Builder().appendSpaParams(destination, entryId).build()
             flags = Intent.FLAG_ACTIVITY_NEW_TASK
         }
 
@@ -130,7 +142,7 @@
     entryId: String
 ): PendingIntent {
     val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
-        .apply { data = Uri.Builder().appendSliceParams(entryId = entryId).build() }
+        .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() }
     return PendingIntent.getBroadcast(
         context, 0 /* requestCode */, intent,
         PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index 934b8f5..c0b7464 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -23,6 +23,8 @@
 import com.android.settingslib.spa.tests.testutils.SppHome
 import com.android.settingslib.spa.tests.testutils.SppLayer1
 import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index a343f6c..f98963c 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -20,6 +20,8 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.core.os.bundleOf
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 15c2db50..1f5de2d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -22,8 +22,8 @@
 import androidx.navigation.navArgument
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.tests.testutils.BlankActivity
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,9 +43,7 @@
         assertThat(page.isCreateBy("NULL")).isTrue()
         assertThat(page.isCreateBy("Spp")).isFalse()
         assertThat(page.hasRuntimeParam()).isFalse()
-        assertThat(page.isBrowsable(context, BlankActivity::class.java)).isFalse()
-        assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNull()
-        assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).isNull()
+        assertThat(page.isBrowsable()).isFalse()
     }
 
     @Test
@@ -58,11 +56,7 @@
         assertThat(page.isCreateBy("NULL")).isFalse()
         assertThat(page.isCreateBy("mySpp")).isTrue()
         assertThat(page.hasRuntimeParam()).isFalse()
-        assertThat(page.isBrowsable(context, BlankActivity::class.java)).isTrue()
-        assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNotNull()
-        assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).contains(
-            "-e spaActivityDestination mySpp"
-        )
+        assertThat(page.isBrowsable()).isTrue()
     }
 
     @Test
@@ -85,11 +79,7 @@
         assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
         assertThat(page.isCreateBy("SppWithParam")).isTrue()
         assertThat(page.hasRuntimeParam()).isFalse()
-        assertThat(page.isBrowsable(context, BlankActivity::class.java)).isTrue()
-        assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNotNull()
-        assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).contains(
-            "-e spaActivityDestination SppWithParam/myStr/10"
-        )
+        assertThat(page.isBrowsable()).isTrue()
     }
 
     @Test
@@ -114,8 +104,6 @@
         assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
         assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
         assertThat(page.hasRuntimeParam()).isTrue()
-        assertThat(page.isBrowsable(context, BlankActivity::class.java)).isFalse()
-        assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNull()
-        assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).isNull()
+        assertThat(page.isBrowsable()).isFalse()
     }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
new file mode 100644
index 0000000..1854728
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaIntentTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+
+    @Before
+    fun setEnvironment() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+    }
+
+    @Test
+    fun testCreateIntent() {
+        val nullPage = SettingsPage.createNull()
+        Truth.assertThat(nullPage.createIntent()).isNull()
+        Truth.assertThat(SettingsEntryBuilder.createInject(nullPage).build().createIntent())
+            .isNull()
+
+        val page = spaEnvironment.createPage("SppHome")
+        val pageIntent = page.createIntent()
+        Truth.assertThat(pageIntent).isNotNull()
+        Truth.assertThat(pageIntent!!.getDestination()).isEqualTo(page.buildRoute())
+        Truth.assertThat(pageIntent.getEntryId()).isNull()
+        Truth.assertThat(pageIntent.getSessionName()).isNull()
+
+        val entry = SettingsEntryBuilder.createInject(page).build()
+        val entryIntent = entry.createIntent(SESSION_SEARCH)
+        Truth.assertThat(entryIntent).isNotNull()
+        Truth.assertThat(entryIntent!!.getDestination()).isEqualTo(page.buildRoute())
+        Truth.assertThat(entryIntent.getEntryId()).isEqualTo(entry.id)
+        Truth.assertThat(entryIntent.getSessionName()).isEqualTo(SESSION_SEARCH)
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
index 7fc09ff..90e25f9 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -23,11 +23,11 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.common.getUniqueEntryId
 import com.android.settingslib.spa.testutils.InstantTaskExecutorRule
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
 import com.android.settingslib.spa.tests.testutils.SppHome
 import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -50,7 +50,7 @@
         // Slice supported
         val page = SppLayer2.createSettingsPage()
         val entryId = getUniqueEntryId("Layer2Entry1", page)
-        val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build()
+        val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
         assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
         assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
         val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
@@ -59,7 +59,7 @@
 
         // Slice unsupported
         val entryId2 = getUniqueEntryId("Layer2Entry2", page)
-        val sliceUri2 = Uri.Builder().appendSliceParams(page.buildRoute(), entryId2).build()
+        val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
         assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
         assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
         assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
@@ -69,7 +69,7 @@
     fun getActiveSliceDataTest() {
         val page = SppLayer2.createSettingsPage()
         val entryId = getUniqueEntryId("Layer2Entry1", page)
-        val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build()
+        val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
 
         // build slice data first
         val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
index 16a87f6..d1c4e51 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
@@ -43,14 +43,14 @@
         // valid slice uri
         val dest = "myRoute"
         val entryId = "myEntry"
-        val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build()
+        val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
         assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
         assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
         assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
         assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
 
         val sliceUriWithParams =
-            Uri.Builder().appendSliceParams(dest, entryId, bundleOf("p1" to "v1")).build()
+            Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
         assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
         assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
         assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
@@ -67,7 +67,7 @@
         // Valid Slice Uri
         val dest = "myRoute"
         val entryId = "myEntry"
-        val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build()
+        val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
         val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
         assertThat(pendingIntent).isNotNull()
         assertThat(pendingIntent!!.isBroadcast).isTrue()
@@ -87,7 +87,7 @@
         // Valid Slice Uri
         val dest = "myRoute"
         val entryId = "myEntry"
-        val sliceUri = Uri.Builder().appendSliceParams(dest, entryId).build()
+        val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
         val pendingIntent = sliceUri.createBrowsePendingIntent()
         assertThat(pendingIntent).isNotNull()
         assertThat(pendingIntent!!.isActivity).isTrue()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
similarity index 89%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
index 93f9afe..7e51fea 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework.common
+package com.android.settingslib.spa.tests.testutils
 
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.toHashId
 import com.android.settingslib.spa.framework.util.normalize
 
 fun getUniquePageId(
diff --git a/services/api/current.txt b/services/api/current.txt
index 35a4dcb..834ed2f 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -76,6 +76,7 @@
     method @Nullable public String getSdkLibraryName();
     method @NonNull public java.util.List<com.android.server.pm.pkg.AndroidPackageSplit> getSplits();
     method @Nullable public String getStaticSharedLibraryName();
+    method @NonNull public java.util.UUID getStorageUuid();
     method public int getTargetSdkVersion();
     method public boolean isDebuggable();
     method public boolean isIsolatedSplitLoading();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 05cd67f..c5cb08d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -105,7 +105,6 @@
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.sysprop.DisplayProperties;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -451,8 +450,6 @@
         }
     };
 
-    private final boolean mAllowNonNativeRefreshRateOverride;
-
     private final BrightnessSynchronizer mBrightnessSynchronizer;
 
     /**
@@ -506,7 +503,6 @@
         ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
         mWideColorSpace = colorSpaces[1];
         mOverlayProperties = SurfaceControl.getOverlaySupport();
-        mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride();
         mSystemReady = false;
     }
 
@@ -930,24 +926,20 @@
             }
         }
 
-        if (mAllowNonNativeRefreshRateOverride) {
-            overriddenInfo.refreshRateOverride = frameRateHz;
-            if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                    callingUid)) {
-                overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
-                        info.supportedModes.length + 1);
-                overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
-                        new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
-                                currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
-                                overriddenInfo.refreshRateOverride);
-                overriddenInfo.modeId =
-                        overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
-                                .getModeId();
-            }
-            return overriddenInfo;
+        overriddenInfo.refreshRateOverride = frameRateHz;
+        if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
+                callingUid)) {
+            overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
+                    info.supportedModes.length + 1);
+            overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
+                    new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
+                            currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
+                            overriddenInfo.refreshRateOverride);
+            overriddenInfo.modeId =
+                    overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
+                            .getModeId();
         }
-
-        return info;
+        return overriddenInfo;
     }
 
     private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
@@ -2602,11 +2594,6 @@
         long getDefaultDisplayDelayTimeout() {
             return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
         }
-
-        boolean getAllowNonNativeRefreshRateOverride() {
-            return DisplayProperties
-                    .debug_allow_non_native_refresh_rate_override().orElse(true);
-        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 3c72019..283640d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2259,26 +2259,20 @@
                 incrementalStorages.add(storage);
             }
 
-            // Enabling fs-verity is a blocking operation. To reduce the impact to the install time,
-            // run in a background thread.
-            new Thread("fsverity-setup") {
-                @Override public void run() {
-                    try {
-                        if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
-                            VerityUtils.setUpFsverity(pkg.getBaseApkPath(), (byte[]) null);
-                        }
-                        for (String path : pkg.getSplitCodePaths()) {
-                            if (!VerityUtils.hasFsverity(path)) {
-                                VerityUtils.setUpFsverity(path, (byte[]) null);
-                            }
-                        }
-                    } catch (IOException e) {
-                        // There's nothing we can do if the setup failed. Since fs-verity is
-                        // optional, just ignore the error for now.
-                        Slog.e(TAG, "Failed to fully enable fs-verity to " + packageName);
+            try {
+                if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
+                    VerityUtils.setUpFsverity(pkg.getBaseApkPath(), (byte[]) null);
+                }
+                for (String path : pkg.getSplitCodePaths()) {
+                    if (!VerityUtils.hasFsverity(path)) {
+                        VerityUtils.setUpFsverity(path, (byte[]) null);
                     }
                 }
-            }.start();
+            } catch (IOException e) {
+                // There's nothing we can do if the setup failed. Since fs-verity is
+                // optional, just ignore the error for now.
+                Slog.e(TAG, "Failed to fully enable fs-verity to " + packageName);
+            }
 
             // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
             mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index b1e9141..ba36ab7 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -1335,6 +1335,11 @@
     }
 
     @Override
+    public UUID getStorageUuid() {
+        return mStorageUuid;
+    }
+
+    @Override
     public int getTargetSandboxVersion() {
         return targetSandboxVersion;
     }
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index e3dad45..84907a5 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -34,6 +34,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.SigningDetails;
 import android.os.Bundle;
+import android.os.storage.StorageManager;
 import android.processor.immutability.Immutable;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -58,6 +59,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * The representation of an application on disk, as parsed from its split APKs' manifests.
@@ -111,6 +113,13 @@
     String getStaticSharedLibraryName();
 
     /**
+     * @return The {@link UUID} for use with {@link StorageManager} APIs identifying where this
+     * package was installed.
+     */
+    @NonNull
+    UUID getStorageUuid();
+
+    /**
      * @see ApplicationInfo#targetSdkVersion
      * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
      */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f74956b..5d08461 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1559,8 +1559,9 @@
                     try {
                         mReply.sendResult(null);
                     } catch (RemoteException e) {
-                        Binder.restoreCallingIdentity(ident);
                         Slog.d(TAG, "failed to send callback!", e);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
                     }
                     t.traceEnd();
                     mReply = null;
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index ee6196d..7bf9a9e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -48,11 +48,16 @@
 import com.android.server.testutils.whenever
 import java.security.KeyPairGenerator
 import java.security.PublicKey
+import java.util.UUID
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
 class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
 
+    companion object {
+        private val TEST_UUID = UUID.fromString("57554103-df3e-4475-ae7a-8feba49353ac")
+    }
+
     override val defaultImpl = PackageImpl.forTesting("com.example.test")
     override val creator = PackageImpl.CREATOR
 
@@ -88,8 +93,6 @@
         "getLongVersionCode",
         // Tested through constructor
         "getManifestPackageName",
-        // Utility methods
-        "getStorageUuid",
         // Removal not tested, irrelevant for parcelling concerns
         "removeUsesOptionalLibrary",
         "clearAdoptPermissions",
@@ -101,6 +104,7 @@
         // Tested manually
         "getMimeGroups",
         "getRequestedPermissions",
+        "getStorageUuid",
         // Tested through asSplit
         "asSplit",
         "getSplits",
@@ -256,7 +260,7 @@
     )
 
     override fun extraParams() = listOf(
-        getter(AndroidPackage::getVolumeUuid, "57554103-df3e-4475-ae7a-8feba49353ac"),
+        getter(AndroidPackage::getVolumeUuid, TEST_UUID.toString()),
         getter(AndroidPackage::isProfileable, true),
         getter(PackageImpl::getVersionCode, 3),
         getter(PackageImpl::getVersionCodeMajor, 9),
@@ -617,6 +621,8 @@
         expect.that(after.usesStaticLibrariesCertDigests!!.size).isEqualTo(1)
         expect.that(after.usesStaticLibrariesCertDigests!![0]).asList()
                 .containsExactly("testCertDigest2")
+
+        expect.that(after.storageUuid).isEqualTo(TEST_UUID)
     }
 
     private fun testKey() = KeyPairGenerator.getInstance("RSA")
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index 5c3d695..01674bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
@@ -107,12 +108,16 @@
     @Mock
     private BackgroundDexOptJobService mJobServiceForIdle;
 
-    private final JobParameters mJobParametersForPostBoot = new JobParameters(null,
-            BackgroundDexOptService.JOB_POST_BOOT_UPDATE, null, null, null,
-            0, false, false, null, null, null);
-    private final JobParameters mJobParametersForIdle = new JobParameters(null,
-            BackgroundDexOptService.JOB_IDLE_OPTIMIZE, null, null, null,
-            0, false, false, null, null, null);
+    private final JobParameters mJobParametersForPostBoot =
+            createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
+    private final JobParameters mJobParametersForIdle =
+            createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE);
+
+    private static JobParameters createJobParameters(int jobId) {
+        JobParameters params = mock(JobParameters.class);
+        when(params.getJobId()).thenReturn(jobId);
+        return params;
+    }
 
     private BackgroundDexOptService mService;
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 9853b7a..ce35626 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -171,22 +171,6 @@
 
     private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
 
-    private final DisplayManagerService.Injector mAllowNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return true;
-                }
-            };
-
-    private final DisplayManagerService.Injector mDenyNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return false;
-                }
-            };
-
     @Mock InputManagerInternal mMockInputManagerInternal;
     @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken;
@@ -1113,13 +1097,32 @@
     }
 
     /**
-     * Tests that the frame rate override is updated accordingly to the
-     * allowNonNativeRefreshRateOverride policy.
+     * Tests that the frame rate override is returning the correct value from
+     * DisplayInfo#getRefreshRate
      */
     @Test
     public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverride(mDenyNonNativeRefreshRateOverrideInjector);
-        testDisplayInfoNonNativeFrameRateOverride(mAllowNonNativeRefreshRateOverrideInjector);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
     }
 
     /**
@@ -1147,10 +1150,7 @@
     @Test
     @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/ false);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  false);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
     }
 
     /**
@@ -1159,10 +1159,7 @@
     @Test
     @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
     }
 
     /**
@@ -1385,10 +1382,9 @@
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverrideMode(
-            DisplayManagerService.Injector injector, boolean compatChangeEnabled) {
+    private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
         DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
+                new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
         registerDefaultDisplays(displayManager);
@@ -1410,40 +1406,12 @@
         Display.Mode expectedMode;
         if (compatChangeEnabled) {
             expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else if (injector.getAllowNonNativeRefreshRateOverride()) {
-            expectedMode = new Display.Mode(255, 100, 200, 20f);
         } else {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
+            expectedMode = new Display.Mode(255, 100, 200, 20f);
         }
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverride(
-            DisplayManagerService.Injector injector) {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        float expectedRefreshRate = injector.getAllowNonNativeRefreshRateOverride() ? 20f : 60f;
-        assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f);
-    }
-
     private int getDisplayIdForDisplayDevice(
             DisplayManagerService displayManager,
             DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
index f29d9b2..c6f6d45 100644
--- a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
+++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
@@ -54,6 +54,7 @@
             "java.lang.Short",
             "java.lang.String",
             "java.lang.Void",
+            "java.util.UUID",
             "android.os.Parcelable.Creator",
         )